第40章 CAN—通訊實驗
全套200集視頻教程和1000頁PDF教程請到秉火論壇下載:www.firebbs.cn
野火視頻教程優酷觀看網址:http://i.youku.com/firege
本章參考資料:《STM32F4xx 中文參考手冊2》、《STM32F4xx規格書》、庫幫助文檔《stm32f4xx_dsp_stdperiph_lib_um.chm》。
若對CAN通訊協議不了解,可先閱讀《CAN總線入門》、《CAN-bus規范》文檔內容學習。
關於實驗板上的CAN收發器可查閱《TJA1050》文檔了解。
40.1 CAN協議簡介
CAN是控制器局域網絡(Controller Area Network)的簡稱,它是由研發和生產汽車電子產品著稱的德國BOSCH公司開發的,並最終成為國際標准(ISO11519),是國際上應用最廣泛的現場總線之一。
CAN總線協議已經成為汽車計算機控制系統和嵌入式工業控制局域網的標准總線,並且擁有以CAN為底層協議專為大型貨車和重工機械車輛設計的J1939協議。近年來,它具有的高可靠性和良好的錯誤檢測能力受到重視,被廣泛應用於汽車計算機控制系統和環境溫度惡劣、電磁輻射強及振動大的工業環境。
40.1.1 CAN物理層
與I2C、SPI等具有時鍾信號的同步通訊方式不同,CAN通訊並不是以時鍾信號來進行同步的,它是一種異步通訊,只具有CAN_High和CAN_Low兩條信號線,共同構成一組差分信號線,以差分信號的形式進行通訊。
1. 閉環總線網絡
CAN物理層的形式主要有兩種,圖 401中的CAN通訊網絡是一種遵循ISO11898標准的高速、短距離"閉環網絡",它的總線最大長度為40m,通信速度最高為1Mbps,總線的兩端各要求有一個"120歐"的電阻。
圖 401 CAN閉環總線通訊網絡
2. 開環總線網絡
圖 402中的是遵循ISO11519-2標准的低速、遠距離"開環網絡",它的最大傳輸距離為1km,最高通訊速率為125kbps,兩根總線是獨立的、不形成閉環,要求每根總線上各串聯有一個"2.2千歐"的電阻。
圖 402 CAN開環總線通訊網絡
3. 通訊節點
從CAN通訊網絡圖可了解到,CAN總線上可以掛載多個通訊節點,節點之間的信號經過總線傳輸,實現節點間通訊。由於CAN通訊協議不對節點進行地址編碼,而是對數據內容進行編碼的,所以網絡中的節點個數理論上不受限制,只要總線的負載足夠即可,可以通過中繼器增強負載。
CAN通訊節點由一個CAN控制器及CAN收發器組成,控制器與收發器之間通過CAN_Tx及CAN_Rx信號線相連,收發器與CAN總線之間使用CAN_High及CAN_Low信號線相連。其中CAN_Tx及CAN_Rx使用普通的類似TTL邏輯信號,而CAN_High及CAN_Low是一對差分信號線,使用比較特別的差分信號,下一小節再詳細說明。
當CAN節點需要發送數據時,控制器把要發送的二進制編碼通過CAN_Tx線發送到收發器,然后由收發器把這個普通的邏輯電平信號轉化成差分信號,通過差分線CAN_High和CAN_Low線輸出到CAN總線網絡。而通過收發器接收總線上的數據到控制器時,則是相反的過程,收發器把總線上收到的CAN_High及CAN_Low信號轉化成普通的邏輯電平信號,通過CAN_Rx輸出到控制器中。
例如,STM32的CAN片上外設就是通訊節點中的控制器,為了構成完整的節點,還要給它外接一個收發器,在我們實驗板中使用型號為TJA1050的芯片作為CAN收發器。CAN控制器與CAN收發器的關系如同TTL串口與MAX3232電平轉換芯片的關系,MAX3232芯片把TTL電平的串口信號轉換成RS-232電平的串口信號,CAN收發器的作用則是把CAN控制器的TTL電平信號轉換成差分信號(或者相反)。
4. 差分信號
差分信號又稱差模信號,與傳統使用單根信號線電壓表示邏輯的方式有區別,使用差分信號傳輸時,需要兩根信號線,這兩個信號線的振幅相等,相位相反,通過兩根信號線的電壓差值來表示邏輯0和邏輯1。見圖 403,它使用了V+與V-信號的差值表達出了圖下方的信號。
圖 403 差分信號
相對於單信號線傳輸的方式,使用差分信號傳輸具有如下優點:
抗干擾能力強,當外界存在噪聲干擾時,幾乎會同時耦合到兩條信號線上,而接收端只關心兩個信號的差值,所以外界的共模噪聲可以被完全抵消。
能有效抑制它對外部的電磁干擾,同樣的道理,由於兩根信號的極性相反,他們對外輻射的電磁場可以相互抵消,耦合的越緊密,泄放到外界的電磁能量越少。
時序定位精確,由於差分信號的開關變化是位於兩個信號的交點,而不像普通單端信號依靠高低兩個閾值電壓判斷,因而受工藝,溫度的影響小,能降低時序上的誤差,同時也更適合於低幅度信號的電路。
由於差分信號線具有這些優點,所以在USB協議、485協議、以太網協議及CAN協議的物理層中,都使用了差分信號傳輸。
5. CAN協議中的差分信號
CAN協議中對它使用的CAN_High及CAN_Low表示的差分信號做了規定,見表 401及圖 404。以高速CAN協議為例,當表示邏輯1時(隱性電平),CAN_High和CAN_Low線上的電壓均為2.5v,即它們的電壓差VH-VL=0V;而表示邏輯0時(顯性電平),CAN_High的電平為3.5V,CAN_Low線的電平為1.5V,即它們的電壓差為VH-VL=2V。例如,當CAN收發器從CAN_Tx線接收到來自CAN控制器的低電平信號時(邏輯0),它會使CAN_High輸出3.5V,同時CAN_Low輸出1.5V,從而輸出顯性電平表示邏輯0。
表 401 CAN協議標准表示的信號邏輯
信號 |
ISO11898(高速) |
ISO11519-2(低速) |
||||||||||
隱性(邏輯1) |
顯性(邏輯0) |
隱性(邏輯1) |
顯性(邏輯0) |
|||||||||
最小值 |
典型值 |
最大值 |
最小值 |
典型值 |
最大值 |
最小值 |
典型值 |
最大值 |
最小值 |
典型值 |
最大值 |
|
CAN_High(V) |
2.0 |
2.5 |
3.0 |
2.75 |
3.5 |
4.5 |
1.6 |
1.75 |
1.9 |
3.85 |
4.0 |
5.0 |
CAN_Low(V) |
2.0 |
2.5 |
3.0 |
0.5 |
1.5 |
2.25 |
3.10 |
3.25 |
3.4 |
0 |
1.0 |
1.15 |
High-Low電位差 (V) |
-0.5 |
0 |
0.05 |
1.5 |
2.0 |
3.0 |
-0.3 |
-1.5 |
- |
0.3 |
3.0 |
- |
圖 404 CAN的差分信號(高速)
在CAN總線中,必須使它處於隱性電平(邏輯1)或顯性電平(邏輯0)中的其中一個狀態。假如有兩個CAN通訊節點,在同一時間,一個輸出隱性電平,另一個輸出顯性電平,類似I2C總線的"線與"特性將使它處於顯性電平狀態,顯性電平的名字就是這樣來的,即可以認為顯性具有優先的意味。
由於CAN總線協議的物理層只有1對差分線,在一個時刻只能表示一個信號,所以對通訊節點來說,CAN通訊是半雙工的,收發數據需要分時進行。在CAN的通訊網絡中,因為共用總線,在整個網絡中同一時刻只能有一個通訊節點發送信號,其余的節點在該時刻都只能接收。
40.1.2 協議層
以上是CAN的物理層標准,約定了電氣特性,以下介紹的協議層則規定了通訊邏輯。
1. CAN的波特率及位同步
由於CAN屬於異步通訊,沒有時鍾信號線,連接在同一個總線網絡中的各個節點會像串口異步通訊那樣,節點間使用約定好的波特率進行通訊,特別地,CAN還會使用"位同步"的方式來抗干擾、吸收誤差,實現對總線電平信號進行正確的采樣,確保通訊正常。
位時序分解
為了實現位同步,CAN協議把每一個數據位的時序分解成如圖 405所示的SS段、PTS段、PBS1段、PBS2段,這四段的長度加起來即為一個CAN數據位的長度。分解后最小的時間單位是Tq,而一個完整的位由8~25個Tq組成。為方便表示,圖 405中的高低電平直接代表信號邏輯0或邏輯1(不是差分信號)。
圖 405 CAN位時序分解圖
該圖中表示的CAN通訊信號每一個數據位的長度為19Tq,其中SS段占1Tq,PTS段占6Tq,PBS1段占5Tq,PBS2段占7Tq。信號的采樣點位於PBS1段與PBS2段之間,通過控制各段的長度,可以對采樣點的位置進行偏移,以便准確地采樣。
各段的作用如介紹下:
SS段(SYNC SEG)
SS譯為同步段,若通訊節點檢測到總線上信號的跳變沿被包含在SS段的范圍之內,則表示節點與總線的時序是同步的,當節點與總線同步時,采樣點采集到的總線電平即可被確定為該位的電平。SS段的大小固定為1Tq。
PTS段(PROP SEG)
PTS譯為傳播時間段,這個時間段是用於補償網絡的物理延時時間。是總線上輸入比較器延時和輸出驅動器延時總和的兩倍。PTS段的大小可以為1~8Tq。
PBS1段(PHASE SEG1),
PBS1譯為相位緩沖段,主要用來補償邊沿階段的誤差,它的時間長度在重新同步的時候可以加長。PBS1段的初始大小可以為1~8Tq。
PBS2段(PHASE SEG2)
PBS2這是另一個相位緩沖段,也是用來補償邊沿階段誤差的,它的時間長度在重新同步時可以縮短。PBS2段的初始大小可以為2~8Tq。
通訊的波特率
總線上的各個通訊節點只要約定好1個Tq的時間長度以及每一個數據位占據多少個Tq,就可以確定CAN通訊的波特率。
例如,假設上圖中的1Tq=1us,而每個數據位由19個Tq組成,則傳輸一位數據需要時間T1bit =19us,從而每秒可以傳輸的數據位個數為:
1x106/19 = 52631.6 (bps)
這個每秒可傳輸的數據位的個數即為通訊中的波特率。
同步過程分析
波特率只是約定了每個數據位的長度,數據同步還涉及到相位的細節,這個時候就需要用到數據位內的SS、PTS、PBS1及PBS2段了。
根據對段的應用方式差異,CAN的數據同步分為硬同步和重新同步。其中硬同步只是當存在"幀起始信號"時起作用,無法確保后續一連串的位時序都是同步的,而重新同步方式可解決該問題,這兩種方式具體介紹如下:
(1) 硬同步
若某個CAN節點通過總線發送數據時,它會發送一個表示通訊起始的信號(即下一小節介紹的幀起始信號),該信號是一個由高變低的下降沿。而掛載到CAN總線上的通訊節點在不發送數據時,會時刻檢測總線上的信號。
見圖 406,可以看到當總線出現幀起始信號時,某節點檢測到總線的幀起始信號不在節點內部時序的SS段范圍,所以判斷它自己的內部時序與總線不同步,因而這個狀態的采樣點采集得的數據是不正確的。所以節點以硬同步的方式調整,把自己的位時序中的SS段平移至總線出現下降沿的部分,獲得同步,同步后采樣點就可以采集得正確數據了。
圖 406 硬同步過程圖
(2) 重新同步
前面的硬同步只是當存在幀起始信號時才起作用,如果在一幀很長的數據內,節點信號與總線信號相位有偏移時,這種同步方式就無能為力了。因而需要引入重新同步方式,它利用普通數據位的高至低電平的跳變沿來同步(幀起始信號是特殊的跳變沿)。重新同步與硬同步方式相似的地方是它們都使用SS段來進行檢測,同步的目的都是使節點內的SS段把跳變沿包含起來。
重新同步的方式分為超前和滯后兩種情況,以總線跳變沿與SS段的相對位置進行區分。第一種相位超前的情況如圖 407,節點從總線的邊沿跳變中,檢測到它內部的時序比總線的時序相對超前2Tq,這時控制器在下一個位時序中的PBS1段增加2Tq的時間長度,使得節點與總線時序重新同步。
圖 407 相位超前時的重新同步
第二種相位滯后的情況如圖 408,節點從總線的邊沿跳變中,檢測到它的時序比總線的時序相對滯后2Tq,這時控制器在前一個位時序中的PBS2段減少2Tq的時間長度,獲得同步。
圖 408 相位滯后時的重新同步
在重新同步的時候,PBS1和PBS2中增加或減少的這段時間長度被定義為"重新同步補償寬度SJW (reSynchronization Jump Width)"。一般來說CAN控制器會限定SJW的最大值,如限定了最大SJW=3Tq時,單次同步調整的時候不能增加或減少超過3Tq的時間長度,若有需要,控制器會通過多次小幅度調整來實現同步。當控制器設置的SJW極限值較大時,可以吸收的誤差加大,但通訊的速度會下降。
2. CAN的報文種類及結構
在SPI通訊中,片選、時鍾信號、數據輸入及數據輸出這4個信號都有單獨的信號線,I2C協議包含有時鍾信號及數據信號2條信號線,異步串口包含接收與發送2條信號線,這些協議包含的信號都比CAN協議要豐富,它們能輕易進行數據同步或區分數據傳輸方向。而CAN使用的是兩條差分信號線,只能表達一個信號,簡潔的物理層決定了CAN必然要配上一套更復雜的協議,如何用一個信號通道實現同樣、甚至更強大的功能呢?CAN協議給出的解決方案是對數據、操作命令(如讀/寫)以及同步信號進行打包,打包后的這些內容稱為報文。
報文的種類
在原始數據段的前面加上傳輸起始標簽、片選(識別)標簽和控制標簽,在數據的尾段加上CRC校驗標簽、應答標簽和傳輸結束標簽,把這些內容按特定的格式打包好,就可以用一個通道表達各種信號了,各種各樣的標簽就如同SPI中各種通道上的信號,起到了協同傳輸的作用。當整個數據包被傳輸到其它設備時,只要這些設備按格式去解讀,就能還原出原始數據,這樣的報文就被稱為CAN的"數據幀"。
為了更有效地控制通訊,CAN一共規定了5種類型的幀,它們的類型及用途說明如表 402。
表 402 幀的種類及其用途
幀 |
幀用途 |
數據幀 |
用於節點向外傳送數據 |
遙控幀 |
用於向遠端節點請求數據 |
錯誤幀 |
用於向遠端節點通知校驗錯誤,請求重新發送上一個數據 |
過載幀 |
用於通知遠端節點:本節點尚未做好接收准備 |
幀間隔 |
用於將數據幀及遙控幀與前面的幀分離開來 |
數據幀的結構
數據幀是在CAN通訊中最主要、最復雜的報文,我們來了解它的結構,見圖 409。
圖 409 數據幀的結構
數據幀以一個顯性位(邏輯0)開始,以7個連續的隱性位(邏輯1)結束,在它們之間,分別有仲裁段、控制段、數據段、CRC段和ACK段。
幀起始
SOF段(Start Of Frame),譯為幀起始,幀起始信號只有一個數據位,是一個顯性電平,它用於通知各個節點將有數據傳輸,其它節點通過幀起始信號的電平跳變沿來進行硬同步。
仲裁段
當同時有兩個報文被發送時,總線會根據仲裁段的內容決定哪個數據包能被傳輸,這也是它名稱的由來。
仲裁段的內容主要為本數據幀的ID信息(標識符),數據幀具有標准格式和擴展格式兩種,區別就在於ID信息的長度,標准格式的ID為11位,擴展格式的ID為29位,它在標准ID的基礎上多出18位。在CAN協議中,ID起着重要的作用,它決定着數據幀發送的優先級,也決定着其它節點是否會接收這個數據幀。CAN協議不對掛載在它之上的節點分配優先級和地址,對總線的占有權是由信息的重要性決定的,即對於重要的信息,我們會給它打包上一個優先級高的ID,使它能夠及時地發送出去。也正因為它這樣的優先級分配原則,使得CAN的擴展性大大加強,在總線上增加或減少節點並不影響其它設備。
報文的優先級,是通過對ID的仲裁來確定的。根據前面對物理層的分析我們知道如果總線上同時出現顯性電平和隱性電平,總線的狀態會被置為顯性電平,CAN正是利用這個特性進行仲裁。
若兩個節點同時競爭CAN總線的占有權,當它們發送報文時,若首先出現隱性電平,則會失去對總線的占有權,進入接收狀態。見圖 4010,在開始階段,兩個設備發送的電平一樣,所以它們一直繼續發送數據。到了圖中箭頭所指的時序處,節點單元1發送的為隱性電平,而此時節點單元2發送的為顯性電平,由於總線的"線與"特性使它表達出顯示電平,因此單元2競爭總線成功,這個報文得以被繼續發送出去。
圖 4010 仲裁過程
仲裁段ID的優先級也影響着接收設備對報文的反應。因為在CAN總線上數據是以廣播的形式發送的,所有連接在CAN總線的節點都會收到所有其它節點發出的有效數據,因而我們的CAN控制器大多具有根據ID過濾報文的功能,它可以控制自己只接收某些ID的報文。
回看圖 409中的數據幀格式,可看到仲裁段除了報文ID外,還有RTR、IDE和SRR位。
(1) RTR位(Remote Transmission Request Bit),譯作遠程傳輸請求位,它是用於區分數據幀和遙控幀的,當它為顯性電平時表示數據幀,隱性電平時表示遙控幀。
(2) IDE位(Identifier Extension Bit),譯作標識符擴展位,它是用於區分標准格式與擴展格式,當它為顯性電平時表示標准格式,隱性電平時表示擴展格式。
(3) SRR位(Substitute Remote Request Bit),只存在於擴展格式,它用於替代標准格式中的RTR位。由於擴展幀中的SRR位為隱性位,RTR在數據幀為顯性位,所以在兩個ID相同的標准格式報文與擴展格式報文中,標准格式的優先級較高。
控制段
在控制段中的r1和r0為保留位,默認設置為顯性位。它最主要的是DLC段(Data Length Code),譯為數據長度碼,它由4個數據位組成,用於表示本報文中的數據段含有多少個字節,DLC段表示的數字為0~8。
數據段
數據段為數據幀的核心內容,它是節點要發送的原始信息,由0~8個字節組成,MSB先行。
CRC段
為了保證報文的正確傳輸,CAN的報文包含了一段15位的CRC校驗碼,一旦接收節點算出的CRC碼跟接收到的CRC碼不同,則它會向發送節點反饋出錯信息,利用錯誤幀請求它重新發送。CRC部分的計算一般由CAN控制器硬件完成,出錯時的處理則由軟件控制最大重發數。
在CRC校驗碼之后,有一個CRC界定符,它為隱性位,主要作用是把CRC校驗碼與后面的ACK段間隔起來。
ACK段
ACK段包括一個ACK槽位,和ACK界定符位。類似I2C總線,在ACK槽位中,發送節點發送的是隱性位,而接收節點則在這一位中發送顯性位以示應答。在ACK槽和幀結束之間由ACK界定符間隔開。
幀結束
EOF段(End Of Frame),譯為幀結束,幀結束段由發送節點發送的7個隱性位表示結束。
其它報文的結構
關於其它的CAN報文結構,不再展開講解,其主要內容見圖 4011。
圖 4011 各種CAN報文的結構
40.2 STM32的CAN外設簡介
STM32的芯片中具有bxCAN控制器 (Basic Extended CAN),它支持CAN協議2.0A和2.0B標准。
該CAN控制器支持最高的通訊速率為1Mb/s;可以自動地接收和發送CAN報文,支持使用標准ID和擴展ID的報文;外設中具有3個發送郵箱,發送報文的優先級可以使用軟件控制,還可以記錄發送的時間;具有2個3級深度的接收FIFO,可使用過濾功能只接收或不接收某些ID號的報文;可配置成自動重發;不支持使用DMA進行數據收發。
40.2.1 STM32的CAN架構剖析
圖 4012 STM32的CAN外設架構圖
STM32的有兩組CAN控制器,其中CAN1是主設備,框圖中的"存儲訪問控制器"是由CAN1控制的,CAN2無法直接訪問存儲區域,所以使用CAN2的時候必須使能CAN1外設的時鍾。框圖中主要包含CAN控制內核、發送郵箱、接收FIFO以及驗收篩選器,下面對框圖中的各個部分進行介紹。
1. CAN控制內核
框圖中標號處的CAN控制內核包含了各種控制寄存器及狀態寄存器,我們主要講解其中的主控制寄存器CAN_MCR及位時序寄存器CAN_BTR。
主控制寄存器CAN_MCR
主控制寄存器CAN_MCR負責管理CAN的工作模式,它使用以下寄存器位實現控制。
(1) DBF調試凍結功能
DBF(Debug freeze)調試凍結,使用它可設置CAN處於工作狀態或禁止收發的狀態,禁止收發時仍可訪問接收FIFO中的數據。這兩種狀態是當STM32芯片處於程序調試模式時才使用的,平時使用並不影響。
(2) TTCM時間觸發模式
TTCM(Time triggered communication mode)時間觸發模式,它用於配置CAN的時間觸發通信模式,在此模式下,CAN使用它內部定時器產生時間戳,並把它保存在CAN_RDTxR、CAN_TDTxR寄存器中。內部定時器在每個CAN位時間累加,在接收和發送的幀起始位被采樣,並生成時間戳。利用它可以實現ISO 11898-4 CAN標准的分時同步通信功能。
(3) ABOM自動離線管理
ABOM(Automatic bus-off management) 自動離線管理,它用於設置是否使用自動離線管理功能。當節點檢測到它發送錯誤或接收錯誤超過一定值時,會自動進入離線狀態,在離線狀態中,CAN不能接收或發送報文。處於離線狀態的時候,可以軟件控制恢復或者直接使用這個自動離線管理功能,它會在適當的時候自動恢復。
(4) AWUM自動喚醒
AWUM(Automatic bus-off management),自動喚醒功能,CAN外設可以使用軟件進入低功耗的睡眠模式,如果使能了這個自動喚醒功能,當CAN檢測到總線活動的時候,會自動喚醒。
(5) NART自動重傳
NART(No automatic retransmission)報文自動重傳功能,設置這個功能后,當報文發送失敗時會自動重傳至成功為止。若不使用這個功能,無論發送結果如何,消息只發送一次。
(6) RFLM鎖定模式
RFLM(Receive FIFO locked mode)FIFO鎖定模式,該功能用於鎖定接收FIFO。鎖定后,當接收FIFO溢出時,會丟棄下一個接收的報文。若不鎖定,則下一個接收到的報文會覆蓋原報文。
(7) TXFP報文發送優先級的判定方法
TXFP(Transmit FIFO priority)報文發送優先級的判定方法,當CAN外設的發送郵箱中有多個待發送報文時,本功能可以控制它是根據報文的ID優先級還是報文存進郵箱的順序來發送。
位時序寄存器(CAN_BTR)及波特率
CAN外設中的位時序寄存器CAN_BTR用於配置測試模式、波特率以及各種位內的段參數。
(1) 測試模式
為方便調試,STM32的CAN提供了測試模式,配置位時序寄存器CAN_BTR的SILM及LBKM寄存器位可以控制使用正常模式、靜默模式、回環模式及靜默回環模式,見圖 4013。
圖 4013 四種工作模式
各個工作模式介紹如下:
正常模式
正常模式下就是一個正常的CAN節點,可以向總線發送數據和接收數據。
靜默模式
靜默模式下,它自己的輸出端的邏輯0數據會直接傳輸到它自己的輸入端,邏輯1可以被發送到總線,所以它不能向總線發送顯性位(邏輯0),只能發送隱性位(邏輯1)。輸入端可以從總線接收內容。由於它只可發送的隱性位不會強制影響總線的狀態,所以把它稱為靜默模式。這種模式一般用於監測,它可以用於分析總線上的流量,但又不會因為發送顯性位而影響總線。
回環模式
回環模式下,它自己的輸出端的所有內容都直接傳輸到自己的輸入端,輸出端的內容同時也會被傳輸到總線上,即也可使用總線監測它的發送內容。輸入端只接收自己發送端的內容,不接收來自總線上的內容。使用回環模式可以進行自檢。
回環靜默模式
回環靜默模式是以上兩種模式的結合,自己的輸出端的所有內容都直接傳輸到自己的輸入端,並且不會向總線發送顯性位影響總線,不能通過總線監測它的發送內容。輸入端只接收自己發送端的內容,不接收來自總線上的內容。這種方式可以在"熱自檢"時使用,即自我檢查的時候,不會干擾總線。
以上說的各個模式,是不需要修改硬件接線的,如當輸出直連輸入時,它是在STM32芯片內部連接的,傳輸路徑不經過STM32的CAN_Tx/Rx引腳,更不經過外部連接的CAN收發器,只有輸出數據到總線或從總線接收的情況下才會經過CAN_Tx/Rx引腳和收發器。
(2) 位時序及波特率
STM32外設定義的位時序與我們前面解釋的CAN標准時序有一點區別,見圖 4014。
圖 4014 STM32中CAN的位時序
STM32的CAN外設位時序中只包含3段,分別是同步段SYNC_SEG、位段BS1及位段BS2,采樣點位於BS1及BS2段的交界處。其中SYNC_SEG段固定長度為1Tq,而BS1及BS2段可以在位時序寄存器CAN_BTR設置它們的時間長度,它們可以在重新同步期間增長或縮短,該長度SJW也可在位時序寄存器中配置。
理解STM32的CAN外設的位時序時,可以把它的BS1段理解為是由前面介紹的CAN標准協議中PTS段與PBS1段合在一起的,而BS2段就相當於PBS2段。
了解位時序后,我們就可以配置波特率了。通過配置位時序寄存器CAN_BTR的TS1[3:0]及TS2[2:0]寄存器位設定BS1及BS2段的長度后,我們就可以確定每個CAN數據位的時間:
BS1段時間:
TS1=Tq x (TS1[3:0] + 1),
BS2段時間:
TS2= Tq x (TS2[2:0] + 1),
一個數據位的時間:
T1bit =1Tq+TS1+TS2 =1+ (TS1[3:0] + 1)+ (TS2[2:0] + 1)= N Tq
其中單個時間片的長度Tq與CAN外設的所掛載的時鍾總線及分頻器配置有關,CAN1和CAN2外設都是掛載在APB1總線上的,而位時序寄存器CAN_BTR中的BRP[9:0]寄存器位可以設置CAN外設時鍾的分頻值,所以:
Tq = (BRP[9:0]+1) x TPCLK
其中的PCLK指APB1時鍾,默認值為45MHz。
最終可以計算出CAN通訊的波特率:
BaudRate = 1/N Tq
例如表 403說明了一種把波特率配置為1Mbps的方式。
表 403 一種配置波特率為1Mbps的方式
參數 |
說明 |
SYNC_SE段 |
固定為1Tq |
BS1段 |
設置為5Tq (實際寫入TS1[3:0]的值為4) |
BS2段 |
設置為3Tq (實際寫入TS2[2:0]的值為2) |
TPCLK |
APB1按默認配置為F=45MHz,TPCLK=1/45M |
CAN外設時鍾分頻 |
設置為5分頻(實際寫入BRP[9:0]的值為4) |
1Tq時間長度 |
Tq = (BRP[9:0]+1) x TPCLK = 5 x 1/45M=1/9M |
1位的時間長度 |
T1bit =1Tq+TS1+TS2 = 1+5+3 = 9Tq |
波特率 |
BaudRate = 1/N Tq = 1/(1/9M x 9)=1Mbps |
2. CAN發送郵箱
回到圖 245中的CAN外設框圖,在標號處的是CAN外設的發送郵箱,它一共有3個發送郵箱,即最多可以緩存3個待發送的報文。
每個發送郵箱中包含有標識符寄存器CAN_TIxR、數據長度控制寄存器CAN_TDTxR及2個數據寄存器CAN_TDLxR、CAN_TDHxR,它們的功能見表 405。
表 404 發送郵箱的寄存器
寄存器名 |
功能 |
標識符寄存器CAN_TIxR |
存儲待發送報文的ID、擴展ID、IDE位及RTR位 |
數據長度控制寄存器CAN_TDTxR |
存儲待發送報文的DLC段 |
低位數據寄存器CAN_TDLxR |
存儲待發送報文數據段的Data0-Data3這四個字節的內容 |
高位數據寄存器CAN_TDHxR |
存儲待發送報文數據段的Data4-Data7這四個字節的內容 |
當我們要使用CAN外設發送報文時,把報文的各個段分解,按位置寫入到這些寄存器中,並對標識符寄存器CAN_TIxR中的發送請求寄存器位TMIDxR_TXRQ置1,即可把數據發送出去。
其中標識符寄存器CAN_TIxR中的STDID寄存器位比較特別。我們知道CAN的標准標識符的總位數為11位,而擴展標識符的總位數為29位的。當報文使用擴展標識符的時候,標識符寄存器CAN_TIxR中的STDID[10:0]等效於EXTID[18:28]位,它與EXTID[17:0]共同組成完整的29位擴展標識符。
3. CAN接收FIFO
圖 245中的CAN外設框圖,在標號處的是CAN外設的接收FIFO,它一共有2個接收FIFO,每個FIFO中有3個郵箱,即最多可以緩存6個接收到的報文。當接收到報文時,FIFO的報文計數器會自增,而STM32內部讀取FIFO數據之后,報文計數器會自減,我們通過狀態寄存器可獲知報文計數器的值,而通過前面主控制寄存器的RFLM位,可設置鎖定模式,鎖定模式下FIFO溢出時會丟棄新報文,非鎖定模式下FIFO溢出時新報文會覆蓋舊報文。
跟發送郵箱類似,每個接收FIFO中包含有標識符寄存器CAN_RIxR、數據長度控制寄存器CAN_RDTxR及2個數據寄存器CAN_RDLxR、CAN_RDHxR,它們的功能見表 405。
表 405 發送郵箱的寄存器
寄存器名 |
功能 |
標識符寄存器CAN_RIxR |
存儲收到報文的ID、擴展ID、IDE位及RTR位 |
數據長度控制寄存器CAN_RDTxR |
存儲收到報文的DLC段 |
低位數據寄存器CAN_RDLxR |
存儲收到報文數據段的Data0-Data3這四個字節的內容 |
高位數據寄存器CAN_RDHxR |
存儲收到報文數據段的Data4-Data7這四個字節的內容 |
通過中斷或狀態寄存器知道接收FIFO有數據后,我們再讀取這些寄存器的值即可把接收到的報文加載到STM32的內存中。
4. 驗收篩選器
圖 245中的CAN外設框圖,在標號處的是CAN外設的驗收篩選器,一共有28個篩選器組,每個篩選器組有2個寄存器,CAN1和CAN2共用的篩選器的。
在CAN 協議中,消息的標識符與節點地址無關,但與消息內容有關。因此,發送節點將報文廣播給所有接收器時,接收節點會根據報文標識符的值來確定軟件是否需要該消息,為了簡化軟件的工作,STM32的CAN外設接收報文前會先使用驗收篩選器檢查,只接收需要的報文到FIFO中。
篩選器工作的時候,可以調整篩選ID的長度及過濾模式。根據篩選ID長度來分類有有以下兩種:
(1) 檢查STDID[10:0]、EXTID[17:0]、IDE 和RTR 位,一共31位。
(2) 檢查STDID[10:0]、RTR、IDE 和EXTID[17:15],一共16位。
通過配置篩選尺度寄存器CAN_FS1R的FSCx位可以設置篩選器工作在哪個尺度。
而根據過濾的方法分為以下兩種模式:
(1) 標識符列表模式,它把要接收報文的ID列成一個表,要求報文ID與列表中的某一個標識符完全相同才可以接收,可以理解為白名單管理。
(2) 掩碼模式,它把可接收報文ID的某幾位作為列表,這幾位被稱為掩碼,可以把它理解成關鍵字搜索,只要掩碼(關鍵字)相同,就符合要求,報文就會被保存到接收FIFO。
通過配置篩選模式寄存器CAN_FM1R的FBMx位可以設置篩選器工作在哪個模式。
不同的尺度和不同的過濾方法可使篩選器工作在圖 4015的4種狀態。
圖 4015 篩選器的4種工作狀態
每組篩選器包含2個32位的寄存器,分別為CAN_FxR1和CAN_FxR2,它們用來存儲要篩選的ID或掩碼,各個寄存器位代表的意義與圖中兩個寄存器下面"映射"的一欄一致,各個模式的說明見表 406。
表 406 篩選器的工作狀態說明
模式 |
說明 |
32位掩碼模式 |
CAN_FxR1存儲ID,CAN_FxR2存儲哪個位必須要與CAN_FxR1中的ID一致,2個寄存器表示1組掩碼。 |
32位標識符模式 |
CAN_FxR1和CAN_FxR2各存儲1個ID,2個寄存器表示2個篩選的ID |
16位掩碼模式 |
CAN_FxR1高16位存儲ID,低16位存儲哪個位必須要與高16位的ID一致; CAN_FxR2高16位存儲ID,低16位存儲哪個位必須要與高16位的ID一致 2個寄存器表示2組掩碼。 |
16位標識符模式 |
CAN_FxR1和CAN_FxR2各存儲2個ID,2個寄存器表示4個篩選的ID |
例如下面的表格所示,在掩碼模式時,第一個寄存器存儲要篩選的ID,第二個寄存器存儲掩碼,掩碼為1的部分表示該位必須與ID中的內容一致,篩選的結果為表中第三行的ID值,它是一組包含多個的ID值,其中x表示該位可以為1可以為0。
ID |
1 |
0 |
1 |
1 |
1 |
0 |
1 |
… |
掩碼 |
1 |
1 |
1 |
0 |
0 |
1 |
0 |
… |
篩選的ID |
1 |
0 |
1 |
x |
x |
0 |
x |
… |
而工作在標識符模式時,2個寄存器存儲的都是要篩選的ID,它只包含2個要篩選的ID值(32位模式時)。
如果使能了篩選器,且報文的ID與所有篩選器的配置都不匹配,CAN外設會丟棄該報文,不存入接收FIFO。
5. 整體控制邏輯
回到圖 245結構框圖,圖中的標號 處表示的是CAN2外設的結構,它與CAN1外設是一樣的,他們共用篩選器且由於存儲訪問控制器由CAN1控制,所以要使用CAN2的時候必須要使能CAN1的時鍾。
40.3 CAN初始化結構體
從STM32的CAN外設我們了解到它的功能非常多,控制涉及的寄存器也非常豐富,而使用STM32標准庫提供的各種結構體及庫函數可以簡化這些控制過程。跟其它外設一樣,STM32標准庫提供了CAN初始化結構體及初始化函數來控制CAN的工作方式,提供了收發報文使用的結構體及收發函數,還有配置控制篩選器模式及ID的結構體。這些內容都定義在庫文件"stm32f4xx_can.h"及"stm32f4xx_can.c"中,編程時我們可以結合這兩個文件內的注釋使用或參考庫幫助文檔。
首先我們來學習初始化結構體的內容,見代碼清單 241。
代碼清單 401 CAN初始化結構體
1 /**
2 * @brief CAN 初始化結構體
3 */
4 typedef struct {
5 uint16_t CAN_Prescaler; /*配置CAN外設的時鍾分頻,可設置為1-1024*/
6 uint8_t CAN_Mode; /*配置CAN的工作模式,回環或正常模式*/
7 uint8_t CAN_SJW; /*配置SJW極限值 */
8 uint8_t CAN_BS1; /*配置BS1段長度*/
9 uint8_t CAN_BS2; /*配置BS2段長度 */
10 FunctionalState CAN_TTCM; /*是否使能TTCM時間觸發功能*/
11 FunctionalState CAN_ABOM; /*是否使能ABOM自動離線管理功能*/
12 FunctionalState CAN_AWUM; /*是否使能AWUM自動喚醒功能 */
13 FunctionalState CAN_NART; /*是否使能NART自動重傳功能*/
14 FunctionalState CAN_RFLM; /*是否使能RFLM鎖定FIFO功能*/
15 FunctionalState CAN_TXFP; /*配置TXFP報文優先級的判定方法*/
16 } CAN_InitTypeDef;
這些結構體成員說明如下,其中括號內的文字是對應參數在STM32標准庫中定義的宏,這些結構體成員都是"40.2.1 1CAN控制內核"小節介紹的內容,可對比閱讀:
(1) CAN_Prescaler
本成員設置CAN外設的時鍾分頻,它可控制時間片Tq的時間長度,這里設置的值最終會減1后再寫入BRP寄存器位,即前面介紹的Tq計算公式:
Tq = (BRP[9:0]+1) x TPCLK
等效於:Tq = CAN_Prescaler x TPCLK
(2) CAN_Mode
本成員設置CAN的工作模式,可設置為正常模式(CAN_Mode_Normal)、回環模式(CAN_Mode_LoopBack)、靜默模式(CAN_Mode_Silent)以及回環靜默模式(CAN_Mode_Silent_LoopBack)。
(3) CAN_SJW
本成員可以配置SJW的極限長度,即CAN重新同步時單次可增加或縮短的最大長度,它可以被配置為1-4Tq(CAN_SJW_1/2/3/4tq)。
(4) CAN_BS1
本成員用於設置CAN位時序中的BS1段的長度,它可以被配置為1-16個Tq長度(CAN_BS1_1/2/3…16tq)。
(5) CAN_BS2
本成員用於設置CAN位時序中的BS2段的長度,它可以被配置為1-8個Tq長度(CAN_BS2_1/2/3…8tq)。
SYNC_SEG、BS1段及BS2段的長度加起來即一個數據位的長度,即前面介紹的原來計算公式:
T1bit =1Tq+TS1+TS2 =1+ (TS1[3:0] + 1)+ (TS2[2:0] + 1)
等效於:T1bit = 1Tq+CAN_BS1+CAN_BS2
(6) CAN_TTCM
本成員用於設置是否使用時間觸發功能(ENABLE/DISABLE),時間觸發功能在某些CAN標准中會使用到。
(7) CAN_ABOM
本成員用於設置是否使用自動離線管理(ENABLE/DISABLE),使用自動離線管理可以在節點出錯離線后適時自動恢復,不需要軟件干預。
(8) CAN_ AWUM
本成員用於設置是否使用自動喚醒功能(ENABLE/DISABLE),使能自動喚醒功能后它會在監測到總線活動后自動喚醒。
(9) CAN_ABOM
本成員用於設置是否使用自動離線管理功能(ENABLE/DISABLE),使用自動離線管理可以在出錯時離線后適時自動恢復,不需要軟件干預。
(10) CAN_NART
本成員用於設置是否使用自動重傳功能(ENABLE/DISABLE),使用自動重傳功能時,會一直發送報文直到成功為止。
(11) CAN_RFLM
本成員用於設置是否使用鎖定接收FIFO(ENABLE/DISABLE),鎖定接收FIFO后,若FIFO溢出時會丟棄新數據,否則在FIFO溢出時以新數據覆蓋舊數據。
(12) CAN_TXFP
本成員用於設置發送報文的優先級判定方法(ENABLE/DISABLE),使能時,以報文存入發送郵箱的先后順序來發送,否則按照報文ID的優先級來發送。
配置完這些結構體成員后,我們調用庫函數CAN_Init即可把這些參數寫入到CAN控制寄存器中,實現CAN的初始化。
40.4 CAN發送及接收結構體
在發送或接收報文時,需要往發送郵箱中寫入報文信息或從接收FIFO中讀取報文信息,利用STM32標准庫的發送及接收結構體可以方便地完成這樣的工作,它們的定義見代碼清單 402。
代碼清單 402 CAN發送及接收結構體
1 /**
2 * @brief CAN Tx message structure definition
3 * 發送結構體
4 */
5 typedef struct {
6 uint32_t StdId; /*存儲報文的標准標識符11位,0-0x7FF. */
7 uint32_t ExtId; /*存儲報文的擴展標識符29位,0-0x1FFFFFFF. */
8 uint8_t IDE; /*存儲IDE擴展標志 */
9 uint8_t RTR; /*存儲RTR遠程幀標志*/
10 uint8_t DLC; /*存儲報文數據段的長度,0-8 */
11 uint8_t Data[8]; /*存儲報文數據段的內容 */
12 } CanTxMsg;
13
14 /**
15 * @brief CAN Rx message structure definition
16 * 接收結構體
17 */
18 typedef struct {
19 uint32_t StdId; /*存儲了報文的標准標識符11位,0-0x7FF. */
20 uint32_t ExtId; /*存儲了報文的擴展標識符29位,0-0x1FFFFFFF. */
21 uint8_t IDE; /*存儲了IDE擴展標志 */
22 uint8_t RTR; /*存儲了RTR遠程幀標志*/
23 uint8_t DLC; /*存儲了報文數據段的長度,0-8 */
24 uint8_t Data[8]; /*存儲了報文數據段的內容 */
25 uint8_t FMI; /*存儲了本報文是由經過篩選器存儲進FIFO的,0-0xFF */
26 } CanRxMsg;
這些結構體成員都是"40.2.1 2CAN發送郵箱及CAN接收FIFO"小節介紹的內容,可對比閱讀,發送結構體與接收結構體是類似的,只是接收結構體多了一個FMI成員,說明如下:
(1) StdId
本成員存儲的是報文的11位標准標識符,范圍是0-0x7FF。
(2) ExtId
本成員存儲的是報文的29位擴展標識符,范圍是0-0x1FFFFFFF。ExtId與StdId這兩個成員根據下面的IDE位配置,只有一個是有效的。
(3) IDE
本成員存儲的是擴展標志IDE位,當它的值為宏CAN_ID_STD時表示本報文是標准幀,使用StdId成員存儲報文ID;當它的值為宏CAN_ID_EXT時表示本報文是擴展幀,使用ExtId成員存儲報文ID。
(4) RTR
本成員存儲的是報文類型標志RTR位,當它的值為宏CAN_RTR_Data時表示本報文是數據幀;當它的值為宏CAN_RTR_Remote時表示本報文是遙控幀,由於遙控幀沒有數據段,所以當報文是遙控幀時,下面的Data[8]成員的內容是無效的。
(5) DLC
本成員存儲的是數據幀數據段的長度,它的值的范圍是0-8,當報文是遙控幀時DLC值為0。
(6) Data[8]
本成員存儲的就是數據幀中數據段的數據。
(7) FMI
本成員只存在於接收結構體,它存儲了篩選器的編號,表示本報文是經過哪個篩選器存儲進接收FIFO的,可以用它簡化軟件處理。
當需要使用CAN發送報文時,先定義一個上面發送類型的結構體,然后把報文的內容按成員賦值到該結構體中,最后調用庫函數CAN_Transmit把這些內容寫入到發送郵箱即可把報文發送出去。
接收報文時,通過檢測標志位獲知接收FIFO的狀態,若收到報文,可調用庫函數CAN_Receive把接收FIFO中的內容讀取到預先定義的接收類型結構體中,然后再訪問該結構體即可利用報文了。
40.5 CAN篩選器結構體
CAN的篩選器有多種工作模式,利用篩選器結構體可方便配置,它的定義見代碼清單 403。
代碼清單 403 CAN篩選器結構體
1 /**
2 * @brief CAN filter init structure definition
3 * CAN篩選器結構體
4 */
5 typedef struct {
6 uint16_t CAN_FilterIdHigh; /*CAN_FxR1寄存器的高16位 */
7 uint16_t CAN_FilterIdLow; /*CAN_FxR1寄存器的低16位*/
8 uint16_t CAN_FilterMaskIdHigh; /*CAN_FxR2寄存器的高16位*/
9 uint16_t CAN_FilterMaskIdLow; /*CAN_FxR2寄存器的低16位 */
10 uint16_t CAN_FilterFIFOAssignment; /*設置經過篩選后數據存儲到哪個接收FIFO */
11 uint8_t CAN_FilterNumber; /*篩選器編號,范圍0-27*/
12 uint8_t CAN_FilterMode; /*篩選器模式 */
13 uint8_t CAN_FilterScale; /*設置篩選器的尺度 */
14 FunctionalState CAN_FilterActivation; /*是否使能本篩選器*/
15 } CAN_FilterInitTypeDef;
這些結構體成員都是"40.2.1 4驗收篩選器"小節介紹的內容,可對比閱讀,各個結構體成員的介紹如下:
(1) CAN_FilterIdHigh
CAN_FilterIdHigh成員用於存儲要篩選的ID,若篩選器工作在32位模式,它存儲的是所篩選ID的高16位;若篩選器工作在16位模式,它存儲的就是一個完整的要篩選的ID。
(2) CAN_FilterIdLow
類似地,CAN_FilterIdLow成員也是用於存儲要篩選的ID,若篩選器工作在32位模式,它存儲的是所篩選ID的低16位;若篩選器工作在16位模式,它存儲的就是一個完整的要篩選的ID。
(3) CAN_FilterMaskIdHigh
CAN_FilterMaskIdHigh存儲的內容分兩種情況,當篩選器工作在標識符列表模式時,它的功能與CAN_FilterIdHigh相同,都是存儲要篩選的ID;而當篩選器工作在掩碼模式時,它存儲的是CAN_FilterIdHigh成員對應的掩碼,與CAN_FilterIdLow組成一組篩選器。
(4) CAN_FilterMaskIdLow
類似地,CAN_FilterMaskIdLow存儲的內容也分兩種情況,當篩選器工作在標識符列表模式時,它的功能與CAN_FilterIdLow相同,都是存儲要篩選的ID;而當篩選器工作在掩碼模式時,它存儲的是CAN_FilterIdLow成員對應的掩碼,與CAN_FilterIdLow組成一組篩選器。
上面四個結構體的存儲的內容很容易讓人糊塗,請結合前面的圖 4014和下面的表 407理解,如果還搞不清楚,再結合庫函數CAN_FilterInit的源碼來分析。
表 407 不同模式下各結構體成員的內容
模式 |
CAN_FilterIdHigh |
CAN_FilterIdLow |
CAN_FilterMaskIdHigh |
CAN_FilterMaskIdLow |
32位列表模式 |
ID1的高16位 |
ID1的低16位 |
ID2的高16位 |
ID2的低16位 |
16位列表模式 |
ID1的完整數值 |
ID2的完整數值 |
ID3的完整數值 |
ID4的完整數值 |
32位掩碼模式 |
ID1的高16位 |
ID1的低16位 |
ID1掩碼的高16位 |
ID1掩碼的低16位 |
16位掩碼模式 |
ID1的完整數值 |
ID2的完整數值 |
ID1掩碼的完整數值 |
ID2掩碼完整數值 |
對這些結構體成員賦值的時候,還要注意寄存器位的映射,即注意哪部分代表STID,哪部分代表EXID以及IDE、RTR位。
(5) CAN_FilterFIFOAssignment
本成員用於設置當報文通過篩選器的匹配后,該報文會被存儲到哪一個接收FIFO,它的可選值為FIFO0或FIFO1(宏CAN_Filter_FIFO0/1)。
(6) CAN_FilterNumber
本成員用於設置篩選器的編號,即本過濾器結構體配置的是哪一組篩選器,CAN一共有28個篩選器,所以它的可輸入參數范圍為0-27。
(7) CAN_FilterMode
本成員用於設置篩選器的工作模式,可以設置為列表模式(宏CAN_FilterMode_IdList)及掩碼模式(宏CAN_FilterMode_IdMask)。
(8) CAN_FilterScale
本成員用於設置篩選器的尺度,可以設置為32位長(宏CAN_FilterScale_32bit)及16位長(宏CAN_FilterScale_16bit)。
(9) CAN_FilterActivation
本成員用於設置是否激活這個篩選器(宏ENABLE/DISABLE)。
配置完這些結構體成員后,我們調用庫函數CAN_FilterInit即可把這些參數寫入到篩選控制寄存器中,從而使用篩選器。我們前面說如果不理解那幾個ID結構體成員存儲的內容時,可以直接閱讀庫函數CAN_FilterInit的源代碼理解,就是因為它直接對寄存器寫入內容,代碼的邏輯是非常清晰的。
40.6 CAN—雙機通訊實驗
本小節演示如何使用STM32的CAN外設實現兩個設備之間的通訊,該實驗中使用了兩個實驗板,如果您只有一個實驗板,也可以使用CAN的回環模式進行測試,不影響學習的。為此,我們提供了"CAN—雙機通訊"及"CAN—回環測試"兩個工程,可根據自己的實驗環境選擇相應的工程來學習。這兩個工程的主體都是一樣的,本教程主要以"CAN—雙機通訊"工程進行講解。
40.6.1 硬件設計
圖 4016 雙CAN通訊實驗硬件連接圖
圖 4016中的是兩個實驗板的硬件連接。在單個實驗板中,作為CAN控制器的STM32引出CAN_Tx和CAN_Rx兩個引腳與CAN收發器TJA1050相連,收發器使用CANH及CANL引腳連接到CAN總線網絡中。為了方便使用,我們每個實驗板引出的CANH及CANL都連接了1個120歐的電阻作為CAN總線的端電阻,所以要注意如果您要把實驗板作為一個普通節點連接到現有的CAN總線時,是不應添加該電阻的!
要實現通訊,我們還要使用導線把實驗板引出的CANH及CANL兩條總線連接起來,才能構成完整的網絡。實驗板之間CANH1與CANH2連接,CANL1與CANL2連接即可。
要注意的是,由於我們的實驗板CAN使用的信號線與液晶屏共用了,為防止干擾,平時我們默認是不給CAN收發器供電的,使用CAN的時候一定要把CAN接線端子旁邊的"C/4-5V"排針使用跳線帽與"5V"排針連接起來進行供電,並且把液晶屏從板子上拔下來。
如果您使用的是單機回環測試的工程實驗,就不需要使用導線連接板子了,而且也不需要給收發器供電,因為回環模式的信號是不經過收發器的,不過,它還是不能和液晶屏同時使用的。
40.6.2 軟件設計
為了使工程更加有條理,我們把CAN控制器相關的代碼獨立分開存儲,方便以后移植。在"串口實驗"之上新建"bsp_can.c"及"bsp_can.h"文件,這些文件也可根據您的喜好命名,它們不屬於STM32標准庫的內容,是由我們自己根據應用需要編寫的。
1. 編程要點
(1) 初始化CAN通訊使用的目標引腳及端口時鍾;
(2) 使能CAN外設的時鍾;
(3) 配置CAN外設的工作模式、位時序以及波特率;
(4) 配置篩選器的工作方式;
(5) 編寫測試程序,收發報文並校驗。
2. 代碼分析
CAN硬件相關宏定義
我們把CAN硬件相關的配置都以宏的形式定義到"bsp_can.h"文件中,見代碼清單 242。
代碼清單 404 CAN硬件配置相關的宏(bsp_can.h文件)
1
2 /*CAN硬件相關的定義*/
3 #define CANx CAN1
4 #define CAN_CLK RCC_APB1Periph_CAN1
5 /*接收中斷號*/
6 #define CAN_RX_IRQ CAN1_RX0_IRQn
7 /*接收中斷服務函數*/
8 #define CAN_RX_IRQHandler CAN1_RX0_IRQHandler
9
10 /*引腳*/
11 #define CAN_RX_PIN GPIO_Pin_8
12 #define CAN_TX_PIN GPIO_Pin_9
13 #define CAN_TX_GPIO_PORT GPIOB
14 #define CAN_RX_GPIO_PORT GPIOB
15 #define CAN_TX_GPIO_CLK RCC_AHB1Periph_GPIOB
16 #define CAN_RX_GPIO_CLK RCC_AHB1Periph_GPIOB
17 #define CAN_AF_PORT GPIO_AF_CAN1
18 #define CAN_RX_SOURCE GPIO_PinSource8
19 #define CAN_TX_SOURCE GPIO_PinSource9
以上代碼根據硬件連接,把與CAN通訊使用的CAN號、引腳號、引腳源以及復用功能映射都以宏封裝起來,並且定義了接收中斷的中斷向量和中斷服務函數,我們通過中斷來獲知接收FIFO的信息。
初始化CAN的 GPIO
利用上面的宏,編寫CAN的初始化函數,見代碼清單 243。
代碼清單 405 CAN的GPIO初始化函數(bsp_can.c文件)
1 /*
2 * 函數名:CAN_GPIO_Config
3 * 描述:CAN的GPIO 配置
4 * 輸入:無
5 * 輸出 : 無
6 * 調用:內部調用
7 */
8 static void CAN_GPIO_Config(void)
9 {
10 GPIO_InitTypeDef GPIO_InitStructure;
11
12 /* 使能GPIO時鍾*/
13 RCC_AHB1PeriphClockCmd(CAN_TX_GPIO_CLK|CAN_RX_GPIO_CLK, ENABLE);
14
15 /* 引腳源*/
16 GPIO_PinAFConfig(CAN_TX_GPIO_PORT, CAN_RX_SOURCE, CAN_AF_PORT);
17 GPIO_PinAFConfig(CAN_RX_GPIO_PORT, CAN_TX_SOURCE, CAN_AF_PORT);
18
19 /* 配置 CAN TX 引腳 */
20 GPIO_InitStructure.GPIO_Pin = CAN_TX_PIN;
21 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
22 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
23 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
24 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
25 GPIO_Init(CAN_TX_GPIO_PORT, &GPIO_InitStructure);
26
27 /* 配置 CAN RX 引腳 */
28 GPIO_InitStructure.GPIO_Pin = CAN_RX_PIN ;
29 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
30 GPIO_Init(CAN_RX_GPIO_PORT, &GPIO_InitStructure);
31 }
與所有使用到GPIO的外設一樣,都要先把使用到的GPIO引腳模式初始化,配置好復用功能。CAN的兩個引腳都配置成通用推挽輸出模式即可。
配置CAN的工作模式
接下來我們配置CAN的工作模式,由於我們是自己用的兩個板子之間進行通訊,波特率之類的配置只要兩個板子一致即可。如果您要使實驗板與某個CAN總線網絡的通訊的節點通訊,那么實驗板的CAN配置必須要與該總線一致。我們實驗中使用的配置見代碼清單 244。
代碼清單 406 配置CAN的工作模式(bsp_can.c文件)
1 /*
2 * 函數名:CAN_Mode_Config
3 * 描述:CAN的模式配置
4 * 輸入:無
5 * 輸出 : 無
6 * 調用:內部調用
7 */
8 static void CAN_Mode_Config(void)
9 {
10 CAN_InitTypeDef CAN_InitStructure;
11 /************************CAN通信參數設置************************/
12 /* Enable CAN clock */
13 RCC_APB1PeriphClockCmd(CAN_CLK, ENABLE);
14
15 /*CAN寄存器初始化*/
16 CAN_DeInit(CAN1);
17 CAN_StructInit(&CAN_InitStructure);
18
19 /*CAN單元初始化*/
20 CAN_InitStructure.CAN_TTCM=DISABLE; //MCR-TTCM 關閉時間觸發通信模式使能
21 CAN_InitStructure.CAN_ABOM=ENABLE; //MCR-ABOM 使能自動離線管理
22 CAN_InitStructure.CAN_AWUM=ENABLE; //MCR-AWUM 使用自動喚醒模式
23 CAN_InitStructure.CAN_NART=DISABLE; //MCR-NART 禁止報文自動重傳
24 CAN_InitStructure.CAN_RFLM=DISABLE; //MCR-RFLM 接收FIFO 不鎖定
25 // 溢出時新報文會覆蓋原有報文
26 CAN_InitStructure.CAN_TXFP=DISABLE; //MCR-TXFP 發送FIFO優先級取決於報文標示符
27 CAN_InitStructure.CAN_Mode = CAN_Mode_Normal; //正常工作模式
28 CAN_InitStructure.CAN_SJW=CAN_SJW_2tq; //BTR-SJW 重新同步跳躍寬度 2個時間單元
29
30 /* ss=1 bs1=5 bs2=3 位時間寬度為(1+5+3) 波特率即為時鍾周期tq*(1+3+5) */
31 CAN_InitStructure.CAN_BS1=CAN_BS1_5tq; //BTR-TS1 時間段1 占用了5個時間單元
32 CAN_InitStructure.CAN_BS2=CAN_BS2_3tq; //BTR-TS1 時間段2 占用了3個時間單元
33
34 /* CAN Baudrate = 1 MBps (1MBps已為stm32的CAN最高速率) (CAN 時鍾頻率為 APB 1 = 45 MHz) */
35 ////BTR-BRP 波特率分頻器定義了時間單元的時間長度 45/(1+5+3)/5=1 Mbps
36 CAN_InitStructure.CAN_Prescaler =5;
37 CAN_Init(CANx, &CAN_InitStructure);
38 }
這段代碼主要是把CAN的模式設置成了正常工作模式,如果您閱讀的是"CAN—回環測試"的工程,這里是被配置成回環模式的,除此之外,兩個工程就沒有其它差別了。
代碼中還把位時序中的BS1和BS2段分別設置成了5Tq和3Tq,再加上SYNC_SEG段,一個CAN數據位就是9Tq了,加上CAN外設的分頻配置為5分頻,CAN所使用的總線時鍾fAPB1 = 45MHz,於是我們可計算出它的波特率:
1Tq = 1/(45M/5)=1/9 us
T1bit=(5+3+1) x Tq =1us
波特率=1/T1bit =1Mbps
配置篩選器
以上是配置CAN的工作模式,為了方便管理接收報文,我們還要把篩選器用起來,見代碼清單 245。
代碼清單 407 配置CAN的篩選器(bsp_can.c文件)
1
2 /*IDE位的標志*/
3 #define CAN_ID_STD ((uint32_t)0x00000000) /*標准ID */
4 #define CAN_ID_EXT ((uint32_t)0x00000004) /*擴展ID */
5
6 /*RTR位的標志*/
7 #define CAN_RTR_Data ((uint32_t)0x00000000) /*數據幀 */
8 #define CAN_RTR_Remote ((uint32_t)0x00000002) /*遠程幀*/
9
10 /************************************************************************/
11 /*
12 * 函數名:CAN_Filter_Config
13 * 描述:CAN的篩選器配置
14 * 輸入:無
15 * 輸出 : 無
16 * 調用:內部調用
17 */
18 static void CAN_Filter_Config(void)
19 {
20 CAN_FilterInitTypeDef CAN_FilterInitStructure;
21
22 /*CAN篩選器初始化*/
23 CAN_FilterInitStructure.CAN_FilterNumber=0; //篩選器組0
24 //工作在掩碼模式
25 CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;
26 //篩選器位寬為單個32位。
27 CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;
28
29 /* 使能篩選器,按照標志符的內容進行比對篩選,
30 擴展ID不是如下的就拋棄掉,是的話,會存入FIFO0。 */
31//要篩選的ID高位,第0位保留,第1位為RTR標志,第2位為IDE標志,從第3位開始是EXID
32CAN_FilterInitStructure.CAN_FilterIdHigh= ((((u32)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF0000)>>16;
33 //要篩選的ID低位
34 CAN_FilterInitStructure.CAN_FilterIdLow= (((u32)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF;
35 //篩選器高16位每位必須匹配
36 CAN_FilterInitStructure.CAN_FilterMaskIdHigh= 0xFFFF;
37 //篩選器低16位每位必須匹配
38 CAN_FilterInitStructure.CAN_FilterMaskIdLow= 0xFFFF;
39 //篩選器被關聯到FIFO0
40 CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0 ;
41 //使能篩選器
42 CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;
43
44 CAN_FilterInit(&CAN_FilterInitStructure);
45 /*CAN通信中斷使能*/
46 CAN_ITConfig(CANx, CAN_IT_FMP0, ENABLE);
47 }
這段代碼把篩選器第0組配置成了32位的掩碼模式,並且把它的輸出連接到接收FIFO0,若通過了篩選器的匹配,報文會被存儲到接收FIFO0。
篩選器配置的重點是配置ID和掩碼,根據我們的配置,這個篩選器工作在圖 4017中的模式。
圖 4017 一個32位的掩碼模式篩選器
在該配置中,結構體成員CAN_FilterIdHigh和CAN_FilterIdLow存儲的是要篩選的ID,而CAN_FilterMaskIdHigh和CAN_FilterMaskIdLow存儲的是相應的掩碼。在賦值時,要注意寄存器位的映射,在32位的ID中,第0位是保留位,第1位是RTR標志,第2位是IDE標志,從第3位起才是報文的ID(擴展ID)。
因此在上述代碼中我們先把擴展ID"0x1314"、IDE位標志"宏CAN_ID_EXT"以及RTR位標志"宏CAN_RTR_DATA"根據寄存器位映射組成一個32位的數據,然后再把它的高16位和低16位分別賦值給結構體成員CAN_FilterIdHigh和CAN_FilterIdLow。
而在掩碼部分,為簡單起見我們直接對所有位賦值為1,表示上述所有標志都完全一樣的報文才能經過篩選,所以我們這個配置相當於單個ID列表的模式,只篩選了一個ID號,而不是篩選一組ID號。這里只是為了演示方便,實際使用中一般會對不要求相等的數據位賦值為0,從而過濾一組ID,如果有需要,還可以繼續配置多個篩選器組,最多可以配置28個,代碼中只是配置了篩選器組0。
對結構體賦值完畢后調用庫函數CAN_FilterInit把個篩選器組的參數寫入到寄存器中。
配置接收中斷
在配置篩選器代碼的最后部分我們還調用庫函數CAN_ITConfig使能了CAN的中斷,該函數使用的輸入參數宏CAN_IT_FMP0表示當FIFO0接收到數據時會引起中斷,該接收中斷的優先級配置如下,見代碼清單 246。
代碼清單 408 配置CAN接收中斷的優先級(bsp_can.c文件)
1 /*接收中斷號*/
2 #define CAN_RX_IRQ CAN1_RX0_IRQn
3 /*
4 * 函數名:CAN_NVIC_Config
5 * 描述:CAN的NVIC 配置,第1優先級組,0,0優先級
6 * 輸入:無
7 * 輸出 : 無
8 * 調用:內部調用
9 */
10 static void CAN_NVIC_Config(void)
11 {
12 NVIC_InitTypeDef NVIC_InitStructure;
13 /* Configure one bit for preemption priority */
14 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
15 /*中斷設置*/
16 NVIC_InitStructure.NVIC_IRQChannel = CAN_RX_IRQ; //CAN RX中斷
17 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
18 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
19 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
20 NVIC_Init(&NVIC_InitStructure);
21 }
這部分與我們配置其它中斷的優先級無異,都是配置NVIC結構體,優先級可根據自己的需要配置,最主要的是中斷向量,上述代碼中把中斷向量配置成了CAN的接收中斷。
設置發送報文
要使用CAN發送報文時,我們需要先定義一個發送報文結構體並向它賦值,見代碼清單 247。
代碼清單 409 設置要發送的報文(bsp_can.c文件)
1 /*IDE位的標志*/
2 #define CAN_ID_STD ((uint32_t)0x00000000) /*標准ID */
3 #define CAN_ID_EXT ((uint32_t)0x00000004) /*擴展ID */
4
5 /*RTR位的標志*/
6 #define CAN_RTR_Data ((uint32_t)0x00000000) /*數據幀 */
7 #define CAN_RTR_Remote ((uint32_t)0x00000002) /*遠程幀*/
8
9 /*
10 * 函數名:CAN_SetMsg
11 * 描述:CAN通信報文內容設置,設置一個數據內容為0-7的數據包
12 * 輸入:無
13 * 輸出 : 無
14 * 調用:外部調用
15 */
16 void CAN_SetMsg(CanTxMsg *TxMessage)
17 {
18 uint8_t ubCounter = 0;
19
20 //TxMessage.StdId=0x00;
21 TxMessage->ExtId=0x1314; //使用的擴展ID
22 TxMessage->IDE=CAN_ID_EXT; //擴展模式
23 TxMessage->RTR=CAN_RTR_DATA; //發送的是數據
24 TxMessage->DLC=8; //數據長度為8字節
25
26 /*設置要發送的數據0-7*/
27 for (ubCounter = 0; ubCounter < 8; ubCounter++)
28 {
29 TxMessage->Data[ubCounter] = ubCounter;
30 }
31 }
這段代碼是我們為了方便演示而自己定義的設置報文內容的函數,它把報文設置成了擴展模式的數據幀,擴展ID為0x1314,數據段的長度為8,且數據內容分別為0-7,實際應用中您可根據自己的需求發設置報文內容。當我們設置好報文內容后,調用庫函數CAN_Transmit即可把該報文存儲到發送郵箱,然后CAN外設會把它發送出去:
CAN_Transmit(CANx, &TxMessage);
接收報文
由於我們設置了接收中斷,所以接收報文的操作是在中斷的服務函數中完成的,見代碼清單 248。
代碼清單 4010 接收報文(stm32f4xx_it.c)
1
2 /*接收中斷服務函數*/
3 #define CAN_RX_IRQHandler CAN1_RX0_IRQHandler
4
5 extern __IO uint32_t flag ; //用於標志是否接收到數據,在中斷函數中賦值
6 extern CanRxMsg RxMessage; //接收緩沖區
7 /********************************************************************/
8 void CAN_RX_IRQHandler(void)
9 {
10 /*從郵箱中讀出報文*/
11 CAN_Receive(CANx, CAN_FIFO0, &RxMessage);
12
13 /* 比較ID是否為0x1314 */
14 if ((RxMessage.ExtId==0x1314) && (RxMessage.IDE==CAN_ID_EXT) && (RxMessage.DLC==8) )
15 {
16 flag = 1; //接收成功
17 }
18 else
19 {
20 flag = 0; //接收失敗
21 }
22 }
根據我們前面的配置,若CAN接收的報文經過篩選器匹配后會被存儲到FIFO0中,並引起中斷進入到這個中斷服務函數中,在這個函數里我們調用了庫函數CAN_Receive把報文從FIFO復制到自定義的接收報文結構體RxMessage中,並且比較了接收到的報文ID是否與我們希望接收的一致,若一致就設置標志flag=1,否則為0,通過flag標志通知主程序流程獲知是否接收到數據。
要注意如果設置了接收報文中斷,必須要在中斷內調用CAN_Receive函數讀取接收FIFO的內容,因為只有這樣才能清除該FIFO的接收中斷標志,如果不在中斷內調用它清除標志的話,一旦接收到報文,STM32會不斷進入中斷服務函數,導致程序卡死。
3. main函數
最后我們來閱讀main函數,了解整個通訊流程,見代碼清單 2414。
代碼清單 4011 main函數
1
2 _IO uint32_t flag = 0; //用於標志是否接收到數據,在中斷函數中賦值
3 CanTxMsg TxMessage; //發送緩沖區
4 CanRxMsg RxMessage; //接收緩沖區
5
6 /**
7 * @brief 主函數
8 * @param 無
9 * @retval 無
10 */
11 int main(void)
12 {
13 LED_GPIO_Config();
14
15 /*初始化USART1*/
16 Debug_USART_Config();
17
18 /*初始化按鍵*/
19 Key_GPIO_Config();
20
21 /*初始化can,在中斷接收CAN數據包*/
22 CAN_Config();
23
24 printf("\r\n歡迎使用秉火 STM32 F429 開發板。\r\n");
25 printf("\r\n秉火F429 CAN通訊實驗例程\r\n");
26
27 printf("\r\n實驗步驟:\r\n");
28
29 printf("\r\n 1.使用導線連接好兩個CAN訊設備\r\n");
30 printf("\r\n 2.使用跳線帽連接好:5v --- C/4-5V \r\n");
31 printf("\r\n 3.按下開發板的KEY1鍵,會使用CAN向外發送0-7的數據包,包的擴展ID為0x1314 \r\n");
32 printf("\r\n 4.若開發板的CAN接收到擴展ID為0x1314的數據包,會把數據以打印到串口。 \r\n");
33 printf("\r\n 5.本例中的can波特率為1MBps,為stm32的can最高速率。 \r\n");
34
35 while (1)
36 {
37 /*按一次按鍵發送一次數據*/
38 if ( Key_Scan(KEY1_GPIO_PORT,KEY1_PIN) == KEY_ON)
39 {
40 LED_BLUE;
41 /*設置要發送的報文*/
42 CAN_SetMsg(&TxMessage);
43 /*把報文存儲到發送郵箱,發送*/
44 CAN_Transmit(CANx, &TxMessage);
45
46 can_delay(10000);//等待發送完畢,可使用CAN_TransmitStatus查看狀態
47
48 LED_GREEN;
49
50 printf("\r\n已使用CAN發送數據包!\r\n");
51 printf("\r\n發送的報文內容為:\r\n");
52 printf("\r\n擴展ID號ExtId:0x%x \r\n",TxMessage.ExtId);
53 CAN_DEBUG_ARRAY(TxMessage.Data,8);
54 }
55 if (flag==1)
56 {
57 LED_GREEN;
58 printf("\r\nCAN接收到數據:\r\n");
59
60 CAN_DEBUG_ARRAY(RxMessage.Data,8);
61
62 flag=0;
63 }
64 }
65 }
在main函數里,我們調用了CAN_Config函數初始化CAN外設,它包含我們前面解說的GPIO初始化函數CAN_GPIO_Config、中斷優先級設置函數CAN_NVIC_Config、工作模式設置函數CAN_Mode_Config以及篩選器配置函數CAN_Filter_Config。
初始化完成后,我們在while循環里檢測按鍵,當按下實驗板的按鍵1時,它就調用CAN_SetMsg函數設置要發送的報文,然后調用CAN_Transmit函數把該報文存儲到發送郵箱,等待CAN外設把它發送出去。代碼中並沒有檢測發送狀態,如果需要,您可以調用庫函數CAN_TransmitStatus檢查發送狀態。
while循環中在其它時間一直檢查flag標志,當接收到報文時,我們的中斷服務函數會把它置1,所以我們可以通過它獲知接收狀態,當接收到報文時,我們把它使用宏CAN_DEBUG_ARRAY輸出到串口。
40.6.3 下載驗證
下載驗證這個CAN實驗時,我們建議您先使用"CAN—回環測試"的工程進行測試,它的環境配置比較簡單,只需要一個實驗板,用USB線使實驗板"USB TO UART"接口跟電腦連接起來,在電腦端打開串口調試助手,並且把編譯好的該工程下載到實驗板,然后復位。這時在串口調試助手可看到CAN測試的調試信息,按一下實驗板上的KEY1按鍵,實驗板會使用回環模式向自己發送報文,在串口調試助手可以看到相應的發送和接收的信息。
使用回環測試成功后,如果您有兩個實驗板,需要按照"硬件設計"小節中的圖例連接兩個板子的CAN總線,並且一定要接上跳線帽給CAN收發器供電、把液晶屏拔掉防止干擾。用USB線使實驗板"USB TO UART"接口跟電腦連接起來,在電腦端打開串口調試助手,然后使用"CAN—雙機通訊"工程編譯,並給兩個板子都下載該程序,然后復位。這時在串口調試助手可看到CAN測試的調試信息,按一下其中一個實驗板上的KEY1按鍵,另一個實驗板會接收到報文,在串口調試助手可以看到相應的發送和接收的信息。
40.7 每課一問
8. 在工程中嘗試修改發送報文的ID,試驗它能不能經過篩選器被接收到。
9. 修改篩選器的掩碼配置,使得它能接收ID為"0x 13xx"的報文。