灵动微电子 · 2023年08月04日

CAN总线开发一本全(7) - 基于FlexCAN适配CANopenNode

  • 准备微控制器基本工程
  • 在微控制器上适配CANopenNode
  • 配置电路板的时钟和引脚 board_init.c
  • 准备硬件定时器 main.c
  • 对接CAN驱动 CO_driver.c & main.c
本文是《CAN总线开发一本全(6) - CANopenNode组件》的补充其中一个小节。

总结在微控制器平台上移植CANopenNode,需要根据具体硬件条件,适配2个源文件:

  • CANopenNode-1.3/stack/drvTemplate/CO_driver.c 文件。
  • 补充CO_CANmodule_init() 函数:初始化CAN外设硬件,配置CAN协议引擎、收发报文消息的参数,以及启用中断。
  • 补充CO_CANsend() 函数:复制CANopenNode组件中缓冲区的消息帧到硬件引擎,交由CAN硬件外设发送到总线上。
  • 补充CO_CANinterrupt() 函数:由硬件的CAN中断服务调用,实现硬件外设层面上的发送和接收CAN通信帧。
  • 补充CO_CANverifyErrors() 函数:上报硬件CAN外设模块的检测到的错误状态。
  • CANopenNode-1.3/example/main.c 文件。
  • 创建并配置硬件定时器周期中断服务,以1ms为周期,调用CANopenNode的定时器周期执行线程的函数。

接下来,将以集成了FlexCAN外设模块的MM32F0140微控制器为例,实现对CANopenNode v1.3的适配过程。

目前灵动官方的软件开发平台MindSDK已经适配了CANopenNode协议栈,并创建了一系列样例工程:

  • co_basic
  • co_pdo_master
  • co_pdo_slave
  • co_sdo_master
  • co_sdo_slave
  • co_with_eeprom
  • co_with_flash

为了描述适配CANopenNode的过程,这里仍然从零开始,展现完整的移植开发过程。

准备微控制器基本工程

首先从灵动MindSDK的网站上https://mindsdk.mindmotion.com.cn/获取到POKT-F0140(使用MM32F0140主控)开发板的flexcan驱动样例工程,flexcan_loopback,作为模板工程。这个模板工程里包含了MM32F0140微控制器正常工作的所有必要源码,包括芯片头文件、启动程序、中断向量表、以及一系列初始化硬件电路板到进入main()函数的源码,以及flexcan外设模块的驱动程序。

  • 将模板工程的工程名改为fthr-f1040_canopen_demo_mdk
  • 将CANopenNode组件的源码包CANopenNode-v1.3复制到canopen_demo工程的根目录下
  • 将其中stack/drvTemplate目录下的CO_driver.cCO_driver_target.h文件复制到canopen_demo工程的board目录下
  • 将其中example目录下的的CO_OD.cCO_OD.hmain.c文件复制到canopen_demo工程的application目录下

在Keil MDK环境中打开canopen_demo工程。添加源文件和包含路径到工程中,如图x所示。

  • 添加CANopenNode-v1.3目录下,CANopenNode-v1.3/stack目录下所有的C源文件到工程
  • 添加CANopenNode-v1.3目录和CANopenNode-v1.3/stack目录到工程包含路径
  • 添加application目录下新增文件CO_OD.cCO_OD.hmain.c,和board目录下新增文件CO_driver.cCO_driver_target.h,到canopen_demo工程中。

image.png
图x canopen_demo工程中包含CANopenNode源码

整理好文件之后,试着编译一下工程,没有警告和错误,可以正常使用。如图x所示。

image.png
图x canopen_demo工程编译正确

此时的canopen_demo工程中,包含了CANopenNode的所有源码、FlexCAN外设模块的驱动,以及使用MM32F0140微控制器的所有必要的源文件,并且可以通过编译器验证编写程序代码的正确性。后续进行适配工作过程中,将通过开发者自行编码,将CANopenNode和FlexCAN外设模块绑定起来,并可实时编译工程验证编码内容。

在微控制器上适配CANopenNode

CANopeoNode组件中自带main.c文件,约定了整个CANopen协议栈的运行框架。在CANopenNode中的main.c文件中,定义了应用程序入口main()函数,以及定时器中断服务程序入口和CAN外设模块中断服务程序入口。在本例的移植工程中,定时器相关的程序被置于main.c文件中,而具体微控制器平台上的CAN外设模块相关的配置程序代码则位于CO_driver.c文件中。

配置电路板的时钟和引脚 board_init.c

  1. 配置时钟

这里需要至少启用硬件定时器TIM2模块(产生1ms周期中断)和FlexCAN模块,另外,POKT-F0140开发板使用PB8和PB9作为CAN接口引脚,也需要启用对应IO端口的时钟。

在clock_init.c文件中更新BOARD_InitBootClocks()源码:

void BOARD_InitBootClocks(void)  
{  
    CLOCK_ResetToDefault();  
    CLOCK_BootToHSE72MHz();  
  
    /* TIM2.*/  
    RCC_EnableAPB1Periphs(RCC_APB1_PERIPH_TIM2, true);  
    RCC_ResetAPB1Periphs(RCC_APB1_PERIPH_TIM2);  
    /* FLEXCAN. */  
    RCC_EnableAPB1Periphs(RCC_APB1_PERIPH_FLEXCAN, true);  
    RCC_ResetAPB1Periphs(RCC_APB1_PERIPH_FLEXCAN);  
    ...  
    /* GPIOB. */  
    RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOB, true); /* PB8 - CAN1_RX, PB9 - CAN1_TX. */  
    RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOB);  
}  
  1. 配置引脚

FTHR-F0140开发板使用PB8和PB9作为CAN接口引脚,需要配置引脚的复用功能为CAN服务。

在pin_init.c文件中更新BOARD_InitPins()源码:

void BOARD_InitPins(void)  
{  
    ...  
    /* fthr-f0140. */  
    /* PA9 - FLEXCAN_RX. */  
    gpio_init.Pins  = GPIO_PIN_9;  
    gpio_init.PinMode  = GPIO_PinMode_In_Floating;  
    gpio_init.Speed = GPIO_Speed_50MHz;  
    GPIO_Init(GPIOA, &gpio_init);  
    GPIO_PinAFConf(GPIOA, gpio_init.Pins, GPIO_AF_8);  
  
    /* PA10 - FLEXCAN_TX. */  
    gpio_init.Pins  = GPIO_PIN_10;  
    gpio_init.PinMode  = GPIO_PinMode_AF_PushPull;  
    gpio_init.Speed = GPIO_Speed_50MHz;                                                                                                       GPIO_Init(GPIOA, &gpio_init);  
    GPIO_PinAFConf(GPIOA, gpio_init.Pins, GPIO_AF_8);  
}  

配置板子的BOARD_InitBootClocks()函数和BOARD_InitPins()函数,将在board_init.c文件中被BOARD_Init()函数调用,

void BOARD_Init(void)
{
    BOARD_InitBootClocks();
    BOARD_InitPins();

    BOARD_InitDebugConsole();
}

BOARD_Init()函数最终将在main.c文件中被调用,实现对电路板的初始化工作。

/* main ***********************************************************************/  
int main (void){  
    CO_NMT_reset_cmd_t reset = CO_RESET_NOT;  
  
    /* Configure microcontroller. */  
    BOARD_Init();  
    ...  
}  

准备硬件定时器 main.c

CANopenNode的三个线程之一,定时器周期执行线程,以1ms为间隔周期执行。例如,可以配置硬件定时器TIM2产生周期为1ms的中断服务,并在定时器的中断服务程序中嵌入CANopenNode提供main.c文件中的tmrTask_thread()函数。

main.c文件中编写brd_tim_init()函数,配置TIM2硬件定时器,并在main()函数中调用:

#include "board_init.h"  
  
void BOARD_TIM_Init(void);  
  
/* main ***********************************************************************/  
int main (void)  
{  
        ...  
        /* Configure Timer interrupt function for execution every 1 millisecond */  
        BOARD_TIM_Init();  
        ...  
}  
  
/* Setup the hardware timer. */  
void BOARD_TIM_Init(void)  
{  
    /* Set the counter counting step. */  
    TIM_Init_Type tim_init;  
    tim_init.ClockFreqHz = BOARD_TIM_FREQ;  
    tim_init.StepFreqHz = BOARD_TIM_UPDATE_STEP; /* 1ms. */  
    tim_init.Period = BOARD_TIM_UPDATE_PERIOD - 1u;  
    tim_init.EnablePreloadPeriod = false;  
    tim_init.PeriodMode = TIM_PeriodMode_Continuous;  
    tim_init.CountMode = TIM_CountMode_Increasing;  
    TIM_Init(BOARD_TIM_PORT, &tim_init);  
  
    /* Enable interrupt. */  
    TIM_EnableInterrupts(BOARD_TIM_PORT, TIM_INT_UPDATE_PERIOD, true);  
    NVIC_EnableIRQ(BOARD_TIM_IRQn);  
      
    /* Start the timer. */  
    TIM_Start(BOARD_TIM_PORT);  
}  

其中,关于硬件定时器的配置参数的定义统一放置于board_init.h文件。

/* TIM1. */  
#define BOARD_TIM_PORT               (TIM_Type *)TIM2  
#define BOARD_TIM_IRQn               TIM2_IRQn  
#define BOARD_TIM_IRQHandler         TIM2_IRQHandler  
#define BOARD_TIM_FREQ               CLOCK_SYS_FREQ  
#define BOARD_TIM_UPDATE_STEP        1000000u  
#define BOARD_TIM_UPDATE_PERIOD      1000u  

main()函数中调用了BOARD_TIM_Init()函数,配置硬件定时器TIM2产生1ms为周期的中断,并启动定时器。此时,对应在硬件定时器的中断服务程序中调用CANopenNode的定时器线程函数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_STATUS_UPDATE_PERIOD == (TIM_GetInterruptStatus(BOARD_TIM_PORT) & TIM_STATUS_UPDATE_PERIOD) )  
        {  
            CO_errorReport(CO->em, CO_EM_ISR_TIMER_OVERFLOW, CO_EMC_SOFTWARE_INTERNAL, 0u);  
            TIM_ClearInterruptStatus(BOARD_TIM_PORT, TIM_STATUS_UPDATE_PERIOD);  
        }  
    }  
}  
  
/* Timer interrupt function ***************************************************/  
void BOARD_TIM_IRQHandler(void)  
{  
    TIM_ClearInterruptStatus(BOARD_TIM_PORT, TIM_STATUS_UPDATE_PERIOD);  
  
    tmrTask_thread();  
}  

这里要注意,CANopenNode原生的tmrTask_thread()函数的实现模板中,停用了“/* verify timer overflow */”之后的代码。这些被停用的代码,原本可以用来验证tmrTask_thread()函数内部操作,例如处理同步过程、读接收PDO和写发送PDO,在清了上一次1ms中断的硬件标志位后的1ms中是否能够执行完毕。如果tmrTask_thread()函数的处理时间过长,超出了一个周期任务的执行时间,此时检测到1ms定时器中断标志位再次置位,即出现超时。在1ms周期任务超时之后,CANopen协议栈会认为这是一个可能产生风险的任务,因此可调用CO_errorReport()函数将错误情况上报给CANopen协议栈。

对接CAN驱动 CO_driver.c & main.c

CO_driver.c文件中定义了大量的具体微控制器平台的CAN外设硬件模块相关的驱动函数,但在最基础的移植过程中,仅需重点关注4个函数即可:

  • CO_CANmode_init() - 初始化CAN外设模块,并配置好比特率、消息帧过滤器,以及收发中断等。
  • CO_CANsend() - 将消息缓冲区中的数据搬运至CAN外设模块的硬件发送缓冲区中,即将发送CAN消息帧到CAN总线上。
  • CO_CANinterrupt() - 绑定到CAN外设模块的硬件中断的服务程序,主要实现CAN硬件的接收过程,即将CAN外设模块从CAN总线上捕获下来的CAN消息帧数据转存到CANopenNode组件的接收缓冲区中,供协议栈进一步处理。当使用中断方式发送CAN消息帧时,也需要在CO_CANinterrupt()函数中调用CO_CANsend()函数发送CAN消息帧。
  • CO_CANverifiyErrors() - 查看CAN外设模块的硬件错误。因为CAN总线上的消息帧需要经过仲裁才能上线,所以这里查错函数主要检查的是发送消息帧超时的情况。

原生CANopenNode组件包中的CO_driver.c文件中的函数已经实现了绝大部分同协议栈交互的业务逻辑,在具体微控制器平台上是适配时,仅需要将少量同硬件相关的步骤绑定到微控制器硬件的操作上即可。

CO_CANmodule_init()

CO_driver.c文件中向CO_CANmodule_init()函数嵌入初始化FlexCAN外设模块的代码,包括初始化FlexCAN的通信引擎,配置好过滤器等(本移植工程未启硬件过滤器功能,由CANopenNode的软件过滤器处理)。

#include "board_init.h"  
  
/******************************************************************************/  
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<rxSize; i++){  
        rxArray[i].ident = 0U;  
        rxArray[i].mask = 0xFFFFU;  
        rxArray[i].object = NULL;  
        rxArray[i].pFunct = NULL;  
    }  
    for(i=0U; i<txSize; i++){  
        txArray[i].bufferFull = false;  
    }  
  
    /* Configure CAN timing */  
    FLEXCAN_TimConf_Type flexcan_tim_conf;  
    flexcan_tim_conf.EnableExtendedTime = false;  
    flexcan_tim_conf.PhaSegLen1 = 5u;  
    flexcan_tim_conf.PhaSegLen2 = 1u;  
    flexcan_tim_conf.PropSegLen = 2u;  
    flexcan_tim_conf.JumpWidth = 1u;  
  
    /* Configure CAN module registers */  
    FLEXCAN_Init_Type flexcan_init;  
    flexcan_init.MaxXferNum = BOARD_FLEXCAN_XFER_MaxNum; /* The max mb number to be used. */  
    flexcan_init.ClockSource = FLEXCAN_ClockSource_Periph; /* Use peripheral clock. */  
    flexcan_init.BitRate = CANbitRate * 1000u; /* Set bitrate. */  
    flexcan_init.ClockFreqHz = BOARD_FLEXCAN_CLOCK_FREQ; /* Set clock frequency. */  
    flexcan_init.SelfWakeUp = FLEXCAN_SelfWakeUp_BypassFilter; /* Use unfiltered signal to wake up flexcan. */  
    flexcan_init.WorkMode = FLEXCAN_WorkMode_Normal; /* Normal workmode, can receive and transport. */  
    flexcan_init.Mask = FLEXCAN_Mask_Global; /* Use global mask for filtering. */  
    flexcan_init.EnableSelfReception = false; /* Not receiving mb frame sent by self. */  
    flexcan_init.EnableTimerSync = true; /* Every tx or rx done, refresh the timer to start from zero. */  
    flexcan_init.TimConf = &flexcan_tim_conf; /* Set timing sychronization. */  
    FLEXCAN_Init(BOARD_FLEXCAN_PORT, &flexcan_init);  
  
    /* Set tx mb. */  
    FLEXCAN_ResetMb(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_TX_MB_CH);  
    FLEXCAN_SetMbCode(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_TX_MB_CH, FLEXCAN_MbCode_TxInactive);  
  
    /* Set rx mb. */  
    FLEXCAN_RxMbConf_Type flexcan_mb_conf;  
    flexcan_mb_conf.Id = 0x000u; /* Id for filtering with mask and receiving. */  
    flexcan_mb_conf.MbType = FLEXCAN_MbType_Data; /* Only receive standard data frame. */  
    flexcan_mb_conf.MbFormat = FLEXCAN_MbFormat_Standard;  
    FLEXCAN_SetRxMb(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_RX_MB_CH, &flexcan_mb_conf);  
    FLEXCAN_SetMbCode(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_RX_MB_CH, FLEXCAN_MbCode_RxEmpty);/* Set for receiving. */  
  
    /* Configure CAN module hardware filters */  
  
    /* CAN module filters are not used, all messages with standard 11-bit */  
    /* identifier will be received */  
    /* Configure mask 0 so, that all messages with standard identifier are accepted */  
    FLEXCAN_RxMbMaskConf_Type mb_mask_conf;  
    mb_mask_conf.MbType = FLEXCAN_MbType_Data;  
    mb_mask_conf.MbFormat = FLEXCAN_MbFormat_Standard;  
    mb_mask_conf.IdMask = 0x000u;  
    FLEXCAN_EnableFreezeMode(BOARD_FLEXCAN_PORT, true);  
    FLEXCAN_SetGlobalMbMaskConf(BOARD_FLEXCAN_PORT, &mb_mask_conf);  
    FLEXCAN_EnableFreezeMode(BOARD_FLEXCAN_PORT, false);  
  
  
    /* configure CAN interrupt registers */  
    FLEXCAN_EnableMbInterrupts(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_RX_MB_INT | BOARD_FLEXCAN_TX_MB_INT, true);  
    NVIC_EnableIRQ(BOARD_FLEXCAN_IRQn);  
  
    return CO_ERROR_NO;  
}  

此处有两个要点:

  • 开启了FlexCAN的CAN ID过滤器的掩码为0x000(掩码为1的位会对接收帧ID进行约束,要求强制匹配),这意味着可以捕获CAN总线上的所有帧,此时仅能使用了一个MB作为CAN通信帧的数据缓冲(它的CAN ID是任何值已经不重要了)。后续将由CANopen协议栈的软件对不同的CAN ID进行匹配,进而再分别处理。
  • 模板源码中的CO_CANmodule_init()函数还包含了是否使用硬件过滤机制的配置字段useCANrxFilters,在初始化CANopen时,若指定的接收帧缓冲数量小于32时,可启用硬件过滤器(对应硬件多通道)机制。但具体的CAN外设模块在硬件层面上实现的缓冲区可能是不同的,不一定是32个缓冲单元,并且不同CAN外设对缓冲区的操作行为是不同的,例如,MM32F0140集成的FlexCAN在基本模式下是按照CAN ID进行分别存放对应的MB,但在启用FlexCAN的FIFO模式后,就可以以FIFO的方式,用一个队列管理不同CAN ID的消息帧。为了减少对硬件特性的依赖,这里的移植代码固定使用无硬件过滤的机制,对CAN ID的分析由CANopenNode软件完成,设定useCANrxFilters的值为false,对应也不再需要考虑CO_CANrxBufferInit()函数的实现。

CO_CANsend()

在CO_driver.c文件中编写BOARD_FlexCAN_TxFrame()函数,实现通过CAN外设模块发送消息帧,然后嵌入到CANopenNode的CO_CANsend()函数中,并在CO_CANinterrupt()函数中调用。其中,CO_CANinterrupt()是CANopenNode定义的CAN中断服务程序,还需要绑定到目标微控制器平台上的硬件CAN中断服务程序上。

/******************************************************************************/
/* Send a message frame to CAN bus. */
bool BOARD_FlexCAN_TxFrame(CO_CANtx_t *buffer)
{
    FLEXCAN_Mb_Type mb;

    if (!buffer->rtr)
    {
        mb.TYPE = FLEXCAN_MbType_Data; /* Data frame type. */
    }
    else
    {
        mb.TYPE = FLEXCAN_MbType_Remote; /* Remote frame type. */
    }
    mb.ID = buffer->ident; /* Indicated ID number. */
    mb.FORMAT = FLEXCAN_MbFormat_Standard; /* STD frame format. */
    mb.PRIORITY = BOARD_FLEXCAN_XFER_PRIORITY; /* The priority of the frame mb. */

    /* Set the information. */
    mb.BYTE0 = buffer->data[0];
    mb.BYTE1 = buffer->data[1];
    mb.BYTE2 = buffer->data[2];
    mb.BYTE3 = buffer->data[3];
    mb.BYTE4 = buffer->data[4];
    mb.BYTE5 = buffer->data[5];
    mb.BYTE6 = buffer->data[6];
    mb.BYTE7 = buffer->data[7];

    /* Set the workload size. */
    mb.LENGTH = buffer->DLC;

    /* Send. */
    bool status = FLEXCAN_WriteTxMb(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_TX_MB_CH, &mb);
    FLEXCAN_SetMbCode(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_TX_MB_CH, FLEXCAN_MbCode_TxDataOrRemote); /* Write code to send. */

    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();

    if ( BOARD_FlexCAN_TxFrame(buffer) ) /* copy the frame to can hardware. */
    {
        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;
}

在嵌入CO_CANsend()函数时,有个要点:

  • 这里在发送CAN消息帧实际实现了一个中断发送的机制。当在CO_CANsend()函数中调用brd_can_tx()函数时,程序将CANopenNode将要发送的消息帧数据搬运到CAN外设硬件的发送缓冲区中,并触发发送机制,等待在合适的时机将数据送上总线(需要等待获得仲裁才能将消息帧送上总线)。如果当前积压的发送消息数量为0,CANmodule->CANtxCount == 0,则可以向CAN外设的硬件发送缓冲区写数,否则,意味着当前CAN外设的硬件发送缓冲区中还有消息等待上线,此时只能记录一下计数器,CANmodule->CANtxCount++。后续的发送过程就需要在中断中的发送过程中完成了,在当前发送消息帧上线之后,发送完成,会触发CAN外设的中断服务程序,届时将检查CANmodule->CANtxCount计数器的值:如果已经是0,表示后续不需要再发帧了,那就清标志位,结束;如果不是0,那么在前一帧发送完成后,继续载入新的消息帧到硬件发送缓冲区,直到发送完消息队列中的最后一个消息,最后一次进中断,同上。
  • CANmodule->CANtxCount计数器相当于是CAN硬件发送缓冲区的信号量,可以作为缓冲区是否为空的标志:若值为0,则对应硬件发送缓冲区为空;若值不为0,则用buffer->bufferFull标记CAN硬件发送缓冲区正在使用,同时使用CANmodule->CANtxCount计数器的值表示正在排队的数量。

CO_CANinterrupt()

CO_driver.c文件中编写brd_can_rx()函数,从CAN外设模块的硬件接收缓冲区中读收到的消息帧,然后嵌入在CO_CANinterrupt()函数中处理接收过程。

/******************************************************************************/  
void CO_CANinterrupt(CO_CANmodule_t *CANmodule)  
{  
    uint32_t status = FLEXCAN_GetMbStatus(BOARD_FLEXCAN_PORT);  
  
    if (BOARD_FLEXCAN_RX_MB_STATUS == (status & BOARD_FLEXCAN_RX_MB_STATUS))  
    {  
        /* 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;  
        FLEXCAN_Mb_Type flexcan_rx_mb;  
        FLEXCAN_ReadRxMb(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_RX_MB_CH, &flexcan_rx_mb);  
        rcvMsg->ident = flexcan_rx_mb.ID;  
        rcvMsg->DLC = flexcan_rx_mb.LENGTH;  
        rcvMsg->data[0] = flexcan_rx_mb.BYTE0;  
        rcvMsg->data[1] = flexcan_rx_mb.BYTE1;  
        rcvMsg->data[2] = flexcan_rx_mb.BYTE2;  
        rcvMsg->data[3] = flexcan_rx_mb.BYTE3;  
        rcvMsg->data[4] = flexcan_rx_mb.BYTE4;  
        rcvMsg->data[5] = flexcan_rx_mb.BYTE5;  
        rcvMsg->data[6] = flexcan_rx_mb.BYTE6;  
        rcvMsg->data[7] = flexcan_rx_mb.BYTE7;  
  
        rcvMsgIdent = rcvMsg->ident;  
  
        /* 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_ClearMbStatus(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_RX_MB_STATUS);  
    }  
    else if (BOARD_FLEXCAN_TX_MB_STATUS == (status & BOARD_FLEXCAN_TX_MB_STATUS))  
    {  
        /* Clear interrupt flag */  
        FLEXCAN_ClearMbStatus(BOARD_FLEXCAN_PORT, 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_CANinterrupt()函数中,实现了接收CAN消息帧和发送CAN消息帧的过程:

  • 在接收过程中,CAN外设硬件收到总线上的消息帧后触发中断服务程序,程序从硬件接收缓冲区将消息帧读出来,填充到CANmodule结构体的接收帧队列成员中,之后由成员对应的处理函数消化掉接收到的消息帧。
  • 在发送过程中,程序需要逐个处理掉之前的已经压入软件缓冲区中待发送的消息帧。当软件发送缓冲区为空时,由CO_CANsend()函数触发的发送过程会先把当前的消息帧写入硬件发送缓冲区中并启动发送,之后由发送完成事件触发中断。每次进入发送完成中断服务程序时,程序会先检查软件发送缓冲区中的消息帧的数量是不是0:如果是,说明后面没有需要继续发送的消息帧了,直接清标志位,收工;如果不是,说明还需要接着发送已经缓存的消息帧,那就再次调用CO_CANsend()函数搬运帧数据到硬件发送缓冲区中并触发的发送过程,之后由发送完成事件触发中断,直至最后清空发送缓冲区再清标志位。
CO_CANinterrupt()`函数将在`main.c`文件中被硬件的CAN中断服务函数调用。

`/* CAN interrupt function *****************************************************/  
//void /* interrupt */ CO_CAN1InterruptHandler(void){  
void BRD_CAN_IRQHandler(void)  
{  
    CO_CANinterrupt(CO->CANmodule[0]);  
  
    /* clear interrupt flag */  
    /* the interrupt flags are cleared when processing each mb in flexcan. */      
}  

CO_CANverifyErrors()

CO_driver.c文件中CO_CANinterrupt()函数中,嵌入从CAN外设读错误计数值和状态标志位的代码,将硬件的错误状态反馈给CANopenNode协议栈。

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 = CANmodule->txSize;  
    //txErrors = CANmodule->txSize;  
    //overflow = CANmodule->txSize;  
    rxErrors = (uint16_t) ((BOARD_FLEXCAN_PORT->ECR & FLEXCAN_ECR_RXERRCNT_MASK) >> FLEXCAN_ECR_RXERRCNT_SHIFT);  
    txErrors = (uint16_t) ((BOARD_FLEXCAN_PORT->ECR & FLEXCAN_ECR_TXERRCNT_MASK) >> FLEXCAN_ECR_TXERRCNT_SHIFT);  
    overflow = (uint16_t) ((BOARD_FLEXCAN_PORT->ESR1 & FLEXCAN_ESR1_ERROVR_MASK) >> FLEXCAN_ESR1_ERROVR_SHIFT);  
    ...  
}  

这里的rxErrorstxErrors是CAN外设接收帧和发送帧的错误计数器,一般的CAN外设模块(例如FlexCAN),会对从CAN总线上捕获消息帧和发送消息帧进行超时管理,因为CAN总线的发送过程存在仲裁,确实可能在通信繁忙的时间段有一些优先级比较低(CAN ID值比较大)的消息帧无法顺利发出。此时,如果有通信帧久久没有成功发出,则会上报给CANopen协议栈,进一步可能会通过NMT协议调整网络通信的节奏,尽量让关键数据(由于延迟提升的优先级)得以通畅传输。

CO_CANclearPendingSyncPDOs()

另外,还需要在CO_CANclearPendingSyncPDOs()函数中增加对FlexCAN硬件发送缓冲区的检查,当需要发送同步消息时,如果有未上线的消息占用发送消息缓冲区(当新的同步消息准备发出时,之前未发出的同步消息已经失效,不再具有同步的意义),则CANopen可以强制腾空发送缓冲区,为最新的同步消息腾出空间准备发送。

/******************************************************************************/  
void CO_CANclearPendingSyncPDOs(CO_CANmodule_t *CANmodule)  
{  
    uint32_t tpdoDeleted = 0U;  
  
    CO_LOCK_CAN_SEND();  
  
    /* Abort message from CAN module, if there is synchronous TPDO.  
     * Take special care with this functionality. */  
    if (   (BOARD_FLEXCAN_RX_MB_STATUS == (BOARD_FLEXCAN_RX_MB_STATUS & FLEXCAN_GetMbStatus(BOARD_FLEXCAN_PORT)) )  
        && CANmodule->bufferInhibitFlag)  
    {  
        /* clear TXREQ */  
        CANmodule->bufferInhibitFlag = false;  
        tpdoDeleted = 1U;  
    }  
    /* delete also pending synchronous TPDOs in TX buffers */  
    if (CANmodule->CANtxCount != 0U)  
    {  
        CO_CANtx_t *buffer = &CANmodule->txArray[0];  
        for (uint16_t i = CANmodule->txSize; i > 0U; i--)  
        {  
            if (buffer->bufferFull)  
            {  
                if (buffer->syncFlag)  
                {  
                    buffer->bufferFull = false;  
                    CANmodule->CANtxCount--;  
                    tpdoDeleted = 2U;  
                }  
            }  
            buffer++;  
        }  
    }  
    CO_UNLOCK_CAN_SEND();  
  
  
    if (tpdoDeleted != 0U)  
    {  
        CO_errorReport((CO_EM_t*)CANmodule->em, CO_EM_TPDO_OUTSIDE_WINDOW, CO_EMC_COMMUNICATION, tpdoDeleted);  
    }  
}  

至此,一个基本的使用CANopenNode组件实现的CANopen的框架即移植完成。编译项目,清理可能的错误,即可下载工程的开发板运行程序。

Build started: Project: project  
*** Using Compiler 'V6.18', folder: 'C:\Keil_v5\ARM\ARMCLANG\Bin'  
Rebuild target 'Target 1'  
compiling application.c...  
...  
compiling CO_trace.c...  
compiling crc16-ccitt.c...  
compiling eeprom.c...  
linking...  
Program Size: Code=32600 RO-data=2468 RW-data=996 ZI-data=6244    
".\Objects\project.axf" - 0 Error(s), 0 Warning(s).  
Build Time Elapsed:  00:00:02 
作者:安德鲁苏
文章来源:安德鲁的设计笔记本

推荐阅读

更多MM32F5系列资料请关注灵动MM32 MCU专栏。如想进行MM32相关芯片技术交流,请添加极术小姐姐微信(id:aijishu20)加入微信群。
推荐阅读
关注数
6151
内容数
276
灵动MM32 MCU相关技术知识,欢迎关注~
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息