再造STM32---第二十二部分:CAN—通訊實驗

目錄

一、CAN 協議簡介

1、CAN 物理層

2、協議層

二、STM32 的 CAN 外設簡介

1、STM32 的 CAN 架構剖析

三、CAN 初始化結構體

四、CAN 發送及接收結構體

五、CAN 篩選器結構體

六、CAN—雙機通訊實驗

1、硬件設計

2、軟件設計

3、下載驗證


一、CAN 協議簡介

       CAN 是控制器局域網絡(Controller Area Network)的簡稱, 它是由研發和生產汽車電子產品著稱的德國 BOSCH 公司開發的,並最終成爲國際標準( ISO11519) , 是國際上應用最廣泛的現場總線之一。
       CAN 總線協議已經成爲汽車計算機控制系統嵌入式工業控制局域網標準總線,並且擁有以 CAN 爲底層協議專爲大型貨車和重工機械車輛設計的 J1939 協議。近年來, 它具有的高可靠性和良好的錯誤檢測能力受到重視,被廣泛應用於汽車計算機控制系統和環境溫度惡劣、電磁輻射強及振動大的工業環境。

1、CAN 物理層

       與 I2C、 SPI 等具有時鐘信號的同步通訊方式不同,CAN 通訊並不是以時鐘信號來進行同步的,它是一種異步通訊,只具有 CAN_High CAN_Low 兩條信號線,共同構成一組差分信號線以差分信號的形式進行通訊

(1)閉環總線網絡

       CAN 物理層的形式主要有兩種, 下圖(來自野火論壇)中的 CAN 通訊網絡是一種遵循 ISO11898 標準的高速短距離“閉環網絡”,它的總線最大長度爲 40m,通信速度最高1Mbps,總線的兩端各要求有一個 “ 120 歐 ” 的電阻。

(2)開環總線網絡

       下圖(來自野火論壇)中的是遵循 ISO11519-2 標準的低速遠距離“開環網絡”,它的最大傳輸距離爲 1km最高通訊速率爲 125kbps,兩根總線是獨立的、不形成閉環,要求每根總線上各串聯有一個 “ 2.2 千歐 ” 的電阻。

(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 輸出到控制器中。

(4)差分信號

       差分信號又稱差模信號,與傳統使用單根信號線電壓表示邏輯的方式有區別,使用差分信號傳輸時,需要兩根信號線,這兩個信號線的振幅相等,相位相反,通過兩根信號線的電壓差值來表示邏輯 0 和邏輯 1。見下圖(來自野火論壇),它使用了V +V-信號的差值表達出了圖下方的信號。

相對於單信號線傳輸的方式使用差分信號傳輸具有如下優點

  • 抗干擾能力強,當外界存在噪聲干擾時,幾乎會同時耦合到兩條信號線上,而接收端只關心兩個信號的差值,所以外界的共模噪聲可以被完全抵消。
  • 能有效抑制它對外部的電磁干擾,同樣的道理,由於兩根信號的極性相反,他們對外輻射的電磁場可以相互抵消,耦合的越緊密,泄放到外界的電磁能量越少。
  • 時序定位精確, 由於差分信號的開關變化是位於兩個信號的交點,而不像普通單端信號依靠高低兩個閾值電壓判斷,因而受工藝,溫度的影響小,能降低時序上的誤差,同時也更適合於低幅度信號的電路。
     

由於差分信號線具有這些優點,所以在 USB 協議485 協議以太網協議CAN 協議的物理層中,都使用了差分信號傳輸

(5)CAN 協議中的差分信號
       CAN 協議中對它使用的 CAN_High 及 CAN_Low 表示的差分信號做了規定,見下圖和下表。以高速 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。

       在 CAN 總線中,必須使它處於隱性電平(邏輯 1)或顯性電平(邏輯 0)中的其中一個狀態。假如有兩個 CAN 通訊節點,在同一時間,一個輸出隱性電平,另一個輸出顯性電平,類似 I2C 總線的“線與”特性將使它處於顯性電平狀態,顯性電平的名字就是這樣來的, 即可以認爲顯性具有優先的意味。
       由於 CAN 總線協議的物理層只有 1 對差分線,在一個時刻只能表示一個信號,所以對通訊節點來說, CAN 通訊是半雙工的,收發數據需要分時進行。在 CAN 的通訊網絡中,因爲共用總線,在整個網絡中同一時刻只能有一個通訊節點發送信號其餘的節點在該時刻都只能接收

2、協議層

(1)CAN 的波特率及位同步
       由於 CAN 屬於異步通訊,沒有時鐘信號線,連接在同一個總線網絡中的各個節點會像串口異步通訊那樣,節點間使用約定好的波特率進行通訊,特別地, CAN 還會使用“ 位同步” 的方式來抗干擾、吸收誤差,實現對總線電平信號進行正確的採樣,確保通訊正常。

       位時序分解:

       爲了實現位同步, CAN 協議把每一個數據位的時序分解成如下圖所示的 SS 段、PTS 段、 PBS1 段、 PBS2 段,這四段的長度加起來即爲一個 CAN 數據位的長度。分解後最小的時間單位是 Tq,而一個完整的位由 8~25 個 Tq 組成。爲方便表示, 下圖中的高低電平直接代表信號邏輯 0 或邏輯 1(不是差分信號)。


       該圖中表示的 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 的數據同步分爲硬同步和重新同步。其中硬同步只是當存在“幀起始信號”時起作用,無法確保後續一連串的位時序都是同步的,而重新同步方式可解決該問題,這兩種方式具體介紹如下:

NO.1 硬同步
       若某個 CAN 節點通過總線發送數據時,它會發送一個表示通訊起始的信號(即下一小節介紹的幀起始信號),該信號是一個由高變低的下降沿。而掛載到 CAN 總線上的通訊節點在不發送數據時,會時刻檢測總線上的信號。
       見下圖,可以看到當總線出現幀起始信號時,某節點檢測到總線的幀起始信號不在節點內部時序的 SS 段範圍,所以判斷它自己的內部時序與總線不同步,因而這個狀態的採樣點採集得的數據是不正確的。所以節點以硬同步的方式調整,把自己的位時序中的 SS段平移至總線出現下降沿的部分,獲得同步,同步後採樣點就可以採集得正確數據了。

NO.2 重新同步
       前面的硬同步只是當存在幀起始信號時才起作用,如果在一幀很長的數據內,節點信號與總線信號相位有偏移時,這種同步方式就無能爲力了。因而需要引入重新同步方式,它利用普通數據位的高至低電平的跳變沿來同步(幀起始信號是特殊的跳變沿)。重新同步與硬同步方式相似的地方是它們都使用 SS 段來進行檢測,同步的目的都是使節點內的 SS段把跳變沿包含起來。
       重新同步的方式分爲超前和滯後兩種情況,以總線跳變沿與 SS 段的相對位置進行區分。第一種相位超前的情況如下圖,節點從總線的邊沿跳變中,檢測到它內部的時序比總線的時序相對超前 2Tq,這時控制器在下一個位時序中的 PBS1 段增加 2Tq 的時間長度,使得節點與總線時序重新同步。

       第二種相位滯後的情況如下圖,節點從總線的邊沿跳變中,檢測到它的時序比總線的時序相對滯後 2Tq,這時控制器在前一個位時序中的 PBS2 段減少 2Tq 的時間長度,獲得同步。

       在重新同步的時候, PBS1 和 PBS2 中增加或減少的這段時間長度被定義爲“ 重新同步補償寬度 SJW (reSynchronization Jump Width)”。一般來說 CAN 控制器會限定 SJW 的最大值,如限定了最大 SJW=3Tq 時,單次同步調整的時候不能增加或減少超過 3Tq 的時間長度,若有需要,控制器會通過多次小幅度調整來實現同步。當控制器設置的 SJW 極限值較大時,可以吸收的誤差加大,但通訊的速度會下降。

(2)CAN 的報文種類及結構
       CAN協議給出的解決方案是對數據、操作命令(如讀/寫)以及同步信號進行打包,打包後的這些內容稱爲報文。
報文的種類
       在原始數據段的前面加上傳輸起始標籤、片選(識別)標籤和控制標籤,在數據的尾段加上 CRC 校驗標籤、應答標籤和傳輸結束標籤,把這些內容按特定的格式打包好,就可以用一個通道表達各種信號了, 各種各樣的標籤就如同 SPI 中各種通道上的信號,起到了協同傳輸的作用。當整個數據包被傳輸到其它設備時,只要這些設備按格式去解讀,就能還原出原始數據,這樣的報文就被稱爲 CAN 的“ 數據幀”
       爲了更有效地控制通訊, CAN 一共規定了 5 種類型的幀,它們的類型及用途說明如下表。

數據幀的結構
數據幀是在 CAN 通訊中最主要、最複雜的報文,我們來了解它的結構,見下圖。

       數據幀以一個顯性位(邏輯 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 總線的佔有權,當它們發送報文時, 若首先出現隱性電平,則會失去對總線的佔有權,進入接收狀態。見下圖,在開始階段,兩個設備發送的電平一樣,所以它們一直繼續發送數據。到了圖中箭頭所指的時序處,節點單元 1 發送的爲隱性電平,而此時節點單元 2 發送的爲顯性電平,由於總線的“線與”特性使它表達出顯示電平,因此單元 2 競爭總線成功,這個報文得以被繼續發送出去。

       仲裁段 ID 的優先級也影響着接收設備對報文的反應。因爲在 CAN 總線上數據是以廣播的形式發送的,所有連接在 CAN 總線的節點都會收到所有其它節點發出的有效數據,因而我們的 CAN 控制器大多具有根據 ID 過濾報文的功能,它可以控制自己只接收某些 ID的報文。
       回看圖 43-9 中的數據幀格式,可看到仲裁段除了報文 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 段(DataLength 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 報文結構,不再展開講解,其主要內容見下圖。

二、STM32 的 CAN 外設簡介

       STM32 的芯片中具有 bxCAN 控制器 (Basic Extended CAN), 它支持 CAN 協議 2.0A 和2.0B 標準。
       該 CAN 控制器支持最高的通訊速率爲 1Mb/s;可以自動地接收和發送 CAN 報文,支持使用標準 ID 和擴展 ID 的報文;外設中具有 3 個發送郵箱,發送報文的優先級可以使用軟件控制,還可以記錄發送的時間;具有 2 個 3 級深度的接收 FIFO,可使用過濾功能只接收或不接收某些 ID 號的報文;可配置成自動重發;不支持使用 DMA 進行數據收發。

1、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 寄存器位可以控制使用正常模式、靜默模式、迴環模式及靜默迴環模式,見下圖。

各個工作模式介紹如下:
 正常模式
正常模式下就是一個正常的 CAN 節點,可以向總線發送數據和接收數據。
 靜默模式
靜默模式下,它自己的輸出端的邏輯 0 數據會直接傳輸到它自己的輸入端,邏輯1 可以被髮送到總線,所以它不能向總線發送顯性位(邏輯 0),只能發送隱性位(邏輯 1)。輸入端可以從總線接收內容。由於它只可發送的隱性位不會強制影響總線的狀態,所以把它稱爲靜默模式。這種模式一般用於監測,它可以用於分析總線上的流量,但又不會因爲發送顯性位而影響總線。
 迴環模式
迴環模式下,它自己的輸出端的所有內容都直接傳輸到自己的輸入端,輸出端的內容同時也會被傳輸到總線上,即也可使用總線監測它的發送內容。輸入端只接收自己發送端的內容,不接收來自總線上的內容。使用迴環模式可以進行自檢。
 迴環靜默模式

       迴環靜默模式是以上兩種模式的結合,自己的輸出端的所有內容都直接傳輸到自己的輸入端,並且不會向總線發送顯性位影響總線,不能通過總線監測它的發送內容。輸入端只接收自己發送端的內容,不接收來自總線上的內容。這種方式可以在“熱自檢”時使用,即自我檢查的時候,不會干擾總線。
       以上說的各個模式,是不需要修改硬件接線的,如當輸出直連輸入時,它是在 STM32芯片內部連接的,傳輸路徑不經過 STM32 的 CAN_Tx/Rx 引腳,更不經過外部連接的 CAN收發器,只有輸出數據到總線或從總線接收的情況下才會經過 CAN_Tx/Rx 引腳和收發器。
(2) 位時序及波特率
       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

(2)CAN 發送郵箱
       回到 CAN 外設框圖,在標號②處的是 CAN 外設的發送郵箱,它一共有 3個發送郵箱,即最多可以緩存 3 個待發送的報文。
       每個發送郵箱中包含有標識符寄存器 CAN_TIxR、數據長度控制寄存器 CAN_TDTxR及 2 個數據寄存器 CAN_TDLxR、 CAN_TDHxR,它們的功能見下表。

       當我們要使用 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

       CAN 外設框圖,在標號③處的是 CAN 外設的接收 FIFO,它一共有 2 個接收 FIFO,每個 FIFO 中有 3 個郵箱,即最多可以緩存 6 個接收到的報文。當接收到報文時, FIFO 的報文計數器會自增,而 STM32 內部讀取 FIFO 數據之後,報文計數器會自減,我們通過狀態寄存器可獲知報文計數器的值,而通過前面主控制寄存器的 RFLM 位,可設置鎖定模式,鎖定模式下 FIFO 溢出時會丟棄新報文,非鎖定模式下 FIFO 溢出時新報文會覆蓋舊報文。
       跟發送郵箱類似,每個接收 FIFO 中包含有標識符寄存器 CAN_RIxR、數據長度控制寄存器 CAN_RDTxR 及 2 個數據寄存器 CAN_RDLxR、 CAN_RDHxR,它們的功能見下表。

       通過中斷或狀態寄存器知道接收 FIFO 有數據後,我們再讀取這些寄存器的值即可把接收到的報文加載到 STM32 的內存中。

(4)驗收篩選器
       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 位可以設置篩選器工作在哪個模式。不同的尺度和不同的過濾方法可使篩選器工作在下圖 的 4 種狀態。

       每組篩選器包含 2 個 32 位的寄存器,分別爲 CAN_FxR1 和 CAN_FxR2,它們用來存儲要篩選的 ID 或掩碼,各個寄存器位代表的意義與圖中兩個寄存器下面“映射”的一欄一致,各個模式的說明見下表。

       例如下面的表格所示,在掩碼模式時,第一個寄存器存儲要篩選的 ID,第二個寄存器存儲掩碼,掩碼爲 1 的部分表示該位必須與 ID 中的內容一致,篩選的結果爲表中第三行的ID 值,它是一組包含多個的 ID 值,其中 x 表示該位可以爲 1 可以爲 0。

       而工作在標識符模式時, 2 個寄存器存儲的都是要篩選的 ID,它只包含 2 個要篩選的ID 值(32 位模式時)。
       如果使能了篩選器,且報文的 ID 與所有篩選器的配置都不匹配, CAN 外設會丟棄該報文,不存入接收 FIFO。

(5)整體控制邏輯
       結構框圖,圖中的標號⑤處表示的是 CAN2 外設的結構,它與 CAN1 外設是一樣的,他們共用篩選器且由於存儲訪問控制器由 CAN1 控制,所以要使用 CAN2 的時候必須要使能 CAN1 的時鐘。

三、CAN 初始化結構體

       從 STM32 的 CAN 外設我們瞭解到它的功能非常多,控制涉及的寄存器也非常豐富,而使用 STM32 標準庫提供的各種結構體及庫函數可以簡化這些控制過程。跟其它外設一樣,STM32 標準庫提供了 CAN 初始化結構體初始化函數來控制 CAN 的工作方式,提供了收發報文使用的結構體及收發函數,還有配置控制篩選器模式ID 的結構體。這些內容都定義在庫文件“ stm32f4xx_can.h”及“ stm32f4xx_can.c”中,編程時我們可以結合這兩個文件內的註釋使用或參考庫幫助文檔。
首先我們來學習初始化結構體的內容,見代碼清單。
 

/**
* @brief CAN 初始化結構體
*/
typedef struct {
uint16_t CAN_Prescaler; /*配置 CAN 外設的時鐘分頻,可設置爲 1-1024*/
uint8_t CAN_Mode; /*配置 CAN 的工作模式,迴環或正常模式*/
uint8_t CAN_SJW; /*配置 SJW 極限值 */
uint8_t CAN_BS1; /*配置 BS1 段長度*/
uint8_t CAN_BS2; /*配置 BS2 段長度 */
FunctionalState CAN_TTCM; /*是否使能 TTCM 時間觸發功能*/
FunctionalState CAN_ABOM; /*是否使能 ABOM 自動離線管理功能*/
FunctionalState CAN_AWUM; /*是否使能 AWUM 自動喚醒功能 */
FunctionalState CAN_NART; /*是否使能 NART 自動重傳功能*/
FunctionalState CAN_RFLM; /*是否使能 RFLM 鎖定 FIFO 功能*/
FunctionalState CAN_TXFP; /*配置 TXFP 報文優先級的判定方法*/
} CAN_InitTypeDef;

       這些結構體成員說明如下,其中括號內的文字是對應參數在 STM32 標準庫中定義的宏,這些結構體成員都是“ CAN 控制內核”小節介紹的內容,可對比閱讀:
(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 的初始化。

四、CAN 發送及接收結構體

       在發送或接收報文時,需要往發送郵箱中寫入報文信息或從接收 FIFO 中讀取報文信息,利用 STM32 標準庫的發送及接收結構體可以方便地完成這樣的工作,它們的定義見代碼清單。

**
* @brief CAN Tx message structure definition
* 發送結構體
*/
typedef struct {
uint32_t StdId; /*存儲報文的標準標識符 11 位, 0-0x7FF. */
uint32_t ExtId; /*存儲報文的擴展標識符 29 位, 0-0x1FFFFFFF. */
uint8_t IDE; /*存儲 IDE 擴展標誌 */
uint8_t RTR; /*存儲 RTR 遠程幀標誌*/
uint8_t DLC; /*存儲報文數據段的長度, 0-8 */
uint8_t Data[8]; /*存儲報文數據段的內容 */
} CanTxMsg;
/**
* @brief CAN Rx message structure definition
* 接收結構體
*/
typedef struct {
uint32_t StdId; /*存儲了報文的標準標識符 11 位, 0-0x7FF. */
uint32_t ExtId; /*存儲了報文的擴展標識符 29 位, 0-0x1FFFFFFF. */
uint8_t IDE; /*存儲了 IDE 擴展標誌 */
uint8_t RTR; /*存儲了 RTR 遠程幀標誌*/
uint8_t DLC; /*存儲了報文數據段的長度, 0-8 */
uint8_t Data[8]; /*存儲了報文數據段的內容 */
uint8_t FMI; /*存儲了 本報文是由經過篩選器存儲進 FIFO 的, 0-0xFF */
} CanRxMsg;

       這些結構體成員都是“ CAN 發送郵箱及 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 中的內容讀取到預先定義的接收類型結構體中,然後再訪問該結構體即可利用報文了。

五、CAN 篩選器結構體

CAN 的篩選器有多種工作模式,利用篩選器結構體可方便配置,它的定義見代碼清單。

/**
* @brief CAN filter init structure definition
* CAN 篩選器結構體
*/
typedef struct {
uint16_t CAN_FilterIdHigh; /*CAN_FxR1 寄存器的高 16 位 */
uint16_t CAN_FilterIdLow; /*CAN_FxR1 寄存器的低 16 位*/
uint16_t CAN_FilterMaskIdHigh; /*CAN_FxR2 寄存器的高 16 位*/
uint16_t CAN_FilterMaskIdLow; /*CAN_FxR2 寄存器的低 16 位 */
uint16_t CAN_FilterFIFOAssignment; /*設置經過篩選後數據存儲到哪個接收 FIFO
uint8_t CAN_FilterNumber; /*篩選器編號,範圍 0-27*/
uint8_t CAN_FilterMode; /*篩選器模式 */
uint8_t CAN_FilterScale; /*設置篩選器的尺度 */
FunctionalState CAN_FilterActivation; /*是否使能本篩選器*/
} CAN_FilterInitTypeDef;

       這些結構體成員都是“ 43.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 組成一組篩器。

(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 的源代碼理解,就是因爲它直接對寄存器寫入內容,代碼的邏輯是非常清晰的。

六、CAN—雙機通訊實驗

       本小節演示如何使用 STM32 的 CAN 外設實現兩個設備之間的通訊,該實驗中使用了兩個實驗板,如果您只有一個實驗板,也可以使用 CAN 的迴環模式進行測試,不影響學習的。 爲此,我們提供了“ CAN—雙機通訊”及“CAN—迴環測試”兩個工程,可根據自己的實驗環境選擇相應的工程來學習。這兩個工程的主體都是一樣的,本教程主要以“ CAN—雙機通訊”工程進行講解。

1、硬件設計

 

       圖中的是兩個實驗板的硬件連接。在單個實驗板中,作爲 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”排針連接起來進行供電,並且把液晶屏從板子上拔下來。

       如果您使用的是單機迴環測試的工程實驗,就不需要使用導線連接板子了,而且也不需要給收發器供電,因爲迴環模式的信號是不經過收發器的,不過,它還是不能和液晶屏同時使用的。

2、軟件設計

NO.1 編程要點
(0) 初始化 CAN 通訊使用的目標引腳及端口時鐘;
(1) 使能 CAN 外設的時鐘;
(2) 配置 CAN 外設的工作模式、位時序以及波特率;
(3) 配置篩選器的工作方式;
(4) 編寫測試程序,收發報文並校驗。
NO.2 代碼分析
CAN 硬件相關宏定義
我們把 CAN 硬件相關的配置都以宏的形式定義到 “ bsp_can.h”文件中,見代碼清單。

/*CAN 硬件相關的定義*/
#define CANx CAN1
#define CAN_CLK RCC_APB1Periph_CAN1
/*接收中斷號*/
#define CAN_RX_IRQ CAN1_RX0_IRQn
/*接收中斷服務函數*/
#define CAN_RX_IRQHandler CAN1_RX0_IRQHandler
/*引腳*/
#define CAN_RX_PIN GPIO_Pin_8
#define CAN_TX_PIN GPIO_Pin_9
#define CAN_TX_GPIO_PORT GPIOB
#define CAN_RX_GPIO_PORT GPIOB
#define CAN_TX_GPIO_CLK RCC_AHB1Periph_GPIOB
#define CAN_RX_GPIO_CLK RCC_AHB1Periph_GPIOB
#define CAN_AF_PORT GPIO_AF_CAN1
#define CAN_RX_SOURCE GPIO_PinSource8
#define CAN_TX_SOURCE GPIO_PinSource9

       以上代碼根據硬件連接, 把與 CAN 通訊使用的 CAN 號 、引腳號、引腳源以及複用功能映射都以宏封裝起來,並且定義了接收中斷的中斷向量和中斷服務函數,我們通過中斷來獲知接收 FIFO 的信息。
初始化 CAN 的 GPIO
       利用上面的宏,編寫 CAN 的初始化函數,見代碼清單。

/* 函數名: CAN_GPIO_Config
* 描述 : CAN 的 GPIO 配置
* 輸入 :無
* 輸出 : 無
* 調用 :內部調用
*/
static void CAN_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 使能 GPIO 時鐘*/
RCC_AHB1PeriphClockCmd(CAN_TX_GPIO_CLK|CAN_RX_GPIO_CLK, ENABLE);
/* 引腳源*/
GPIO_PinAFConfig(CAN_TX_GPIO_PORT, CAN_RX_SOURCE, CAN_AF_PORT);
GPIO_PinAFConfig(CAN_RX_GPIO_PORT, CAN_TX_SOURCE, CAN_AF_PORT);
/* 配置 CAN TX 引腳 */
GPIO_InitStructure.GPIO_Pin = CAN_TX_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(CAN_TX_GPIO_PORT, &GPIO_InitStructure);
/* 配置 CAN RX 引腳 */
GPIO_InitStructure.GPIO_Pin = CAN_RX_PIN ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_Init(CAN_RX_GPIO_PORT, &GPIO_InitStructure);
}

       與所有使用到 GPIO 的外設一樣,都要先把使用到的 GPIO 引腳模式初始化,配置好複用功能。 CAN 的兩個引腳都配置成通用推輓輸出模式即可。
配置 CAN 的工作模式
       接下來我們配置 CAN 的工作模式,由於我們是自己用的兩個板子之間進行通訊,波特率之類的配置只要兩個板子一致即可。如果您要使實驗板與某個 CAN 總線網絡的通訊的節點通訊,那麼實驗板的 CAN 配置必須要與該總線一致。我們實驗中使用的配置見代碼清單。

/*
* 函數名: CAN_Mode_Config
* 描述 : CAN 的模式 配置
* 輸入 :無
* 輸出 : 無
* 調用 :內部調用
*/
static void CAN_Mode_Config(void)
{
CAN_InitTypeDef CAN_InitStructure;
/************************CAN 通信參數設置************************/
/* Enable CAN clock */
RCC_APB1PeriphClockCmd(CAN_CLK, ENABLE);
/*CAN 寄存器初始化*/
CAN_DeInit(CAN1);
CAN_StructInit(&CAN_InitStructure);
/*CAN 單元初始化*/
CAN_InitStructure.CAN_TTCM=DISABLE; //MCR-TTCM 關閉時間觸發通信模式使能
CAN_InitStructure.CAN_ABOM=ENABLE; //MCR-ABOM 使能自動離線管理
CAN_InitStructure.CAN_AWUM=ENABLE; //MCR-AWUM 使用自動喚醒模式
CAN_InitStructure.CAN_NART=DISABLE; //MCR-NART 禁止報文自動重傳
CAN_InitStructure.CAN_RFLM=DISABLE; //MCR-RFLM 接收 FIFO 不鎖定
// 溢出時新報文會覆蓋原有報文
CAN_InitStructure.CAN_TXFP=DISABLE; //MCR-TXFP 發送 FIFO 優先級 取決於報文標識符
CAN_InitStructure.CAN_Mode = CAN_Mode_Normal; //正常工作模式
CAN_InitStructure.CAN_SJW=CAN_SJW_2tq; //BTR-SJW 重新同步跳躍寬度 2 個時間單元
/* ss=1 bs1=5 bs2=3 位時間寬度爲(1+5+3) 波特率即爲時鐘週期 tq*(1+3+5) */
CAN_InitStructure.CAN_BS1=CAN_BS1_5tq; //BTR-TS1 時間段 1 佔用了 5 個時間單元
CAN_InitStructure.CAN_BS2=CAN_BS2_3tq; //BTR-TS1 時間段 2 佔用了 3 個時間單元
/* CAN Baudrate = 1 MBps (1MBps 已爲 stm32 的 CAN 最高速率) (CAN 時鐘頻率爲 APB 1 = 45 MHz) */
////BTR-BRP 波特率分頻器 定義了時間單元的時間長度 45/(1+5+3)/5=1 Mbps
CAN_InitStructure.CAN_Prescaler =5;
CAN_Init(CANx, &CAN_InitStructure);
}

       這段代碼主要是把 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 的工作模式,爲了方便管理接收報文,我們還要把篩選器用起來,見代碼清單。

/*IDE 位的標誌*/
#define CAN_ID_STD ((uint32_t)0x00000000) /*標準 ID */
#define CAN_ID_EXT ((uint32_t)0x00000004) /*擴展 ID */
/*RTR 位的標誌*/
#define CAN_RTR_Data ((uint32_t)0x00000000) /*數據幀 */
#define CAN_RTR_Remote ((uint32_t)0x00000002) /*遠程幀*/
/***********************************************************************/
/*
* 函數名: CAN_Filter_Config
* 描述 : CAN 的篩選器 配置
* 輸入 :無
* 輸出 : 無
* 調用 :內部調用
*/
static void CAN_Filter_Config(void)
{
CAN_FilterInitTypeDef CAN_FilterInitStructure;
/*CAN 篩選器初始化*/
CAN_FilterInitStructure.CAN_FilterNumber=0; //篩選器組 0
//工作在掩碼模式
CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;
//篩選器位寬爲單個 32 位。
CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;
/* 使能篩選器,按照標誌符的內容進行比對篩選,
擴展 ID 不是如下的就拋棄掉,是的話,會存入 FIFO0。 */
//要篩選的 ID 高位,第 0 位保留,第 1 位爲 RTR 標誌,第 2 位爲 IDE 標誌,從第 3 位開始是 EXID
CAN_FilterInitStructure.CAN_FilterIdHigh= ((((u32)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF0000)>>16;
//要篩選的 ID 低位
CAN_FilterInitStructure.CAN_FilterIdLow= (((u32)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF;
//篩選器高 16 位每位必須匹配
CAN_FilterInitStructure.CAN_FilterMaskIdHigh= 0xFFFF;
//篩選器低 16 位每位必須匹配
CAN_FilterInitStructure.CAN_FilterMaskIdLow= 0xFFFF;
//篩選器被關聯到 FIFO0
CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0 ;
//使能篩選器
CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;
CAN_FilterInit(&CAN_FilterInitStructure);
/*CAN 通信中斷使能*/
CAN_ITConfig(CANx, CAN_IT_FMP0, ENABLE);
}

       這段代碼把篩選器第 0 組配置成了 32 位的掩碼模式,並且把它的輸出連接到接收FIFO0,若通過了篩選器的匹配,報文會被存儲到接收 FIFO0。
       篩選器配置的重點是配置 ID 和掩碼,根據我們的配置,這個篩選器工作在圖中的模式。

       在該配置中,結構體成員 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 接收到數據時會引起中斷,該接收中斷的優先級配置如下,見代碼清單。

/*接收中斷號*/
#define CAN_RX_IRQ CAN1_RX0_IRQn
/*
* 函數名: CAN_NVIC_Config
* 描述 : CAN 的 NVIC 配置,第 1 優先級組, 0, 0 優先級
* 輸入 :無
* 輸出 : 無
* 調用 :內部調用
*/
static void CAN_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Configure one bit for preemption priority */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/*中斷設置*/
NVIC_InitStructure.NVIC_IRQChannel = CAN_RX_IRQ; //CAN RX 中斷
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}

       這部分與我們配置其它中斷的優先級無異,都是配置 NVIC 結構體,優先級可根據自己的需要配置,最主要的是中斷向量,上述代碼中把中斷向量配置成了 CAN 的接收中斷。設置發送報文要使用 CAN 發送報文時,我們需要先定義一個發送報文結構體並向它賦值,見代碼清單。

*IDE 位的標誌*/
#define CAN_ID_STD ((uint32_t)0x00000000) /*標準 ID */
#define CAN_ID_EXT ((uint32_t)0x00000004) /*擴展 ID */
/*RTR 位的標誌*/
#define CAN_RTR_Data ((uint32_t)0x00000000) /*數據幀 */
#define CAN_RTR_Remote ((uint32_t)0x00000002) /*遠程幀*/
/*
* 函數名: CAN_SetMsg
* 描述 : CAN 通信報文內容設置,設置一個數據內容爲 0-7 的數據包
* 輸入 :無
* 輸出 : 無
* 調用 :外部調用
*/
void CAN_SetMsg(CanTxMsg *TxMessage)
{
uint8_t ubCounter = 0;
//TxMessage.StdId=0x00;
TxMessage->ExtId=0x1314; //使用的擴展 ID
TxMessage->IDE=CAN_ID_EXT; //擴展模式
TxMessage->RTR=CAN_RTR_DATA; //發送的是數據
TxMessage->DLC=8; //數據長度爲 8 字節
/*設置要發送的數據 0-7*/
for (ubCounter = 0; ubCounter < 8; ubCounter++)
{
TxMessage->Data[ubCounter] = ubCounter;
}
}

       這段代碼是我們爲了方便演示而自己定義的設置報文內容的函數,它把報文設置成了擴展模式的數據幀,擴展 ID 爲 0x1314,數據段的長度爲 8,且數據內容分別爲 0-7,實際應用中您可根據自己的需求發設置報文內容。 當我們設置好報文內容後,調用庫函數CAN_Transmit 即可把該報文存儲到發送郵箱,然後 CAN 外設會把它發送出去:CAN_Transmit(CANx, &TxMessage);
接收報文
由於我們設置了接收中斷,所以接收報文的操作是在中斷的服務函數中完成的,見代碼清單 。

/*接收中斷服務函數*/
#define CAN_RX_IRQHandler CAN1_RX0_IRQHandler
extern __IO uint32_t flag ; //用於標誌是否接收到數據,在中斷函數中賦值
extern CanRxMsg RxMessage; //接收緩衝區
/********************************************************************/
void CAN_RX_IRQHandler(void)
{
/*從郵箱中讀出報文*/
CAN_Receive(CANx, CAN_FIFO0, &RxMessage);
/* 比較 ID 是否爲 0x1314 */
if ((RxMessage.ExtId==0x1314) && (RxMessage.IDE==CAN_ID_EXT) &&
(RxMessage.DLC==8) )
{
flag = 1; //接收成功
}
else
{
flag = 0; //接收失敗
}
}

       根據我們前面的配置,若 CAN 接收的報文經過篩選器匹配後會被存儲到 FIFO0 中,並引起中斷進入到這個中斷服務函數中,在這個函數裏我們調用了庫函數 CAN_Receive 把報文從 FIFO 複製到自定義的接收報文結構體 RxMessage 中,並且比較了接收到的報文 ID是否與我們希望接收的一致,若一致就設置標誌 flag=1,否則爲 0,通過 flag 標誌通知主程序流程獲知是否接收到數據。
       要注意如果設置了接收報文中斷,必須要在中斷內調用 CAN_Receive 函數讀取接收FIFO 的內容,因爲只有這樣才能清除該 FIFO 的接收中斷標誌,如果不在中斷內調用它清除標誌的話,一旦接收到報文, STM32 會不斷進入中斷服務函數,導致程序卡死。
(3) main 函數
最後我們來閱讀 main 函數,瞭解整個通訊流程,見代碼清單。

_IO uint32_t flag = 0; //用於標誌是否接收到數據,在中斷函數中賦值
CanTxMsg TxMessage; //發送緩衝區
CanRxMsg RxMessage; //接收緩衝區
/**
* @brief 主函數
* @param 無
* @retval 無
*/
int main(void)
{
LED_GPIO_Config();
/*初始化 USART1*/
Debug_USART_Config();
/*初始化按鍵*/
Key_GPIO_Config();
/*初始化 can,在中斷接收 CAN 數據包*/
CAN_Config();
printf("\r\n 歡迎使用野火 STM32 F429 開發板。 \r\n");
printf("\r\n 野火 F429 CAN 通訊實驗例程\r\n");
printf("\r\n 實驗步驟: \r\n");
printf("\r\n 1.使用導線連接好兩個 CAN 訊設備\r\n");
printf("\r\n 2.使用跳線帽連接好:5v --- C/4-5V \r\n");
printf("\r\n 3.按下開發板的 KEY1 鍵,會使用 CAN 向外發送 0-7 的數據包,包的擴展 ID 爲 0x1314
\r\n");
printf("\r\n 4.若開發板的 CAN 接收到擴展 ID 爲 0x1314 的數據包,會把數據以打印到串口。 \r\n");
printf("\r\n 5.本例中的 can 波特率爲 1MBps,爲 stm32 的 can 最高速率。 \r\n");
while (1)
{
/*按一次按鍵發送一次數據*/
if ( Key_Scan(KEY1_GPIO_PORT,KEY1_PIN) == KEY_ON)
{
LED_BLUE;
/*設置要發送的報文*/
CAN_SetMsg(&TxMessage);
/*把報文存儲到發送郵箱,發送*/
CAN_Transmit(CANx, &TxMessage);
can_delay(10000);//等待發送完畢,可使用 CAN_TransmitStatus 查看狀態
LED_GREEN;
printf("\r\n 已使用 CAN 發送數據包! \r\n");
printf("\r\n 發送的報文內容爲: \r\n");
printf("\r\n 擴展 ID 號 ExtId: 0x%x \r\n",TxMessage.ExtId);
CAN_DEBUG_ARRAY(TxMessage.Data,8);
}
if (flag==1)
{
LED_GREEN;
printf("\r\nCAN 接收到數據: \r\n");
CAN_DEBUG_ARRAY(RxMessage.Data,8);
flag=0;
}
}
}

       在 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 輸出到串口。

3、下載驗證

       下載驗證這個 CAN 實驗時,我們建議您先使用“ CAN—迴環測試”的工程進行測試,它的環境配置比較簡單,只需要一個實驗板,用 USB 線使實驗板“USB TO UART”接口跟電腦連接起來,在電腦端打開串口調試助手,並且把編譯好的該工程下載到實驗板,然後復位。這時在串口調試助手可看到 CAN 測試的調試信息,按一下實驗板上的 KEY1 按鍵,實驗板會使用迴環模式向自己發送報文,在串口調試助手可以看到相應的發送和接收的信息。
       使用迴環測試成功後,如果您有兩個實驗板,需要按照“硬件設計”小節中的圖例連接兩個板子的 CAN 總線,並且一定要接上跳線帽給 CAN 收發器供電、把液晶屏拔掉防止干擾。用 USB 線使實驗板“ USB TO UART”接口跟電腦連接起來,在電腦端打開串口調試助手,然後使用“ CAN—雙機通訊”工程編譯,並給兩個板子都下載該程序,然後復位。這時在串口調試助手可看到 CAN 測試的調試信息,按一下其中一個實驗板上的 KEY1 按鍵,另一個實驗板會接收到報文,在串口調試助手可以看到相應的發送和接收的信息。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章