引言
在現(xiàn)代工業(yè)自動化和汽車電子領(lǐng)域,CAN總線以其高可靠性和實時性成為通信的主流選擇。而CANopen協(xié)議,作為CAN總線上的一種上層通信協(xié)議,廣泛應(yīng)用于各種設(shè)備間的通信。本文將介紹如何基于靈動MM32G5330的FlexCAN實現(xiàn)CANopenNode協(xié)議棧的移植,并使用靈動官方提供的開發(fā)板Mini-G5333進行驗證。
CANopen簡介
CANopen是由CiA (CAN-in-Automation)組織開發(fā)的上層通信協(xié)議,它定義了一組用于工業(yè)自動化的通信對象,并在CAN總線之上實現(xiàn)了網(wǎng)絡(luò)管理、設(shè)備配置和數(shù)據(jù)交換等功能。CANopen協(xié)議規(guī)范了設(shè)備如何通過CAN總線進行通信,使得不同廠商的設(shè)備能夠無縫集成和協(xié)同工作。
CANopen從應(yīng)用端到CAN總線的結(jié)構(gòu):
應(yīng)用層(Application)
用于實現(xiàn)各種應(yīng)用對象
對象字典(Object dictionary)
用于描述CANopen節(jié)點設(shè)備的參數(shù)
通信接口(Communication interface)
定義了CANopen協(xié)議通信規(guī)則以及CAN控制器驅(qū)動之間對應(yīng)關(guān)系
CANopen網(wǎng)絡(luò)中用到的三種通信模型:
主機/從機模型(Master/Salve)
一個節(jié)點(例如控制接口)充當應(yīng)用程序主機控制器,從機(例如伺服電機)發(fā)送/請求數(shù)據(jù),一般在診斷或狀態(tài)管理中使用。
通信樣例:NMT主機與NMT從機的通信
所有節(jié)點通信地位平等,運行時允許自行發(fā)送報文,但CANopen網(wǎng)絡(luò)為了穩(wěn)定可靠可控,都需要設(shè)置一個網(wǎng)絡(luò)管理主機 NMT-Master。
NMT主機一般是CANopen網(wǎng)絡(luò)中具備監(jiān)控的PLC或者PC(當然也可以是一般的功能節(jié)點),所以也成為CANopen主站。相對應(yīng)的其他CANopen節(jié)點就是NMT從機(NMT-slaves)。
客戶端/服務(wù)端模型(Client/Server)
客戶機向服務(wù)器發(fā)送數(shù)據(jù)請求,服務(wù)器進行響應(yīng)。例如,當應(yīng)用程序主機需要來自從機OD的數(shù)據(jù)時使用。
通信樣例:SDO客戶端與SDO服務(wù)端的通信
發(fā)送節(jié)點需要指定接收節(jié)點的地址(Node-ID)回應(yīng)CAN報文來確認已經(jīng)接收,如果超時沒有確認,則發(fā)送節(jié)點將會重新發(fā)送原報文。
生產(chǎn)者/消費者模型(Producer/Consumer)
生產(chǎn)者節(jié)點向網(wǎng)絡(luò)廣播數(shù)據(jù),而網(wǎng)絡(luò)由使用者節(jié)點使用。生產(chǎn)者可以根據(jù)請求發(fā)送此數(shù)據(jù),也可以不發(fā)送特定請求。
通信樣例:心跳生產(chǎn)者與心跳消費者的通信
單向發(fā)送傳輸,無需接收節(jié)點回應(yīng)CAN報文來確認。
CANopen的七種報文類型:
NMT(Network Management)
控制CANopen設(shè)備狀態(tài),用于網(wǎng)絡(luò)管理。
SYNC(Synchronization)
SYNC 消息用于同步多個 CANopen 設(shè)備的輸入感應(yīng)和驅(qū)動——通常由應(yīng)用程序 Master 觸發(fā)。
EMCY(Emergency)
在設(shè)備發(fā)生錯誤(例如傳感器故障)時使用的,發(fā)送設(shè)備內(nèi)部錯誤代碼。
TIME
用于分配網(wǎng)絡(luò)時間,議采用廣播方式,無需節(jié)點應(yīng)答,CAN-ID 為 100h,數(shù)據(jù)長度為 6,數(shù)據(jù)為當前時刻與1984年1月1日0時的時間差。節(jié)點將此時間存儲在對象字典1012h的索引中。
PDO(Process Object)
PDO服務(wù)用于在設(shè)備之間傳輸實時數(shù)據(jù),例如測量數(shù)據(jù)(如位置數(shù)據(jù))或命令數(shù)據(jù)(如扭矩請求)。
SDO(Sever D Object)
用于訪問/更改CANopen設(shè)備的對象字典中的值——例如,當應(yīng)用程序主機需要更改CANopen設(shè)備的某些配置時。
Heartbeat
Heartbeat服務(wù)有兩個用途: 提供“活動”消息和確認NMT命令。
CANopenNode協(xié)議棧
CANopenNode是一款免費和開源的CANopen協(xié)議棧,使用ANSI C語言以面向?qū)ο蟮姆绞骄帉懙摹K梢栽诓煌?a target="_blank">微控制器上運行,作為獨立的應(yīng)用程序或與RTOS一起運行。變量(通信、設(shè)備、自定義)被收集在CANopen對象字典中,并且可以以兩種方式修改:C源代碼和CANopen網(wǎng)絡(luò)。
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移植中涉及到三個文件需要被復(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 文件中實現(xiàn) tmrTask_thread() 函數(shù)
通加載進入1ms 定時中斷服務(wù)函數(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 軟件包解壓后原封不動地復(fù)制到新建的 CANopenNode 文件夾中。

圖 1
這里我們在 CANopenNode 文件夾下創(chuàng)建 Demos 文件夾用于按照LibSamples的樣例結(jié)構(gòu)創(chuàng)建關(guān)于 CANopenNode 相關(guān)的樣例工程。接下來將CANopenNode源碼中提供的example文件夾的結(jié)構(gòu)如下圖2所示,其中CO_OD.c/h是 CANopen中使用到的對象字典, 我們將這兩個文件復(fù)制到? Demos/CANopen_Basic 文件夾下。main.c是 CANopenNode的主程序文件,我們將原有的main.c文件進行替換。

圖 2
將如圖3所示的位于CANopenNode-1.3/stack/drvTemplate文件夾下的CO_driver.c及CO_driver_target.h這兩個文件復(fù)制到樣例工程的文件夾下。

圖 3
在CANopen_Basic文件夾下參照LibSample中的樣例工程創(chuàng)建MDK-ARM樣例工程并添加編譯路徑,CANopen_Basic樣例完成移植后效果如下圖所示:

圖 4
由于本次移植是基于裸機移植,故按照CANopenNode的設(shè)計將Mainline線程放入while(1)中,CAN接收線程放入flexcan的中斷服務(wù)程序中,定時線程放在一個1ms的定時中斷服務(wù)程序中。
在 main.c 文件中配置定時器
這里初始化和配置了定時器 TIM1,并實現(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 文件中實現(xiàn)定時線程任務(wù)處理
這里對 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 的中斷服務(wù)函數(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 相關(guān)的 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總線錯誤檢測
關(guān)于 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);
????????}
????}
}
?
至此,驅(qū)動代碼適配完成。
板載驗證
驗證環(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上電并復(fù)位通過PC端上位機PCAN-View測試如下指令,觀察CANopen節(jié)點其對指令的響應(yīng),來判斷該CANopen節(jié)點是否處于正常運行狀態(tài)。
節(jié)點上線:
MCU上電后,CANopen節(jié)點應(yīng)成功啟動并向網(wǎng)絡(luò)發(fā)送上線報文。
CANopen節(jié)點上線向CAN網(wǎng)絡(luò)發(fā)送CANopen節(jié)點上線報文,PC上位機將收到一條如下報文:

表2
之后該CANopen節(jié)點以 1000ms 的時間間隔向CAN網(wǎng)絡(luò)發(fā)送節(jié)點心跳報文,上位機以1000ms的時間間隔收到如下報文:

表 3
如圖6所示。

圖 6
至此,可驗證該CANopen節(jié)點設(shè)備成功啟動并開始正常運行。
模式切換:
通過上位機發(fā)送NMT命令,驗證節(jié)點能夠正確響應(yīng)Start、Stop和Pre-operation等模式切換指令。
將NODE-ID為0x0A的節(jié)點設(shè)置為 Stop 模式,上位機PCAN-View發(fā)送如下指令:

表 4
如下圖7所示,可接收到如下報文:

圖 7
將NODE-ID為0x0A的節(jié)點設(shè)置為 Start 模式,上位機PCAN-View發(fā)送如下指令:

表 5
如下圖8所示,可接收到如下報文:

圖8
將NODE-ID為0x0A的節(jié)點設(shè)置為Pre-operation模式,上位機PCAN-View發(fā)送如下指令:

表6
如下圖9所示,該節(jié)點進入Pre-operation模式,可接收到如下報文:

圖 9
將NODE-ID為0x0A節(jié)點復(fù)位,上位機PCAN-View發(fā)送如下指令:

表 7
如下圖10所示,該節(jié)點被復(fù)位:

圖 10
將NODE-ID為0x0A節(jié)點的通信層復(fù)位,上位機PCAN-View發(fā)送如下指令:

表 8
如下圖11所示,該節(jié)點通信層被復(fù)位,重新上線:

圖 11
心跳檢測:
節(jié)點應(yīng)周期性發(fā)送心跳報文,以表明其處于活躍狀態(tài)。
獲取NODE-ID為0x0A節(jié)點的心跳發(fā)送間隔時間,上位機PCAN-View發(fā)送如下指令:

表 9
如下圖12所示,返回該節(jié)點當前心跳發(fā)送間隔時間為1000(0x03E8)ms:

圖 12
設(shè)置NODE-ID為0x0A節(jié)點的心跳發(fā)送間隔時間為500(0x01F4)ms,上位機PCAN-View發(fā)送如下指令:

表 10
如下圖13所示,該節(jié)點當前心跳發(fā)送間隔時間變?yōu)?00ms:

圖 13
總結(jié)
通過本文的介紹,我們了解了CANopen協(xié)議的基本概念,并基于MM32G5330的FlexCAN完成了CANopenNode協(xié)議棧的移植工作。通過板載驗證,我們確認了移植后的協(xié)議棧能夠正常工作,為后續(xù)的設(shè)備集成和通信提供了進一步開發(fā)的基礎(chǔ)。同樣的開發(fā)者可以根據(jù)實際應(yīng)用需求使用靈動其他帶有FlexCAN的MCU,參考本文的方法進行相應(yīng)的移植和驗證工作,以實現(xiàn)高效可靠的CANopen通信。
?
關(guān)于靈動
上海靈動微電子股份有限公司成立于 2011 年,是中國本土領(lǐng)先的通用 32 位 MCU 產(chǎn)品及解決方案供應(yīng)商。公司基于 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è)備、手機和電腦等應(yīng)用領(lǐng)域。靈動是中國為數(shù)不多的同時獲得了 Arm-KEIL、IAR、SEGGER 官方支持的本土 MCU 公司,并建立了獨立、完整的通用 MCU 生態(tài)體系。靈動始終秉承著“誠信、承諾、創(chuàng)新、合作”的精神,為客戶提供從硬件芯片到軟件算法、從參考方案到系統(tǒng)設(shè)計的全方位支持。
電子發(fā)燒友App
















評論