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