CAN 總線最初由博世于1980年代為汽車行業開發,能夠簡化復雜的布線網絡,還確保可靠和安全的數據傳輸。
1.CAN技術解釋
CAN網絡中的每個節點,都是平等的,沒有主次之分,這一點和SPI和I2C不同。每個節點都可以在需要的時候收發數據,同時也在監聽來自其他節點的數據傳輸。CAN還具有如下優勢:
- 降低網絡復雜性:單個 CAN 網絡可以取代多條通信線路,從而降低復雜性和成本;
- 抗干擾性強:CAN 總線采用抗電磁干擾設計,即使在高干擾環境中也能確保可靠和穩定的通信。
1.1 CAN總線的拓撲物理結構
CAN分為高速和低速,如下圖所示:
CAN總線拓撲結構就像一個地鐵,每個站點就是節點。每個節點可以和線路上的其他節點進行通信,有如下物理特點:
雙絞線結構
成對的電線相互纏繞,可最大限度地減少電磁干擾。想象一下這樣一個場景,CAN 電纜安裝在電源附近,例如車輛中的交流發電機,這會產生明顯的電磁噪聲。通常這種噪聲可能會干擾和扭曲通信信號。然而,CAN 總線在 CAN 高電平和 CAN 低電平線路之間使用了巧妙的差分信號系統。這意味著任何影響一條線路的干擾信號也會以類似的方式影響另一條線路,從而允許系統相互抵消。因此,即使存在大量噪聲,傳輸的數據也能保持清晰且未損壞。此特性與以太網等系統相同。
CAN High和CAN Low線
根據ISO 11898(高速CAN)和ISO 11519(低速CAN)標準,CAN_H和CAN_L的電壓和顯/隱性(邏輯0和邏輯1)是不一樣的。
類型 | 狀態 | CAN_H電壓范圍 | CAN_L電壓范圍 | 差分電壓 | 傳輸速率 |
---|---|---|---|---|---|
高速CAN | 顯性(邏輯0) | 2.75V ~ 4.5V | 0.5V ~ 2.75V | ≥1.5V | 1Mbps |
隱性(邏輯1) | 2.0V ~ 3.0V | 2.0V ~ 3.0V | ≈0V (±0.05V) | ||
低速CAN | 顯性(邏輯0) | ≥3.0V | ≤2.0V | ≥1.5V | 10-125Kbps |
隱性 (邏輯1) | ≤1.0V | ≥4.0V | ≤-2.0V |
1.2 CAN總線終端電阻作用
高速CAN總線需在物理兩端各接一個120Ω電阻,形成總電阻60Ω的網絡,具有如下作用:
- 提高抗干擾能力。若無終端電阻,隱性無壓差時,外部很小的干擾就可能導致進入顯性。
- 快速進入隱性狀態。加個終端電阻,加速寄生電容放電。
- 提高信號質量。
一、提高抗干擾能力
CAN總線有“顯性”和“隱性”兩種狀態,“顯性”有壓差,“隱性”無壓差,由CAN收發器決定。下圖是一個CAN收發器的典型內部結構圖,CANH、CANL連接總線。
總線顯性時,收發器內部Q1、Q2導通,CANH、CANL之間產生壓差;隱性時,Q1、Q2截止,CANH、CANL處于無源狀態,壓差為0。
總線若無負載,隱性時差分電阻阻值很大,內部的MOS管屬于高阻態,外部的干擾只需要極小的能量即可令總線進入顯性(一般的收發器顯性門限最小電壓僅500mV)。這個時候如果有差模干擾過來,總線上就會有明顯的波動,而這些波動沒有地方能夠吸收掉他們,就會在總線上創造一個顯性位出來。所以為提升總線隱性時的抗干擾能力,可以增加一個差分負載電阻,且阻值盡可能小,以杜絕大部分噪聲能量的影響。然而,為了避免需要過大的電流總線才能進入顯性,阻值也不能過小。
二、快速進入隱性狀態
加電阻前,可以看到由高變低時,不夠快:
加上終端電阻:
三、提高信號質量
高頻信號在傳輸線末端會因阻抗突變產生反射波,反射與原始信號疊加形成振鈴(信號振蕩),導致電平失真。終端電阻通過匹配傳輸線特征阻抗(約120Ω),吸收反射能量,避免波形畸變。
帶振鈴:
加上電阻后:
為何120Ω?
什么是阻抗?在電學中,常把對電路中電流所起的阻礙作用叫做阻抗。阻抗單位為歐姆,常用Z表示,是一個復數Z= R+i( ωL–1/(ωC))。具體說來阻抗可分為兩個部分,電阻(實部)和電抗(虛部)。其中電抗又包括容抗和感抗,由電容引起的電流阻礙稱為容抗,由電感引起的電流阻礙稱為感抗。這里的阻抗是指Z的模。
任何一根線纜的特征阻抗都可以通過實驗的方式得出。線纜的一端接方波發生器,另一端接一個可調電阻,并通過示波器觀察電阻上的波形。調整電阻阻值的大小,直到電阻上的信號是一個良好的無振鈴的方波,此時的電阻值可以認為與線纜的特征阻抗一致。
采用兩根汽車使用的典型線纜,將它們扭制成雙絞線,就可根據上述方法得到特征阻抗大約為120Ω,這也是CAN標準推薦的終端電阻阻值,所以這個120Ω是測出來的,不是算出來的,都是根據實際的線束特性進行計算得到的。當然在ISO 11898-2這個標準里面也是有定義的。
不接120Ω后果?
不接終端電阻的后果
- 通信故障,信號反射導致電平波動,可能誤觸發顯性位,引發CRC校驗錯誤或數據幀丟失
- 波形異常,示波器觀測顯示信號下降沿變緩、振鈴明顯,隱性狀態恢復時間延長,影響高速通信(如CAN FD)的時序容限
- 抗干擾能力下降,隱性狀態易受外部噪聲干擾,增加誤碼率。
為什么功率還要選0.25W?
這個就要結合一些故障狀態也計算,汽車ECU的所有接口都需要考慮短路到電源和短路到地的情況,所以我們也需要考慮CAN總線的節點短路到電源的情況,根據標準需要考慮短路到18V的情況,假設CANH短路到18V,電流會通過終端電阻流到CANL上,而CANL內部由于限流的原因,最大注入電流為50mA(TJA1145的規格書上標注),這時候120Ω電阻的功率就是50mA50mA120Ω=0.3W。考慮到高溫情況下的降額,終端電阻的功率就是0.5W。
1.3 CAN幀結構
總的來說CAN協議幀有5種類型:
整體的結構如下:
- 幀開始 (SOF): 單bit表示幀開始。
- 仲裁域(Arbitration):包括確定幀優先級的消息標識符 (ID) 和遠程請求的遠程傳輸請求 (RTR) 位。
- 控制域 :包括 DLC(數據長度代碼),指示數據的字節數。
- 數據字段:包含幀的實際數據,最多 8 個字節。
- CRC字段 :用于錯誤檢查,包括 CRC 序列和 CRC 分隔符。
- ACK: 包括確認位和確認分隔符。
- EOF: 由 7 個隱性位(1)組成,標記幀的結束。
- 間歇場:幀間隔專用,由3個隱性位組成。
- 用于錯誤或過載的其他字段。
1.3.1 數據幀 - 標準幀(11位ID)
1.3.2 數據幀 - 擴展幀(29位ID)
1.3.2 擴展幀與標準幀使用場景
使用場景 | ID類型 | ID設計思路 |
---|---|---|
簡單網絡 | 標準幀 | 預留固定ID段給不同節點,如0x100-0x1FF |
復雜網絡 | 擴展幀 | 設計ID高位為設備類型/功能碼,低位為節點編號 |
多廠家系統 | 擴展幀 | 高位區分廠家ID,低位區分設備和功能 |
廣播與點對點混合 | 標準或擴展幀 | 廣播使用固定ID的標準幀,點對點用不同ID的擴展幀 |
1.3.3 實際場景舉例
比如有多個板卡,每個板卡帶有板卡類型+板卡ID,他們之間通過CAN總線連接到一起,板卡可以進行廣播發送和點對點發送,此時CAN幀消息結構可以這樣定義:
標準幀用來發送廣播消息,一個板子可以給其他板子廣播發送數據。消息類別中可以用來區分消息的優先級,可以支持4種優先級、16種板卡類型和32個板子互聯:
點對點使用擴展幀,ID的前11bit和標準幀一樣,但后18bit中可以描述目標板卡的信息:
這樣設計很方便的進行消息的優先級控制以及板卡的消息過濾功能,比如只過濾指定板卡類型的消息,甚至是特定的消息!
1.4 總線仲裁機制
當多個節點可能同時嘗試發送數據時,需要一種機制,保證總線上只有一個節點能繼續發送,其他節點延遲發送。
仲裁保證高優先級消息優先傳輸,且總線無碰撞。
【核心原理】
- 基于CAN報文的ID字段進行仲裁。 CAN總線采用差分信號,邏輯“0”(Dominant)比邏輯“1”(Recessive)電平優先。即ID數值越小,優先級越高(ID低 = 優先級高)
- 節點在發送ID位時,同時監聽總線上的實際電平。
- 如果節點發送“1”,但讀總線實際是“0”,說明有高優先級節點發送“0”,當前節點立即停止發送(放棄仲裁)。如果發送1,讀總線是1,繼續發送。直到只剩下一個節點繼續發送,該節點獲得總線控制權。
- CAN控制器自動完成仲裁,無需軟件干預
1.5 總線標識符ID過濾
CAN 總線使用獨特的過濾機制來處理消息。這種方法增強了其效率和靈活性。過濾在多個節點同時通信的復雜系統中特別有用。它確保重要信息到達適當的接收者,而不會讓其他節點因非必要數據而變得混亂。
正如前面所述,可以配置過濾器,指定匹配ID中的部分字段,比如該字段可能描述目標設備ID,實現當目標設備ID配置為自身時,只接收發往自己的單點通信的功能。
基本概念:
- 濾波器(Filter):硬件模塊,負責檢測報文ID是否匹配。
- 掩碼(Mask):決定ID中哪些位參與匹配,哪些位忽略。
- FIFO0 和 FIFO1:兩個獨立的接收FIFO,過濾后的報文存放位置。
【注意】CAN控制器的濾波器硬件設計只支持對ID的匹配,不支持根據數據位內容過濾。
兩種模式:掩碼模式和列表模式。
- 掩碼模式
過濾器ID與掩碼組合,匹配過濾器ID中掩碼指定的位。即報文ID 與濾波器ID 按位進行比較,掩碼為1的位置必須匹配,掩碼為0的位置忽略。
公式如下:
(Received_ID & Mask) == (Filter_ID & Mask)
例如,掩碼為0x7FF(0111 1111 1111),過濾器ID為0x100(0001 0000 0000 ),只能匹配0x100;
掩碼為0x700(0111 0000 0000),過濾器ID為0x100(0001 0000 0000 ),對應上后,001和掩碼匹配上了,只能匹配(001 0000 0000 ~ 001 1111 1111)即0x100~0x1FF。
- 列表模式
最簡單,就是和指定的ID直接匹配。
1.6 CAN FD
CAN FD(CAN with Flexible Data-Rate) 是 CAN 總線協議的增強版本,由 Bosch 公司在 2012 年提出,意在突破傳統 CAN 總線的數據傳輸速率和數據長度限制。
特性 | 傳統 CAN | CAN FD |
---|---|---|
最大數據長度 | 8 字節 | 最多 64 字節 |
數據傳輸速率 | 通常最高 1 Mbps | 數據段最高可達 8 Mbps(甚至更高) |
傳輸效率 | 較低(受限于幀長和速率) | 更高,支持更大數據量和更高速率 |
兼容性 | 傳統設備 | 兼容傳統 CAN,需支持 FD 的設備 |
STM32F7、STM32H7等部分型號MCU內置CAN FD模塊,適合嵌入式開發。
Jetson AGX Orin支持CAN FD。
2.STM32中的CAN通信
2.1 CAN外設介紹
2.1.1 CAN 框圖
CAN2的(只有互聯型設備才有CAN2)。
本次使用的STM32F103C8T6只有CAN1。
簡單看一下,發送的數據會進入3個發送郵箱進行數據發送,接收的數據首先進過濾器,然后放到FIFO中,每個FIFO有3個接收郵箱。
2.1.2 發送過程
發送的數據通過CPU寫入到郵箱中,然后給出請求發送的命令,然后發送和接收控制器就會等待總線空閑,然后自動把這個報文廣播到總線上。
為何需要3個郵箱?
簡單來說就是3級緩存,減少發送的CPU等待。如果全滿了那就沒辦法了。
當兩個或者三個郵箱都有數據要發送,那么會先發哪一個呢?手冊中是這么寫的:
實際上,如果 hcan.Init.TransmitFifoPrioritys (即MX中Transmit Fifo Priority選項)設置為了Enable,表示第二種方法,按發送請求次序確定。
如果是Disable,則按消息的ID優先級來確認,當ID相同時,按郵箱號確定。
2.1.3 接收過程
接收到的報文,首先會進入過濾器,根據過濾規則,被存儲在過濾器指定的3級郵箱深度的FIFO中(即過濾器綁定的FIFIO0還是FIFO1中。一個過濾器都沒有則會進入FIFO0。)。FIFO完全由硬件來管理,從而節省了CPU的處理負荷,簡化了軟件并保證了數據的一致性。應用程序只能通過讀取FIFO輸出郵箱,來讀取FIFO中最先收到的報文。
FIFO的3個郵箱都是滿的,再收到一個數據會怎樣?
- 如果禁用了FIFO鎖定功能(CAN_MCR寄存器的RFLM位被清’0’),那么FIFO中最后收到的報文就被新報文所覆蓋。這樣,最新收到的報文不會被丟棄掉(但實際上被覆蓋的報文是丟掉的),此時發送端應該不會重傳。對應ReceiveFifoLocked 為DISABLE;
- 如果啟用了FIFO鎖定功能(CAN_MCR寄存器的RFLM位被置’1’),那么新收到的報文就被丟棄,軟件可以讀到FIFO中最早收到的3個報文。此時發送端應該會重傳吧(不確定)。對應ReceiveFifoLocked 為ENABLE。
接收相關的中斷
- 一旦往FIFO存入一個報文,會產生一個中斷請求。
- 當FIFO變滿時(即第3個報文被存入),會產生一個滿中斷請求。
- 在溢出的情況下,會產生一個溢出中斷請求。
2.2 MX配置
基本配置:
- 波特率計算
Prescaler Time Quanta in Bit Segment 1和2是為了計算波特率使用的。
CAN掛在APB1總線上,這里配置的是36MHz。
位段1(BS1,Bit Segment 1):采樣點之前的時間段
位段2(BS2,Bit Segment 2):采樣點之后的時間段
最終的波特率是下面公式計算出來的:
36M/分頻系數/(BS1 + BS2 + 1)
我們目標是配置出1Mbit/s的速率,那么可以使用小工具進行計算,CAN波特率計算工具:
gpt得知,采樣點在75%-87.5% 左右(靠近比特末尾)可確保數據穩定,這里選擇了BS1:14 BS2:3這一條。
-
Basic Parameters(基本參數)
- Automatic Retransmission 是否允許自動重傳錯誤幀,建議開啟
- Transmit Fifo Priority 發送 FIFO 優先級模式,啟用后按 FIFO 順序發送,禁用則按報文標識符優先級發送。建議啟用,保證發送的順序。
- Receive Fifo Locked 設置為ENABLE,接收FIFO滿了,會將報文丟棄(對端應該會重傳吧)。建議啟用。
-
Test Mode
有四種選擇:
- Normal 正常模式
生產環境或實際應用中的標準工作模式,多機通信。 - Loop Back 環回模式
軟件調試,驗證發送與接收功能。CAN 控制器內部環回發送和接收數據,不發送到總線。在環回模式下,bxCAN在內部把Tx輸出回饋到Rx輸入上,而完全忽略CANRX引腳的實際狀態。在環回模式下CAN內核忽略確認錯誤(在數據/遠程幀的確認位時刻,不檢測是否有顯性位),但會進過濾器。 - Silent 靜默模式
控制器只接收總線數據,但不發送任何信號或應答。監聽模式,用于被動監聽總線數據。 - Silent Loop Back 靜默環回模式
軟件調試,驗證接收功能,無干擾總線。結合環回和靜默,內部環回數據,不送出總線,也不應答
- Normal 正常模式
中斷配置:
- CAN1 TX interrupts
觸發條件:發送完成中斷。
用途:確認報文已成功發送,釋放發送緩沖區,可以繼續發送新的數據。
應用:在中斷服務函數中通常清標志位,通知上層發送完成。 - CAN1 RX0 interrupts
觸發條件:接收 FIFO 0 有新報文到達。
用途:讀取 FIFO 0 中的 CAN 報文,進行數據處理。
特點:FIFO 0 常用于普通消息接收,響應速度較快。 - CAN1 RX1 interrupt
觸發條件:接收 FIFO 1 有新報文到達。
用途:讀取 FIFO 1 中的 CAN 報文,適合區分不同消息隊列。
特點:可以用來區分高優先級和低優先級消息,或不同來源的消息。 - CAN1 SCE interrupt
觸發條件:CAN 狀態變化或錯誤事件。
包含事件:
錯誤計數器變化(Error Passive / Active 狀態)
總線錯誤(Bit Error、Stuff Error 等)
總線關閉(Bus-Off)
喚醒事件
用途:監控 CAN 總線健康狀態,及時響應和處理錯誤,保證通信可靠。
【創建代碼】
創建了can.c,其中的初始化:
void MX_CAN_Init(void)
{hcan.Instance = CAN1;hcan.Init.Prescaler = 2;hcan.Init.Mode = CAN_MODE_NORMAL;hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;hcan.Init.TimeSeg1 = CAN_BS1_14TQ;hcan.Init.TimeSeg2 = CAN_BS2_3TQ;hcan.Init.TimeTriggeredMode = DISABLE;hcan.Init.AutoBusOff = DISABLE;hcan.Init.AutoWakeUp = DISABLE;hcan.Init.AutoRetransmission = ENABLE;hcan.Init.ReceiveFifoLocked = DISABLE;hcan.Init.TransmitFifoPriority = ENABLE;if (HAL_CAN_Init(&hcan) != HAL_OK){Error_Handler();}
}
2.3 過濾器詳解
接收到的數據,首先會經過過濾器,符合條件的才會進入FIFO。
還得掏出下面這個圖:
中文版:
英文版:
【這里推薦英文版的,中文版的過濾器編號和過濾器組把人搞懵逼】 中文版中的過濾器編號并不和程序中的FilterBank
字段對應,和FilterBank
字段對應的中文版中是過濾器組!
簡單看一下,前面提到了,過濾器有兩種模式:掩碼
和純ID
,這張圖中,按照FilterScale
的不同,還得分16位還是32位,所有總共有4種寄存器配置方式:
- 1個32位的針對標識符屏蔽過濾器(即掩碼模式)
- 2個32位的只針對標識符的過濾器(即純ID模式),也就是你只能配置2個ID列表方式的過濾器,只能過濾2個ID號
- 2個16位的針對標識符屏蔽過濾器(即掩碼模式)
- 4個32位的只針對標識符的過濾器(即純ID模式)
**【注意-易錯點】**這里的1個、2個和4個針對的是一個過濾器編號中對應了幾個。比如當使用HAL_CAN_ConfigFilter
設置一個過濾器時,可以給它配置2個32為列表標識的過濾器。這樣推斷,STM32F1X只有一個CAN,一共可配置14個過濾器,每個過濾器可以按16位還是32位進行 1個、2個和4個這樣的配置!
2.3.1 32位掩碼模式的過濾器配置
注意STF1/4/7都支持32bit配置!
這里的Mapping實際就是我們需要配置的位。為了簡化配置,我們可以提煉為一個結構體,可支持對掩碼和ID的配置:
typedef struct CanFilterCfg
{uint32_t b1Reserve : 1; //最低位未用 uint32_t b1RTR : 1; //RTR過濾位 uint32_t b1IDE : 1; //擴展/標準幀標識 可用來過濾擴展幀還標準幀 uint32_t b18EXIDL : 18;//擴展幀ID,可以根據實際業務再進行拆分uint32_t b11STID : 11;//標準幀ID,可以根據實際業務再進行拆分
}TCanFilterCfg; //正好32位typedef union CanFilterDesc
{TCanFilterCfg Cfg;uint32_t Val;
}UCanFilterDesc;
比如只過濾標準幀:
CAN_FilterTypeDef sFilterConfig;UCanFilterDesc uFilterMask; //過濾掩碼選定UCanFilterDesc uFilterId; //過濾目標選定//掩碼,只對擴展幀/標準幀b1IDE進行標記,這里對只關心的字段按bit賦值1即可uFilterMask.Val = 0; //初始化uFilterMask.Cfg.b1IDE = 1; //實際ID,只設置標準幀標記uFilterId.Val = 0;uFilterId.Cfg.b1IDE = 0; //標準幀,1是擴展幀sFilterConfig.FilterBank = 0; sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; //32位sFilterConfig.FilterIdHigh = ((uFilterId.Val >> 16) & 0x0000FFFF); //高16bitsFilterConfig.FilterIdLow = (uFilterId.Val & 0x0000FFFF);sFilterConfig.FilterMaskIdHigh = ((uFilterMask.Val >> 16) & 0x0000FFFF);sFilterConfig.FilterMaskIdLow = (uFilterMask.Val & 0x0000FFFF);sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;sFilterConfig.FilterActivation = ENABLE;sFilterConfig.SlaveStartFilterBank = 14; //固定配置,指定第二個CAN的Bank從哪個編號開始if (HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK){return -1;}
2.3.2 16位掩碼模式的過濾器配置(踩坑)
和程序中配置字段的對應關系:
同樣的,Mapping抽象為一個位域結構。不同的是,因為是16位的,一個過濾器Bank可對應兩個,FilterIdHigh對應一個,FilterIdLow 對應另一個。
typedef struct CanFilterCfg
{uint32_t b3EXID : 3; //擴展幀ID的高[17:15]位 uint32_t b1IDE : 1; //擴展/標準幀標識 可用來過濾擴展幀還標準幀 uint32_t b1RTR : 1; //RTR過濾位 uint32_t b11STID : 11;//標準幀ID,可以根據實際業務再進行拆分
}TCanFilterCfg16; typedef union CanFilterDesc
{TCanFilterCfg16 Cfg;uint32_t Val;
}UCanFilterDesc16;
這里我們也是只過濾標準幀,擴展幀收不到了:
//CAN初始化過濾器設置
int CAN_Init()
{//過濾器配置 - 只接收標準幀CAN_FilterTypeDef sFilterConfig;UCanFilterDesc16 uFilterMask; //過濾掩碼選定UCanFilterDesc16 uFilterId; //過濾目標選定//掩碼,只對擴展幀/標準幀b1IDE進行標記,這里對只關心的字段按bit賦值1即可uFilterMask.Val = 0; //初始化uFilterMask.Cfg.b1IDE = 1; //實際ID,只設置標準幀標記uFilterId.Val = 0;uFilterId.Cfg.b1IDE = 0; //標準幀sFilterConfig.FilterBank = 0; sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;sFilterConfig.FilterScale = CAN_FILTERSCALE_16BIT; sFilterConfig.FilterIdHigh = uFilterId.Val; //注意這里直接賦值即可sFilterConfig.FilterIdLow = uFilterId.Val //0; 注意千萬別配置為0sFilterConfig.FilterMaskIdHigh = uFilterMask.Val;sFilterConfig.FilterMaskIdLow = uFilterMask.Val;//0;sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;sFilterConfig.FilterActivation = ENABLE;//sFilterConfig.SlaveStartFilterBank = 14; //F1中這個配置也無效if (HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) != HAL_OK){return -1;}return 0;
}
【巨坑】
下面這段代碼一開始,Low都賦值為0,結果發現過濾器根本不起作用。這里還是自己理解有誤,手冊說的也不清楚。
像16bit的配置模式,對應兩個過濾器,這其中有一個為0的話,實際上是什么都不過濾的!。
High和Low配置成一樣的就好了!
建議直接用32bit的!
sFilterConfig.FilterIdHigh = uFilterId.Val; //注意這里直接賦值即可sFilterConfig.FilterIdLow = uFilterId.Val //0; 注意千萬別配置為0sFilterConfig.FilterMaskIdHigh = uFilterMask.Val;sFilterConfig.FilterMaskIdLow = uFilterMask.Val;//0;
2.4 回環模式測試
mx配置中,Test Mode配置為lookback模式,進行單機測試。
初始化時,啟用中斷和CAN
//CAN初始化設置int CAN_Init(){//過濾器配置 - 只接收標準幀CAN_FilterTypeDef sFilterConfig;UCanFilterDesc16 uFilterMask; //過濾掩碼選定UCanFilterDesc16 uFilterId; //過濾目標選定//掩碼,只對擴展幀/標準幀b1IDE進行標記,這里對只關心的字段按bit賦值1即可uFilterMask.Val = 0; //初始化uFilterMask.Cfg.b1IDE = 1; //實際ID,只設置標準幀標記uFilterId.Val = 0;uFilterId.Cfg.b1IDE = 0; //標準幀sFilterConfig.FilterBank = 0; sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;sFilterConfig.FilterScale = CAN_FILTERSCALE_16BIT; sFilterConfig.FilterIdHigh = uFilterId.Val; sFilterConfig.FilterIdLow = 0; //這個不配置了sFilterConfig.FilterMaskIdHigh = uFilterMask.Val;sFilterConfig.FilterMaskIdLow = 0;sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;sFilterConfig.FilterActivation = ENABLE;//sFilterConfig.SlaveStartFilterBank = 14; //F1中這個配置也無效if (HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) != HAL_OK){return -1;}//激活接收中斷 FIFO0中只要收到消息就進中斷if (HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK){return -1;}//使能發送MailBox if (HAL_CAN_ActivateNotification(&hcan, CAN_IT_TX_MAILBOX_EMPTY) != HAL_OK){return -1;}//使能CAN節點收發if (HAL_CAN_Start(&hcan) != HAL_OK){return -1;}return 0;
}
2.4.1 各種中斷
/* Transmit Interrupt /
#define CAN_IT_TX_MAILBOX_EMPTY ((uint32_t)CAN_IER_TMEIE) /!< 用來驅動發送流程,比如發送完成后通知程序準備加載下一幀數據。必須確保發送郵箱確實空后才寫數據,否則可能導致數據覆蓋。*/
/* Receive Interrupts /
#define CAN_IT_RX_FIFO0_MSG_PENDING ((uint32_t)CAN_IER_FMPIE0) /!< FIFO 0 中有新的消息進入且等待處理時觸發。 中斷服務程序要盡快讀取消息,防止 FIFO 堆積導致溢出。/
#define CAN_IT_RX_FIFO0_FULL ((uint32_t)CAN_IER_FFIE0) /!<FIFO 0 緩沖區滿,無法再接收新消息時觸發。 /
#define CAN_IT_RX_FIFO0_OVERRUN ((uint32_t)CAN_IER_FOVIE0) /!< FIFO 0 出現溢出,丟失一條或多條消息時觸發。 /
#define CAN_IT_RX_FIFO1_MSG_PENDING ((uint32_t)CAN_IER_FMPIE1) /!< FIFO 1 message pending interrupt /
#define CAN_IT_RX_FIFO1_FULL ((uint32_t)CAN_IER_FFIE1) /!< FIFO 1 full interrupt /
#define CAN_IT_RX_FIFO1_OVERRUN ((uint32_t)CAN_IER_FOVIE1) /!< FIFO 1 overrun interrupt */
/* Operating Mode Interrupts /
#define CAN_IT_WAKEUP ((uint32_t)CAN_IER_WKUIE) /!< CAN 控制器從休眠模式(sleep)被喚醒時觸發。適用于低功耗應用,確保喚醒后重新初始化通信。 /
#define CAN_IT_SLEEP_ACK ((uint32_t)CAN_IER_SLKIE) /!< CAN 控制器進入休眠模式時觸發,表示睡眠模式已被確認。確認進入低功耗狀態,系統可進入節能狀態。Sleep acknowledge interrupt */
/* Error Interrupts /
#define CAN_IT_ERROR_WARNING ((uint32_t)CAN_IER_EWGIE) /!< CAN 控制器檢測到錯誤計數器達到警告閾值(如接收或發送錯誤計數器超過某值)。 提示總線錯誤增加,需關注通信質量。 /
#define CAN_IT_ERROR_PASSIVE ((uint32_t)CAN_IER_EPVIE) /!< CAN 控制器進入錯誤被動狀態,即總線錯誤嚴重,但尚未完全失效。 /
#define CAN_IT_BUSOFF ((uint32_t)CAN_IER_BOFIE) /!< CAN 控制器進入總線關閉狀態,因錯誤計數器超過閾值,停止總線通信。 /
#define CAN_IT_LAST_ERROR_CODE ((uint32_t)CAN_IER_LECIE) /!< 總線錯誤發生時觸發,記錄最后一次錯誤代碼。 /
#define CAN_IT_ERR OR ((uint32_t)CAN_IER_ERRIE) /!< 總線出現任意錯誤時觸發。 需要結合錯誤狀態寄存器分析具體錯誤類型。*/
2.4.2 數據發送 - 中斷方式
需使能中斷:
//使能發送MailBox if (HAL_CAN_ActivateNotification(&hcan, CAN_IT_TX_MAILBOX_EMPTY) != HAL_OK){return -1;}
發送測試消息:
//CAN初始化CAN_Init();uint8_t u8Data[8] = {1,2,3,4,5,6,7,8};//發送uint8_t u8TxBoxesNum = HAL_CAN_GetTxMailboxesFreeLevel(&hcan); //空閑發送郵箱的個數if (u8TxBoxesNum > 0){CAN_Send_STD_Frame(u8Data); //發送標準幀}u8TxBoxesNum = HAL_CAN_GetTxMailboxesFreeLevel(&hcan); //空閑發送郵箱的個數if (u8TxBoxesNum > 0){CAN_Send_EXT_Frame(u8Data); //發送擴展幀}
發送郵箱空閑中斷,該中斷只要有一個郵箱空閑出來,就會觸發中斷:
//CAN 發送郵箱發送成功中斷回調
void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef *hcan)
{// 投遞消息到消息隊列,可以發送下個消息了。。TODO
}
void HAL_CAN_TxMailbox1CompleteCallback(CAN_HandleTypeDef *hcan)
{// 投遞消息到消息隊列,可以發送下個消息了。。TODO
}
void HAL_CAN_TxMailbox2CompleteCallback(CAN_HandleTypeDef *hcan)
{// 投遞消息到消息隊列,可以發送下個消息了。。TODO
}
2.4.4 數據接收 - 中斷方式
配置了過濾器只接標準幀后,這個回調中擴展幀就收不到了!
//CAN FIFO0的接收中斷處理
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{CAN_RxHeaderTypeDef rxHeader;uint8_t rxData[8]; //接收8字節數據// 讀取 FIFO0 中的一條消息if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rxHeader, rxData) == HAL_OK){// 打印ID和數據printf("Received CAN message: ID=0x%X DLC=%d\n", rxHeader.StdId, rxHeader.DLC);// 把數據放入你的消息隊列。。TODO}else{// 讀取失敗處理,比如錯誤計數或重啟CAN等}
}
2.5 使用經驗總結
-
CAN通信還需考慮重傳嗎?
- CAN 硬件自帶重傳機制(將開關打開),大多數不需要重傳。
- 廣播情況下,發送時不考慮重傳,數據壓入發送郵箱即可認為發送成功;發送完后,由對端回復丟包情況再進行重傳。(此種丟包應該是應用層丟包,比如隊列溢出等等。)
-
發送郵箱狀態檢查很重要
- 發送前應檢查發送郵箱是否空閑,避免覆蓋未發送完成的報文。
- 使用
HAL_CAN_GetTxMailboxesFreeLevel()
函數可以方便獲取空閑郵箱數量。
-
中斷中投遞消息到任務中處理
- 發送空閑中斷或者接收中斷中,不要直接處理數據,投遞消息到任務中進行處理。
-
其他待補充
3.SocketCan
待完善。
4.CanOpen協議
待完善。