前言:為什么CAN總線是嵌入式通信的"硬通貨"?
在嵌入式通信領域,CAN(Controller Area Network)總線憑借其高可靠性、實時性和多節點通信能力,成為汽車電子、工業控制、智能設備等領域的"標配"。想象一下:一輛汽車中有幾十個ECU(電子控制單元),從發動機控制到車窗調節,都需要實時交換數據——CAN總線正是為這種多節點、高干擾環境設計的通信協議。
STM32幾乎全系列都集成了高性能CAN控制器(bxCAN),支持標準幀/擴展幀、中斷/DMA傳輸、靈活的濾波功能,完美適配工業級應用。本文將從CAN總線的基本原理講起,深入解析STM32的CAN外設結構、配置方法、通信流程和實戰案例,幫助你徹底掌握STM32 CAN通信開發。
一、CAN總線基礎:為什么它能在惡劣環境中可靠通信?
在學習STM32的CAN外設前,我們需要先理解CAN總線的核心特性——這是掌握后續內容的基礎。
1.1 CAN總線的核心優勢
CAN總線由博世公司在1980年代為汽車電子開發,經過30多年發展,已成為國際標準(ISO 11898),其核心優勢包括:
- 多主通信:總線上的每個節點都可主動發送數據,無需中央控制器,避免單點故障;
- 非破壞性仲裁:多個節點同時發送時,通過ID優先級仲裁,優先級高的節點繼續發送,低優先級的自動退讓,不會破壞數據;
- 差分信號傳輸:通過CAN_H和CAN_L兩根線傳輸差分信號,抗電磁干擾能力極強(適合工業和汽車環境);
- 遠距離傳輸:速率125kbps時傳輸距離可達500m,滿足大多數工業場景;
- 錯誤檢測與自動重傳:內置CRC校驗、位填充、應答機制,確保數據可靠傳輸,錯誤幀會自動重傳。
1.2 CAN總線的基本概念
(1)幀結構:CAN數據的"包裝格式"
CAN總線通過"幀"傳輸數據,最常用的是數據幀(用于傳輸有效數據),其結構如下(標準幀):
字段 | 長度(位) | 功能描述 |
---|---|---|
起始位 | 1 | 幀開始標志(低電平) |
仲裁 | 11 | 包含標準ID(11位),用于仲裁 |
控制場 | 6 | 包含數據長度(DLC,0~8字節) |
數據場 | 0~64 | 有效數據(0~8字節,擴展幀支持更多) |
CRC場 | 16 | 循環冗余校驗,檢測數據錯誤 |
應答場 | 2 | 接收節點確認收到正確數據 |
結束位 | 7 | 幀結束標志(高電平) |
擴展幀與標準幀的區別是仲裁場包含29位ID(11位標準ID+18位擴展ID),支持更多節點和更復雜的ID規劃。
(2)位時序:如何保證不同節點的同步?
CAN總線是異步通信,節點間沒有統一的時鐘線,通過位時序實現同步。每個位被分為4個時間段:
- 同步段(SS):用于同步各節點時鐘,長度1TQ(Time Quantum,時間量子);
- 傳播段(PS):補償信號傳輸延遲,長度1~8TQ;
- 相位緩沖段1(PBS1):可延長,用于重同步,長度1~8TQ;
- 相位緩沖段2(PBS2):等于PBS1或信號傳播時間,長度1~8TQ。
波特率計算:波特率 = 1 / (總TQ),其中總TQ = SS + PS + PBS1 + PBS2(通常總TQ=8~25)。
例如:STM32的CAN時鐘為36MHz,若總TQ=18,則波特率=36MHz / 18 = 2Mbps。
1.3 CAN節點的硬件組成
一個完整的CAN節點包括:
- MCU中的CAN控制器(如STM32的bxCAN):負責幀的組裝、發送、接收和錯誤檢測;
- CAN收發器(如SN65HVD230):將控制器輸出的TTL電平轉換為CAN總線的差分信號(CAN_H/CAN_L);
- 終端電阻(120Ω):接在總線兩端,匹配阻抗,防止信號反射。
典型電路:STM32的CAN_TX(如PB6)和CAN_RX(如PB5)連接到SN65HVD230的TXD和RXD,SN65HVD230的CAN_H和CAN_L接總線,兩端各接120Ω電阻。
二、STM32的CAN外設:bxCAN控制器的強大之處
STM32的CAN控制器稱為bxCAN(Basic Extended CAN),支持CAN 2.0A/B標準,不同系列(F1/F4/L4)的CAN外設功能略有差異,但核心結構一致。
2.1 bxCAN控制器的核心特性
- 支持標準幀(11位ID)和擴展幀(29位ID);
- 3個發送郵箱:可緩存3幀待發送數據,支持優先級發送;
- 2個接收FIFO:每個FIFO有3級深度,可緩存3幀接收數據,減輕CPU負擔;
- 靈活的濾波功能:14個濾波器組,支持屏蔽位模式和列表模式,精準過濾目標報文;
- 多種中斷源:發送完成、接收FIFO滿、錯誤警告等,支持中斷和DMA傳輸;
- 總線錯誤管理:檢測總線錯誤(位錯誤、CRC錯誤等),自動進入錯誤狀態。
2.2 bxCAN的硬件結構
理解bxCAN的結構有助于后續配置,核心模塊包括:
(1)發送部分:3個發送郵箱
發送郵箱(Tx Mailbox)是發送數據的"緩沖區",每個郵箱包含:
- 標識符寄存器(TXID):存儲標準/擴展ID;
- 數據長度和數據寄存器(TXDLR、TXDATA):存儲數據長度和有效數據;
- 控制寄存器(TXCTRL):配置發送優先級、幀類型等。
發送流程:當郵箱狀態為"空"時,CPU填充郵箱數據,設置"發送請求",CAN控制器會自動 arbitration(仲裁)并發送,發送完成后郵箱狀態變為"空"。
(2)接收部分:2個FIFO + 14個濾波器
- 接收FIFO:FIFO0和FIFO1,用于緩存接收的有效報文。當FIFO中的報文數達到閾值(如3幀),會觸發中斷;
- 濾波器組:共14個(STM32F103),每個濾波器可配置為屏蔽位模式(按ID掩碼過濾)或列表模式(精確匹配ID),過濾后的報文才會存入FIFO。
(3)波特率發生器
根據輸入時鐘(APB1時鐘,最高36MHz for F1)和配置的位時序參數(預分頻器、同步段、相位段),生成CAN總線需要的波特率。
三、STM32 CAN配置步驟:從CubeMX到代碼實現
本節以STM32F103C8T6和STM32CubeMX 6.6.0為例,結合HAL庫,詳細講解CAN通信的配置流程。
3.1 硬件準備
-
開發板:STM32F103C8T6最小系統板;
-
CAN收發器:TJA1050模塊(帶120Ω終端電阻,可通過跳線選擇);
-
接線:STM32的PA11(CAN_RX)接TJA1050的RXD,PA12(CAN_TX)接TJA1050的TXD,TJA1050的CAN_H和CAN_L接總線(若單節點測試,可短接CAN_H和CAN_L,或接2個節點形成回路)。
3.2 CubeMX配置步驟
步驟1:新建工程,選擇芯片
打開CubeMX,搜索"STM32F103C8T6",創建新工程。
步驟2:配置時鐘樹
CAN外設掛載在APB1總線上,需確保APB1時鐘正確(最高36MHz for F1):
- 配置RCC:HSE選擇"Crystal/Ceramic Resonator"(8MHz外部晶振);
- 配置PLL:PLLMUL=×9,使系統時鐘=72MHz;
- APB1 Prescaler=×2,使APB1時鐘=36MHz(滿足CAN時鐘需求)。
步驟3:配置CAN外設
- 引腳配置:在Pinout視圖中,將PA11配置為"CAN_RX",PA12配置為"CAN_TX";
- 配置CAN模式:在Configuration→Connectivity→CAN中,設置:
- Mode:“Normal”(正常模式,收發數據)或"Loopback"(回環模式,用于自測,發送的報文會自己接收);
- Prescaler(預分頻器):根據波特率需求設置,例如"6"(后續計算波特率);
- 位時序參數(Bit Timing):
- Sync Jump Width (SJW):“1tq”;
- Time Segment 1 (BS1):“8tq”;
- Time Segment 2 (BS2):“3tq”;
(總TQ = Prescaler × (SJW+BS1+BS2) = 6×(1+8+3)=72 → 波特率=36MHz/72=500kbps)
步驟4:配置中斷(可選)
若需要通過中斷處理收發,需配置NVIC:
- 在Configuration→NVIC中,勾選"CAN_RX0_IRQn"(FIFO0接收中斷)和"CAN_TX_IRQn"(發送完成中斷);
- 設置中斷優先級(如搶占優先級1,子優先級0)。
步驟5:生成代碼
設置工程路徑和IDE(如MDK-ARM),點擊"Generate Code"生成初始化代碼。
3.3 HAL庫CAN核心函數解析
生成的代碼中,CAN相關核心函數位于can.c
和stm32f1xx_hal_can.h
,主要包括:
(1)初始化函數:MX_CAN_Init()
自動生成的初始化函數,配置CAN模式、波特率、濾波器等(后續需手動完善濾波器配置):
CAN_HandleTypeDef hcan;static void MX_CAN_Init(void)
{hcan.Instance = CAN1;hcan.Init.Prescaler = 6;hcan.Init.Mode = CAN_MODE_NORMAL; // 正常模式hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;hcan.Init.TimeSeg1 = CAN_BS1_8TQ;hcan.Init.TimeSeg2 = CAN_BS2_3TQ;hcan.Init.TimeTriggeredMode = DISABLE;hcan.Init.AutoBusOff = ENABLE; // 自動退出總線關閉狀態hcan.Init.AutoRetransmission = ENABLE; // 自動重傳hcan.Init.ReceiveFifoLocked = DISABLE;hcan.Init.TransmitFifoPriority = DISABLE;if (HAL_CAN_Init(&hcan) != HAL_OK){Error_Handler();}
}
(2)濾波器配置函數:CAN_Filter_Config()
濾波器需要手動配置,用于過濾目標ID的報文,示例:
void CAN_Filter_Config(void)
{CAN_FilterTypeDef can_filter_st;can_filter_st.FilterActivation = ENABLE; // 使能濾波器can_filter_st.FilterMode = CAN_FILTERMODE_IDMASK; // 屏蔽位模式can_filter_st.FilterScale = CAN_FILTERSCALE_32BIT; // 32位濾波器// 配置濾波器ID和掩碼(只接收ID=0x123的標準幀)can_filter_st.FilterIdHigh = 0x123 << 5; // 標準ID的高16位(左移5位是因為標準ID占11位)can_filter_st.FilterIdLow = 0x0000;can_filter_st.FilterMaskIdHigh = 0xFFF << 5; // 掩碼高16位(全1表示嚴格匹配)can_filter_st.FilterMaskIdLow = 0x0000;can_filter_st.FilterBank = 0; // 使用第0個濾波器組can_filter_st.FilterFIFOAssignment = CAN_FILTER_FIFO0; // 匹配的報文存入FIFO0if(HAL_CAN_ConfigFilter(&hcan, &can_filter_st) != HAL_OK){Error_Handler();}
}
濾波器配置說明:
- 屏蔽位模式:
FilterId
是目標ID,FilterMaskId
是掩碼,掩碼為1的位必須嚴格匹配,為0的位可忽略; - 列表模式:
FilterId
是需要匹配的ID列表,只有完全匹配的ID才會被接收; - 32位/16位模式:32位模式可同時過濾標準幀和擴展幀,16位模式適合單獨過濾。
(3)發送函數:CAN_Send_Message()
封裝發送流程,填充報文并啟動發送:
uint8_t CAN_Send_Message(uint32_t id, uint8_t *data, uint8_t len)
{CAN_TxHeaderTypeDef tx_header;uint8_t tx_mailbox; // 存儲使用的發送郵箱編號// 配置發送頭部tx_header.StdId = id; // 標準IDtx_header.ExtId = 0; // 擴展ID(不使用)tx_header.RTR = CAN_RTR_DATA; // 數據幀tx_header.IDE = CAN_ID_STD; // 標準幀tx_header.DLC = len; // 數據長度(0~8)tx_header.TransmitGlobalTime = DISABLE;// 等待發送郵箱空閑if(HAL_CAN_GetTxMailboxesFreeLevel(&hcan) == 0){return 1; // 郵箱滿,發送失敗}// 發送數據if(HAL_CAN_AddTxMessage(&hcan, &tx_header, data, &tx_mailbox) != HAL_OK){return 2; // 發送失敗}return 0; // 發送成功
}
(4)接收函數:CAN_Receive_Message()
從FIFO接收報文(查詢方式):
uint8_t CAN_Receive_Message(uint32_t *id, uint8_t *data, uint8_t *len)
{CAN_RxHeaderTypeDef rx_header;// 檢查FIFO0是否有數據if(HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &rx_header, data) != HAL_OK){return 1; // 接收失敗}*id = rx_header.StdId; // 獲取接收的ID*len = rx_header.DLC; // 獲取數據長度return 0; // 接收成功
}
(5)中斷服務函數
若使用中斷接收,需實現中斷服務函數和回調函數:
// FIFO0接收中斷服務程序
void CAN1_RX0_IRQHandler(void)
{HAL_CAN_IRQHandler(&hcan);
}// 接收回調函數
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{uint32_t id;uint8_t data[8], len;// 接收數據if(CAN_Receive_Message(&id, data, &len) == 0){// 處理接收的數據(如打印)printf("收到ID:0x%X, 數據:", id);for(uint8_t i=0; i<len; i++){printf("%02X ", data[i]);}printf("\r\n");}
}
四、實戰案例:STM32 CAN節點通信測試
本節通過兩個案例(回環測試和雙節點通信),驗證CAN配置的正確性。
4.1 回環模式測試(單節點)
回環模式下,STM32發送的CAN報文會被自己接收,無需外部節點,適合調試:
步驟1:修改CAN模式為回環
在MX_CAN_Init()
中,將hcan.Init.Mode
改為CAN_MODE_LOOPBACK
。
步驟2:主函數代碼
int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init(); // 初始化串口(用于打印信息)MX_CAN_Init();// 配置濾波器CAN_Filter_Config();// 啟動CANif(HAL_CAN_Start(&hcan) != HAL_OK){Error_Handler();}// 使能FIFO0接收中斷if(HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK){Error_Handler();}uint8_t send_data[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};uint8_t send_cnt = 0;while (1){// 每1秒發送一次數據if(send_cnt >= 100){if(CAN_Send_Message(0x123, send_data, 8) == 0){printf("發送成功: ID=0x123, 數據:11 22 33 44 55 66 77 88\r\n");}else{printf("發送失敗\r\n");}send_cnt = 0;}HAL_Delay(10);send_cnt++;}
}
測試結果
程序運行后,串口會打印"發送成功"和"收到數據"的信息,說明回環通信正常。
4.2 雙節點通信(多節點)
準備兩個STM32節點(Node A和Node B),配置相同波特率(500kbps),實現雙向通信:
- Node A:發送ID=0x123的報文,接收ID=0x456的報文;
- Node B:發送ID=0x456的報文,接收ID=0x123的報文。
Node A核心代碼(發送0x123,接收0x456)
// 濾波器配置(接收0x456)
can_filter_st.FilterIdHigh = 0x456 <<5;
can_filter_st.FilterMaskIdHigh = 0xFFF<<5;// 主循環發送0x123
while(1)
{CAN_Send_Message(0x123, send_data, 8);HAL_Delay(1000);
}
Node B核心代碼(發送0x456,接收0x123)
// 濾波器配置(接收0x123)
can_filter_st.FilterIdHigh = 0x123 <<5;
can_filter_st.FilterMaskIdHigh = 0xFFF<<5;// 主循環發送0x456
while(1)
{CAN_Send_Message(0x456, send_data, 8);HAL_Delay(1000);
}
測試結果
兩個節點通過CAN總線連接后,Node A會收到Node B發送的0x456報文,Node B會收到Node A發送的0x123報文,實現雙向通信。
五、CAN高級特性:中斷、DMA與錯誤處理
5.1 中斷與DMA:提高通信效率
- 中斷方式:適合報文數量少、實時性要求高的場景,接收/發送完成后立即觸發中斷,CPU及時處理;
- DMA方式:適合大量報文傳輸,通過DMA直接將數據搬運到內存,減少CPU干預(僅部分STM32系列支持,如F4)。
中斷配置補充:除了接收FIFO中斷,還可配置以下中斷:
CAN_IT_TX_MAILBOX_EMPTY
:發送郵箱空(可用于連續發送);CAN_IT_ERROR
:總線錯誤(用于錯誤處理);CAN_IT_WAKEUP
:休眠喚醒(低功耗場景)。
5.2 CAN錯誤處理:保證總線可靠性
CAN控制器會檢測總線錯誤,并根據錯誤計數進入不同狀態:
- 主動錯誤狀態:錯誤較少,可正常發送錯誤幀,通知其他節點;
- 被動錯誤狀態:錯誤較多,只能接收,發送前需等待總線空閑;
- 總線關閉狀態:錯誤嚴重,無法參與通信,需軟件復位(
HAL_CAN_ResetError
)恢復。
錯誤處理示例:
void CAN_Error_Handler(void)
{if((hcan.Instance->ESR & CAN_ESR_BOFF) != 0) // 檢測到總線關閉{printf("CAN總線關閉,嘗試恢復...\r\n");HAL_CAN_ResetError(&hcan); // 復位錯誤狀態HAL_CAN_Start(&hcan); // 重新啟動CAN}
}// 在主循環中定期檢查
while(1)
{if(HAL_CAN_GetError(&hcan) != HAL_CAN_ERROR_NONE){CAN_Error_Handler();}// ... 其他代碼 ...
}
六、常見問題與解決方案:避坑指南
6.1 通信失敗:波特率不匹配
現象:發送報文后,接收方收不到,或收到亂碼。
原因:
- 兩個節點的波特率計算錯誤,導致時鐘不同步;
- 位時序參數(BS1、BS2、SJW)配置不一致。
解決方案:
- 重新計算波特率:確保
APB1時鐘 / (Prescaler × (SJW+BS1+BS2))
在兩個節點完全一致; - 推薦位時序參數:對于500kbps,可使用
Prescaler=6, SJW=1, BS1=8, BS2=3
(總TQ=12,36MHz/6/12=500kbps)。
6.2 接收不到報文:濾波器配置錯誤
現象:總線有數據,但本節點收不到。
原因:
- 濾波器未使能(
FilterActivation=DISABLE
); - 濾波器ID或掩碼配置錯誤,目標ID被過濾;
- FIFO溢出(報文過多未及時處理,導致新報文被丟棄)。
解決方案:
- 檢查濾波器使能狀態,確保
FilterActivation=ENABLE
; - 用回環模式測試濾波器:發送目標ID,若能收到,說明濾波器配置正確;
- 及時處理FIFO數據,避免溢出(可增加FIFO滿中斷)。
6.3 總線錯誤:硬件或接線問題
現象:頻繁進入總線錯誤狀態,甚至總線關閉。
原因:
- CAN_H和CAN_L接反或短路;
- 終端電阻缺失或阻值錯誤(應為120Ω);
- 總線長度過長,超出對應波特率的最大距離;
- 電源干擾(未接地或紋波過大)。
解決方案:
- 用萬用表檢查CAN_H和CAN_L是否短路,接線是否正確;
- 確保總線兩端各接一個120Ω終端電阻;
- 降低波特率(如從1Mbps降為500kbps),延長傳輸距離;
- 加強電源濾波,確保接地良好。
七、擴展
- CAN FD:支持更高數據速率(8Mbps)和更長數據幀(64字節),適合大數據量傳輸;
- CANopen協議:在CAN基礎上的高層協議,定義了標準化的通信對象和設備模型;
- 多節點網絡管理:學習如何設計CAN網絡的ID分配、優先級規劃和故障診斷。
掌握STM32 CAN通信,能為工業控制、汽車電子等領域的開發打下堅實基礎——這是嵌入式工程師進階的重要技能。建議結合實際硬件多做測試,尤其是濾波器配置和錯誤處理,才能真正理解CAN總線的精髓。