引言
在現代工業(yè)自動化和汽車電子領域,CAN總線以其高可靠性和實時性成為通信的主流選擇。而CANopen協議,作為CAN總線上的一種上層通信協議,廣泛應用于各種設備間的通信。本文將介紹如何基于靈動MM32G5330的FlexCAN實現CANopenNode協議棧的移植,并使用靈動官方提供的開發(fā)板Mini-G5333進行驗證。
CANopen簡介
CANopen是由CiA (CAN-in-Automation)組織開發(fā)的上層通信協議,它定義了一組用于工業(yè)自動化的通信對象,并在CAN總線之上實現了網絡管理、設備配置和數據交換等功能。CANopen協議規(guī)范了設備如何通過CAN總線進行通信,使得不同廠商的設備能夠無縫集成和協同工作。
CANopen從應用端到CAN總線的結構:
應用層(Application)
用于實現各種應用對象
對象字典(Object dictionary)
用于描述CANopen節(jié)點設備的參數
通信接口(Communication interface)
定義了CANopen協議通信規(guī)則以及CAN控制器驅動之間對應關系
CANopen網絡中用到的三種通信模型:
主機/從機模型(Master/Salve)
一個節(jié)點(例如控制接口)充當應用程序主機控制器,從機(例如伺服電機)發(fā)送/請求數據,一般在診斷或狀態(tài)管理中使用。
通信樣例:NMT主機與NMT從機的通信
所有節(jié)點通信地位平等,運行時允許自行發(fā)送報文,但CANopen網絡為了穩(wěn)定可靠可控,都需要設置一個網絡管理主機 NMT-Master。
NMT主機一般是CANopen網絡中具備監(jiān)控的PLC或者PC(當然也可以是一般的功能節(jié)點),所以也成為CANopen主站。相對應的其他CANopen節(jié)點就是NMT從機(NMT-slaves)。
客戶端/服務端模型(Client/Server)
客戶機向服務器發(fā)送數據請求,服務器進行響應。例如,當應用程序主機需要來自從機OD的數據時使用。
通信樣例:SDO客戶端與SDO服務端的通信
發(fā)送節(jié)點需要指定接收節(jié)點的地址(Node-ID)回應CAN報文來確認已經接收,如果超時沒有確認,則發(fā)送節(jié)點將會重新發(fā)送原報文。
生產者/消費者模型(Producer/Consumer)
生產者節(jié)點向網絡廣播數據,而網絡由使用者節(jié)點使用。生產者可以根據請求發(fā)送此數據,也可以不發(fā)送特定請求。
通信樣例:心跳生產者與心跳消費者的通信
單向發(fā)送傳輸,無需接收節(jié)點回應CAN報文來確認。
CANopen的七種報文類型:
NMT(Network Management)
控制CANopen設備狀態(tài),用于網絡管理。
SYNC(Synchronization)
SYNC 消息用于同步多個 CANopen 設備的輸入感應和驅動——通常由應用程序 Master 觸發(fā)。
EMCY(Emergency)
在設備發(fā)生錯誤(例如傳感器故障)時使用的,發(fā)送設備內部錯誤代碼。
TIME
用于分配網絡時間,議采用廣播方式,無需節(jié)點應答,CAN-ID 為 100h,數據長度為 6,數據為當前時刻與1984年1月1日0時的時間差。節(jié)點將此時間存儲在對象字典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都是用于在嵌入式系統(tǒng)上實現CANopen協議通信的開源軟件協議棧。需要注意的是它們使用了不同的開放程度的開源協議。CANFestival使用LGPLv2開源協議。這意味著CANFestival的源代碼雖是免費提供的,任何人都可以使用、修改和分發(fā),只要任何衍生作品使用相同的GPL許可證,但如果一個公司在產品中使用CANFestival,他們也必須按照同樣的LGPLv2開源協議提供其產品的源代碼。而CANopenNode使用 Apache v2.0開源協議,這是一個自由度比LGPLv2更為開發(fā)的一個開源協議,允許在使用軟件方面有更大的靈活性。任何人都可以使用、修改和發(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詳情見靈動官網:
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 文件中實現 tmrTask_thread() 函數
通加載進入1ms 定時中斷服務函數進行 1ms 定時的信息同步
在 CO_driver.c 文件中實現 CO_CANmodule_init() 函數
用于對 MCU 中的 CAN 模塊進行初始,并配置CAN報文的收發(fā)參數以及開啟 flexcan 中斷。
在 CO_driver.C 文件中實現 CO_CANinterrupt() 函數
用于實現接收和發(fā)送CAN信息。該功能從高優(yōu)先級的CAN中斷中直接調用。
在 CO_driver.C 文件中實現 CO_CANverifyErrorst() 函數
用于對 CAN 總線進行錯誤檢測和上報。
下面我們將以MM32G5330微控制器上集成的FlexCAN為例,完成對CANopenNode v1.3的移植,并實現一個 CANopen_Basic 樣例進行基本功能驗證。
首先在靈動官網下載基于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,并實現了與之相關的中斷處理程序。
?
/*?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報文收發(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 文件中實現FlexCAN的報文收發(fā)
對 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?<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協議與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é)點應成功啟動并向網絡發(fā)送上線報文。
CANopen節(jié)點上線向CAN網絡發(fā)送CANopen節(jié)點上線報文,PC上位機將收到一條如下報文:

表2
之后該CANopen節(jié)點以 1000ms 的時間間隔向CAN網絡發(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協議的基本概念,并基于MM32G5330的FlexCAN完成了CANopenNode協議棧的移植工作。通過板載驗證,我們確認了移植后的協議棧能夠正常工作,為后續(xù)的設備集成和通信提供了進一步開發(fā)的基礎。同樣的開發(fā)者可以根據實際應用需求使用靈動其他帶有FlexCAN的MCU,參考本文的方法進行相應的移植和驗證工作,以實現高效可靠的CANopen通信。
?
關于靈動
上海靈動微電子股份有限公司成立于 2011 年,是中國本土領先的通用 32 位 MCU 產品及解決方案供應商。公司基于 Arm Cortex-M 系列內核開發(fā)的 MM32 MCU 產品目前已量產近 300 款型號,累計交付超 5 億顆,每年都有近億臺配備了靈動 MM32MCU 的優(yōu)秀產品交付到客戶手中,在本土通用 32 位 MCU 公司中位居前列。
靈動客戶涵蓋智能工業(yè)、汽車電子、通信基建、醫(yī)療健康、智慧家電、物聯網、個人設備、手機和電腦等應用領域。靈動是中國為數不多的同時獲得了 Arm-KEIL、IAR、SEGGER 官方支持的本土 MCU 公司,并建立了獨立、完整的通用 MCU 生態(tài)體系。靈動始終秉承著“誠信、承諾、創(chuàng)新、合作”的精神,為客戶提供從硬件芯片到軟件算法、從參考方案到系統(tǒng)設計的全方位支持。
電子發(fā)燒友App


























評論