又踩坑了!這次敗給CAN總線了
Warning: file_get_contents(): SSL: connection timeout in /var/www/html/www.edw.com.cn/www/rootapp/controllersspace/ArticlesmanageController.php on line 270 Warning: file_get_contents(): Failed to enable crypto in /var/www/html/www.edw.com.cn/www/rootapp/controllersspace/ArticlesmanageController.php on line 270 Warning: file_get_contents(https://mmbiz.qpic.cn/mmbiz_png/zcVcDoKYUnZl6JkGyy6tyFnYlC18c27HK6twaSRm6A4sibculYDv7dnicqDrqw4ph9XicrYQokE0QcNpbjcejjvmg/640?wx_fmt=png&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1): failed to open stream: operation failed in /var/www/html/www.edw.com.cn/www/rootapp/controllersspace/ArticlesmanageController.php on line 270
前言最近負(fù)責(zé)的一個(gè)項(xiàng)目用的主控芯片是STM32F407IGT6,需要和幾個(gè)電機(jī)控制器進(jìn)行通訊,有很多參數(shù)需要進(jìn)行監(jiān)控。
有一個(gè)問題一直無法解決。在開啟CAN的接收中斷,接收不到數(shù)據(jù),問題卡了很久,下面簡單分享一下解決的過程和思路。
背景
CAN總線
CAN控制器
CAN收發(fā)器
調(diào)試過程
硬件排查
CAN分析儀
芯片CAN控制器調(diào)試
總結(jié)
CAN總線是一種串行通信協(xié)議,用于在微控制器和其他設(shè)備之間傳輸數(shù)據(jù)。CAN總線通常用于汽車、工業(yè)自動(dòng)化和機(jī)器人等領(lǐng)域。
CAN總線的硬件通常由以下幾個(gè)部分組成:
- 控制器區(qū)域:包括CAN控制器和CAN收發(fā)器;
- 總線電纜:用于連接CAN總線上的所有設(shè)備;
- 終端電阻:用于終止總線,以減少反射和信號(hào)干擾;
- 外部電源:用于為CAN總線提供電源;
CAN總線的控制器區(qū)域通常包括CAN控制器和CAN收發(fā)器。
- CAN控制器負(fù)責(zé)處理CAN總線上的數(shù)據(jù)傳輸,包括數(shù)據(jù)發(fā)送和接收、錯(cuò)誤檢測(cè)和糾正等;
- CAN收發(fā)器則負(fù)責(zé)將CAN控制器的信號(hào)轉(zhuǎn)換為總線上的電信號(hào),并將總線上的電信號(hào)轉(zhuǎn)換為CAN控制器可以理解的信號(hào)。
主板上的芯片STM32F407IGT6中帶有兩路的CAN控制器,分別為CAN1 和 CAN2,具體如下圖所示;
CAN收發(fā)器主板上使用的是芯片SN65HVD230,這是TI公司的一款性能強(qiáng)大且具體低功耗功能的CAN收發(fā)器,具體的典型應(yīng)用電路如下所示;
調(diào)試過程硬件排查設(shè)備的調(diào)試過程中,首先要確保硬件鏈路上是否正常。最常見的方法就是直接用示波器進(jìn)行檢查。具體如下所示;
- 檢查CAN控制器和CAN收發(fā)器之間是否正常;
- 檢查CAN收發(fā)器的差分信號(hào)是否正常,這里可能要了解一下CAN總線電平的顯性電平和隱性電平的特點(diǎn),以及CAN底層協(xié)議的細(xì)節(jié),會(huì)比較復(fù)雜;
個(gè)人比較推薦使用上述步驟檢查硬件鏈路是否存在問題,那如何對(duì)數(shù)據(jù)進(jìn)行分析呢?當(dāng)然可以對(duì)著示波器的波形一點(diǎn)一點(diǎn)進(jìn)行分析,但是這樣是很低效的,這里我建議使用CAN分析儀進(jìn)行數(shù)據(jù)抓包,下面我們繼續(xù)進(jìn)行介紹。
CAN分析儀至于數(shù)據(jù)傳輸是否正確,可以使用CAN盒進(jìn)行數(shù)據(jù)監(jiān)聽,下面是我使用的一款CAN分析儀,如圖;
將CAN分析儀的CAN_H和CAN_L分別并聯(lián)到CAN收發(fā)器的CAN_H和CAN_L上,然后打開CAN分析儀廠家提供的PC軟件,就可以對(duì)CAN總線的數(shù)據(jù)進(jìn)行監(jiān)聽;
- 將CAN分析儀接入到CAN總線;
- 將CAN分析儀連接到電腦(這里是USB接口),需要配置相同的波特率;
- 打開CAN分析儀配套的PC軟件,進(jìn)行數(shù)據(jù)的收發(fā);
- 進(jìn)行到這里,我在項(xiàng)目中遇到的問題是,發(fā)送正常,但是STM32F407無法接收到連續(xù)的數(shù)據(jù),可以接收到一次數(shù)據(jù),后面便無法再進(jìn)入中斷。這時(shí)候,只能再芯片端進(jìn)行Debug了。
這里的代碼用的HAL庫,庫版本相對(duì)來說比較老,是V1.7.10版本的,如下圖所示;
當(dāng)時(shí)我把項(xiàng)目升級(jí)到最新的HAL庫,發(fā)現(xiàn)CAN部分的驅(qū)動(dòng)改動(dòng)比較大,另外,下文都是基于V1.7.10版本的HAL庫。
CAN控制器的初始化代碼如下所示;
void MX_CAN_Init(void) { CAN_FilterConfTypeDef sFilterConfig; /*CAN單元初始化*/ hCAN.Instance = CANx; /* CAN外設(shè) */ hCAN.pTxMsg = &TxMessage; hCAN.pRxMsg = &RxMessage; hCAN.Init.Prescaler = 6; /* BTR-BRP 波特率分頻器 定義了時(shí)間單元的時(shí)間長度 42/(1+6+7)/6 = 500Kbps */ hCAN.Init.Mode = CAN_MODE_NORMAL; /* 正常工作模式 */ hCAN.Init.SJW = CAN_SJW_1TQ; /* BTR-SJW 重新同步跳躍寬度 1個(gè)時(shí)間單元 */ hCAN.Init.BS1 = CAN_BS1_6TQ; /* BTR-TS1 時(shí)間段1 占用了6個(gè)時(shí)間單元 */ hCAN.Init.BS2 = CAN_BS2_7TQ; /* BTR-TS1 時(shí)間段2 占用了7個(gè)時(shí)間單元 */ hCAN.Init.TTCM = DISABLE; /* MCR-TTCM 關(guān)閉時(shí)間觸發(fā)通信模式使能 */ hCAN.Init.ABOM = ENABLE; /* MCR-ABOM 自動(dòng)離線管理 */ hCAN.Init.AWUM = ENABLE; /* MCR-AWUM 使用自動(dòng)喚醒模式 */ hCAN.Init.NART = DISABLE; /* MCR-NART 禁止報(bào)文自動(dòng)重傳 DISABLE-自動(dòng)重傳 */ hCAN.Init.RFLM = DISABLE; /* MCR-RFLM 接收FIFO 鎖定模式 DISABLE-溢出時(shí)新報(bào)文會(huì)覆蓋原有報(bào)文 */ hCAN.Init.TXFP = DISABLE; /* MCR-TXFP 發(fā)送FIFO優(yōu)先級(jí) DISABLE-優(yōu)先級(jí)取決于報(bào)文標(biāo)示符 */ HAL_CAN_Init(&hCAN); /*CAN過濾器初始化*/ sFilterConfig.FilterNumber = 0; /* 過濾器組0 */ sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; /* 工作在標(biāo)識(shí)符屏蔽位模式 */ sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; /* 過濾器位寬為單個(gè)32位。*/ /* 使能報(bào)文標(biāo)示符過濾器按照標(biāo)示符的內(nèi)容進(jìn)行比對(duì)過濾,擴(kuò)展ID不是如下的就拋棄掉,是的話,會(huì)存入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; /* 過濾器被關(guān)聯(lián)到FIFO 0 */ sFilterConfig.FilterActivation = ENABLE; /* 使能過濾器 */ sFilterConfig.BankNumber = 14; HAL_CAN_ConfigFilter(&hCAN, &sFilterConfig); }
根據(jù)注釋,可以大概看懂,另外再簡單分析一下關(guān)鍵的幾點(diǎn);
- 波特率設(shè)置為 500Kbps;
- 對(duì)報(bào)文不進(jìn)行過濾,可以接收任何擴(kuò)展ID的數(shù)據(jù);
雖然不進(jìn)行任何過濾,但是還是無法接收到CAN回傳的數(shù)據(jù),無法進(jìn)入的接收中斷;
從STM32F407的編程手冊(cè)里了解到;
不難發(fā)現(xiàn),CAN1的FIFO0產(chǎn)生接收中斷需要滿足三個(gè)條件中的任意一個(gè);
- FMPIE0置1 且 FMP0置1;FIFO不為空會(huì)產(chǎn)生中斷
- FFIE0置1 且 FULL置1;FIFO滿,會(huì)產(chǎn)生中斷
- FOVIE0置1 且 FOVR0置1;FIFO溢出,會(huì)產(chǎn)生中斷
手冊(cè)里是這樣描述的,如下圖所示;
使用仿真器對(duì)芯片進(jìn)行調(diào)試,設(shè)置斷點(diǎn),發(fā)現(xiàn)FMPIE0被清空了,具體如下圖所示;
FMPIE0這一位是FIFO0中有掛起的消息會(huì)產(chǎn)生中斷的中斷使能標(biāo)志位;
所以到這里,問題有點(diǎn)明朗了,為什么無法進(jìn)入中斷?是中斷使能位被清空了。
那么下面就是檢查代碼,看看是哪里把中斷給disable了。
繼續(xù)調(diào)試,發(fā)現(xiàn)在ESR寄存器中,TEC的值一直增加,然后EWGF被值1了;具體如下所示;
TEC和REC分別是發(fā)送錯(cuò)誤計(jì)數(shù)器和接收錯(cuò)誤計(jì)數(shù)器;
如 CAN 協(xié)議所述,錯(cuò)誤管理完全由硬件通過發(fā)送錯(cuò)誤計(jì)數(shù)器( CAN_ESR 寄存器中的 TEC 值)和接收錯(cuò)誤計(jì)數(shù)器( CAN_ESR 寄存器中的 REC 值)來處理,這兩個(gè)計(jì)數(shù)器根據(jù)錯(cuò)誤 狀況進(jìn)行遞增或遞減。有關(guān) TEC 和 REC 管理的詳細(xì)信息,請(qǐng)參見 CAN 標(biāo)準(zhǔn)。兩者均可由軟件讀取,用以確定網(wǎng)絡(luò)的穩(wěn)定性。此外, CAN 硬件還將在 CAN_ESR 寄存器中 提供當(dāng)前錯(cuò)誤狀態(tài)的詳細(xì)信息。通過 CAN_IER 寄存器( ERRIE 位等),軟件可以非常靈活 地配置在檢測(cè)到錯(cuò)誤時(shí)生成的中斷。
當(dāng)TEC大于96的時(shí)候,硬件會(huì)將EWGF置1(錯(cuò)誤警告標(biāo)志位);在代碼中找到了相應(yīng)的宏定義;這下問題越來越清晰了。
全文搜索這個(gè)宏定義,在HAL_CAN_IRQHandler中找到了__HAL_CAN_DISABLE_IT(CAN_IT_FMP0),關(guān)閉了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); } }
最后,找到無法進(jìn)入接收中斷的原因,是CAN總線出現(xiàn)發(fā)送錯(cuò)誤的情況,從而觸發(fā)了錯(cuò)誤警告標(biāo)志位EWGF,進(jìn)而將關(guān)閉了消息掛起中斷。
總結(jié)本文簡單介紹了在STM32F407上的CAN總線調(diào)試過程,項(xiàng)目中難免會(huì)遇到各種問題,解決之后,大家要及時(shí)做好總結(jié)和復(fù)盤,技術(shù)在于積累和沉淀,相互學(xué)習(xí),共同進(jìn)步。
*博客內(nèi)容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀點(diǎn),如有侵權(quán)請(qǐng)聯(lián)系工作人員刪除。