引言
在現(xiàn)代工業(yè)自動化和汽車電子領域,CAN總線以其高可靠性和實時性成為通信的主流選擇。而CANopen協(xié)議,作為CAN總線上的一種上層通信協(xié)議,廣泛應用于各種設備間的通信。本文將介紹如何基于靈動MM32G5330的FlexCAN實現(xiàn)CANopenNode協(xié)議棧的移植,并使用靈動官方提供的開發(fā)板Mini-G5333進行驗證。
CANopen簡介
CANopen是由CiA (CAN-in-Automation)組織開發(fā)的上層通信協(xié)議,它定義了一組用于工業(yè)自動化的通信對象,并在CAN總線之上實現(xiàn)了網(wǎng)絡管理、設備配置和數(shù)據(jù)交換等功能。CANopen協(xié)議規(guī)范了設備如何通過CAN總線進行通信,使得不同廠商的設備能夠無縫集成和協(xié)同工作。
CANopen從應用端到CAN總線的結構:
應用層(Application)
用于實現(xiàn)各種應用對象
對象字典(Object dictionary)
用于描述CANopen節(jié)點設備的參數(shù)
通信接口(Communication interface)
定義了CANopen協(xié)議通信規(guī)則以及CAN控制器驅動之間對應關系
CANopen網(wǎng)絡中用到的三種通信模型:
主機/從機模型(Master/Salve)
一個節(jié)點(例如控制接口)充當應用程序主機控制器,從機(例如伺服電機)發(fā)送/請求數(shù)據(jù),一般在診斷或狀態(tài)管理中使用。
通信樣例:NMT主機與NMT從機的通信
所有節(jié)點通信地位平等,運行時允許自行發(fā)送報文,但CANopen網(wǎng)絡為了穩(wěn)定可靠可控,都需要設置一個網(wǎng)絡管理主機 NMT-Master。
NMT主機一般是CANopen網(wǎng)絡中具備監(jiān)控的PLC或者PC(當然也可以是一般的功能節(jié)點),所以也成為CANopen主站。相對應的其他CANopen節(jié)點就是NMT從機(NMT-slaves)。
客戶端/服務端模型(Client/Server)
客戶機向服務器發(fā)送數(shù)據(jù)請求,服務器進行響應。例如,當應用程序主機需要來自從機OD的數(shù)據(jù)時使用。
通信樣例:SDO客戶端與SDO服務端的通信
發(fā)送節(jié)點需要指定接收節(jié)點的地址(Node-ID)回應CAN報文來確認已經(jīng)接收,如果超時沒有確認,則發(fā)送節(jié)點將會重新發(fā)送原報文。
生產(chǎn)者/消費者模型(Producer/Consumer)
生產(chǎn)者節(jié)點向網(wǎng)絡廣播數(shù)據(jù),而網(wǎng)絡由使用者節(jié)點使用。生產(chǎn)者可以根據(jù)請求發(fā)送此數(shù)據(jù),也可以不發(fā)送特定請求。
通信樣例:心跳生產(chǎn)者與心跳消費者的通信
單向發(fā)送傳輸,無需接收節(jié)點回應CAN報文來確認。
CANopen的七種報文類型:
NMT(Network Management)
控制CANopen設備狀態(tài),用于網(wǎng)絡管理。
SYNC(Synchronization)
SYNC 消息用于同步多個 CANopen 設備的輸入感應和驅動——通常由應用程序 Master 觸發(fā)。
EMCY(Emergency)
在設備發(fā)生錯誤(例如傳感器故障)時使用的,發(fā)送設備內(nèi)部錯誤代碼。
TIME
用于分配網(wǎng)絡時間,議采用廣播方式,無需節(jié)點應答,CAN-ID 為 100h,數(shù)據(jù)長度為 6,數(shù)據(jù)為當前時刻與1984年1月1日0時的時間差。節(jié)點將此時間存儲在對象字典1012h的索引中。
PDO(Process Object)
PDO服務用于在設備之間傳輸實時數(shù)據(jù),例如測量數(shù)據(jù)(如位置數(shù)據(jù))或命令數(shù)據(jù)(如扭矩請求)。
SDO(Sever D Object)
用于訪問/更改CANopen設備的對象字典中的值——例如,當應用程序主機需要更改CANopen設備的某些配置時。
Heartbeat
Heartbeat服務有兩個用途: 提供“活動”消息和確認NMT命令。
CANopenNode協(xié)議棧
CANopenNode是一款免費和開源的CANopen協(xié)議棧,使用ANSI C語言以面向對象的方式編寫的。它可以在不同的微控制器上運行,作為獨立的應用程序或與RTOS一起運行。變量(通信、設備、自定義)被收集在CANopen對象字典中,并且可以以兩種方式修改:C源代碼和CANopen網(wǎng)絡。
CANopenNode主頁位于:
https://github.com/CANopenNode/CANopenNode
CANopenNode vs CAN Festival
表1
CANopenNode和CANFestival都是用于在嵌入式系統(tǒng)上實現(xiàn)CANopen協(xié)議通信的開源軟件協(xié)議棧。需要注意的是它們使用了不同的開放程度的開源協(xié)議。CANFestival使用LGPLv2開源協(xié)議。這意味著CANFestival的源代碼雖是免費提供的,任何人都可以使用、修改和分發(fā),只要任何衍生作品使用相同的GPL許可證,但如果一個公司在產(chǎn)品中使用CANFestival,他們也必須按照同樣的LGPLv2開源協(xié)議提供其產(chǎn)品的源代碼。而CANopenNode使用 Apache v2.0開源協(xié)議,這是一個自由度比LGPLv2更為開發(fā)的一個開源協(xié)議,允許在使用軟件方面有更大的靈活性。任何人都可以使用、修改和發(fā)布CANopenNode,甚至用于商業(yè)目的,而不需要發(fā)布其衍生作品的源代碼。
移植前準備
獲取CANopenNode源碼
選擇 CANopenNode v1.3,該版本為CANopenNode 官方發(fā)布版本,獲取源碼鏈接:
https://github.com/CANopenNode/CANopenNode/releases/tag/v1.3。
獲取 MiniBoard-OB (MM32G5333D6QV) 例程及開發(fā)板資料
開發(fā)板及LibSamples詳情見靈動官網(wǎng):
https://www.mindmotion.com.cn/support/development_tools/evaluation_boards/miniboard/mm32g5330d6qv/。
編譯工具和開發(fā)環(huán)境
使用基于 Keil MDK-ARM 創(chuàng)建工程。
基于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 文件中實現(xiàn) tmrTask_thread() 函數(shù)
通加載進入1ms 定時中斷服務函數(shù)進行 1ms 定時的信息同步
在 CO_driver.c 文件中實現(xiàn) CO_CANmodule_init() 函數(shù)
用于對 MCU 中的 CAN 模塊進行初始,并配置CAN報文的收發(fā)參數(shù)以及開啟 flexcan 中斷。
在 CO_driver.C 文件中實現(xiàn) CO_CANinterrupt() 函數(shù)
用于實現(xiàn)接收和發(fā)送CAN信息。該功能從高優(yōu)先級的CAN中斷中直接調(diào)用。
在 CO_driver.C 文件中實現(xiàn) CO_CANverifyErrorst() 函數(shù)
用于對 CAN 總線進行錯誤檢測和上報。
下面我們將以MM32G5330微控制器上集成的FlexCAN為例,完成對CANopenNode v1.3的移植,并實現(xiàn)一個 CANopen_Basic 樣例進行基本功能驗證。
首先在靈動官網(wǎng)下載基于Mini-G5330開發(fā)板的LibSamples_MM32G5330軟件包,并在該軟件包的根目錄文件夾下創(chuàng)建?~/3rdPartySoftwarePorting/CANopenNode?文件夾,如下圖1所示,將獲取的 CANopenNode-1.3 軟件包解壓后原封不動地復制到新建的 CANopenNode 文件夾中。
圖 1
這里我們在 CANopenNode 文件夾下創(chuàng)建 Demos 文件夾用于按照LibSamples的樣例結構創(chuàng)建關于 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中的樣例工程創(chuàng)建MDK-ARM樣例工程并添加編譯路徑,CANopen_Basic樣例完成移植后效果如下圖所示:
圖 4
由于本次移植是基于裸機移植,故按照CANopenNode的設計將Mainline線程放入while(1)中,CAN接收線程放入flexcan的中斷服務程序中,定時線程放在一個1ms的定時中斷服務程序中。
在 main.c 文件中配置定時器
這里初始化和配置了定時器 TIM1,并實現(xiàn)了與之相關的中斷處理程序。
?
/*?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 文件中實現(xiàn)定時線程任務處理
這里對 tmrTask_thread() 函數(shù)進行完善。
?
/*?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 文件中實現(xiàn) FlexCAN 的中斷服務函數(shù)
?
/*?CAN?interrupt?function?*****************************************************/ void?FLEXCAN_IRQHandler(void) { ????FLEXCAN_TransferHandleIRQ(FLEXCAN,?&FlexCAN_Handle); ????CO_CANinterrupt(CO->CANmodule[0]); ????__DSB(); }
?
在 CO_driver.c 文件中實現(xiàn)FlexCAN模塊配置
實現(xiàn)包括對 FlexCAN 相關的 GPIO引腳、時鐘、CAN報文收發(fā)消息緩沖區(qū)的配置。
?
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 文件中實現(xiàn)FlexCAN的報文收發(fā)
對 flexcan_tx() 函數(shù)及 CO_CANinterrupt()函數(shù)的實現(xiàn)。
?
/*?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 文件中實現(xiàn)CAN總線錯誤檢測
關于 CO_CANverifyErrors() 函數(shù)的實現(xiàn)。
?
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?<16)?|?((uint32_t)rxErrors?<8)?|?overflow; ????if(CANmodule->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?96U)?&&?(txErrors?96U)){???????/*?no?error?*/ ????????????????CO_errorReset(em,?CO_EM_CAN_BUS_WARNING,?err); ????????????} ????????} ????????if(overflow?!=?0U){?????????????????????????????????/*?CAN?RX?bus?overflow?*/ ????????????CO_errorReport(em,?CO_EM_CAN_RXB_OVERFLOW,?CO_EMC_CAN_OVERRUN,?err); ????????} ????} }?
至此,驅動代碼適配完成。
板載驗證
驗證環(huán)境
使用搭載了MM32G5330 MCU的開發(fā)板Mini-G5330 ,以CANopen_Basic樣例工程為例,將開發(fā)板上的CAN收發(fā)器與PCAN相連接,并將PCAN與PC機通過USB相連接,在PC端(基于Win10操作系統(tǒng))使用PCAN-View上位機模擬CANopen主站,來通過CANopen協(xié)議與CANopen從站(即 MM32 MCU)進行通信,如圖5所示。
圖 5 MCU與PC機交互示意圖
注:這里我們使用了PCAN-USB,并使用了配套上位機PCAN-View。
驗證過程
上述環(huán)境搭建好后,將上述工程代碼編譯后刷寫固件進MCU,將MCU上電并復位通過PC端上位機PCAN-View測試如下指令,觀察CANopen節(jié)點其對指令的響應,來判斷該CANopen節(jié)點是否處于正常運行狀態(tài)。
節(jié)點上線:
MCU上電后,CANopen節(jié)點應成功啟動并向網(wǎng)絡發(fā)送上線報文。
CANopen節(jié)點上線向CAN網(wǎng)絡發(fā)送CANopen節(jié)點上線報文,PC上位機將收到一條如下報文:
表2
之后該CANopen節(jié)點以 1000ms 的時間間隔向CAN網(wǎng)絡發(fā)送節(jié)點心跳報文,上位機以1000ms的時間間隔收到如下報文:
表 3
如圖6所示。
圖 6
至此,可驗證該CANopen節(jié)點設備成功啟動并開始正常運行。
模式切換:
通過上位機發(fā)送NMT命令,驗證節(jié)點能夠正確響應Start、Stop和Pre-operation等模式切換指令。
將NODE-ID為0x0A的節(jié)點設置為 Stop 模式,上位機PCAN-View發(fā)送如下指令:
表 4
如下圖7所示,可接收到如下報文:
圖 7
將NODE-ID為0x0A的節(jié)點設置為 Start 模式,上位機PCAN-View發(fā)送如下指令:
表 5
如下圖8所示,可接收到如下報文:
圖8
將NODE-ID為0x0A的節(jié)點設置為Pre-operation模式,上位機PCAN-View發(fā)送如下指令:
表6
如下圖9所示,該節(jié)點進入Pre-operation模式,可接收到如下報文:
圖 9
將NODE-ID為0x0A節(jié)點復位,上位機PCAN-View發(fā)送如下指令:
表 7
如下圖10所示,該節(jié)點被復位:
圖 10
將NODE-ID為0x0A節(jié)點的通信層復位,上位機PCAN-View發(fā)送如下指令:
表 8
如下圖11所示,該節(jié)點通信層被復位,重新上線:
圖 11
心跳檢測:
節(jié)點應周期性發(fā)送心跳報文,以表明其處于活躍狀態(tài)。
獲取NODE-ID為0x0A節(jié)點的心跳發(fā)送間隔時間,上位機PCAN-View發(fā)送如下指令:
表 9
如下圖12所示,返回該節(jié)點當前心跳發(fā)送間隔時間為1000(0x03E8)ms:
圖 12
設置NODE-ID為0x0A節(jié)點的心跳發(fā)送間隔時間為500(0x01F4)ms,上位機PCAN-View發(fā)送如下指令:
表 10
如下圖13所示,該節(jié)點當前心跳發(fā)送間隔時間變?yōu)?00ms:
圖 13
總結
通過本文的介紹,我們了解了CANopen協(xié)議的基本概念,并基于MM32G5330的FlexCAN完成了CANopenNode協(xié)議棧的移植工作。通過板載驗證,我們確認了移植后的協(xié)議棧能夠正常工作,為后續(xù)的設備集成和通信提供了進一步開發(fā)的基礎。同樣的開發(fā)者可以根據(jù)實際應用需求使用靈動其他帶有FlexCAN的MCU,參考本文的方法進行相應的移植和驗證工作,以實現(xiàn)高效可靠的CANopen通信。
?
關于靈動
上海靈動微電子股份有限公司成立于 2011 年,是中國本土領先的通用 32 位 MCU 產(chǎn)品及解決方案供應商。公司基于 Arm Cortex-M 系列內(nèi)核開發(fā)的 MM32 MCU 產(chǎn)品目前已量產(chǎn)近 300 款型號,累計交付超 5 億顆,每年都有近億臺配備了靈動 MM32MCU 的優(yōu)秀產(chǎn)品交付到客戶手中,在本土通用 32 位 MCU 公司中位居前列。
靈動客戶涵蓋智能工業(yè)、汽車電子、通信基建、醫(yī)療健康、智慧家電、物聯(lián)網(wǎng)、個人設備、手機和電腦等應用領域。靈動是中國為數(shù)不多的同時獲得了 Arm-KEIL、IAR、SEGGER 官方支持的本土 MCU 公司,并建立了獨立、完整的通用 MCU 生態(tài)體系。靈動始終秉承著“誠信、承諾、創(chuàng)新、合作”的精神,為客戶提供從硬件芯片到軟件算法、從參考方案到系統(tǒng)設計的全方位支持。
評論
查看更多