[MM32软件] 基于MM32G5330的FlexCAN实现CANopenNode协议栈移植

[复制链接]
 楼主| MindMotion 发表于 2024-4-12 09:44 | 显示全部楼层 |阅读模式
本帖最后由 MindMotion 于 2024-4-12 09:44 编辑

在现代工业自动化和汽车电子领域,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.png

表1

CANopenNode和CANFestival都是用于在嵌入式系统上实现CANopen协议通信的开源软件协议栈。需要注意的是它们使用了不同的开放程度的开源协议。CANFestival使用LGPLv2开源协议。这意味着CANFestival的源代码虽是免费提供的,任何人都可以使用、修改和分发,只要任何衍生作品使用相同的GPL许可证,但如果一个公司在产品中使用CANFestival,他们也必须按照同样的LGPLv2开源协议提供其产品的源代码。而CANopenNode使用 Apache v2.0开源协议,这是一个自由度比LGPLv2更为开发的一个开源协议,允许在使用软件方面有更大的灵活性。任何人都可以使用、修改和发布CANopenNode,甚至用于商业目的,而不需要发布其衍生作品的源代码。

移植前准备

1)获取CANopenNode源码
选择 CANopenNode v1.3,该版本为CANopenNode 官方发布版本,获取源码链接:https://github.com/CANopenNode/CANopenNode/releases/tag/v1.3

2)获取 MiniBoard-OB (MM32G5333D6QV) 例程及开发板资料
开发板及LibSamples详情见灵动官网:https://www.mindmotion.com.cn/support/development_tools/evaluation_boards/miniboard/mm32g5330d6qv/

3)编译工具和开发环境
使用基于 Keil MDK-ARM 创建工程。

基于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 文件。

其中:

1)在 mian.c 文件中实现 tmrTask_thread() 函数
通加载进入1ms 定时中断服务函数进行 1ms 定时的信息同步

2)在 CO_driver.c 文件中实现 CO_CANmodule_init() 函数
用于对 MCU 中的 CAN 模块进行初始,并配置CAN报文的收发参数以及开启 flexcan 中断。

3)在 CO_driver.C 文件中实现 CO_CANinterrupt() 函数
用于实现接收和发送CAN信息。该功能从高优先级的CAN中断中直接调用。

4)在 CO_driver.C 文件中实现 CO_CANverifyErrorst() 函数
用于对 CAN 总线进行错误检测和上报。

下面我们将以MM32G5330微控制器上集成的FlexCAN为例,完成对CANopenNode v1.3的移植,并实现一个 CANopen_Basic 样例进行基本功能验证。

首先在灵动官网下载基于Mini-G5330开发板的LibSamples_MM32G5330软件包,并在该软件包的根目录文件夹下创建 ~/3rdPartySoftwarePorting/CANopenNode 文件夹,如下图1所示,将获取的 CANopenNode-1.3 软件包解压后原封不动地复制到新建的 CANopenNode 文件夹中。

image-20240410111934751.png

图1

这里我们在 CANopenNode 文件夹下创建 Demos 文件夹用于按照LibSamples的样例结构创建关于 CANopenNode 相关的样例工程。接下来将CANopenNode源码中提供的example文件夹的结构如下图2所示,其中CO_OD.c/h是 CANopen中使用到的对象字典, 我们将这两个文件复制到  Demos/CANopen_Basic 文件夹下。main.c是 CANopenNode的主程序文件,我们将原有的main.c文件进行替换。

CANopenNode-example-1712719442750-1.png

图2

将如图3所示的位于CANopenNode-1.3/stack/drvTemplate文件夹下的CO_driver.c及CO_driver_target.h这两个文件复制到样例工程的文件夹下。

CANopenNode-drvTemp.png

图3

在CANopen_Basic文件夹下参照LibSample中的样例工程创建MDK-ARM样例工程并添加编译路径,CANopen_Basic样例完成移植后效果如下图所示:

image-20240410140738380.png

图4

由于本次移植是基于裸机移植,故按照CANopenNode的设计将Mainline线程放入while(1)中,CAN接收线程放入flexcan的中断服务程序中,定时线程放在一个1ms的定时中断服务程序中。

在 main.c 文件中配置定时器
这里初始化和配置了定时器 TIM1,并实现了与之相关的中断处理程序。
  1. /* Setup the timer. */
  2. void app_tim_init(void)
  3. {
  4.     NVIC_InitTypeDef        NVIC_InitStruct;
  5.     TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;

  6.     RCC_ClocksTypeDef RCC_Clocks;

  7.     RCC_GetClocksFreq(&RCC_Clocks);

  8.     RCC_APB2PeriphClockCmd(RCC_APB2ENR_TIM1, ENABLE);

  9.     TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct);
  10.     TIM_TimeBaseInitStruct.TIM_Prescaler         = (RCC_Clocks.PCLK2_Frequency / APP_TIM_UPDATE_STEP - 1);
  11.     TIM_TimeBaseInitStruct.TIM_CounterMode       = TIM_COUNTERMODE_UP;
  12.     TIM_TimeBaseInitStruct.TIM_Period            = (APP_TIM_UPDATE_PERIOD - 1);
  13.     TIM_TimeBaseInitStruct.TIM_ClockDivision     = TIM_CKD_DIV1;
  14.     TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
  15.     TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStruct);

  16.     TIM_ClearFlag(TIM1, TIM_IT_UPDATE);
  17.     TIM_ITConfig(TIM1, TIM_IT_UPDATE, ENABLE);

  18.     NVIC_InitStruct.NVIC_IRQChannel = TIM1_UP_IRQn;
  19.     NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
  20.     NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
  21.     NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
  22.     NVIC_Init(&NVIC_InitStruct);
  23. }

  24. void TIM1_UP_IRQHandler(void)
  25. {
  26.     TIM_ClearITPendingBit(TIM1, TIM_IT_UPDATE);
  27.     tmrTask_thread();
  28. }

在 main.c 文件中实现定时线程任务处理
这里对 tmrTask_thread() 函数进行完善。
  1. /* timer thread executes in constant intervals ********************************/
  2. void tmrTask_thread(void){

  3.     INCREMENT_1MS(CO_timer1ms);

  4.     if (CO->CANmodule[0]->CANnormal) {
  5.         bool_t syncWas;

  6.         /* Process Sync */
  7.         syncWas = CO_process_SYNC(CO, TMR_TASK_INTERVAL);

  8.         /* Read inputs */
  9.         CO_process_RPDO(CO, syncWas);

  10.         /* Further I/O or nonblocking application code may go here. */

  11.         /* Write outputs */
  12.         CO_process_TPDO(CO, syncWas, TMR_TASK_INTERVAL);

  13.         /* verify timer overflow */
  14.         if((TIM_GetITStatus(TIM1, TIM_IT_UPDATE) & TIM_IT_UPDATE) != 0u) {
  15.             CO_errorReport(CO->em, CO_EM_ISR_TIMER_OVERFLOW, CO_EMC_SOFTWARE_INTERNAL, 0u);
  16.             TIM_ClearITPendingBit(TIM1, TIM_IT_UPDATE);
  17.         }
  18.     }
  19. }

在 main.c 文件中实现 FlexCAN 的中断服务函数
  1. /* CAN interrupt function *****************************************************/
  2. void FLEXCAN_IRQHandler(void)
  3. {
  4.     FLEXCAN_TransferHandleIRQ(FLEXCAN, &FlexCAN_Handle);
  5.     CO_CANinterrupt(CO->CANmodule[0]);
  6.     __DSB();
  7. }

在 CO_driver.c 文件中实现FlexCAN模块配置
实现包括对 FlexCAN 相关的 GPIO引脚、时钟、CAN报文收发消息缓冲区的配置。
  1. void FlexCAN_Configure(uint32_t can_bitrate)
  2. {
  3.     GPIO_InitTypeDef GPIO_InitStruct;
  4.     NVIC_InitTypeDef NVIC_InitStruct;
  5.     RCC_ClocksTypeDef RCC_Clocks;

  6.     flexcan_config_t       FlexCAN_ConfigStruct;
  7.     flexcan_rx_mb_config_t FlexCAN_RxMB_ConfigStruct;

  8.     RCC_GetClocksFreq(&RCC_Clocks);

  9.     RCC_APB1PeriphClockCmd(RCC_APB1PERIPH_FLEXCAN, ENABLE);
  10.     RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);

  11.     GPIO_PinAFConfig(GPIOA, GPIO_PINSOURCE11, GPIO_AF_9);
  12.     GPIO_PinAFConfig(GPIOA, GPIO_PINSOURCE12, GPIO_AF_9);

  13.     GPIO_StructInit(&GPIO_InitStruct);
  14.     GPIO_InitStruct.GPIO_Pin   = GPIO_PIN_11;
  15.     GPIO_InitStruct.GPIO_Speed = GPIO_SPEED_HIGH;
  16.     GPIO_InitStruct.GPIO_Mode  = GPIO_MODE_FLOATING;
  17.     GPIO_Init(GPIOA, &GPIO_InitStruct);

  18.     GPIO_StructInit(&GPIO_InitStruct);
  19.     GPIO_InitStruct.GPIO_Pin   = GPIO_PIN_12;
  20.     GPIO_InitStruct.GPIO_Speed = GPIO_SPEED_HIGH;
  21.     GPIO_InitStruct.GPIO_Mode  = GPIO_MODE_AF_PP;
  22.     GPIO_Init(GPIOA, &GPIO_InitStruct);

  23.     NVIC_InitStruct.NVIC_IRQChannel = FLEXCAN_IRQn;
  24.     NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
  25.     NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
  26.     NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
  27.     NVIC_Init(&NVIC_InitStruct);

  28.     FLEXCAN_GetDefaultConfig(&FlexCAN_ConfigStruct);
  29.     FlexCAN_ConfigStruct.baudRate             = can_bitrate*1000;
  30.     FlexCAN_ConfigStruct.clkSrc               = Enum_Flexcan_ClkSrc1;
  31.     FlexCAN_ConfigStruct.enableLoopBack       = false;
  32.     FlexCAN_ConfigStruct.disableSelfReception = true;
  33.     FlexCAN_ConfigStruct.enableIndividMask    = true;

  34.     #if 1    /* Baudrate calculate by automatically */
  35.     FLEXCAN_CalculateImprovedTimingValues(FlexCAN_ConfigStruct.baudRate, RCC_Clocks.PCLK1_Frequency, &FlexCAN_ConfigStruct.timingConfig);
  36. #else  /* You can modify the parameters yourself */
  37.     FlexCAN_ConfigStruct.timingConfig.preDivider = 23;
  38.     FlexCAN_ConfigStruct.timingConfig.propSeg    = 6;
  39.     FlexCAN_ConfigStruct.timingConfig.phaseSeg1  = 3;
  40.     FlexCAN_ConfigStruct.timingConfig.phaseSeg2  = 3;   
  41.     FlexCAN_ConfigStruct.timingConfig.rJumpwidth = 3;
  42. #endif

  43.     FLEXCAN_Init(FLEXCAN, &FlexCAN_ConfigStruct);

  44.     /* Set Tx MB_2. */
  45.     FLEXCAN_TxMbConfig(FLEXCAN, BOARD_FLEXCAN_TX_MB_CH, ENABLE);
  46.     FLEXCAN_TransferCreateHandle(FLEXCAN, &FlexCAN_Handle, FlexCAN_Transfer_Callback, NULL);

  47.     /* Set Rx MB_0. */
  48.     FlexCAN_RxMB_ConfigStruct.id     = FLEXCAN_ID_STD(0x222);
  49.     FlexCAN_RxMB_ConfigStruct.format = Enum_Flexcan_FrameFormatStandard;
  50.     FlexCAN_RxMB_ConfigStruct.type   = Enum_Flexcan_FrameTypeData;
  51.     FLEXCAN_RxMbConfig(FLEXCAN, BOARD_FLEXCAN_RX_MB_CH, &FlexCAN_RxMB_ConfigStruct, ENABLE);

  52.     /* Set Rx Individual Mask. */
  53.     FLEXCAN_SetRxIndividualMask(FLEXCAN, BOARD_FLEXCAN_RX_MB_CH, FLEXCAN_RX_MB_STD_MASK(0x000, 0, 0));

  54.     FlexCAN_MB0_FrameStruct.length = (uint8_t)(8);
  55.     FlexCAN_MB0_FrameStruct.type   = (uint8_t)Enum_Flexcan_FrameTypeData;
  56.     FlexCAN_MB0_FrameStruct.format = (uint8_t)Enum_Flexcan_FrameFormatStandard;
  57.     FlexCAN_MB0_FrameStruct.id     = FLEXCAN_ID_STD(0x222);

  58.     FlexCAN_MB0_TransferStruct.mbIdx = BOARD_FLEXCAN_RX_MB_CH;
  59.     FlexCAN_MB0_TransferStruct.frame = &FlexCAN_MB0_FrameStruct;
  60.     FLEXCAN_TransferReceiveNonBlocking(FLEXCAN, &FlexCAN_Handle, &FlexCAN_MB0_TransferStruct);
  61. }

  62. /******************************************************************************/
  63. CO_ReturnError_t CO_CANmodule_init(
  64.         CO_CANmodule_t         *CANmodule,
  65.         void                   *CANdriverState,
  66.         CO_CANrx_t              rxArray[],
  67.         uint16_t                rxSize,
  68.         CO_CANtx_t              txArray[],
  69.         uint16_t                txSize,
  70.         uint16_t                CANbitRate)
  71. {
  72.     uint16_t i;

  73.     /* verify arguments */
  74.     if(CANmodule==NULL || rxArray==NULL || txArray==NULL){
  75.         return CO_ERROR_ILLEGAL_ARGUMENT;
  76.     }

  77.     /* Configure object variables */
  78.     CANmodule->CANdriverState = CANdriverState;
  79.     CANmodule->rxArray = rxArray;
  80.     CANmodule->rxSize = rxSize;
  81.     CANmodule->txArray = txArray;
  82.     CANmodule->txSize = txSize;
  83.     CANmodule->CANnormal = false;
  84.     CANmodule->useCANrxFilters = false;/* microcontroller dependent */
  85.     CANmodule->bufferInhibitFlag = false;
  86.     CANmodule->firstCANtxMessage = true;
  87.     CANmodule->CANtxCount = 0U;
  88.     CANmodule->errOld = 0U;
  89.     CANmodule->em = NULL;

  90.     for(i=0U; i<rxSize; i++){
  91.         rxArray[i].ident = 0U;
  92.         rxArray[i].mask = 0xFFFFU;
  93.         rxArray[i].object = NULL;
  94.         rxArray[i].pFunct = NULL;
  95.     }
  96.     for(i=0U; i<txSize; i++){
  97.         txArray[i].bufferFull = false;
  98.     }

  99.     FlexCAN_Configure(CANbitRate);

  100.     return CO_ERROR_NO;
  101. }

在 CO_driver.c 文件中实现FlexCAN的报文收发
对 flexcan_tx() 函数及 CO_CANinterrupt()函数的实现。
  1. /* Send a message frame. */
  2. bool flexcan_tx(CO_CANtx_t *buffer)
  3. {
  4.     bool status = false;

  5.     flexcan_frame_t       FlexCAN_FrameStruct;
  6.     flexcan_mb_transfer_t FlexCAN_MB_TransferStruct;

  7.     if (!buffer->rtr)
  8.     {
  9.         FlexCAN_FrameStruct.type = (uint8_t)Enum_Flexcan_FrameTypeData; /* Data frame type. */
  10.     }
  11.     else
  12.     {
  13.         FlexCAN_FrameStruct.type = (uint8_t)Enum_Flexcan_FrameTypeRemote; /* Remote frame type. */
  14.     }

  15.     FlexCAN_FrameStruct.length = (uint8_t)buffer->DLC;
  16.     FlexCAN_FrameStruct.format = (uint8_t)Enum_Flexcan_FrameFormatStandard;
  17.     FlexCAN_FrameStruct.id     = FLEXCAN_ID_STD(buffer->ident); /* Indicated ID number. */

  18.     FlexCAN_FrameStruct.dataByte0 = buffer->data[0];
  19.     FlexCAN_FrameStruct.dataByte1 = buffer->data[1];
  20.     FlexCAN_FrameStruct.dataByte2 = buffer->data[2];
  21.     FlexCAN_FrameStruct.dataByte3 = buffer->data[3];
  22.     FlexCAN_FrameStruct.dataByte4 = buffer->data[4];
  23.     FlexCAN_FrameStruct.dataByte5 = buffer->data[5];
  24.     FlexCAN_FrameStruct.dataByte6 = buffer->data[6];
  25.     FlexCAN_FrameStruct.dataByte7 = buffer->data[7];

  26.     FlexCAN_MB_TransferStruct.mbIdx = 2;
  27.     FlexCAN_MB_TransferStruct.frame = &FlexCAN_FrameStruct;

  28.     if (Status_Flexcan_Success == FLEXCAN_TransferSendNonBlocking(FLEXCAN, &FlexCAN_Handle, &FlexCAN_MB_TransferStruct))
  29.     {
  30.         status = true;
  31.     }

  32.     return status;
  33. }


  34. /******************************************************************************/
  35. CO_ReturnError_t CO_CANsend(CO_CANmodule_t *CANmodule, CO_CANtx_t *buffer){
  36.     CO_ReturnError_t err = CO_ERROR_NO;

  37.     /* Verify overflow */
  38.     if(buffer->bufferFull){
  39.         if(!CANmodule->firstCANtxMessage){
  40.             /* don't set error, if bootup message is still on buffers */
  41.             CO_errorReport((CO_EM_t*)CANmodule->em, CO_EM_CAN_TX_OVERFLOW, CO_EMC_CAN_OVERRUN, buffer->ident);
  42.         }
  43.         err = CO_ERROR_TX_OVERFLOW;
  44.     }

  45.     CO_LOCK_CAN_SEND();
  46.     bool tx_mb_status = flexcan_tx(buffer);

  47.     if(tx_mb_status == true){
  48.         CANmodule->bufferInhibitFlag = buffer->syncFlag;
  49.     }
  50.     /* if no buffer is free, message will be sent by interrupt */
  51.     else{
  52.         buffer->bufferFull = true;
  53.         CANmodule->CANtxCount++;
  54.     }
  55.     CO_UNLOCK_CAN_SEND();

  56.     return err;
  57. }
  1. void CO_CANinterrupt(CO_CANmodule_t *CANmodule){

  2.     uint32_t status = FLEXCAN->IFLAG1;

  3.     if (0 != (status & (BOARD_FLEXCAN_RX_MB_STATUS)) || (FlexCAN_MB0_RxCompleteFlag))
  4.     {
  5.         /* receive interrupt */
  6.         CO_CANrxMsg_t *rcvMsg;      /* pointer to received message in CAN module */
  7.         CO_CANrxMsg_t rcvMsgBuff;

  8.         uint16_t index;             /* index of received message */
  9.         uint32_t rcvMsgIdent;       /* identifier of the received message */
  10.         CO_CANrx_t *buffer = NULL;  /* receive message buffer from CO_CANmodule_t object. */
  11.         bool_t msgMatched = false;

  12.         /* get message from module here */
  13.         rcvMsg = &rcvMsgBuff;

  14.         rcvMsg->ident   = (FlexCAN_MBTemp_FrameStruct.id>> CAN_ID_STD_SHIFT)&0x7FF;
  15.         rcvMsg->DLC     = FlexCAN_MBTemp_FrameStruct.length;
  16.         rcvMsg->data[0] = FlexCAN_MBTemp_FrameStruct.dataByte0;
  17.         rcvMsg->data[1] = FlexCAN_MBTemp_FrameStruct.dataByte1;
  18.         rcvMsg->data[2] = FlexCAN_MBTemp_FrameStruct.dataByte2;
  19.         rcvMsg->data[3] = FlexCAN_MBTemp_FrameStruct.dataByte3;
  20.         rcvMsg->data[4] = FlexCAN_MBTemp_FrameStruct.dataByte4;
  21.         rcvMsg->data[5] = FlexCAN_MBTemp_FrameStruct.dataByte5;
  22.         rcvMsg->data[6] = FlexCAN_MBTemp_FrameStruct.dataByte6;
  23.         rcvMsg->data[7] = FlexCAN_MBTemp_FrameStruct.dataByte7;

  24.         rcvMsgIdent = rcvMsg->ident;

  25.         FlexCAN_MB0_RxCompleteFlag = 0;

  26.         /* CAN module filters are not used, message with any standard 11-bit identifier */
  27.         /* has been received. Search rxArray form CANmodule for the same CAN-ID. */
  28.         buffer = &CANmodule->rxArray[0];
  29.         for(index = CANmodule->rxSize; index > 0U; index--){
  30.             if(((rcvMsgIdent ^ buffer->ident) & buffer->mask) == 0U){
  31.                 msgMatched = true;
  32.                 break;
  33.             }
  34.             buffer++;
  35.         }

  36.         /* Call specific function, which will process the message */
  37.         if(msgMatched && (buffer != NULL) && (buffer->pFunct != NULL)){
  38.             buffer->pFunct(buffer->object, rcvMsg);
  39.         }

  40.         /* Clear interrupt flag */
  41.         FLEXCAN_ClearMbStatusFlags(FLEXCAN, BOARD_FLEXCAN_RX_MB_STATUS);
  42.     }
  43.     else if (0 != (status & BOARD_FLEXCAN_TX_MB_STATUS))
  44.     {
  45.         /* Clear interrupt flag */
  46.         FLEXCAN_ClearMbStatusFlags(FLEXCAN, BOARD_FLEXCAN_TX_MB_STATUS);

  47.         /* First CAN message (bootup) was sent successfully */
  48.         CANmodule->firstCANtxMessage = false;
  49.         /* clear flag from previous message */
  50.         CANmodule->bufferInhibitFlag = false;
  51.         /* Are there any new messages waiting to be send */
  52.         if(CANmodule->CANtxCount > 0U){
  53.             uint16_t i;             /* index of transmitting message */

  54.             /* first buffer */
  55.             CO_CANtx_t *buffer = &CANmodule->txArray[0];
  56.             /* search through whole array of pointers to transmit message buffers. */
  57.             for(i = CANmodule->txSize; i > 0U; i--){
  58.                 /* if message buffer is full, send it. */
  59.                 if(buffer->bufferFull){
  60.                     buffer->bufferFull = false;
  61.                     CANmodule->CANtxCount--;

  62.                     /* Copy message to CAN buffer */
  63.                     CANmodule->bufferInhibitFlag = buffer->syncFlag;
  64.                     CO_CANsend(CANmodule, buffer);
  65.                     break;                      /* exit for loop */
  66.                 }
  67.                 buffer++;
  68.             }/* end of for loop */

  69.             /* Clear counter if no more messages */
  70.             if(i == 0U){
  71.                 CANmodule->CANtxCount = 0U;
  72.             }
  73.         }
  74.     }
  75.     else{
  76.         /* some other interrupt reason */
  77.     }
  78. }

在 CO_driver.c 文件中实现CAN总线错误检测
关于 CO_CANverifyErrors() 函数的实现。
  1. void CO_CANverifyErrors(CO_CANmodule_t *CANmodule){
  2.     uint16_t rxErrors, txErrors, overflow;
  3.     CO_EM_t* em = (CO_EM_t*)CANmodule->em;
  4.     uint32_t err;

  5.     /* get error counters from module. Id possible, function may use different way to
  6.      * determine errors. */
  7.     rxErrors = (uint16_t) ((FLEXCAN->ECR & CAN_ECR_RXERRCNT_MASK) >> CAN_ECR_RXERRCNT_SHIFT);
  8.     txErrors = (uint16_t) ((FLEXCAN->ECR & CAN_ECR_TXERRCNT_MASK) >> CAN_ECR_TXERRCNT_SHIFT);
  9.     overflow = (uint16_t) ((FLEXCAN->ESR1 & CAN_ESR1_ERROVR_MASK) >> CAN_ESR1_ERROVR_SHIFT);

  10.     err = ((uint32_t)txErrors << 16) | ((uint32_t)rxErrors << 8) | overflow;

  11.     if(CANmodule->errOld != err){
  12.         CANmodule->errOld = err;

  13.         if(txErrors >= 256U){                               /* bus off */
  14.             CO_errorReport(em, CO_EM_CAN_TX_BUS_OFF, CO_EMC_BUS_OFF_RECOVERED, err);
  15.         }
  16.         else{                                               /* not bus off */
  17.             CO_errorReset(em, CO_EM_CAN_TX_BUS_OFF, err);

  18.             if((rxErrors >= 96U) || (txErrors >= 96U)){     /* bus warning */
  19.                 CO_errorReport(em, CO_EM_CAN_BUS_WARNING, CO_EMC_NO_ERROR, err);
  20.             }

  21.             if(rxErrors >= 128U){                           /* RX bus passive */
  22.                 CO_errorReport(em, CO_EM_CAN_RX_BUS_PASSIVE, CO_EMC_CAN_PASSIVE, err);
  23.             }
  24.             else{
  25.                 CO_errorReset(em, CO_EM_CAN_RX_BUS_PASSIVE, err);
  26.             }

  27.             if(txErrors >= 128U){                           /* TX bus passive */
  28.                 if(!CANmodule->firstCANtxMessage){
  29.                     CO_errorReport(em, CO_EM_CAN_TX_BUS_PASSIVE, CO_EMC_CAN_PASSIVE, err);
  30.                 }
  31.             }
  32.             else{
  33.                 bool_t isError = CO_isError(em, CO_EM_CAN_TX_BUS_PASSIVE);
  34.                 if(isError){
  35.                     CO_errorReset(em, CO_EM_CAN_TX_BUS_PASSIVE, err);
  36.                     CO_errorReset(em, CO_EM_CAN_TX_OVERFLOW, err);
  37.                 }
  38.             }

  39.             if((rxErrors < 96U) && (txErrors < 96U)){       /* no error */
  40.                 CO_errorReset(em, CO_EM_CAN_BUS_WARNING, err);
  41.             }
  42.         }

  43.         if(overflow != 0U){                                 /* CAN RX bus overflow */
  44.             CO_errorReport(em, CO_EM_CAN_RXB_OVERFLOW, CO_EMC_CAN_OVERRUN, err);
  45.         }
  46.     }
  47. }
至此,驱动代码适配完成。

板载验证

验证环境

使用搭载了MM32G5330 MCU的开发板Mini-G5330 ,以CANopen_Basic样例工程为例,将开发板上的CAN收发器与PCAN相连接,并将PCAN与PC机通过USB相连接,在PC端(基于Win10操作系统)使用PCAN-View上位机模拟CANopen主站,来通过CANopen协议与CANopen从站(即 MM32 MCU)进行通信,如图5所示。

image-mcu&amp;pc.png

图5

注:这里我们使用了PCAN-USB,并使用了配套上位机PCAN-View。

验证过程

上述环境搭建好后,将上述工程代码编译后刷写固件进MCU,将MCU上电并复位通过PC端上位机PCAN-View测试如下指令,观察CANopen节点其对指令的响应,来判断该CANopen节点是否处于正常运行状态。

节点上线:
MCU上电后,CANopen节点应成功启动并向网络发送上线报文。
CANopen节点上线向CAN网络发送CANopen节点上线报文,PC上位机将收到一条如下报文:

表2.png

表2


之后该CANopen节点以 1000ms 的时间间隔向CAN网络发送节点心跳报文,上位机以1000ms的时间间隔收到如下报文:

表3.png

表3

如图6所示。

PCAN-View-1712731244197-14.png

图6

至此,可验证该CANopen节点设备成功启动并开始正常运行。

模式切换:
通过上位机发送NMT命令,验证节点能够正确响应Start、Stop和Pre-operation等模式切换指令。
将NODE-ID为0x0A的节点设置为 Stop 模式,上位机PCAN-View发送如下指令:

表4.png

表4

如下图7所示,可接收到如下报文:

Stop-the-Node.png

图7

将NODE-ID为0x0A的节点设置为 Start 模式,上位机PCAN-View发送如下指令:

表5.png

表5

如下图8所示,可接收到如下报文:

Start-the-Node.png

图8

将NODE-ID为0x0A的节点设置为Pre-operation模式,上位机PCAN-View发送如下指令:

表6.png

表6

如下图9所示,该节点进入Pre-operation模式,可接收到如下报文:

Preoperaion-the-Node.png

图9

将NODE-ID为0x0A节点复位,上位机PCAN-View发送如下指令:

表7.png

表7

如下图10所示,该节点被复位:

Reset--the-Node.png

图10

将NODE-ID为0x0A节点的通信层复位,上位机PCAN-View发送如下指令:

表8.png

表8

如下图11所示,该节点通信层被复位,重新上线:

Reset-commu-the-Node.png

图11

心跳检测:
节点应周期性发送心跳报文,以表明其处于活跃状态。
获取NODE-ID为0x0A节点的心跳发送间隔时间,上位机PCAN-View发送如下指令:

表9.png

表9

如下图12所示,返回该节点当前心跳发送间隔时间为1000(0x03E8)ms:

Get-heartbeat.png

图12

设置NODE-ID为0x0A节点的心跳发送间隔时间为500(0x01F4)ms,上位机PCAN-View发送如下指令:

表10.png

表10

如下图13所示,该节点当前心跳发送间隔时间变为500ms:

Set-heartbeat.png

图13

总结

通过本文的介绍,我们了解了CANopen协议的基本概念,并基于MM32G5330的FlexCAN完成了CANopenNode协议栈的移植工作。通过板载验证,我们确认了移植后的协议栈能够正常工作,为后续的设备集成和通信提供了进一步开发的基础。同样的开发者可以根据实际应用需求使用灵动其他带有FlexCAN的MCU,参考本文的方法进行相应的移植和验证工作,以实现高效可靠的CANopen通信。



image-20240410112746118.png
tpgf 发表于 2024-5-6 15:26 | 显示全部楼层
这是要从哪个成熟的应用上进行移植呢
paotangsan 发表于 2024-5-6 16:13 | 显示全部楼层
动态邮箱配置,储存 1-8,16,32 或 64 的数据长度
wakayi 发表于 2024-5-6 21:24 | 显示全部楼层
flexcan每一个邮箱都配有单独 Rx 标志寄存器
renzheshengui 发表于 2024-5-6 21:55 | 显示全部楼层
FlexCAN 模块带有动态数据率(CAN FD)协议和 CAN 协议0 B 版本:标准数据帧、拓展数据帧、0 至 64 字节数据长度、可编程波特率和内容相关地址

keaibukelian 发表于 2024-5-6 22:26 | 显示全部楼层
    FlexCAN 模块是 CAN 协议的一个高完成度版本,带有动态数据率(Flexible Data rate,CAN FD)协议和 CAN 2.0 B 版本协议,支持标准和拓展数据帧和长达 64 字节的数据传输,频率最大可达到 8Mbps
heimaojingzhang 发表于 2024-5-6 22:57 | 显示全部楼层
FLEXCAN模块具有四种不同的功能模式:正常模式(用户和管理员)、冻结模式、监听模式以及闭环模式;同样具有三种不同的低功耗模式:禁止模式、睡眠模式以及停止模式。
钓鱼大师 发表于 2025-6-3 08:52 | 显示全部楼层
“main.c是 CANopenNode的主程序文件,我们将原有的main.c文件进行替换。”,这里是要用哪个main.c来替换这里的main.c文件?
chenjun89 发表于 2025-6-4 08:02 来自手机 | 显示全部楼层
CANOPEN不是免费的通信协议库吧
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:上海灵动微电子股份有限公司
简介:上海灵动微电子股份有限公司成立于 2011 年,是中国本土通用 32 位 MCU 产品及解决方案供应商。 灵动股份的 MCU 产品以 MM32 为标识,基于 Arm Cortex-M 系列内核,自主研发软硬件和生态系统。目前已量产近 300 多款型号,累计交付超 4 亿颗,在本土通用 32 位 MCU 公司中位居前列。

93

主题

111

帖子

10

粉丝
快速回复 在线客服 返回列表 返回顶部