引言
在現代工業自動化和汽車電子領域,CAN總線以其高可靠性和實時性成為通信的主流選擇。而CANopen協議,作為CAN總線上的一種上層通信協議,廣泛應用于各種設備間的通信。本文將介紹如何基于靈動MM32G5330的FlexCAN實現CANopenNode協議棧的移植,并使用靈動官方提供的開發板Mini-G5333進行驗證。
CANopen簡介
CANopen是由CiA (CAN-in-Automation)組織開發的上層通信協議,它定義了一組用于工業自動化的通信對象,并在CAN總線之上實現了網絡管理、設備配置和數據交換等功能。CANopen協議規范了設備如何通過CAN總線進行通信,使得不同廠商的設備能夠無縫集成和協同工作。
CANopen從應用端到CAN總線的結構:
應用層(Application)
用于實現各種應用對象
對象字典(Object dictionary)
用于描述CANopen節點設備的參數
通信接口(Communication interface)
定義了CANopen協議通信規則以及CAN控制器驅動之間對應關系
CANopen網絡中用到的三種通信模型:
主機/從機模型(Master/Salve)
一個節點(例如控制接口)充當應用程序主機控制器,從機(例如伺服電機)發送/請求數據,一般在診斷或狀態管理中使用。
通信樣例:NMT主機與NMT從機的通信
所有節點通信地位平等,運行時允許自行發送報文,但CANopen網絡為了穩定可靠可控,都需要設置一個網絡管理主機 NMT-Master。
NMT主機一般是CANopen網絡中具備監控的PLC或者PC(當然也可以是一般的功能節點),所以也成為CANopen主站。相對應的其他CANopen節點就是NMT從機(NMT-slaves)。
客戶端/服務端模型(Client/Server)
客戶機向服務器發送數據請求,服務器進行響應。例如,當應用程序主機需要來自從機OD的數據時使用。
通信樣例:SDO客戶端與SDO服務端的通信
發送節點需要指定接收節點的地址(Node-ID)回應CAN報文來確認已經接收,如果超時沒有確認,則發送節點將會重新發送原報文。
生產者/消費者模型(Producer/Consumer)
生產者節點向網絡廣播數據,而網絡由使用者節點使用。生產者可以根據請求發送此數據,也可以不發送特定請求。
通信樣例:心跳生產者與心跳消費者的通信
單向發送傳輸,無需接收節點回應CAN報文來確認。
CANopen的七種報文類型:
NMT(Network Management)
控制CANopen設備狀態,用于網絡管理。
SYNC(Synchronization)
SYNC 消息用于同步多個 CANopen 設備的輸入感應和驅動——通常由應用程序 Master 觸發。
EMCY(Emergency)
在設備發生錯誤(例如傳感器故障)時使用的,發送設備內部錯誤代碼。
TIME
用于分配網絡時間,議采用廣播方式,無需節點應答,CAN-ID 為 100h,數據長度為 6,數據為當前時刻與1984年1月1日0時的時間差。節點將此時間存儲在對象字典1012h的索引中。
PDO(Process Object)
PDO服務用于在設備之間傳輸實時數據,例如測量數據(如位置數據)或命令數據(如扭矩請求)。
SDO(Sever D Object)
用于訪問/更改CANopen設備的對象字典中的值——例如,當應用程序主機需要更改CANopen設備的某些配置時。
Heartbeat
Heartbeat服務有兩個用途: 提供“活動”消息和確認NMT命令。
CANopenNode協議棧
CANopenNode是一款免費和開源的CANopen協議棧,使用ANSI C語言以面向對象的方式編寫的。它可以在不同的微控制器上運行,作為獨立的應用程序或與RTOS一起運行。變量(通信、設備、自定義)被收集在CANopen對象字典中,并且可以以兩種方式修改:C源代碼和CANopen網絡。
CANopenNode主頁位于:
https://github.com/CANopenNode/CANopenNode
CANopenNode vs CAN Festival
表1
CANopenNode和CANFestival都是用于在嵌入式系統上實現CANopen協議通信的開源軟件協議棧。需要注意的是它們使用了不同的開放程度的開源協議。CANFestival使用LGPLv2開源協議。這意味著CANFestival的源代碼雖是免費提供的,任何人都可以使用、修改和分發,只要任何衍生作品使用相同的GPL許可證,但如果一個公司在產品中使用CANFestival,他們也必須按照同樣的LGPLv2開源協議提供其產品的源代碼。而CANopenNode使用 Apache v2.0開源協議,這是一個自由度比LGPLv2更為開發的一個開源協議,允許在使用軟件方面有更大的靈活性。任何人都可以使用、修改和發布CANopenNode,甚至用于商業目的,而不需要發布其衍生作品的源代碼。
移植前準備
獲取CANopenNode源碼
選擇 CANopenNode v1.3,該版本為CANopenNode 官方發布版本,獲取源碼鏈接:
https://github.com/CANopenNode/CANopenNode/releases/tag/v1.3。
獲取 MiniBoard-OB (MM32G5333D6QV) 例程及開發板資料
開發板及LibSamples詳情見靈動官網:
https://www.mindmotion.com.cn/support/development_tools/evaluation_boards/miniboard/mm32g5330d6qv/。
編譯工具和開發環境
基于FlexCAN移植CANopenNode
在CANopenNode移植中涉及到三個文件需要被復制引用和修改:
CANopenNode-1.3/example/main.c 文件。
CANopenNode-1.3/stack/drvTemplate/CO_driver.c 文件。
CANopenNode-1.3/stack/drvTemplate/CO_driver_target.h 文件。
其中:
在 mian.c 文件中實現 tmrTask_thread() 函數
通加載進入1ms 定時中斷服務函數進行 1ms 定時的信息同步
在 CO_driver.c 文件中實現 CO_CANmodule_init() 函數
用于對 MCU 中的 CAN 模塊進行初始,并配置CAN報文的收發參數以及開啟 flexcan 中斷。
在 CO_driver.C 文件中實現 CO_CANinterrupt() 函數
用于實現接收和發送CAN信息。該功能從高優先級的CAN中斷中直接調用。
在 CO_driver.C 文件中實現 CO_CANverifyErrorst() 函數
用于對 CAN 總線進行錯誤檢測和上報。
下面我們將以MM32G5330微控制器上集成的FlexCAN為例,完成對CANopenNode v1.3的移植,并實現一個 CANopen_Basic 樣例進行基本功能驗證。
首先在靈動官網下載基于Mini-G5330開發板的LibSamples_MM32G5330軟件包,并在該軟件包的根目錄文件夾下創建?~/3rdPartySoftwarePorting/CANopenNode?文件夾,如下圖1所示,將獲取的 CANopenNode-1.3 軟件包解壓后原封不動地復制到新建的 CANopenNode 文件夾中。
圖 1
這里我們在 CANopenNode 文件夾下創建 Demos 文件夾用于按照LibSamples的樣例結構創建關于 CANopenNode 相關的樣例工程。接下來將CANopenNode源碼中提供的example文件夾的結構如下圖2所示,其中CO_OD.c/h是 CANopen中使用到的對象字典, 我們將這兩個文件復制到? Demos/CANopen_Basic 文件夾下。main.c是 CANopenNode的主程序文件,我們將原有的main.c文件進行替換。
圖 2
將如圖3所示的位于CANopenNode-1.3/stack/drvTemplate文件夾下的CO_driver.c及CO_driver_target.h這兩個文件復制到樣例工程的文件夾下。
圖 3
在CANopen_Basic文件夾下參照LibSample中的樣例工程創建MDK-ARM樣例工程并添加編譯路徑,CANopen_Basic樣例完成移植后效果如下圖所示:
圖 4
由于本次移植是基于裸機移植,故按照CANopenNode的設計將Mainline線程放入while(1)中,CAN接收線程放入flexcan的中斷服務程序中,定時線程放在一個1ms的定時中斷服務程序中。
在 main.c 文件中配置定時器
這里初始化和配置了定時器 TIM1,并實現了與之相關的中斷處理程序。
?
/*?Setup?the?timer.?*/ void?app_tim_init(void) { ????NVIC_InitTypeDef????????NVIC_InitStruct; ????TIM_TimeBaseInitTypeDef?TIM_TimeBaseInitStruct; ????RCC_ClocksTypeDef?RCC_Clocks; ????RCC_GetClocksFreq(&RCC_Clocks); ????RCC_APB2PeriphClockCmd(RCC_APB2ENR_TIM1,?ENABLE); ????TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct); ????TIM_TimeBaseInitStruct.TIM_Prescaler?????????=?(RCC_Clocks.PCLK2_Frequency?/?APP_TIM_UPDATE_STEP?-?1); ????TIM_TimeBaseInitStruct.TIM_CounterMode???????=?TIM_COUNTERMODE_UP; ????TIM_TimeBaseInitStruct.TIM_Period????????????=?(APP_TIM_UPDATE_PERIOD?-?1); ????TIM_TimeBaseInitStruct.TIM_ClockDivision?????=?TIM_CKD_DIV1; ????TIM_TimeBaseInitStruct.TIM_RepetitionCounter?=?0; ????TIM_TimeBaseInit(TIM1,?&TIM_TimeBaseInitStruct); ????TIM_ClearFlag(TIM1,?TIM_IT_UPDATE); ????TIM_ITConfig(TIM1,?TIM_IT_UPDATE,?ENABLE); ????NVIC_InitStruct.NVIC_IRQChannel?=?TIM1_UP_IRQn; ????NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority?=?0; ????NVIC_InitStruct.NVIC_IRQChannelSubPriority?=?0; ????NVIC_InitStruct.NVIC_IRQChannelCmd?=?ENABLE; ????NVIC_Init(&NVIC_InitStruct); } void?TIM1_UP_IRQHandler(void) { ????TIM_ClearITPendingBit(TIM1,?TIM_IT_UPDATE); ????tmrTask_thread(); }
?
在 main.c 文件中實現定時線程任務處理
這里對 tmrTask_thread() 函數進行完善。
?
/*?timer?thread?executes?in?constant?intervals?********************************/ void?tmrTask_thread(void){ ????INCREMENT_1MS(CO_timer1ms); ????if?(CO->CANmodule[0]->CANnormal)?{ ????????bool_t?syncWas; ????????/*?Process?Sync?*/ ????????syncWas?=?CO_process_SYNC(CO,?TMR_TASK_INTERVAL); ????????/*?Read?inputs?*/ ????????CO_process_RPDO(CO,?syncWas); ????????/*?Further?I/O?or?nonblocking?application?code?may?go?here.?*/ ????????/*?Write?outputs?*/ ????????CO_process_TPDO(CO,?syncWas,?TMR_TASK_INTERVAL); ????????/*?verify?timer?overflow?*/ ????????if((TIM_GetITStatus(TIM1,?TIM_IT_UPDATE)?&?TIM_IT_UPDATE)?!=?0u)?{ ????????????CO_errorReport(CO->em,?CO_EM_ISR_TIMER_OVERFLOW,?CO_EMC_SOFTWARE_INTERNAL,?0u); ????????????TIM_ClearITPendingBit(TIM1,?TIM_IT_UPDATE); ????????} ????} }
?
在 main.c 文件中實現 FlexCAN 的中斷服務函數
?
/*?CAN?interrupt?function?*****************************************************/ void?FLEXCAN_IRQHandler(void) { ????FLEXCAN_TransferHandleIRQ(FLEXCAN,?&FlexCAN_Handle); ????CO_CANinterrupt(CO->CANmodule[0]); ????__DSB(); }
?
在 CO_driver.c 文件中實現FlexCAN模塊配置
實現包括對 FlexCAN 相關的 GPIO引腳、時鐘、CAN報文收發消息緩沖區的配置。
?
void?FlexCAN_Configure(uint32_t?can_bitrate) { ????GPIO_InitTypeDef?GPIO_InitStruct; ????NVIC_InitTypeDef?NVIC_InitStruct; ????RCC_ClocksTypeDef?RCC_Clocks; ????flexcan_config_t???????FlexCAN_ConfigStruct; ????flexcan_rx_mb_config_t?FlexCAN_RxMB_ConfigStruct; ????RCC_GetClocksFreq(&RCC_Clocks); ????RCC_APB1PeriphClockCmd(RCC_APB1PERIPH_FLEXCAN,?ENABLE); ????RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA,?ENABLE); ????GPIO_PinAFConfig(GPIOA,?GPIO_PINSOURCE11,?GPIO_AF_9); ????GPIO_PinAFConfig(GPIOA,?GPIO_PINSOURCE12,?GPIO_AF_9); ????GPIO_StructInit(&GPIO_InitStruct); ????GPIO_InitStruct.GPIO_Pin???=?GPIO_PIN_11; ????GPIO_InitStruct.GPIO_Speed?=?GPIO_SPEED_HIGH; ????GPIO_InitStruct.GPIO_Mode??=?GPIO_MODE_FLOATING; ????GPIO_Init(GPIOA,?&GPIO_InitStruct); ????GPIO_StructInit(&GPIO_InitStruct); ????GPIO_InitStruct.GPIO_Pin???=?GPIO_PIN_12; ????GPIO_InitStruct.GPIO_Speed?=?GPIO_SPEED_HIGH; ????GPIO_InitStruct.GPIO_Mode??=?GPIO_MODE_AF_PP; ????GPIO_Init(GPIOA,?&GPIO_InitStruct); ????NVIC_InitStruct.NVIC_IRQChannel?=?FLEXCAN_IRQn; ????NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority?=?0; ????NVIC_InitStruct.NVIC_IRQChannelSubPriority?=?0; ????NVIC_InitStruct.NVIC_IRQChannelCmd?=?ENABLE; ????NVIC_Init(&NVIC_InitStruct); ????FLEXCAN_GetDefaultConfig(&FlexCAN_ConfigStruct); ????FlexCAN_ConfigStruct.baudRate?????????????=?can_bitrate*1000; ????FlexCAN_ConfigStruct.clkSrc???????????????=?Enum_Flexcan_ClkSrc1; ????FlexCAN_ConfigStruct.enableLoopBack???????=?false; ????FlexCAN_ConfigStruct.disableSelfReception?=?true; ????FlexCAN_ConfigStruct.enableIndividMask????=?true; ????#if?1????/*?Baudrate?calculate?by?automatically?*/ ????FLEXCAN_CalculateImprovedTimingValues(FlexCAN_ConfigStruct.baudRate,?RCC_Clocks.PCLK1_Frequency,?&FlexCAN_ConfigStruct.timingConfig); #else??/*?You?can?modify?the?parameters?yourself?*/ ????FlexCAN_ConfigStruct.timingConfig.preDivider?=?23; ????FlexCAN_ConfigStruct.timingConfig.propSeg????=?6; ????FlexCAN_ConfigStruct.timingConfig.phaseSeg1??=?3; ????FlexCAN_ConfigStruct.timingConfig.phaseSeg2??=?3;???? ????FlexCAN_ConfigStruct.timingConfig.rJumpwidth?=?3;? #endif ????FLEXCAN_Init(FLEXCAN,?&FlexCAN_ConfigStruct); ????/*?Set?Tx?MB_2.?*/ ????FLEXCAN_TxMbConfig(FLEXCAN,?BOARD_FLEXCAN_TX_MB_CH,?ENABLE); ????FLEXCAN_TransferCreateHandle(FLEXCAN,?&FlexCAN_Handle,?FlexCAN_Transfer_Callback,?NULL); ????/*?Set?Rx?MB_0.?*/ ????FlexCAN_RxMB_ConfigStruct.id?????=?FLEXCAN_ID_STD(0x222); ????FlexCAN_RxMB_ConfigStruct.format?=?Enum_Flexcan_FrameFormatStandard; ????FlexCAN_RxMB_ConfigStruct.type???=?Enum_Flexcan_FrameTypeData; ????FLEXCAN_RxMbConfig(FLEXCAN,?BOARD_FLEXCAN_RX_MB_CH,?&FlexCAN_RxMB_ConfigStruct,?ENABLE); ????/*?Set?Rx?Individual?Mask.?*/ ????FLEXCAN_SetRxIndividualMask(FLEXCAN,?BOARD_FLEXCAN_RX_MB_CH,?FLEXCAN_RX_MB_STD_MASK(0x000,?0,?0)); ????FlexCAN_MB0_FrameStruct.length?=?(uint8_t)(8); ????FlexCAN_MB0_FrameStruct.type???=?(uint8_t)Enum_Flexcan_FrameTypeData; ????FlexCAN_MB0_FrameStruct.format?=?(uint8_t)Enum_Flexcan_FrameFormatStandard; ????FlexCAN_MB0_FrameStruct.id?????=?FLEXCAN_ID_STD(0x222); ????FlexCAN_MB0_TransferStruct.mbIdx?=?BOARD_FLEXCAN_RX_MB_CH; ????FlexCAN_MB0_TransferStruct.frame?=?&FlexCAN_MB0_FrameStruct; ????FLEXCAN_TransferReceiveNonBlocking(FLEXCAN,?&FlexCAN_Handle,?&FlexCAN_MB0_TransferStruct); } /******************************************************************************/ CO_ReturnError_t?CO_CANmodule_init( ????????CO_CANmodule_t?????????*CANmodule, ????????void???????????????????*CANdriverState, ????????CO_CANrx_t??????????????rxArray[], ????????uint16_t????????????????rxSize, ????????CO_CANtx_t??????????????txArray[], ????????uint16_t????????????????txSize, ????????uint16_t????????????????CANbitRate) { ????uint16_t?i; ????/*?verify?arguments?*/ ????if(CANmodule==NULL?||?rxArray==NULL?||?txArray==NULL){ ????????return?CO_ERROR_ILLEGAL_ARGUMENT; ????} ????/*?Configure?object?variables?*/ ????CANmodule->CANdriverState?=?CANdriverState; ????CANmodule->rxArray?=?rxArray; ????CANmodule->rxSize?=?rxSize; ????CANmodule->txArray?=?txArray; ????CANmodule->txSize?=?txSize; ????CANmodule->CANnormal?=?false; ????CANmodule->useCANrxFilters?=?false;/*?microcontroller?dependent?*/ ????CANmodule->bufferInhibitFlag?=?false; ????CANmodule->firstCANtxMessage?=?true; ????CANmodule->CANtxCount?=?0U; ????CANmodule->errOld?=?0U; ????CANmodule->em?=?NULL; ????for(i=0U;?i?
在 CO_driver.c 文件中實現FlexCAN的報文收發
對 flexcan_tx() 函數及 CO_CANinterrupt()函數的實現。
?
/*?Send?a?message?frame.?*/ bool?flexcan_tx(CO_CANtx_t?*buffer) { ????bool?status?=?false; ????flexcan_frame_t???????FlexCAN_FrameStruct; ????flexcan_mb_transfer_t?FlexCAN_MB_TransferStruct; ????if?(!buffer->rtr) ????{ ????????FlexCAN_FrameStruct.type?=?(uint8_t)Enum_Flexcan_FrameTypeData;?/*?Data?frame?type.?*/ ????} ????else ????{ ????????FlexCAN_FrameStruct.type?=?(uint8_t)Enum_Flexcan_FrameTypeRemote;?/*?Remote?frame?type.?*/ ????} ????FlexCAN_FrameStruct.length?=?(uint8_t)buffer->DLC; ????FlexCAN_FrameStruct.format?=?(uint8_t)Enum_Flexcan_FrameFormatStandard; ????FlexCAN_FrameStruct.id?????=?FLEXCAN_ID_STD(buffer->ident);?/*?Indicated?ID?number.?*/ ????FlexCAN_FrameStruct.dataByte0?=?buffer->data[0]; ????FlexCAN_FrameStruct.dataByte1?=?buffer->data[1]; ????FlexCAN_FrameStruct.dataByte2?=?buffer->data[2]; ????FlexCAN_FrameStruct.dataByte3?=?buffer->data[3]; ????FlexCAN_FrameStruct.dataByte4?=?buffer->data[4]; ????FlexCAN_FrameStruct.dataByte5?=?buffer->data[5]; ????FlexCAN_FrameStruct.dataByte6?=?buffer->data[6]; ????FlexCAN_FrameStruct.dataByte7?=?buffer->data[7]; ????FlexCAN_MB_TransferStruct.mbIdx?=?2; ????FlexCAN_MB_TransferStruct.frame?=?&FlexCAN_FrameStruct; ????if?(Status_Flexcan_Success?==?FLEXCAN_TransferSendNonBlocking(FLEXCAN,?&FlexCAN_Handle,?&FlexCAN_MB_TransferStruct)) ????{ ????????status?=?true; ????} ????return?status; } /******************************************************************************/ CO_ReturnError_t?CO_CANsend(CO_CANmodule_t?*CANmodule,?CO_CANtx_t?*buffer){ ????CO_ReturnError_t?err?=?CO_ERROR_NO; ????/*?Verify?overflow?*/ ????if(buffer->bufferFull){ ????????if(!CANmodule->firstCANtxMessage){ ????????????/*?don't?set?error,?if?bootup?message?is?still?on?buffers?*/ ????????????CO_errorReport((CO_EM_t*)CANmodule->em,?CO_EM_CAN_TX_OVERFLOW,?CO_EMC_CAN_OVERRUN,?buffer->ident); ????????} ????????err?=?CO_ERROR_TX_OVERFLOW; ????} ????CO_LOCK_CAN_SEND(); ????bool?tx_mb_status?=?flexcan_tx(buffer); ????if(tx_mb_status?==?true){ ????????CANmodule->bufferInhibitFlag?=?buffer->syncFlag; ????} ????/*?if?no?buffer?is?free,?message?will?be?sent?by?interrupt?*/ ????else{ ????????buffer->bufferFull?=?true; ????????CANmodule->CANtxCount++; ????} ????CO_UNLOCK_CAN_SEND(); ????return?err; }?
?
void?CO_CANinterrupt(CO_CANmodule_t?*CANmodule){ ????uint32_t?status?=?FLEXCAN->IFLAG1; ????if?(0?!=?(status?&?(BOARD_FLEXCAN_RX_MB_STATUS))?||?(FlexCAN_MB0_RxCompleteFlag)) ????{ ????????/*?receive?interrupt?*/ ????????CO_CANrxMsg_t?*rcvMsg;??????/*?pointer?to?received?message?in?CAN?module?*/ ????????CO_CANrxMsg_t?rcvMsgBuff; ????????uint16_t?index;?????????????/*?index?of?received?message?*/ ????????uint32_t?rcvMsgIdent;???????/*?identifier?of?the?received?message?*/ ????????CO_CANrx_t?*buffer?=?NULL;??/*?receive?message?buffer?from?CO_CANmodule_t?object.?*/ ????????bool_t?msgMatched?=?false; ????????/*?get?message?from?module?here?*/ ????????rcvMsg?=?&rcvMsgBuff; ????????rcvMsg->ident???=?(FlexCAN_MBTemp_FrameStruct.id>>?CAN_ID_STD_SHIFT)&0x7FF; ????????rcvMsg->DLC?????=?FlexCAN_MBTemp_FrameStruct.length; ????????rcvMsg->data[0]?=?FlexCAN_MBTemp_FrameStruct.dataByte0; ????????rcvMsg->data[1]?=?FlexCAN_MBTemp_FrameStruct.dataByte1; ????????rcvMsg->data[2]?=?FlexCAN_MBTemp_FrameStruct.dataByte2; ????????rcvMsg->data[3]?=?FlexCAN_MBTemp_FrameStruct.dataByte3; ????????rcvMsg->data[4]?=?FlexCAN_MBTemp_FrameStruct.dataByte4; ????????rcvMsg->data[5]?=?FlexCAN_MBTemp_FrameStruct.dataByte5; ????????rcvMsg->data[6]?=?FlexCAN_MBTemp_FrameStruct.dataByte6; ????????rcvMsg->data[7]?=?FlexCAN_MBTemp_FrameStruct.dataByte7; ????????rcvMsgIdent?=?rcvMsg->ident; ????????FlexCAN_MB0_RxCompleteFlag?=?0; ????????/*?CAN?module?filters?are?not?used,?message?with?any?standard?11-bit?identifier?*/ ????????/*?has?been?received.?Search?rxArray?form?CANmodule?for?the?same?CAN-ID.?*/ ????????buffer?=?&CANmodule->rxArray[0]; ????????for(index?=?CANmodule->rxSize;?index?>?0U;?index--){ ????????????if(((rcvMsgIdent?^?buffer->ident)?&?buffer->mask)?==?0U){ ????????????????msgMatched?=?true; ????????????????break; ????????????} ????????????buffer++; ????????} ????????/*?Call?specific?function,?which?will?process?the?message?*/ ????????if(msgMatched?&&?(buffer?!=?NULL)?&&?(buffer->pFunct?!=?NULL)){ ????????????buffer->pFunct(buffer->object,?rcvMsg); ????????} ????????/*?Clear?interrupt?flag?*/ ????????FLEXCAN_ClearMbStatusFlags(FLEXCAN,?BOARD_FLEXCAN_RX_MB_STATUS); ????} ????else?if?(0?!=?(status?&?BOARD_FLEXCAN_TX_MB_STATUS)) ????{ ????????/*?Clear?interrupt?flag?*/ ????????FLEXCAN_ClearMbStatusFlags(FLEXCAN,?BOARD_FLEXCAN_TX_MB_STATUS); ????????/*?First?CAN?message?(bootup)?was?sent?successfully?*/ ????????CANmodule->firstCANtxMessage?=?false; ????????/*?clear?flag?from?previous?message?*/ ????????CANmodule->bufferInhibitFlag?=?false; ????????/*?Are?there?any?new?messages?waiting?to?be?send?*/ ????????if(CANmodule->CANtxCount?>?0U){ ????????????uint16_t?i;?????????????/*?index?of?transmitting?message?*/ ????????????/*?first?buffer?*/ ????????????CO_CANtx_t?*buffer?=?&CANmodule->txArray[0]; ????????????/*?search?through?whole?array?of?pointers?to?transmit?message?buffers.?*/ ????????????for(i?=?CANmodule->txSize;?i?>?0U;?i--){ ????????????????/*?if?message?buffer?is?full,?send?it.?*/ ????????????????if(buffer->bufferFull){ ????????????????????buffer->bufferFull?=?false; ????????????????????CANmodule->CANtxCount--; ????????????????????/*?Copy?message?to?CAN?buffer?*/ ????????????????????CANmodule->bufferInhibitFlag?=?buffer->syncFlag; ????????????????????CO_CANsend(CANmodule,?buffer); ????????????????????break;??????????????????????/*?exit?for?loop?*/ ????????????????} ????????????????buffer++; ????????????}/*?end?of?for?loop?*/ ????????????/*?Clear?counter?if?no?more?messages?*/ ????????????if(i?==?0U){ ????????????????CANmodule->CANtxCount?=?0U; ????????????} ????????} ????} ????else{ ????????/*?some?other?interrupt?reason?*/ ????} }?
在 CO_driver.c 文件中實現CAN總線錯誤檢測
關于 CO_CANverifyErrors() 函數的實現。
?
void?CO_CANverifyErrors(CO_CANmodule_t?*CANmodule){ ????uint16_t?rxErrors,?txErrors,?overflow; ????CO_EM_t*?em?=?(CO_EM_t*)CANmodule->em; ????uint32_t?err; ????/*?get?error?counters?from?module.?Id?possible,?function?may?use?different?way?to ?????*?determine?errors.?*/ ????rxErrors?=?(uint16_t)?((FLEXCAN->ECR?&?CAN_ECR_RXERRCNT_MASK)?>>?CAN_ECR_RXERRCNT_SHIFT); ????txErrors?=?(uint16_t)?((FLEXCAN->ECR?&?CAN_ECR_TXERRCNT_MASK)?>>?CAN_ECR_TXERRCNT_SHIFT); ????overflow?=?(uint16_t)?((FLEXCAN->ESR1?&?CAN_ESR1_ERROVR_MASK)?>>?CAN_ESR1_ERROVR_SHIFT); ????err?=?((uint32_t)txErrors?<errOld?!=?err){ ????????CANmodule->errOld?=?err; ????????if(txErrors?>=?256U){???????????????????????????????/*?bus?off?*/ ????????????CO_errorReport(em,?CO_EM_CAN_TX_BUS_OFF,?CO_EMC_BUS_OFF_RECOVERED,?err); ????????} ????????else{???????????????????????????????????????????????/*?not?bus?off?*/ ????????????CO_errorReset(em,?CO_EM_CAN_TX_BUS_OFF,?err); ????????????if((rxErrors?>=?96U)?||?(txErrors?>=?96U)){?????/*?bus?warning?*/ ????????????????CO_errorReport(em,?CO_EM_CAN_BUS_WARNING,?CO_EMC_NO_ERROR,?err); ????????????} ????????????if(rxErrors?>=?128U){???????????????????????????/*?RX?bus?passive?*/ ????????????????CO_errorReport(em,?CO_EM_CAN_RX_BUS_PASSIVE,?CO_EMC_CAN_PASSIVE,?err); ????????????} ????????????else{ ????????????????CO_errorReset(em,?CO_EM_CAN_RX_BUS_PASSIVE,?err); ????????????} ????????????if(txErrors?>=?128U){???????????????????????????/*?TX?bus?passive?*/ ????????????????if(!CANmodule->firstCANtxMessage){ ????????????????????CO_errorReport(em,?CO_EM_CAN_TX_BUS_PASSIVE,?CO_EMC_CAN_PASSIVE,?err); ????????????????} ????????????} ????????????else{ ????????????????bool_t?isError?=?CO_isError(em,?CO_EM_CAN_TX_BUS_PASSIVE); ????????????????if(isError){ ????????????????????CO_errorReset(em,?CO_EM_CAN_TX_BUS_PASSIVE,?err); ????????????????????CO_errorReset(em,?CO_EM_CAN_TX_OVERFLOW,?err); ????????????????} ????????????} ????????????if((rxErrors??
至此,驅動代碼適配完成。
板載驗證
驗證環境
使用搭載了MM32G5330 MCU的開發板Mini-G5330 ,以CANopen_Basic樣例工程為例,將開發板上的CAN收發器與PCAN相連接,并將PCAN與PC機通過USB相連接,在PC端(基于Win10操作系統)使用PCAN-View上位機模擬CANopen主站,來通過CANopen協議與CANopen從站(即 MM32 MCU)進行通信,如圖5所示。
圖 5 MCU與PC機交互示意圖
注:這里我們使用了PCAN-USB,并使用了配套上位機PCAN-View。
驗證過程
上述環境搭建好后,將上述工程代碼編譯后刷寫固件進MCU,將MCU上電并復位通過PC端上位機PCAN-View測試如下指令,觀察CANopen節點其對指令的響應,來判斷該CANopen節點是否處于正常運行狀態。
節點上線:
MCU上電后,CANopen節點應成功啟動并向網絡發送上線報文。
CANopen節點上線向CAN網絡發送CANopen節點上線報文,PC上位機將收到一條如下報文:
表2
之后該CANopen節點以 1000ms 的時間間隔向CAN網絡發送節點心跳報文,上位機以1000ms的時間間隔收到如下報文:
表 3
如圖6所示。
圖 6
至此,可驗證該CANopen節點設備成功啟動并開始正常運行。
模式切換:
通過上位機發送NMT命令,驗證節點能夠正確響應Start、Stop和Pre-operation等模式切換指令。
將NODE-ID為0x0A的節點設置為 Stop 模式,上位機PCAN-View發送如下指令:
表 4
如下圖7所示,可接收到如下報文:
圖 7
將NODE-ID為0x0A的節點設置為 Start 模式,上位機PCAN-View發送如下指令:
表 5
如下圖8所示,可接收到如下報文:
圖8
將NODE-ID為0x0A的節點設置為Pre-operation模式,上位機PCAN-View發送如下指令:
表6
如下圖9所示,該節點進入Pre-operation模式,可接收到如下報文:
圖 9
將NODE-ID為0x0A節點復位,上位機PCAN-View發送如下指令:
表 7
如下圖10所示,該節點被復位:
圖 10
將NODE-ID為0x0A節點的通信層復位,上位機PCAN-View發送如下指令:
表 8
如下圖11所示,該節點通信層被復位,重新上線:
圖 11
心跳檢測:
節點應周期性發送心跳報文,以表明其處于活躍狀態。
獲取NODE-ID為0x0A節點的心跳發送間隔時間,上位機PCAN-View發送如下指令:
表 9
如下圖12所示,返回該節點當前心跳發送間隔時間為1000(0x03E8)ms:
圖 12
設置NODE-ID為0x0A節點的心跳發送間隔時間為500(0x01F4)ms,上位機PCAN-View發送如下指令:
表 10
如下圖13所示,該節點當前心跳發送間隔時間變為500ms:
圖 13
總結
通過本文的介紹,我們了解了CANopen協議的基本概念,并基于MM32G5330的FlexCAN完成了CANopenNode協議棧的移植工作。通過板載驗證,我們確認了移植后的協議棧能夠正常工作,為后續的設備集成和通信提供了進一步開發的基礎。同樣的開發者可以根據實際應用需求使用靈動其他帶有FlexCAN的MCU,參考本文的方法進行相應的移植和驗證工作,以實現高效可靠的CANopen通信。
?
關于靈動
上海靈動微電子股份有限公司成立于 2011 年,是中國本土領先的通用 32 位 MCU 產品及解決方案供應商。公司基于 Arm Cortex-M 系列內核開發的 MM32 MCU 產品目前已量產近 300 款型號,累計交付超 5 億顆,每年都有近億臺配備了靈動 MM32MCU 的優秀產品交付到客戶手中,在本土通用 32 位 MCU 公司中位居前列。
靈動客戶涵蓋智能工業、汽車電子、通信基建、醫療健康、智慧家電、物聯網、個人設備、手機和電腦等應用領域。靈動是中國為數不多的同時獲得了 Arm-KEIL、IAR、SEGGER 官方支持的本土 MCU 公司,并建立了獨立、完整的通用 MCU 生態體系。靈動始終秉承著“誠信、承諾、創新、合作”的精神,為客戶提供從硬件芯片到軟件算法、從參考方案到系統設計的全方位支持。
評論