大家好,我是小麥。最近在調試一個CAN總線的設備時遇到了一些問題,在此簡單總結一下。本文將對CAN總線進行簡單介紹,包括CAN的硬件鏈路層、協議層,以及調試的心得。
目錄什麼是CAN總線?
物理層
差分信號
連接方式
CAN節點
CAN協議
如何尋址?
幀類型
數據幀
遠程幀
錯誤幀
過載幀
消息時序以及同步
位時序
波特率
消息過濾器
如何配置?
總結
參考
什麼是CAN總線?Controller Area Network,簡稱CAN或者CAN bus) 是一種功能豐富的串行總線標準,最早的CAN控制芯片在奔馳車上應用並量產,因為支持多主機,多從機的優點,所以一輛車所有控制器,傳感器,電子設備直接的通信只需要兩條線就夠了,大大優化了整車的布線。[^wiki can bus]
隨着技術的不斷發展,CAN發布了相應的標準,國際化標準組織,公布了CAN的不同標準;
ISO 11898-1 ,ISO 11898-2是對應的設計標準,去搜索就可以知道這個技術點是如何進行設計的。
物理層差分信號這裡我們介紹一下物理層,什麼是物理層呢?就是CAN的電信號的傳輸過程。CAN是串行異步通訊,只有CAN_HIGH和CAN_LOW兩條差分信號線,數據通過差分信號的方式進行通訊,其優點就是可以增加信號的抗干擾能力,抑制共模信號的干擾;
具體如下圖所示;

所以,信號在變成一個字節一個字節的數字信號之前,就是按照這種差分形式的模擬信號來傳輸的。
我們可以簡單地理解一下,當CAN_HIGH減去CAN_LOW大於某個閾值的時候,可以把它當做邏輯高,反之,當小於某一個閾值時,就變成邏輯低。
下面我們再來看看CAN總線設備之間是如何連接的。
連接方式CAN總線支持多個節點掛載在總線上,比較類似I2C總線,可以在SCL和SDA上掛載多個從機,具體如下圖所示;

不過CAN總線其實沒有主從的概念,每個設備都是一個節點(Node),節點直接可以相互通訊,相較於I2C總線,CAN總線設置了終端電阻,常見的一種閉環連接模式,相對的還有開環的連接模式。
不同的連接模式,他們的通訊速率也大不相同,這裡也就是高速CAN和低速CAN的區別。
兩條電線組成一條雙絞線,並且接有120Ω的特性阻抗。ISO 11898-2,也稱為高速度CAN。它在總線的兩端均接有120Ω電阻。
使用了120Ω終端電阻(這是CAN的ISO標準里規定的),這種模式的最高通訊速率可以達到1Mbps,下面是傳輸距離和傳輸速度的關係;

高速CAN的拓撲結構具體如下所示;
還有一種是低速CAN,或者也叫做容錯CAN,低速容錯 CAN 總線將通訊的最大帶寬從 1 Mbps 降低到 125 Kbps,並且不再在總線的起點和終點使用兩個終端電阻,而是將電阻分布在每個節點上。具體如下圖所示;
由於高速CAN和低速CAN的拓撲結構不同,另外終端電阻的分布也不同,所以CAN_HIGH和CAN_LOW上的電平是不相同的,這裡有隱性電平和顯性電平。
硬件上的連接基本上都搞清楚了,下面就是如何去實現一個具體的CAN節點。我們來簡單地介紹一下。
CAN節點CAN節點通常分為三個部分;
通常一些單片機內部就集成了相應的CAN控制器外設,比如我們比較常用的單片機——STM32,所以我們常見的結構一般是這樣子的。

所以整體的流程是這樣的,如下:
那麼,對於單片機開發者而言,需要關注的就是最終CAN控制器傳輸給MCU的數據,如何去配置CAN控制器,以及使用CAN控制進行數據的讀取和發送。
既然這樣,我們就不得不去了解一下CAN總線的通信原理,如何尋址,上層協議如何規定的。
CAN協議CAN協議和網絡協議比較類似,進行了分層的設計思想;

按照我的理解;
CAN總線上的每個節點不需要設置節點的地址,而是通過消息的標識符(Identifier)來區別信息。因為CAN總線的消息是廣播的(就是大家都可以收到消息),比如總線上有節點A,節點B,節點C,那麼節點A發消息,節點B和節點C都會收到消息;
節點B 和 節點C 會根據消息中的標識符,以及B和C中的消息過濾規則進行比較,如果不滿足規則,就不接受這條信息。
這裡需要注意的是:
在這裡我們已經了解如何尋址,下面就看一下消息幀了。
幀類型CAN有4種幀類型:
這裡我們有必要重點了解一下數據幀,下面繼續介紹各種幀之間的區別。
數據幀數據幀分為標準幀和擴展幀兩種格式;
數據幀的結構具體如下所示;

簡單介紹一下數據幀的細節;
sof:start of frame,表示數據幀開始;(1 bit)
Identifier:標準格式11 bit,擴展格式29 bit包括Base Identifier(11 bit)和Extended Identifier(18 bit),該區段標識數據幀的優先級,數值越小,優先級越高;
RTR:遠程傳輸請求位,0時表示為數據幀,1表示為遠程幀,也就是說RTR=1時,消息幀的Data Field為空;(1 bit)
IDE:標識符擴展位,0時表示為標準格式,1表示為擴展格式;(1 bit)
DLC:數據長度代碼,0~8表示數據長度為0~8 Byte;(4 bit)
Data Field:數據域;(0~8 Byte)
CRC Sequence:校驗域,校驗算法,
DEL:校驗域和應答域的隱性界定符;(1 bit)
ACK:應答,確認數據是否正常接收,所謂正常接收是指不含填充錯誤、格式錯誤、 CRC 錯誤。發送節點將此位為1,接收節點正常接收數據後將此位置為0;(1 bit)
SRR:替代遠程請求位,在擴展格式中占位用,必須為1;(1 bit)
EOF:連續7個隱性位(1)表示幀結束;(7 bit)
ITM:幀間空間,Intermission (ITM),又稱Interframe Space (IFS),連續3個隱性位,但它不屬於數據幀。幀間空間是用於將數據幀和遠程幀與前面的幀分離開來的幀。數據幀和遠程幀可通過插入幀間空間將本幀與前面的任何幀(數據幀、遙控幀、錯誤幀、過載幀)分開。過載幀和錯誤幀前不能插入幀間空間。
一般地,數據是由發送單元主動向總線上發送的,但也存在接收單元主動向發送單元請求數據的情況。遠程幀的作用就在於此,它是接收單元向發送單元請求發送數據的幀。遠程幀與數據幀的幀結構類似,如上圖X所示。遠程幀與數據幀的幀結構區別有兩點:
遠程幀的 DLC 塊表示請求發送單元發送的數據長度(Byte)。當總線上具有相同標識符的數據幀和遠程幀同時發送時,由於數據幀的 RTR 位是顯性的,數據幀將在仲裁中贏得總線控制權。
錯誤幀用於在接收和發送消息時檢測出錯誤時,通知錯誤的幀。錯誤幀由錯誤標誌和錯誤界定符構成。錯誤幀的幀結構如圖11示。
錯誤標誌:
個顯性/隱性重疊位
錯誤界定符:8 個隱性位
過載幀是用於接收單元通知發送單元它尚未完成接收準備的幀。在兩種情況下,節點會發送過載幀:
每個節點最多連續發送兩條過載幀。過載幀由過載標誌和過載界定符(8 個隱性位)構成。數據幀的幀結構如圖12所示。

這裡基本把幀介紹完了,但是每個節點之間的通訊,我們如何知道這一幀開始接收了,這一幀已經接收結束了呢?下面就需要了解一下消息的時序和消息同步的方法。
消息時序以及同步位時序在講CAN消息時序和同步之前,我們可以對照一下UART串口的傳輸協議,他有起始位和停止位,然後大家都規定使用相同的通訊速率(波特率);
其實CAN通訊也是類似的方式,它屬於異步通訊,沒有時鐘信號線,所以所有節點之間要約定好使用相同的波特率來傳輸數據。
在總線空閒一段時間後,在(起始位) 進行硬同步,同步方式是將每一位劃分成多個稱為量子的時間段(time quanta),並分配一定數量的量子到位中的四個階段完成的。
這四個階段分別為:
具體如下圖所示;

如何計算波特率,需要知道每個量子時間的長度(time quanta),以及每一位需要多少個量子時間,
假設這裡time quanta = 1us ,並且1 bit = 8 tq,那麼上圖中的波特率就應該是:
消息過濾器
前面有提到消息在CAN總線上是廣播式的,但並不是所有節點都會對總線上所有消息感興趣。節點通過控制器中過濾碼(Filter Code )和掩碼(Mask Code),再檢驗總線上消息的標識符,來判斷是否接收該消息(Message Filtering)。
對於掩碼,「1」表示該位與本節點相關,「0」表示該位與本節點不相關。舉例如下:
例1:僅接收消息標識符為00001567(十六進制)的幀
節點檢測消息的標識符的所有位(29位),如果標識符為00001567接收,否則捨棄。
例2:接收消息標識符為00001567 到0000156F 的幀
節點檢測消息的標識符的高25位,最低的4位則不care。如果標識符最高25位相同則接收,否則捨棄。
例3:接收消息標識符為00001560 到 00001567 的幀
節點檢測消息的標識符的高26位,最低的3位則不care。如果標識符最高26位相同則接收,否則捨棄。
例4:接收所有消息幀幀
節點接收總線上所有消息。
如何配置?上面介紹了幀類型,那麼如何基於MCU進行配置呢?這裡以STM32F407為硬件平台,使用HAL庫進行初始化,看一下都對哪些地方進行了配置。一般來說,我們需要配置CAN的波特率,消息過濾器等等,下面是簡單的配置的代碼;
CAN_HandleTypeDefhCAN;voidMX_CAN_Init(void){CAN_FilterTypeDefsFilterConfig;/*CAN單元初始化*/hCAN.Instance=CAN1;/*CAN外設*//*BTR-BRP波特率分頻器定義了時間單元的時間長度42/(1+6+7)/6=500Kbps*/hCAN.Init.Prescaler=6;hCAN.Init.Mode=CAN_MODE_NORMAL;/*正常工作模式*/hCAN.Init.SyncJumpWidth=CAN_SJW_1TQ;/*BTR-SJW重新同步跳躍寬度1個時間單元*/hCAN.Init.TimeSeg1=CAN_BS1_6TQ;/*BTR-TS1時間段1占用了6個時間單元*/hCAN.Init.TimeSeg2=CAN_BS2_7TQ;/*BTR-TS1時間段2占用了7個時間單元*/hCAN.Init.TimeTriggeredMode=DISABLE;/*MCR-TTCM關閉時間觸發通信模式使能*/hCAN.Init.AutoBusOff=ENABLE;/*MCR-ABOM自動離線管理*/hCAN.Init.AutoWakeUp=ENABLE;/*MCR-AWUM使用自動喚醒模式*/hCAN.Init.AutoRetransmission=DISABLE;/*MCR-NART禁止報文自動重傳DISABLE-自動重傳*//*MCR-RFLM接收FIFO鎖定模式DISABLE-溢出時新報文會覆蓋原有報文*/hCAN.Init.ReceiveFifoLocked=DISABLE;/*MCR-TXFP發送FIFO優先級DISABLE-優先級取決於報文標示符*/hCAN.Init.TransmitFifoPriority=DISABLE;if(HAL_CAN_Init(&hCAN)!=HAL_OK){//Error_Handler();}//初始化發送器hCAN1_TxMessage.IDE=CAN_ID_STD;hCAN1_TxMessage.RTR=CAN_RTR_DATA;hCAN1_TxMessage.TransmitGlobalTime=ENABLE;//初始化濾波器設置為0則不對消息進行過濾hCAN1_Filter.FilterIdHigh=0;/*要過濾的ID高位*/hCAN1_Filter.FilterIdLow=0;/*要過濾的ID低位*/hCAN1_Filter.FilterMaskIdHigh=0;/*過濾器高16位每位必須匹配*/hCAN1_Filter.FilterMaskIdLow=0;/*過濾器低16位每位必須匹配*/hCAN1_Filter.FilterFIFOAssignment=CAN_FILTER_FIFO0;/*過濾器被關聯到FIFO0*/hCAN1_Filter.FilterBank=0;hCAN1_Filter.FilterMode=CAN_FILTERMODE_IDMASK;/*工作在標識符屏蔽位模式*/hCAN1_Filter.FilterScale=CAN_FILTERSCALE_32BIT;/*過濾器位寬為單個32位。*/hCAN1_Filter.FilterActivation=ENABLE;hCAN1_Filter.SlaveStartFilterBank=0;HAL_CAN_ConfigFilter(&hCAN,&hCAN1_Filter);while(HAL_CAN_Start(&hCAN)!=HAL_OK){printf("\nCAN_StartFailed!!");HAL_Delay(100);}HAL_CAN_ActivateNotification(&hCAN,CAN_IT_RX_FIFO0_MSG_PENDING);}下面是CAN發送的函數,我們需要自己構建相應的消息幀格式,通常需要設置消息幀的ID格式,消息長度,具體如下;
voidCAN_TxMessage(CAN_HandleTypeDef*hcan,uint16_tID,uint8_taData[],uint8_tDLC){uint32_tTx_MailBox;/*-1-配置數據段長度----------------------------------------*/hCAN1_TxMessage.IDE=CAN_ID_STD;hCAN1_TxMessage.RTR=CAN_RTR_DATA;hCAN1_TxMessage.StdId=ID;hCAN1_TxMessage.DLC=DLC;hCAN1_TxMessage.TransmitGlobalTime=ENABLE;/*-2-發送aData---------------------------------------------*/while(HAL_CAN_AddTxMessage(hcan,&hCAN1_TxMessage,aData,&Tx_MailBox)!=HAL_OK){HAL_Delay(5);}}上述代碼設置發送消息:
整體可以參考前面介紹的消息幀格式,篇幅有限,這裡就先簡單的介紹一下。
總結本文對CAN總結進行了簡單的介紹,CAN通訊的特點可以總結如下;
本着對自己和對他人負責的原則,由於我接觸CAN的時間也不是很久,本文很多是基於個人在項目中學習和總結的經驗,所以文中難免存在錯誤和不足,請各位不吝賜教,及時指出。如果文章幫到了你,請幫忙點個讚、點個在看吧,謝謝!