前言
最近負責的一個項目用的主控芯片是STM32F407IGT6
,需要和幾個電機控制器進行通訊,有很多參數需要進行監控。有一個問題一直無法解決。在開啟
CAN
的接收中斷,接收不到數據,問題卡了很久,下面簡單分享一下解決的過程和思路。目錄
CAN總線
CAN總線是一種串行通信協議,用于在微控制器和其他設備之間傳輸數據。CAN總線通常用于汽車、工業自動化和機器人等領域。本號發過很多CAN總線干貨文章,大家可以點擊下方標題直接閱讀。秀!靠這篇我竟然2天理解了CAN協議!
秒懂CAN總線,你學會了嗎?
CAN總線不加終端電阻,會出現什么后果?
為什么CAN總線最高速度為1Mbps?
從今天起,你就是CAN專家了。
3個原因告訴你,CAN為什么比RS-485更好?
詳解CAN總線協議
...更多CAN總線可以關注下方公眾號在歷史消息中搜索
CAN總線的硬件通常由以下幾個部分組成:
CAN總線的控制器區域通常包括CAN控制器和CAN收發器。- CAN控制器負責處理CAN總線上的數據傳輸,包括數據發送和接收、錯誤檢測和糾正等;
- CAN收發器則負責將CAN控制器的信號轉換為總線上的電信號,并將總線上的電信號轉換為CAN控制器可以理解的信號。
CAN控制器
主板上的芯片STM32F407IGT6
中帶有兩路的CAN控制器,分別為CAN1
和CAN2
,具體如下圖所示;
CAN收發器
主板上使用的是芯片SN65HVD230
,這是TI公司的一款性能強大且具體低功耗功能的CAN收發器,具體的典型應用電路如下所示;
調試過程
硬件排查
設備的調試過程中,首先要確保硬件鏈路上是否正常。最常見的方法就是直接用示波器進行檢查。具體如下所示;
- 檢查CAN控制器和CAN收發器之間是否正常;
- 檢查CAN收發器的差分信號是否正常,這里可能要了解一下CAN總線電平的顯性電平和隱性電平的特點,以及CAN底層協議的細節,會比較復雜;
CAN分析儀
至于數據傳輸是否正確,可以使用CAN盒進行數據監聽,下面是我使用的一款CAN分析儀,如圖;
將CAN分析儀的CAN_H
和CAN_L
分別并聯到CAN收發器的CAN_H
和CAN_L
上,然后打開CAN分析儀廠家提供的PC軟件,就可以對CAN總線的數據進行監聽;
- 將CAN分析儀接入到CAN總線;
- 將CAN分析儀連接到電腦(這里是USB接口),需要配置相同的波特率;
- 打開CAN分析儀配套的PC軟件,進行數據的收發; ?
-
進行到這里,我在項目中遇到的問題是,發送正常,但是
STM32F407
無法接收到連續的數據,可以接收到一次數據,后面便無法再進入中斷。這時候,只能再芯片端進行Debug
了。
芯片CAN控制器調試
這里的代碼用的HAL庫,庫版本相對來說比較老,是V1.7.10
版本的,如下圖所示;當時我把項目升級到最新的HAL庫,發現CAN部分的驅動改動比較大,另外,下文都是基于
V1.7.10
版本的HAL庫。CAN控制器的初始化代碼如下所示;
void MX_CAN_Init(void)
{
CAN_FilterConfTypeDef sFilterConfig;
/*CAN單元初始化*/
hCAN.Instance = CANx; /* CAN外設 */
hCAN.pTxMsg = &TxMessage;
hCAN.pRxMsg = &RxMessage;
hCAN.Init.Prescaler = 6; /* BTR-BRP 波特率分頻器 定義了時間單元的時間長度 42/(1+6+7)/6 = 500Kbps */
hCAN.Init.Mode = CAN_MODE_NORMAL; /* 正常工作模式 */
hCAN.Init.SJW = CAN_SJW_1TQ; /* BTR-SJW 重新同步跳躍寬度 1個時間單元 */
hCAN.Init.BS1 = CAN_BS1_6TQ; /* BTR-TS1 時間段1 占用了6個時間單元 */
hCAN.Init.BS2 = CAN_BS2_7TQ; /* BTR-TS1 時間段2 占用了7個時間單元 */
hCAN.Init.TTCM = DISABLE; /* MCR-TTCM 關閉時間觸發通信模式使能 */
hCAN.Init.ABOM = ENABLE; /* MCR-ABOM 自動離線管理 */
hCAN.Init.AWUM = ENABLE; /* MCR-AWUM 使用自動喚醒模式 */
hCAN.Init.NART = DISABLE; /* MCR-NART 禁止報文自動重傳 DISABLE-自動重傳 */
hCAN.Init.RFLM = DISABLE; /* MCR-RFLM 接收FIFO 鎖定模式 DISABLE-溢出時新報文會覆蓋原有報文 */
hCAN.Init.TXFP = DISABLE; /* MCR-TXFP 發送FIFO優先級 DISABLE-優先級取決于報文標示符 */
HAL_CAN_Init(&hCAN);
/*CAN過濾器初始化*/
sFilterConfig.FilterNumber = 0; /* 過濾器組0 */
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; /* 工作在標識符屏蔽位模式 */
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; /* 過濾器位寬為單個32位。*/
/* 使能報文標示符過濾器按照標示符的內容進行比對過濾,擴展ID不是如下的就拋棄掉,是的話,會存入FIFO0。*/
sFilterConfig.FilterIdHigh = 0x0000; //(((uint32_t)0x1314<<3)&0xFFFF0000)>>16; /* 要過濾的ID高位 */
sFilterConfig.FilterIdLow = 0x0000; //(((uint32_t)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF; /* 要過濾的ID低位 */
sFilterConfig.FilterMaskIdHigh = 0x0000; /* 過濾器高16位每位必須匹配 */
sFilterConfig.FilterMaskIdLow = 0x0000; /* 過濾器低16位每位必須匹配 */
sFilterConfig.FilterFIFOAssignment = 0; /* 過濾器被關聯到FIFO 0 */
sFilterConfig.FilterActivation = ENABLE; /* 使能過濾器 */
sFilterConfig.BankNumber = 14;
HAL_CAN_ConfigFilter(&hCAN, &sFilterConfig);
}
根據注釋,可以大概看懂,另外再簡單分析一下關鍵的幾點;- 波特率設置為 500Kbps;
- 對報文不進行過濾,可以接收任何擴展ID的數據;
不難發現,
CAN1
的FIFO0
產生接收中斷需要滿足三個條件中的任意一個;-
FMPIE0
置1
且FMP0
置1
;FIFO不為空會產生中斷 -
FFIE0
置1
且FULL
置1
;FIFO滿,會產生中斷 -
FOVIE0
置1
且FOVR0
置1
;FIFO溢出,會產生中斷
使用仿真器對芯片進行調試,設置斷點,發現
FMPIE0
被清空了,具體如下圖所示;
FMPIE0
這一位是FIFO0中有掛起的消息會產生中斷的中斷使能標志位;所以到這里,問題有點明朗了,為什么無法進入中斷?是中斷使能位被清空了。那么下面就是檢查代碼,看看是哪里把中斷給
disable
了。繼續調試,發現在ESR
寄存器中,TEC
的值一直增加,然后EWGF
被值1
了;具體如下所示;
TEC
和REC
分別是發送錯誤計數器和接收錯誤計數器;如 CAN 協議所述,錯誤管理完全由硬件通過發送錯誤計數器( CAN_ESR 寄存器中的 TEC 值)和接收錯誤計數器( CAN_ESR 寄存器中的 REC 值)來處理,這兩個計數器根據錯誤 狀況進行遞增或遞減。有關 TEC 和 REC 管理的詳細信息,請參見 CAN 標準。兩者均可由軟件讀取,用以確定網絡的穩定性。此外, CAN 硬件還將在 CAN_ESR 寄存器中 提供當前錯誤狀態的詳細信息。通過 CAN_IER 寄存器( ERRIE 位等),軟件可以非常靈活 地配置在檢測到錯誤時生成的中斷。當
TEC
大于96的時候,硬件會將EWGF
置1
(錯誤警告標志位);在代碼中找到了相應的宏定義;這下問題越來越清晰了。全文搜索這個宏定義,在
HAL_CAN_IRQHandler
中找到了__HAL_CAN_DISABLE_IT(CAN_IT_FMP0)
,關閉了FIFO0
的消息掛起中斷, 整體代碼如下;
/**
* @brief Handles CAN interrupt request
* @param hcan: pointer to a CAN_HandleTypeDef structure that contains
* the configuration information for the specified CAN.
* @retval None
*/
void HAL_CAN_IRQHandler(CAN_HandleTypeDef* hcan)
{
uint32_t tmp1 = 0U, tmp2 = 0U, tmp3 = 0U;
uint32_t errorcode = HAL_CAN_ERROR_NONE;
/* Check Overrun flag for FIFO0 */
tmp1 = __HAL_CAN_GET_FLAG(hcan, CAN_FLAG_FOV0);
tmp2 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_FOV0);
if(tmp1 && tmp2)
{
/* Set CAN error code to FOV0 error */
errorcode |= HAL_CAN_ERROR_FOV0;
/* Clear FIFO0 Overrun Flag */
__HAL_CAN_CLEAR_FLAG(hcan, CAN_FLAG_FOV0);
}
/* Check Overrun flag for FIFO1 */
tmp1 = __HAL_CAN_GET_FLAG(hcan, CAN_FLAG_FOV1);
tmp2 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_FOV1);
if(tmp1 && tmp2)
{
/* Set CAN error code to FOV1 error */
errorcode |= HAL_CAN_ERROR_FOV1;
/* Clear FIFO1 Overrun Flag */
__HAL_CAN_CLEAR_FLAG(hcan, CAN_FLAG_FOV1);
}
/* Check End of transmission flag */
if(__HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_TME))
{
tmp1 = __HAL_CAN_TRANSMIT_STATUS(hcan, CAN_TXMAILBOX_0);
tmp2 = __HAL_CAN_TRANSMIT_STATUS(hcan, CAN_TXMAILBOX_1);
tmp3 = __HAL_CAN_TRANSMIT_STATUS(hcan, CAN_TXMAILBOX_2);
if(tmp1 || tmp2 || tmp3)
{
tmp1 = __HAL_CAN_GET_FLAG(hcan, CAN_FLAG_TXOK0);
tmp2 = __HAL_CAN_GET_FLAG(hcan, CAN_FLAG_TXOK1);
tmp3 = __HAL_CAN_GET_FLAG(hcan, CAN_FLAG_TXOK2);
/* Check Transmit success */
if(tmp1 || tmp2 || tmp3)
{
/* Call transmit function */
CAN_Transmit_IT(hcan);
}
else /* Transmit failure */
{
/* Set CAN error code to TXFAIL error */
errorcode |= HAL_CAN_ERROR_TXFAIL;
}
/* Clear transmission status flags (RQCPx and TXOKx) */
SET_BIT(hcan->Instance->TSR, CAN_TSR_RQCP0 | CAN_TSR_RQCP1 | CAN_TSR_RQCP2 |
CAN_FLAG_TXOK0 | CAN_FLAG_TXOK1 | CAN_FLAG_TXOK2);
}
}
tmp1 = __HAL_CAN_MSG_PENDING(hcan, CAN_FIFO0);
tmp2 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_FMP0);
/* Check End of reception flag for FIFO0 */
if((tmp1 != 0U) && tmp2)
{
/* Call receive function */
CAN_Receive_IT(hcan, CAN_FIFO0);
}
tmp1 = __HAL_CAN_MSG_PENDING(hcan, CAN_FIFO1);
tmp2 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_FMP1);
/* Check End of reception flag for FIFO1 */
if((tmp1 != 0U) && tmp2)
{
/* Call receive function */
CAN_Receive_IT(hcan, CAN_FIFO1);
}
/* Set error code in handle */
hcan->ErrorCode |= errorcode;
tmp1 = __HAL_CAN_GET_FLAG(hcan, CAN_FLAG_EWG);
tmp2 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_EWG);
tmp3 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_ERR);
/* Check Error Warning Flag */
if(tmp1 && tmp2 && tmp3)
{
/* Set CAN error code to EWG error */
hcan->ErrorCode |= HAL_CAN_ERROR_EWG;
}
tmp1 = __HAL_CAN_GET_FLAG(hcan, CAN_FLAG_EPV);
tmp2 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_EPV);
tmp3 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_ERR);
/* Check Error Passive Flag */
if(tmp1 && tmp2 && tmp3)
{
/* Set CAN error code to EPV error */
hcan->ErrorCode |= HAL_CAN_ERROR_EPV;
}
tmp1 = __HAL_CAN_GET_FLAG(hcan, CAN_FLAG_BOF);
tmp2 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_BOF);
tmp3 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_ERR);
/* Check Bus-Off Flag */
if(tmp1 && tmp2 && tmp3)
{
/* Set CAN error code to BOF error */
hcan->ErrorCode |= HAL_CAN_ERROR_BOF;
}
tmp1 = HAL_IS_BIT_CLR(hcan->Instance->ESR, CAN_ESR_LEC);
tmp2 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_LEC);
tmp3 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_ERR);
/* Check Last error code Flag */
if((!tmp1) && tmp2 && tmp3)
{
tmp1 = (hcan->Instance->ESR) & CAN_ESR_LEC;
switch(tmp1)
{
case(CAN_ESR_LEC_0):
/* Set CAN error code to STF error */
hcan->ErrorCode |= HAL_CAN_ERROR_STF;
break;
case(CAN_ESR_LEC_1):
/* Set CAN error code to FOR error */
hcan->ErrorCode |= HAL_CAN_ERROR_FOR;
break;
case(CAN_ESR_LEC_1 | CAN_ESR_LEC_0):
/* Set CAN error code to ACK error */
hcan->ErrorCode |= HAL_CAN_ERROR_ACK;
break;
case(CAN_ESR_LEC_2):
/* Set CAN error code to BR error */
hcan->ErrorCode |= HAL_CAN_ERROR_BR;
break;
case(CAN_ESR_LEC_2 | CAN_ESR_LEC_0):
/* Set CAN error code to BD error */
hcan->ErrorCode |= HAL_CAN_ERROR_BD;
break;
case(CAN_ESR_LEC_2 | CAN_ESR_LEC_1):
/* Set CAN error code to CRC error */
hcan->ErrorCode |= HAL_CAN_ERROR_CRC;
break;
default:
break;
}
/* Clear Last error code Flag */
hcan->Instance->ESR &= ~(CAN_ESR_LEC);
}
/* Call the Error call Back in case of Errors */
if(hcan->ErrorCode != HAL_CAN_ERROR_NONE)
{
/* Clear ERRI Flag */
hcan->Instance->MSR = CAN_MSR_ERRI;
/* Set the CAN state ready to be able to start again the process */
hcan->State = HAL_CAN_STATE_READY;
/* Disable interrupts: */
/* - Disable Error warning Interrupt */
/* - Disable Error passive Interrupt */
/* - Disable Bus-off Interrupt */
/* - Disable Last error code Interrupt */
/* - Disable Error Interrupt */
/* - Disable FIFO 0 message pending Interrupt */
/* - Disable FIFO 0 Overrun Interrupt */
/* - Disable FIFO 1 message pending Interrupt */
/* - Disable FIFO 1 Overrun Interrupt */
/* - Disable Transmit mailbox empty Interrupt */
__HAL_CAN_DISABLE_IT(hcan, CAN_IT_EWG |
CAN_IT_EPV |
CAN_IT_BOF |
CAN_IT_LEC |
CAN_IT_ERR |
CAN_IT_FMP0|
CAN_IT_FOV0|
CAN_IT_FMP1|
CAN_IT_FOV1|
CAN_IT_TME);
/* Call Error callback function */
HAL_CAN_ErrorCallback(hcan);
}
}
最后,找到無法進入接收中斷的原因,是CAN總線出現發送錯誤的情況,從而觸發了錯誤警告標志位EWGF
,進而將關閉了消息掛起中斷。總結
本文簡單介紹了在STM32F407上的CAN總線調試過程,項目中難免會遇到各種問題,解決之后,大家要及時做好總結和復盤,技術在于積累和沉淀。
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。
舉報投訴
-
寄存器
+關注
關注
31文章
5343瀏覽量
120377 -
CAN
+關注
關注
57文章
2754瀏覽量
463718 -
總線
+關注
關注
10文章
2881瀏覽量
88090
原文標題:踩坑了,踩坑了!這次又敗在CAN總線手上了!
文章出處:【微信號:mcu168,微信公眾號:硬件攻城獅】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
使用STM32采集電池電壓踩過的那些坑
本文來解析一個盆友在使用STM32采集電池電壓踩過的坑。以STM32F4 的ADC屬于逐次逼近SAR 型ADC為例進行分析,參考STM32F405xxDatasheet,對于如何編寫ADC程序就不做描述了。
發表于 03-01 07:39
使用樹莓派搭建stm32開發環境踩過的坑以及碰到的問題
使用樹莓派搭建stm32開發環境踩了很多坑,下面主要是記錄一下踩過的坑,以及碰到的問題。##開發方式的選擇1.使用Eclipse+GDB+O
發表于 08-24 07:47
NodeMCU開發板踩坑經歷分享
寫在前面今天入手了一個NodeMCU的板子,準備學習一下物聯網相關的知識。不過由于博主學藝不精,在第一步燒寫固件上就踩坑了,所以就想著把自己的踩
發表于 11-01 07:55
移植debian系統踩過的坑
基本的linux系統,板子的交叉編譯器是arm-linux-gnueabihf-gcc,這給我帶來了不少的麻煩,以至于想重新移植一下debian系統。ok,轉入正題,說說這兩天我踩的坑吧。首先...
發表于 12-14 08:42
Xavier入門踩坑PWM問題解決方法
Xavier入門踩坑PWM問題解決方法GPIO問題解決方法PWM問題由于需要做外部傳感器的觸發同步,所以需要一個方波,考慮用Xavier的PWM,結果折騰了好久發現需要配置內部硬件,折騰了
發表于 01-10 08:11
又踩坑了!這次敗給CAN總線了
個人比較推薦使用上述步驟檢查硬件鏈路是否存在問題,那如何對數據進行分析呢?當然可以對著示波器的波形一點一點進行分析,但是這樣是很低效的,這里我建議使用CAN分析儀進行數據抓包,下面我們繼續進行介紹。
在學習go語言的過程踩過的坑
作為一個5年的phper,這兩年公司和個人都在順應技術趨勢,新項目慢慢從php轉向了go語言,從2021年到現在,筆者手上也先后開發了兩個go項目。在學習go語言的過程中也學習并總結了一些相關的東西,這篇文章就分享下自己踩過的一
評論