目錄
一、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 對差分線,在一個時刻隻能表示一個信号,是以對通訊節點來說, 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 競争總線成功,這個封包得以被繼續發送出去。
![]()
再造STM32---第二十二部分:CAN—通訊實驗 仲裁段 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 寄存器位可以控制使用正常模式、靜默模式、回環模式及靜默回環模式,見下圖。
![]()
再造STM32---第二十二部分:CAN—通訊實驗 各個工作模式介紹如下:
正常模式
正常模式下就是一個正常的 CAN 節點,可以向總線發送資料和接收資料。
靜默模式
靜默模式下,它自己的輸出端的邏輯 0 資料會直接傳輸到它自己的輸入端,邏輯1 可以被發送到總線,是以它不能向總線發送顯性位(邏輯 0),隻能發送隐性位(邏輯 1)。輸入端可以從總線接收内容。由于它隻可發送的隐性位不會強制影響總線的狀态,是以把它稱為靜默模式。這種模式一般用于監測,它可以用于分析總線上的流量,但又不會因為發送顯性位而影響總線。
回環模式
回環模式下,它自己的輸出端的所有内容都直接傳輸到自己的輸入端,輸出端的内容同時也會被傳輸到總線上,即也可使用總線監測它的發送内容。輸入端隻接收自己發送端的内容,不接收來自總線上的内容。使用回環模式可以進行自檢。
回環靜默模式
回環靜默模式是以上兩種模式的結合,自己的輸出端的所有内容都直接傳輸到自己的輸入端,并且不會向總線發送顯性位影響總線,不能通過總線監測它的發送内容。輸入端隻接收自己發送端的内容,不接收來自總線上的内容。這種方式可以在“熱自檢”時使用,即自我檢查的時候,不會幹擾總線。
以上說的各個模式,是不需要修改硬體接線的,如當輸出直連輸入時,它是在 STM32晶片内部連接配接的,傳輸路徑不經過 STM32 的 CAN_Tx/Rx 引腳,更不經過外部連接配接的 CAN收發器,隻有輸出資料到總線或從總線接收的情況下才會經過 CAN_Tx/Rx 引腳和收發器。
(2) 位時序及波特率
STM32 外設定義的位時序與我們前面解釋的 CAN 标準時序有一點差別,見下圖。
![]()
再造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
![]()
再造STM32---第二十二部分:CAN—通訊實驗
(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 長度來分類有以下兩種:
- 檢查 STDID[10:0]、 EXTID[17:0]、 IDE 和 RTR 位,一共 31 位。
- 檢查 STDID[10:0]、 RTR、 IDE 和 EXTID[17:15],一共 16 位。
通過配置篩選尺度寄存器 CAN_FS1R 的 FSCx 位可以設定篩選器工作在哪個尺度。
而根據過濾的方法分為以下兩種模式:
- 辨別符清單模式,它把要接收封包的 ID 列成一個表,要求封包 ID 與清單中的某一個辨別符完全相同才可以接收,可以了解為白名單管理。
- 掩碼模式,它把可接收封包 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; //接收失敗
}
}
_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;
}
}
}