30.1 實驗內(nèi)容
通過本實驗主要學(xué)習(xí)以下內(nèi)容:
? CAN的簡介
? GD32F303 CAN工作原理
? 通過CAN實現(xiàn)回環(huán)收發(fā)
30.2 實驗原理
30.2.1 CAN概述
CAN 是 Controller Area Network 的縮寫,是由德國BOSCH公司開發(fā)的,已成為ISO 國際標準化的串行通信協(xié)議。其主要應(yīng)用場合為汽車和工業(yè)控制。 CAN具有傳輸距離長,傳輸可靠、強大的糾錯機制等特點,其高性能和可靠性已被廣泛認同,現(xiàn)在已經(jīng)成為汽車、工業(yè)自動化、醫(yī)療設(shè)備等領(lǐng)域應(yīng)用最廣泛的總線之一。
30.2.2 CAN總線拓撲
CAN總線拓撲圖如下:
CAN 控制器根據(jù)兩根線上的電位差來判斷總線電平,一般將兩根線分別命名為CAN_H和CAN_L??偩€電平分為顯性電平和隱性電平,二者必居其一。發(fā)送方通過使總線電平發(fā)生變化,將消息發(fā)送給接收方。 當CAN總線上的電位差為0V時,表示隱性電平,隱性電平代表邏輯“1”;當CAN總線上有電位差時(大概在2.5V左右),表示顯性電平,顯性電平代表邏輯“0”??偩€空閑時,默認為隱性電平,即總線電位差為0。
關(guān)于電位差、隱性/顯性電平及邏輯電平,非常容易弄混,讀者需要熟記。
CAN總線的特點可以總結(jié)為:
與USART-485這種一主多從類型總線不同,CAN總線是多主控制,即總線上沒有主機從機之分,所有設(shè)備都是處于平等的地位。
CAN總線上的消息都以固定格式發(fā)送。當兩個以上的單元同時開始發(fā)送消息時,根據(jù)標識符( Identifier 以下稱為 ID)決定優(yōu)先級。ID 并不是表示發(fā)送的目的地址,而是表示訪問總線的消息的優(yōu)先級。
CAN最高可達1Mbps波特率,理論最遠距離可達10Km,當然此時通訊速率較低,只有5Kbps以下。
CAN總線具有強大的錯誤檢測、通知和恢復(fù)功能。當一個單元發(fā)生錯誤時其他單元會進行報錯,正在發(fā)送的單元檢測到錯誤后,會立即強制結(jié)束當前發(fā)送,并嘗試重新發(fā)送(功能可配置),當發(fā)送錯誤的次數(shù)達到一定值后,該單元會自動從總線中退出,直到應(yīng)用程序讓其重新加入總線為止。
CAN的總線的查分信號,決定了CAN總線實際為半雙工通訊,另外由于CAN總線沒有時鐘線,所以是異步通訊,故要求CAN總線上的所有單元的波特率都要設(shè)置一致。
30.2.3 CAN幀的種類
CAN總共有如下五種類型的幀種類:
? 數(shù)據(jù)幀
用于數(shù)據(jù)傳輸?shù)膸?,也是最常用的一種幀種類
? 遙控幀
接收單元向具有相同ID的發(fā)送單元請求數(shù)據(jù)時所需要的幀種類
? 錯誤幀
一種非常重要的幀種類,錯誤幀是當總線上有錯誤時,檢測到錯誤的單元向其他單元通知錯誤的幀。
? 過載幀
用于接收單元通知其他單元其還沒有準備好的幀種類
? 幀間隔
用于幀和幀之間分離的幀
30.2.4 CAN協(xié)議的解析
介紹了CAN的一些基本指示后,可能讀者還是不太明白幀ID是什么,CAN的發(fā)送和接收是怎么實現(xiàn)的,是否就像串口一樣發(fā)送數(shù)據(jù)就可以?實際上CAN需要遵循CAN協(xié)議,這樣每個CAN單元才可以準確無誤的發(fā)送和接收數(shù)據(jù),CAN強大的錯誤檢測、錯誤通知等機制也是依托于標準CAN協(xié)議。下面以數(shù)據(jù)幀來解析下CAN的協(xié)議。
數(shù)據(jù)幀的格式如下圖,其中D表示顯性位,即邏輯“0”,R表示隱性位,即邏輯“1”,D/R表示隱性位或顯性位,另外下圖中的數(shù)字表示bit位數(shù):
? 幀開始
每次CAN通訊都始于幀開始段,幀開始是1個bit位的顯性電平(邏輯“0”),由發(fā)送方發(fā)出,接收方檢測到一個bit邏輯“0”,開始準備接收數(shù)據(jù)。
? 仲裁段
好了,我們終于看到了幀ID的廬山真面目了。數(shù)據(jù)幀分為兩種——標準幀和擴展幀,標準幀的幀ID由11bit組成,擴展幀有11+18共29bit組成。需要注意,無論是標準幀ID還是擴展幀ID,都不允許設(shè)置最高7bit為1(即不允許幀ID=0b,1111111xxx··),以為幀結(jié)束段就是7個“1”組成。
沖裁段中的 RTR 位用于標識是否是遠程幀(0:數(shù)據(jù)幀; 1:遠程幀),IDE 位為標識符選擇位( 0:使用標準標符; 1:使用擴展標識符),SRR 位為代替遠程請求位,為隱性位,它代替了標準幀中的 RTR 位。
我們需要先明確一個概念,CAN總線上的每個單元并不是只能發(fā)送固定幀ID,而是可以發(fā)送任意幀ID,幀ID不代表CAN設(shè)備號,代表的是當前數(shù)據(jù)幀/遠程幀的ID號。當一幀數(shù)據(jù)發(fā)送到總線后,所有接收方會對幀ID進行識別,當識別到是自己需要的ID時,則會將該幀數(shù)據(jù)收取到內(nèi)部;而當識別到不是自己需要的ID時,則不會接收數(shù)據(jù)。
因為有可能出現(xiàn)兩個或更多CAN單元同時發(fā)送數(shù)據(jù)的情況,此時幀ID還起到仲裁的作用,各發(fā)送單元從幀ID的第一位開始進行仲裁。連續(xù)輸出顯性電平最多的單元可繼續(xù)發(fā)送,比如兩個CAN單元同時發(fā)送數(shù)據(jù),其中單元一發(fā)出的數(shù)標準幀的ID為0b,00110000000,而單元二發(fā)出的標準幀ID為0b,00100000000,可以看到發(fā)送到第4位時,單元一發(fā)出的是邏輯“1”,單元二的是邏輯“0”,因為總線上“0”比“1”優(yōu)先級搞,所以此時單元二獲得總線發(fā)送權(quán),單元一將自動停止發(fā)送。
? 控制段
控制段由6個bit組成,其中DLC占用4個bit,表示要發(fā)送的字節(jié)數(shù)。
? 數(shù)據(jù)段
數(shù)據(jù)段即幀載有的有效數(shù)據(jù)了,CAN最多一次可發(fā)送8個字節(jié)(CANFD最多可以發(fā)送64字節(jié),這個后面介紹帶CANFD功能的開發(fā)板時再細說),也就是說上面描述的DLC最大值為0b,1000。
? CRC段
CRC段總共有16bit,其中15個bit表示CRC值,另1個bit為CRC界定符。此段 CRC 的值計算范圍包括:幀起始、仲裁段、控制段、數(shù)據(jù)段。發(fā)送會根據(jù)發(fā)送的數(shù)據(jù)來計算出一個CRC值并通過CRC段發(fā)到總線,接收方以同樣的算法計算CRC值并和發(fā)送方發(fā)出的CRC段進行比較,當不一致時接收方會向總線報錯。
? ACK段
ACK段用來確認接收方是否正常接收。ACK段由 ACK 槽(ACK Slot)和 ACK 界定符 2 個位組成。
發(fā)送單元的 ACK,發(fā)送 2 個位的隱性位,而接收到正確消息的單元在 ACK 槽(ACK Slot)發(fā)送顯性位,通知發(fā)單元正常接收結(jié)束,這個過程叫發(fā)送 ACK/返回 ACK。發(fā)送 ACK 的是在既不處于總線關(guān)閉態(tài)也不處于休眠態(tài)的所有收單元中,接收到正常消息的單元(發(fā)送單元不發(fā)送 ACK)。
? 幀結(jié)束
幀結(jié)束由7個bit的邏輯“1”組成,表示該幀結(jié)束。
30.2.5 CAN的波特率
前面有提到CAN的波特率,波特率代表每秒鐘傳輸?shù)奈粩?shù)(1位就表示1bit),我們來看下GD32F303的波特率是怎么計算的。
首先需要了解一個概念,CAN時序的最小單位是一個叫Tq的東西,CAN的每個位即每個bit是由若干個Tq組成的,那么這個Tq的長度是多少呢?GD32F303的CAN是掛載在APB1總線的:
如果讀者將GD32F303的主頻配置為最高120M,且將APB1的時鐘配置為最高60M的話,那么CAN的時鐘就是60M,然后CAN有個分頻系數(shù),在位時序寄存器CAN_BT中:
一個Tq占用的時間計算公式:分頻系數(shù)/CAN時鐘,舉個例子,我們設(shè)置這個分頻系數(shù)為6的話,一個Tq的時間就是6/60M = 100us,也可以說Tq的傳輸速率為10M。那么由多少個Tq組成CAN的一個位呢?還是看CAN_BT寄存器:
寄存器中有BS1和BS2,這兩個位域用于設(shè)置位段1和位段2(關(guān)于位段后面會介紹),一個CAN位占用的Tq個數(shù)等于位段1+位段2+1,舉個例子,設(shè)置位段1為5(即BS1=4),設(shè)置位段2為4(即BS2=3),那么一個CAN位占用的Tq個數(shù)為5+4+1=10。好,現(xiàn)在就可以來算CAN的波特率了,按照CAN分頻系數(shù)為6,位段1為5,位段2為4,一個位占用的時間為6/60M*10 = 1ms,也就是波特率=1M。我們可以把這個計算轉(zhuǎn)化為公式:
30.2.6 CAN的位時序和采樣點
我們現(xiàn)在來看下上一節(jié)提到的位段1和位段2。CAN總線控制器將位時間分為3個部分。
? 同步段(Synchronization segment),記為SYNC_SEG。該段占用1個時間單元(1 × ??????)。
? 位段1(Bit segment 1),記為BS1。該段占用1到16個Tq(由位時序寄存器配置)。相對于CAN協(xié)議而言, BS1相當于傳播時間段(Propagation delay segment)和相位緩沖段1(Phase buffer segment 1)。
? 位段2(Bit segment 2),記為BS2。該段占用1到8個Tq(由位時序寄存器配置)。相對于CAN協(xié)議而言, BS2相當于相位緩沖段2(Phase buffer segment 2)。
位時序圖:
這里提到的BS1(即位段1)和CAN時序寄存器中的BS1[3:0]位域不是一個概念,位段1=BS1[3:0]+1
說完位時序我們來介紹下GD32F303的采樣點。
對于接收方來說,需要對發(fā)送方發(fā)出的每個bit進行采樣,那么具體是采樣哪個點呢?按照CAN標準來說,采樣點為BS1和BS2的交界處,即:
而GD32F303為了更好的容錯性,會在標準采樣點前一個以及前前一個Tq加了兩個采樣點,取兩個有效位,所以GD32F303的采樣點為:
如果三個采樣點分別采樣到的為010,則認為該位為“1”。
30.2.7 GD32F303 CAN過濾器
前面提到CAN節(jié)點發(fā)送數(shù)據(jù)的時候,幀ID是任意的,那么接收方是不是任意ID都可以接收呢?當然是可以的,但一般不會這么做,一個CAN節(jié)點一般只接收一個ID或幾個ID的報文,那么如何實現(xiàn)呢?這就要介紹CAN的過濾器了,只有總線上的報文幀ID通過了CAN節(jié)點的過濾,才會被接收。
GD32F303共有14個過濾器組(對于互聯(lián)性GD32F305/F307,是28個過濾器組),每個過濾器組有兩個過濾器寄存器。程序中需要設(shè)置過濾器對應(yīng)哪個接收FIFO(接收FIFO會在下一節(jié)中介紹)。
GD32F303過濾器( x) 數(shù)據(jù)( y) 寄存器(CAN_FxDATAy)( x= 0...13, y = 0,1)( 僅 CAN0可用):
過濾器可以配置為2種位寬:32-bit位寬和16-bit位寬。 32-bit 位寬 CAN_FDATAx 包含字段: SFID[10:0], EFID[17:0], FF 和FT。
16-bit 位寬 CAN_FDATAx 包含字段: SFID[10:0], FT, FF和 EFID[17:15]。
過濾器可以設(shè)置為兩種模式——掩碼模式和列表模式:
? 掩碼模式
對于一個待過濾的數(shù)據(jù)幀的標識符(Identifier),掩碼模式用來指定哪些位必須與預(yù)設(shè)的標識符相同,哪些位無需判斷。掩碼模式有兩種位寬:32bit和16bit。
一個 32-bit 位寬掩碼模式過濾器如下:
可以看到,在掩碼模式下,F(xiàn)DATA0用于目標ID,F(xiàn)DATA1用于Mask。舉個例子,設(shè)置FDATA0為0x55550000,F(xiàn)DATA1為0xFF00FF00(第31~24以及第15~8位為1),那么意味著總線上的報文ID的第31~24以及第15~8位必須和FDATA0相應(yīng)位相同,就可以通過這個過濾器,而其他位則不需要關(guān)心,也就是說幀ID為0x55xx00xx可以通過過濾器。
明白了32bit掩碼模式過濾器,16位位寬就很好理解了。一個16-bit位寬掩碼模式過濾器如下:
和32-bit的不用,16-bit位寬掩碼過濾器的ID為FDATA0的高16bit,Mask為FDATA0的低16bit,這也意味著16-bit位寬掩碼模式可以設(shè)置28個過濾ID掩碼類型。
? 列表模式
列表模式和掩碼模式不同,列表模式設(shè)置了一個個具體ID,只有和這些ID完全相同的幀才可以通過過濾器,同樣分成兩種位寬模式。
32-bit位寬列表模式過濾器:
16-bit位寬列表模式過濾器:
30.2.8 GD32F303 CAN的發(fā)送和接收
通過上面的學(xué)習(xí),我們已經(jīng)基本了解了CAN的工作原理了,這節(jié)我們來講GD32F303 CAN的收發(fā)。首先我們需要了解GD32F303 CAN的結(jié)構(gòu)框圖:
可以看到,GD32F303是有3個發(fā)送郵箱和兩個深度為3的接收FIFO,下面我們分別介紹數(shù)據(jù)發(fā)送和數(shù)據(jù)接收。
? 數(shù)據(jù)發(fā)送
發(fā)送寄存器的框圖:
三個發(fā)送郵箱對于三組發(fā)送寄存器TMIx、TMPx、TMDATA0x和TMDATA1x(x=0,1,2):
發(fā)送郵箱標識符寄存器(CAN_TMIx)
發(fā)送郵箱屬性寄存器(CAN_TMPx):
發(fā)送郵箱 data0 寄存器(CAN_TMDATA0x):
發(fā)送郵箱 data1 寄存器(CAN_TMDATA1x):
當需要發(fā)送數(shù)據(jù)時,選擇一個空閑(empty)的郵箱(讀取CAN_TSTAT寄存器獲?。?,然后將該郵箱對應(yīng)TMIx、TMPx、TMDATA0x和TMDATA1x寄存器填好后,使能TMIx的TEN位,寄存器中的數(shù)據(jù)就自動轉(zhuǎn)移到郵箱。
實際上數(shù)據(jù)到郵箱后也不一定就馬上發(fā)送到總線,因為有可能總線上正有數(shù)據(jù)發(fā)送,或者其他的郵箱中也有數(shù)據(jù),這就涉及到CAN發(fā)送郵箱的調(diào)度:
當發(fā)送郵箱被填入新的數(shù)據(jù)后,郵箱狀態(tài)從empty轉(zhuǎn)到pending狀態(tài)。當超過1 個郵箱處于 pending 狀態(tài)時,需要對多個郵箱進行調(diào)度,這時發(fā)送郵箱處于 scheduled 狀態(tài)。當調(diào)度完成后,發(fā)送郵箱中的數(shù)據(jù)開始向 CAN 總線上發(fā)送,這時發(fā)送郵箱處于 transmit 狀態(tài)。當數(shù)據(jù)發(fā)送完成,郵箱變?yōu)榭臻e,可以再次交給應(yīng)用程序使用,這時發(fā)送郵箱重新變?yōu)?empty 狀態(tài)。
發(fā)送郵箱狀態(tài)轉(zhuǎn)換圖:
當多個發(fā)送郵箱處于等待狀態(tài)下時,可以通過CAN_CTL的TFO位的值可以決定發(fā)送順序:
當TFO為1,所有等待發(fā)送的郵箱按照先來先發(fā)送(FIFO)的順序進行。
當TFO為0,具有最小標識符(Identifier)的郵箱最先發(fā)送。如果所有的標識符(Identifier)相等,具有最小郵箱編號的郵箱最先發(fā)送。
? 數(shù)據(jù)接收
接收寄存器的框圖:
兩個接收FIFO對應(yīng)了兩組接收寄存器RFIFOMIx, RFIFOMPx,RFIFOMDATA0x和RFIFOMDATA1x(x=0,1):
接收 FIFO 郵箱標識符寄存器(CAN_RFIFOMIx):
接收 FIFO 郵箱屬性寄存器(CAN_RFIFOMPx) :
接收 FIFO 郵箱 data0 寄存器(CAN_RFIFOMDATA0x) :
接收 FIFO 郵箱 data1 寄存器(CAN_RFIFOMDATA1x) :
當總線上報文通過CAN接收過濾器后(過濾器需要設(shè)置對應(yīng)的FIFO號),數(shù)據(jù)就會保存到接收郵箱中,每個接收FIFO包含3個接收郵箱,用來接收存儲數(shù)據(jù)幀。這些郵箱按照先進先出方式進行組織,最早從CAN網(wǎng)絡(luò)接收的數(shù)據(jù),最早被應(yīng)用程序處理。
寄存器CAN_RFIFOx包含F(xiàn)IFO狀態(tài)信息和幀的數(shù)量。當FIFO中包含數(shù)據(jù)時,可以通過寄存器CAN_RFIFOMIx, CAN_RFIFOMPx, CAN_RFIFOMDATA0x和CAN_RFIFOMDATA1x讀取數(shù)據(jù),之后將寄存器CAN_RFIFOx的RFD置1釋放郵箱。
用戶可以通過讀取寄存器CAN_RFIFOx來獲取FIFO的一些信息,比如接收FIFO中目前還有多少個郵箱內(nèi)容沒有被讀取,是否有FIFO溢出的情況等。關(guān)于溢出時的處理方式,可以通過CAN_CTL寄存器的RFOD位來進行設(shè)置(讀者可閱讀GD32F30x用戶手冊來查看相關(guān)寄存器含義)。
30.2.9 GD32F303 CAN工作模式
CAN 總線控制器有 3 種工作模式:
? 睡眠工作模式;
? 初始化工作模式;
? 正常工作模式。
睡眠工作模式
芯片復(fù)位后, CAN總線控制器處于睡眠工作模式。該模式下CAN總線控制器的時鐘停止工作并處于一種低功耗狀態(tài)。
將CAN_CTL寄存器的SLPWMOD位置1,可以使CAN總線控制器進入睡眠工作模式。 當進入睡眠工作模式后, CAN_STAT寄存器的SLPWS位將被硬件置1。
將CAN_CTL寄存器的AWU位置1,并當CAN檢測到總線活動時, CAN總線控制器將自動退出睡眠工作模式。將CAN_CTL寄存器的SLPWMOD位清0,也可以退出睡眠工作模式。
由睡眠模式進入初始化工作模式:將CAN_CTL寄存器的IWMOD位置1, SLPWMOD位清0。
由睡眠模式進入正常工作模式:將CAN_CTL寄存器的IWMOD位和SLPWMOD位清0。
初始化工作模式
如果需要配置 CAN 總線通信參數(shù), CAN 總線控制器必須進入初始化工作模式。將 CAN_CTL寄存器的 IWMOD 位置 1,使 CAN 總線控制器進入初始化工作模式,將其清 0 則離開初始化 工作模式。在進入初始化工作模式后, CAN_STAT 寄存器的IWS 位將被硬件置 1。
由初始化模式進入睡眠模式: CAN_CTL 寄存器的 SLPWMOD 位置 1, IWMOD 位清 0。
由初始化模式進入正常工作模式: CAN_CTL 寄存器的 SLPWMOD 位和 IWMOD 位清0。
正常工作模式
在初始化工作模式中配置完CAN 總線通信參數(shù)后,將 CAN_CTL 寄存器的IWMOD位清0可以進入正常工作模式并與 CAN 總線網(wǎng)絡(luò)中的節(jié)點進行正常通信。
由正常工作模式進入睡眠工作模式: CAN_CTL 寄存器的 SLPWMOD 位置 1,并等待當前數(shù)據(jù)收發(fā)過程結(jié)束。
由正常工作模式初始化工作模式: CAN_CTL 寄存器的 IWMOD 位置 1,并等待當前數(shù)據(jù)收發(fā)過程結(jié)束。
30.2.10 GD32F303 CAN通信模式
CAN 總線控制器有 4 種通信模式:
? 靜默(Silent)通信模式;
? 回環(huán)(Loopback)通信模式;
? 回環(huán)靜默(Loopback and Silent)通信模式;
? 正常(Normal)通信模式。
靜默(Silent)通信模式
在靜默通信模式下,可以從 CAN 總線接收數(shù)據(jù),但不向總線發(fā)送任何數(shù)據(jù)。將 CAN_BT寄存器中的 SCMOD 位置 1,使 CAN 總線控制器進入靜默通信模式,將其清0 可以退出靜默通信模式。
靜默通信模式可以用來監(jiān)控CAN 網(wǎng)絡(luò)上的數(shù)據(jù)傳輸。
回環(huán)(Loopback)通信模式
在回環(huán)通信模式下,由 CAN 總線控制器發(fā)送的數(shù)據(jù)可以被自己接收并存入接收FIFO,同時這些發(fā)送數(shù)據(jù)也送至CAN 網(wǎng)絡(luò)。將CAN_BT 寄存器中的 LCMOD 位置 1,使 CAN總線控制器進入回環(huán)通信模式,將其清0 可以退出回環(huán)通信模式。本實驗中就用到了CAN的回環(huán)通訊模式。
回環(huán)通信模式通常用來進行CAN 通信自測。
回環(huán)靜默(Loopback and Silent)通信模式
在回環(huán)靜默通信模式下, CAN 的 RX 和 TX 引腳與 CAN 網(wǎng)絡(luò)斷開。 CAN 總線控制器既不從CAN 網(wǎng)絡(luò)接收數(shù)據(jù),也不向 CAN 網(wǎng)絡(luò)發(fā)送數(shù)據(jù),其發(fā)送的數(shù)據(jù)僅可以被自己接收。將CAN_BT寄存器中的 LCMOD 位和 SCMOD 位置 1,使 CAN 總線控制器進入回環(huán)靜默通信模式,將它們清 0 可以退出回環(huán)靜默通信模式。
回環(huán)靜默通信模式通常用來進行CAN 通信自測。對外 TX 引腳保持隱性狀態(tài)(邏輯1), RX 引腳保持高阻態(tài)。
正常(Normal)通信模式
CAN 總線控制器通常工作在正常通信模式下,可以從 CAN 總線接收數(shù)據(jù),也可以向 CAN 總線發(fā)送數(shù)據(jù)。這時需要將 CAN_BT 寄存器的LCMOD 位和 SCMOD 位清0。
30.3 硬件設(shè)計
本實驗CAN的硬件設(shè)計如下:
30.4 代碼解析
30.4.1 CAN 配置函數(shù)
在driver_can.c中定義了driver_can_config函數(shù),用于CAN的基本參數(shù)和過濾器配置:
C
void driver_can_config(typdef_can_general can_general)
{
rcu_periph_clock_enable(can_general.rcu_can); //CAN時鐘使能
rcu_periph_clock_enable(can_general.rcu_IO_port); //IO時鐘使能
if(can_general.can_remap != 0) //如IO有remap,需要配置remap功能
{
rcu_periph_clock_enable(RCU_AF);
gpio_pin_remap_config(can_general.can_remap,ENABLE);
}
gpio_init(can_general.IO_port,GPIO_MODE_IPU,can_general.gpio_speed,can_general.pin_rx); //CAN RX IO配置
gpio_init(can_general.IO_port,GPIO_MODE_AF_PP,can_general.gpio_speed,can_general.pin_tx); //CAN TX IO配置
can_struct_para_init(CAN_INIT_STRUCT, &can_general.can_parameter); //CAN初始化結(jié)構(gòu)體的初始化
can_struct_para_init(CAN_INIT_STRUCT, &can_general.can_filter); //CAN過濾器結(jié)構(gòu)體的初始化
can_deinit(can_general.can_port); //CAN的deinit
can_general.can_parameter.time_triggered = DISABLE; ?//時間觸發(fā)功能
can_general.can_parameter.auto_bus_off_recovery = DISABLE;//busoff自恢復(fù)功能
can_general.can_parameter.auto_wake_up = DISABLE; //自動喚醒功能
can_general.can_parameter.no_auto_retrans = DISABLE;//自動重發(fā)功能,需要注意DISABLE為使能自動重發(fā)
can_general.can_parameter.rec_fifo_overwrite = DISABLE;//接收溢出模式
can_general.can_parameter.trans_fifo_order = DISABLE;//發(fā)送郵箱順序配置
can_general.can_parameter.working_mode = CAN_LOOPBACK_MODE;//回環(huán)模式
can_general.can_parameter.resync_jump_width = CAN_BT_SJW_1TQ;//再同步補償
can_general.can_parameter.time_segment_1 = CAN_BT_BS1_5TQ;//BS1設(shè)置,注意這里設(shè)置為5,寄存器BS1[3:0]實際為4
can_general.can_parameter.time_segment_2 = CAN_BT_BS2_4TQ;//BS2設(shè)置,注意這里設(shè)置為4,寄存器BS2[2:0]實際為3
/* 1MBps */
can_general.can_parameter.prescaler = 6;
/* 500KBps */
# elif CAN_BAUDRATE == 500
can_general.can_parameter.prescaler = 12;
/* 250KBps */
can_general.can_parameter.prescaler = 24;
/* 125KBps */
can_general.can_parameter.prescaler = 48;
/* 100KBps */
can_general.can_parameter.prescaler = 60;
/* 50KBps */
can_general.can_parameter.prescaler = 120;
/* 20KBps */
can_general.can_parameter.prescaler = 300;
# endif
# error "please select list can baudrate in private defines in main.c "
# endif
/* initialize CAN */
can_init(can_general.can_port, &can_general.can_parameter);//CAN初始化
/* initialize filter */
can_general.can_filter.filter_number=0; //過濾器號
can_general.can_filter.filter_mode = CAN_FILTERMODE_MASK;//掩碼模式
can_general.can_filter.filter_bits = CAN_FILTERBITS_32BIT;//掩碼位寬
can_general.can_filter.filter_list_high = 0x3000<<1; //掩碼和ID設(shè)置
can_general.can_filter.filter_list_low = 0x0000;
can_general.can_filter.filter_mask_high = 0x3000<<1;
can_general.can_filter.filter_mask_low = 0x0000;
can_general.can_filter.filter_fifo_number = CAN_FIFO0; //過濾器關(guān)聯(lián)接收FIFO號
can_general.can_filter.filter_enable = ENABLE; //過濾器使能
can_filter_init(&can_general.can_filter); //過濾器初始化
can_general.can_filter.filter_number=1;
can_general.can_filter.filter_list_high = 0x5000<<1;
can_general.can_filter.filter_list_low = 0x0000;
can_general.can_filter.filter_mask_high = 0x5000<<1;
can_general.can_filter.filter_mask_low = 0x0000;
can_general.can_filter.filter_fifo_number = CAN_FIFO1;
can_filter_init(&can_general.can_filter);
if(can_general.can_rx_use_interrupt == SET)//打開CAN接收中斷
{
can_interrupt_enable(can_general.can_port, CAN_INT_RFNE0);
can_interrupt_enable(can_general.can_port, CAN_INT_RFNE1);
}
}
其中波特率CAN_BAUDRATE在driver_can.h中預(yù)定義:
C
/* select CAN baudrate */
/* 1MBps */
/* 500kBps */
/* #define CAN_BAUDRATE ?500 */
/* 250kBps */
/* #define CAN_BAUDRATE ?250 */
/* 125kBps */
/* #define CAN_BAUDRATE ?125 */
/* 100kBps */
/* #define CAN_BAUDRATE ?100 */
/* 50kBps */
/* #define CAN_BAUDRATE ?50 */
/* 20kBps */
/* #define CAN_BAUDRATE ?20 */
30.4.2 CAN 發(fā)送函數(shù)
在driver_can.c中定義了CAN發(fā)送函數(shù):
C
void driver_can_transmit(typdef_can_general can_general,can_trasnmit_message_struct *transmit_message)
{
can_message_transmit(can_general.can_port,transmit_message);
}
30.4.3 CAN中斷接收函數(shù)
在bsp_can.c中定義了CAN FIFO0和FIFO1的中斷接收處理函數(shù):
C
void can0_rx0_interrupt_handler(void)
{
can_message_receive(CAN0, CAN_FIFO0, &can0_receive_message_fifo0);//將數(shù)據(jù)從FIFO中轉(zhuǎn)移到接收寄存器組中
if((0x300 == can0_receive_message_fifo0.rx_sfid)&&(CAN_FF_STANDARD == can0_receive_message_fifo0.rx_ff)&&(2 == can0_receive_message_fifo0.rx_dlen)){
can0_receive_fifo0_flag = SET;
}else{
can0_receive_fifo0_flag = RESET;
}
}
C
void can0_rx1_interrupt_handler(void)
{
can_message_receive(CAN0, CAN_FIFO1, &can0_receive_message_fifo1);//將數(shù)據(jù)從FIFO中轉(zhuǎn)移到接收寄存器組中
if((0x500 == can0_receive_message_fifo1.rx_sfid)&&(CAN_FF_STANDARD == can0_receive_message_fifo1.rx_ff)&&(2 == can0_receive_message_fifo1.rx_dlen)){
can0_receive_fifo1_flag = SET;
}else{
can0_receive_fifo1_flag = RESET;
}
}
30.4.4 main函數(shù)實現(xiàn)
main函數(shù)實現(xiàn)如下:
C
int main(void)
{
driver_init();//delay函數(shù)初始化
bsp_uart_init(&BOARD_UART);//BOARD_UART串口初始化
bsp_can_config(BSP_CAN);//BOARD_CAN初始化
nvic_irq_enable(USBD_LP_CAN0_RX0_IRQn,0,0);//使能CAN0 FIFO0 NVIC
nvic_irq_enable(CAN0_RX1_IRQn,0,0);//使能CAN0 FIFO1 NVIC
while (1)
{
bsp_can_transmit(BSP_CAN,&bsp_can_transmit_message_1);//發(fā)送一幀數(shù)據(jù)
printf("\r\n can0 transmit data:%x,%x", bsp_can_transmit_message_1.tx_data[0], bsp_can_transmit_message_1.tx_data[1]);//發(fā)送數(shù)據(jù)打印
delay_ms(1000); //延時1s
bsp_can_transmit(BSP_CAN,&bsp_can_transmit_message_2);//發(fā)送一幀數(shù)據(jù)
printf("\r\n can0 transmit data:%x,%x", bsp_can_transmit_message_2.tx_data[0], bsp_can_transmit_message_2.tx_data[1]);//發(fā)送數(shù)據(jù)打印
delay_ms(1000);
bsp_can_transmit(BSP_CAN,&bsp_can_transmit_message_3);//發(fā)送一幀數(shù)據(jù)
printf("\r\n can0 transmit data:%x,%x", bsp_can_transmit_message_3.tx_data[0], bsp_can_transmit_message_3.tx_data[1]);//發(fā)送數(shù)據(jù)打印
delay_ms(1000);
if(can0_receive_fifo0_flag == SET)
{
printf("\r\n can0_fifo0 receive ID = %x data:%x,%x", can0_receive_message_fifo0.rx_sfid,can0_receive_message_fifo0.rx_data[0], can0_receive_message_fifo0.rx_data[1]);//接收數(shù)據(jù)打印
can0_receive_fifo0_flag = RESET; //標志位清除
}
if(can0_receive_fifo1_flag == SET)
{
printf("\r\n can0_fifo1 receive ID = %x data:%x,%x", can0_receive_message_fifo1.rx_sfid,can0_receive_message_fifo1.rx_data[0], can0_receive_message_fifo1.rx_data[1]);//接收數(shù)據(jù)打印
can0_receive_fifo1_flag = RESET;
}
}
}
BSP_CAN實參結(jié)構(gòu)體初始化在bsp_can.c中:
C
typdef_can_general BSP_CAN =
{
.can_port = CAN0,
.rcu_can = RCU_CAN0,
.rcu_IO_port = RCU_GPIOB,
.IO_port = GPIOB,
.pin_tx = GPIO_PIN_9,
.pin_rx = GPIO_PIN_8,
.can_remap = GPIO_CAN_PARTIAL_REMAP,
.gpio_speed = GPIO_OSPEED_50MHZ ???????,
.can_rx_use_interrupt = SET
};
main函數(shù)中實現(xiàn)的功能是每隔1s,分別發(fā)送幀ID為0x300,0x500和0x400的文到CAN總線,每幀發(fā)送兩個數(shù)據(jù),數(shù)據(jù)結(jié)構(gòu)體初始化在bsp_can.c中:
C
can_trasnmit_message_struct bsp_can_transmit_message_1 = {
.tx_sfid = 0x300,
.tx_efid = 0x00,
.tx_ft = CAN_FT_DATA,
.tx_ff = CAN_FF_STANDARD,
.tx_dlen = 2,
.tx_data[0] = 0x55,
.tx_data[1] = 0xAA,
};
can_trasnmit_message_struct bsp_can_transmit_message_2 = {
.tx_sfid = 0x500,
.tx_efid = 0x00,
.tx_ft = CAN_FT_DATA,
.tx_ff = CAN_FF_STANDARD,
.tx_dlen = 2,
.tx_data[0] = 0x01,
.tx_data[1] = 0x02,
};
can_trasnmit_message_struct bsp_can_transmit_message_3 = {
.tx_sfid = 0x400,
.tx_efid = 0x00,
.tx_ft = CAN_FT_DATA,
.tx_ff = CAN_FF_STANDARD,
.tx_dlen = 2,
.tx_data[0] = 0x02,
.tx_data[1] = 0x01,
};
因為使用了回環(huán)模式,故發(fā)送的報文同時也會被CAN接收,而由于過濾器的配置,ID為0x300的會被接收到FIFO0中,ID為0x500的會被接收到FIFO1中,而ID為0x400的由于無法通過過濾器,被CAN舍棄。
30.5 實驗結(jié)果
使用USB-TypeC線,連接電腦和板上USB to UART口后,配置好串口調(diào)試助手,即可看到CAN發(fā)送和接收數(shù)據(jù)的情況: