
1. 項目概述從零開始理解CAN通信最近在整理嵌入式項目資料發現很多朋友對CAN通信這塊又愛又怕。愛的是它在汽車電子、工業控制等領域無處不在是工程師的必備技能怕的是它協議棧看起來復雜各種幀格式、仲裁機制、錯誤處理讓人頭大。我自己也是從STM32的HAL庫調CAN調得一頭霧水開始慢慢啃手冊、做實驗才逐漸摸清了門道。這篇筆記我就把自己學習CAN通信的核心脈絡和實操心得梳理出來目標是讓你看完后不僅能理解CAN協議在“說什么”更能自己動手用一塊STM32開發板把CAN通信跑起來完成兩個節點之間的數據收發。無論你是正在做車載項目還是接觸工業總線這篇內容都能提供一個清晰的入門路徑和可靠的調試參考。CAN全稱Controller Area Network中文叫控制器局域網。它本質上是一種串行通信協議最大的特點是多主、廣播、基于優先級仲裁。想象一下公司開會誰有話要說有數據要發就舉手發送報文如果兩個人同時舉手職位高的報文ID小的先講其他人自動變成聽眾接收模式。這種機制保證了在復雜的網絡環境中重要的消息總能優先傳遞不會因為總線沖突而導致系統癱瘓。我們常說的CAN通信的詳細講解往往就圍繞這個核心機制展開。2. CAN通信核心原理深度拆解要玩轉CAN死記硬背幀格式是沒用的必須理解其設計哲學。它誕生于汽車電子首要任務是可靠和實時。一輛汽車里有幾十甚至上百個ECU電子控制單元發動機轉速、剎車信號、車門狀態等信息需要實時共享。傳統的點對點布線會變得極其復雜而CAN總線用兩根線CAN_H和CAN_L就把所有節點串聯起來極大地簡化了線束。2.1 物理層差分信號與顯性/隱性電平CAN的物理層采用差分信號傳輸這是其抗干擾能力的基石。兩根線CAN_H和CAN_L平時都維持在約2.5V的隱性電平邏輯‘1’。當需要發送顯性位邏輯‘0’時CAN_H被拉高至約3.5VCAN_L被拉低至約1.5V形成一個2V的電壓差。接收端只關心這個電壓差而不是對地的絕對電壓因此共模噪聲如發動機點火產生的電磁干擾會被極大地抑制。注意在實際布線時CAN_H和CAN_L必須使用雙絞線并且兩端需要各接一個120歐姆的終端電阻。這個電阻至關重要它用于阻抗匹配消除信號在總線末端的反射。如果通信不穩定第一個要檢查的就是終端電阻是否接好、阻值是否正確。很多新手調試不通問題都出在這里。2.2 數據鏈路層幀格式與仲裁機制這是CAN協議的核心。我們主要接觸兩種幀數據幀和遠程幀。數據幀用于發送數據遠程幀用于請求某個ID的數據。這里我們重點剖析最常用的標準數據幀11位ID。一幀CAN報文就像一列火車由多個連續的部分組成幀起始SOF一個顯性位0就像發車鈴告訴所有節點“我要開始發送了”。仲裁場包含11位標識符ID和1位遠程傳輸請求位RTR數據幀為顯性0。仲裁就發生在這里。所有節點同時發送ID從最高位MSB開始逐位比較。每個節點在發送的同時也在監聽總線。如果它發送了一個隱性位1但監聽到的是顯性位0它就立刻知道自己“競爭”失敗自動退出發送轉為接收并且不會破壞正在進行的傳輸。ID值越小優先級越高。控制場包含1位標識符擴展位IDE標準幀為顯性0和4位數據長度碼DLC0-8表示后續數據場有多少個字節。數據場真正要發送的數據0-8個字節。CAN是面向內容尋址的接收方只關心ID不關心數據來自哪個節點。CRC場15位循環冗余校驗碼和1位CRC界定符用于校驗數據傳輸是否正確。應答場ACK發送方發出兩個隱性位任何正確接收到幀的節點無論是不是目標節點都會在ACK槽位回一個顯性位告訴發送方“我收到了”。如果發送方沒收到這個應答它會認為傳輸失敗并啟動重發。幀結束EOF7個連續的隱性位表示幀結束。這種基于ID優先級的非破壞性仲裁是CAN實現多主、實時響應的關鍵。它保證了高優先級的消息延遲是確定且有上限的。2.3 錯誤處理與故障界定一個可靠的協議必須有強大的自愈能力。CAN節點有5種錯誤類型位錯誤、填充錯誤、CRC錯誤、格式錯誤、應答錯誤。每個節點內部有兩個計數器發送錯誤計數器TEC和接收錯誤計數器REC。根據錯誤發生的頻率節點會處于三種狀態主動錯誤狀態正常狀態可以正常收發報文檢測到錯誤時發送主動錯誤標志6個連續的顯性位。被動錯誤狀態錯誤計數較高節點可以收發但出錯時只能發送被動錯誤標志6個連續的隱性位并且發送每幀之間要有額外延遲。總線關閉狀態錯誤計數極高節點自動從總線脫離不再參與任何通信只能等待復位或滿足恢復條件。這個機制能防止一個故障節點“拖死”整個網絡體現了CAN的魯棒性。3. STM32的CAN外設配置要點理論懂了我們上硬件。以常見的STM32F1/F4系列為例其內置的bxCAN外設功能很全。配置的關鍵在于理解幾個核心概念和寄存器或HAL庫函數。3.1 工作模式與波特率計算CAN外設有幾種工作模式我們最常用的是正常模式。在初始化階段必須先進入初始化模式來配置波特率、過濾器等然后再切換到正常模式開始通信。波特率計算是第一個攔路虎。CAN總線上的位時間被劃分為4個段同步段SYNC_SEG固定為1個時間份額Tq用于同步。時間段1BS1包含傳播時間段和相位緩沖段1可以設置為1到16個Tq。時間段2BS2相位緩沖段2可以設置為1到8個Tq。再同步跳轉寬度SJW1到4個Tq用于在再同步時調整位時間。波特率 APB1時鐘頻率 / (分頻系數 * (1 BS1 BS2))例如STM32F103的APB1時鐘為36MHz要配置125Kbps的波特率汽車常用。我們選擇分頻系數為12則Tq頻率為3MHz每個位時間為8微秒。設BS15 Tq BS22 Tq則總時間份額為1528 Tq。波特率 3MHz / 8 375Kbps不對。這里有個關鍵點公式中的(1BS1BS2)就是總時間份額數。所以正確的計算是波特率 36MHz / (12 * (152)) 36MHz / (12*8) 36MHz / 96 375Kbps。要達到125Kbps需要調整分頻系數為36波特率 36MHz / (36 * 8) 125Kbps。實操心得波特率配置不對是通信失敗的最常見原因。務必保證通信網絡中的所有節點波特率設置完全一致包括分頻系數、BS1、BS2。建議先用計算工具如STM32CubeMX算好再手動核對。調試時可以用示波器測量CAN_H和CAN_L之間的差分信號一個位的時長應該是1/波特率125Kbps對應8微秒。3.2 過濾器配置硬件篩選的藝術STM32的CAN有一個非常實用的硬件過濾器單元它能根據ID自動篩選報文減輕CPU負擔。過濾器可以工作在兩種模式標識符列表模式就像一個白名單只接收ID完全匹配的報文。標識符屏蔽位模式可以設置一個掩碼Mask掩碼位為1表示必須匹配為0表示不關心。例如設置ID0x123 Mask0x7F0。那么所有ID的高7位0x12x必須匹配低4位任意。這常用于接收一組ID連續的報文。過濾器還有32位和16位尺度之分。32位尺度下一個過濾器可以存一個32位的擴展ID29位或兩個16位的標準ID。配置時需要根據你的ID類型和篩選需求靈活選擇。// 示例使用HAL庫配置一個過濾器接收標準ID為0x123的報文 CAN_FilterTypeDef sFilterConfig; sFilterConfig.FilterBank 0; // 使用過濾器0 sFilterConfig.FilterMode CAN_FILTERMODE_IDMASK; // 掩碼模式 sFilterConfig.FilterScale CAN_FILTERSCALE_32BIT; // 32位尺度 sFilterConfig.FilterIdHigh 0x123 5; // 標準ID左移5位到高位寄存器 sFilterConfig.FilterIdLow 0x0000; sFilterConfig.FilterMaskIdHigh 0xFFFF 5; // 掩碼所有位都必須匹配 sFilterConfig.FilterMaskIdLow 0x0000; sFilterConfig.FilterFIFOAssignment CAN_RX_FIFO0; // 匹配的報文放入FIFO0 sFilterConfig.FilterActivation ENABLE; sFilterConfig.SlaveStartFilterBank 14; if (HAL_CAN_ConfigFilter(hcan, sFilterConfig) ! HAL_OK) { Error_Handler(); }3.3 發送與接收流程配置好波特率和過濾器后就可以進行通信了。發送流程相對簡單填充一個CAN_TxHeaderTypeDef結構體設置ID、DLC、幀類型等將數據填入數組然后調用HAL_CAN_AddTxMessage將消息放入發送郵箱硬件會自動發送。接收則通常采用中斷方式。使能接收FIFOFIFO0或FIFO1非空中斷當有報文存入FIFO時觸發中斷在中斷服務函數中調用HAL_CAN_GetRxMessage讀取報文。// 發送示例 uint8_t txData[8] {0x01, 0x02, 0x03, 0x04}; CAN_TxHeaderTypeDef txHeader; txHeader.StdId 0x456; // 標準ID txHeader.RTR CAN_RTR_DATA; // 數據幀 txHeader.IDE CAN_ID_STD; // 標準幀 txHeader.DLC 4; // 發送4個字節 txHeader.TransmitGlobalTime DISABLE; uint32_t txMailbox; if (HAL_CAN_AddTxMessage(hcan, txHeader, txData, txMailbox) ! HAL_OK) { // 發送錯誤處理 } // 在CAN接收中斷服務函數中讀取 void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { CAN_RxHeaderTypeDef rxHeader; uint8_t rxData[8]; HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, rxHeader, rxData); // 處理接收到的數據例如根據rxHeader.StdId進行不同的操作 }4. 雙節點CAN通信實戰搭建現在我們用兩塊STM32開發板比如常見的F103C8T6核心板搭建一個最簡單的雙節點通信測試環境。4.1 硬件連接與準備你需要準備兩塊STM32開發板帶CAN外設如F103/F407。兩個CAN收發器芯片模塊如TJA1050或SN65HVD230。STM32的CAN外設是控制器需要收發器才能連接到物理總線。若干杜邦線。連接步驟如下電源確保兩個收發器模塊和兩個開發板共地。控制器與收發器將STM32的CAN_TX引腳如PA12連接到收發器模塊的TXD引腳將CAN_RX引腳如PA11連接到收發器模塊的RXD引腳。組建總線將兩個收發器模塊的CAN_H連在一起CAN_L連在一起。終端電阻在總線最遠的兩端各接一個120歐姆電阻在CAN_H和CAN_L之間。對于只有兩個節點的短距離測試在任意一個模塊上接一個120歐姆電阻通常也能工作但規范做法是兩端都接。4.2 軟件代碼編寫我們以STM32CubeMX配合HAL庫為例。CubeMX配置在Pinout視圖啟用CAN外設。在Configuration視圖的CAN參數設置中將模式設為“Normal”。配置波特率參數例如125Kbps Prescaler36 BS15 BS22。在NVIC Settings中使能CAN RX0中斷或RX1中斷。生成代碼后在main.c中補充用戶代碼。初始化CAN在main()函數中系統初始化后調用HAL_CAN_Start(hcan)啟動CAN然后調用HAL_CAN_ActivateNotification(hcan, CAN_IT_RX_FIFO0_MSG_PENDING)激活接收中斷。編寫發送函數可以封裝一個函數內部調用HAL_CAN_AddTxMessage。編寫中斷回調函數如上節示例在HAL_CAN_RxFifo0MsgPendingCallback中讀取和處理數據。4.3 測試與驗證編寫一個簡單的測試程序讓節點A每隔1秒發送一幀ID為0x123數據為遞增計數器的報文。節點B配置過濾器接收ID 0x123的報文并在收到后通過串口打印出來同時點亮一個LED。關鍵調試步驟確保硬件連接正確特別是CAN_H和CAN_L沒有接反終端電阻已接。核對兩邊的波特率配置必須一字不差。在節點A發送函數后檢查HAL_CAN_AddTxMessage的返回值并可以查詢發送郵箱狀態。在節點B如果進不了接收中斷檢查過濾器配置是否正確是否已激活接收中斷。終極武器——CAN分析儀如果軟件排查無果強烈建議使用USB-CAN分析儀如周立功、PCAN等。將其并聯到總線上可以直觀地看到總線上是否有報文、報文的ID和數據是什么、波特率是否正確。這是定位硬件問題還是軟件問題的利器。5. 常見問題排查與調試心得實錄在實際動手過程中你幾乎一定會遇到下面這些問題。我把它們和我的排查思路整理出來希望能幫你快速脫坑。5.1 問題一根本收不到任何數據發送似乎也沒成功檢查清單終端電阻這是新手第一殺手。用萬用表測量CAN_H和CAN_L之間的電阻在總線兩端都接120歐姆電阻的情況下應該是60歐姆左右并聯結果。如果電阻無窮大或很大說明終端電阻沒接或接觸不良。波特率再次用CubeMX或手動計算確認兩個節點的波特率分頻、BS1、BS2設置完全一致。哪怕有一個參數不同通信都無法建立。收發器電源與使能確認TJA1050等收發器模塊的VCC已供電通常是5V或3.3V并且STB待機引腳如果有已拉高或拉低至工作狀態查芯片手冊。引腳映射確認STM32的CAN_RX和CAN_TX引腳是否與收發器模塊的RXD和TXD正確交叉連接控制器TX接收發器TX不對應該是控制器TX接收發器RXD控制器RX接收發器TXD。這里極易接反。工作模式確認CAN外設已從初始化模式切換到正常模式HAL_CAN_Start。5.2 問題二能收到數據但數據錯誤或時有時無可能原因與解決總線干擾如果布線靠近電機、繼電器等強干擾源可能導致誤碼。確保使用雙絞線并盡可能遠離干擾源。可以嘗試降低波特率如從1Mbps降到125Kbps來增強抗干擾性。地線噪聲確保所有節點共地良好地線回路盡量短粗。在復雜系統中地電位差可能引入共模干擾。軟件處理不及時接收FIFO溢出。如果報文非常密集而你的中斷服務函數處理太慢或沒有及時讀取可能導致FIFO溢出新報文丟失。可以在中斷里只做標記和拷貝在主循環里處理業務邏輯。過濾器配置不當你可能設置了過濾器但ID或掩碼設置錯誤導致想收的報文被硬件過濾掉了。調試階段可以先將過濾器配置為不使能FilterActivation DISABLE這樣會接收所有報文看看總線到底有沒有數據。確認有數據后再精細配置過濾器。5.3 問題三發送正常但自己收不到自己的報文自發自收測試失敗理解與設置這是一個常見的測試方法。CAN協議默認情況下發送節點是不會接收自己發出的報文的除非開啟“回環模式”或“靜默回環模式”進行自測試。在正常模式、硬件正常連接的情況下自己發、自己收需要另一個節點應答才行。如果你想做自發自收測試有兩種方法硬件短接將本節點的CAN_TX和CAN_RX在收發器后端或通過軟件配置連接起來注意電平匹配。不推薦容易損壞。使用CAN分析儀這是最標準的方法。用分析儀作為另一個節點發送請求幀或驗證發送的幀是否正確。5.4 調試心得與高級技巧善用狀態寄存器HAL庫提供了HAL_CAN_GetError和HAL_CAN_GetState函數但更底層的信息在hcan.Instance-ESR錯誤狀態寄存器和hcan.Instance-MSR主狀態寄存器里。通過讀取這些寄存器可以知道是哪種錯誤位錯誤、格式錯誤等、錯誤計數是多少這對定位深層問題非常有幫助。理解“監聽模式”在CubeMX中可以將CAN模式設為“Silent”。在這個模式下節點只能接收不能發送也不會發送ACK位或錯誤幀。這就像是一個“網絡監聽器”非常適合用來監測總線流量而不干擾總線或者用于總線分析儀的搭建。關于幀類型除了數據幀還有遠程幀。遠程幀的RTR位為隱性1沒有數據場用于請求另一個節點發送指定ID的數據。在汽車診斷中常用。配置發送遠程幀時注意設置txHeader.RTR CAN_RTR_REMOTE。擴展幀標準幀ID是11位擴展幀是29位。當需要更多節點或更復雜的標識時使用擴展幀。配置時設置txHeader.IDE CAN_ID_EXT并使用ExtId字段。過濾器也需要相應配置為32位模式來處理29位ID。CAN通信的入門關鍵在于把物理層連接做扎實把波特率算準確然后通過一個簡單的收發實驗建立信心。一旦最基礎的鏈路通了再去深入研究過濾器、錯誤管理、更復雜的網絡管理協議如CANopen、J1939就會順暢很多。我建議你在吃透本篇筆記的基礎上找一個開發板親手做一遍遇到問題就對照第五部分來排查這個過程積累的經驗比讀十篇文檔都管用。