引言
在现代工业自动化和汽车电子领域,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?<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); ????????} ????} }?
至此,驱动代码适配完成。
板载验证
验证环境
使用搭载了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 生态体系。灵动始终秉承着“诚信、承诺、创新、合作”的精神,为客户提供从硬件芯片到软件算法、从参考方案到系统设计的全方位支持。
评论