灵动微电子 · 2023年07月28日

CAN总线开发一本全(4) - FlexCAN的驱动程序

  • 引言
  • 从MindSDK获取FlexCAN驱动程序
  • 数据结构
  • 配置通信引擎的结构体类型
  • 访问MB的结构体类型
  • 配置ID过滤器的结构体类型
  • FIFO相关的结构体类型
  • API清单
  • 配置通信引擎的API
  • 访问MB的API
  • 配置ID过滤器的API
  • 中断和状态的API
  • FIFO相关的API
  • 样例工程
  • 回环通信 flexcan_loopback
  • 板对板直接通信  flexcan_b2b_tx & flexcan_b2b_rx
  • 板对板请求远程帧通信 flexcan_b2b_req & flexcan_b2b_ack
  • 总结
  • 参考文献

引言

前文介绍了FlexCAN外设模块,一种典型的CAN总线引擎子系统的工作机制。那么,用户在软件开发平台对CAN总线引擎进行编程,需要根据硬件外设模块的功能进行建模,将对CAN总线通信引擎的操作封装起来,让开发者通过软件开发平台的数据结构和用户可编程应用接口(API)函数使用FlexCAN模块。基于灵动微电子微控制器的软件开发平台MindSDK,包含了集成FlexCAN外设的MM32F5270和MM32F0140微控制器,其中就有FlexCAN外设模块的驱动程序以及样例工程,以及对CAN总线通信协议CANopen的适配工程。本文将介绍MindSDK中FlexCAN驱动程序及样例工程,展现一种典型的CAN总线驱动程序的实现及应用场景。

从MindSDK获取FlexCAN驱动程序

通过MindSDK在线发布网站 https://mindsdk.mindmotion.com.cn 选择搭载了MM32F0140微控制器的POKT-F0140开发板,就可以得到MM32F0140微控制器的软件开发包。如图x所示。

image.png
图x 在MindSDK在线网站现在MM32F0140软件包

下载的软件开发包后pokt-f0140_mdk.zip,其中就包含了FlexCAN的驱动程序源码,具体就是hal_flexcan.hhal_flexcan.c两个源文件。如图x所示。

image.png
图x FlexCAN驱动程序源码

数据结构

这里列写FlexCAN驱动程序中的主要数据结构。

配置通信引擎的结构体类型

FLEXCAN_Init_Type类型的结构体的变量,用于在初始化FlexCAN总线引擎时传入配置参数。

/*!  
 * @brief This type of structure instance is used to keep the settings when calling the @ref FLEXCAN_Init() to initialize the general setting of FLEXCAN.  
 */  
typedef struct  
{  
    uint8_t MaxXferNum;                   /*!< Max number of message buffer to be used. */  
    uint32_t BitRate;                     /*!< Data bit per second when using FLEXCAN for transmision and reception. */  
    uint32_t ClockFreqHz;                 /*!< Clock source frequency. */  
    FLEXCAN_ClockSource_Type ClockSource; /*!< Clock source selection. */  
    FLEXCAN_SelfWakeUp_Type  SelfWakeUp;  /*!< Stop mode self wake up source. */  
    FLEXCAN_WorkMode_Type WorkMode;       /*!< FLEXCAN function mode. */  
    FLEXCAN_Mask_Type Mask;               /*!< Filter work range for filtering the received frames. */  
    FLEXCAN_TimConf_Type * TimConf;       /*!< FLEXCAN timer and time synchronization setup. */  
    bool EnableSelfReception;             /*!< Whether to receive frames sent by FLEXCAN itself. */  
    bool EnableTimerSync;                 /*!< Refresh the timer every frame reception. */  
} FLEXCAN_Init_Type;  

其中,FLEXCAN_TimConf_Type类型用于指定FlexCAN总线位时间的配置参数。关于如何配置CAN总线的位时间,后续将有专门文章详解。

/*!  
 * @brief This type of structure instance is used to keep the settings when calling the @ref FLEXCAN_SetTimingConf() to initialize the time configuration.  
 */  
typedef struct  
{  
    bool EnableExtendedTime;  /*!< If enable, the setting time range can be broader. */  
    uint32_t PhaSegLen1;      /*!< Phase segment 1. */  
    uint32_t PhaSegLen2;      /*!< Phase segment 2. */  
    uint32_t PropSegLen;      /*!< Propagation segment. Compensate for signal delays across the network.*/  
    uint32_t JumpWidth;       /*!< Resynchronize jump width. */  
    uint32_t PreDiv;          /*!< The divider for FLEXCAN clock source. */  
} FLEXCAN_TimConf_Type;  

访问MB的结构体类型

FLEXCAN_Mb_Type类型的结构体,建立了对MB在物理存储空间的映射结构,软件使用该类型的结构体整体传入或读出硬件MB存储区中的内容。

/*!  
 * @brief This type of structure instance is used to keep the settings when calling the @ref FLEXCAN_WriteTxMb() to set the mask for buffer.  
 */  
typedef struct  
{  
    struct  
    {  
        uint32_t TIMESTAMP  : 16;   /*!< Free-running counter time stamp. */  
        uint32_t LENGTH     : 4;    /*!< Length of Data in Bytes. */  
        uint32_t TYPE       : 1;    /*!< Frame data type or remote type. */  
        uint32_t FORMAT     : 1;    /*!< Frame extended format or standard format. */  
        uint32_t RESERVED_0 : 1;    /*!< Reservation. */  
        uint32_t IDHIT      : 9;    /*!< Id filter number hit by fifo. */  
    };  
    struct  
    {  
        uint32_t ID :29;            /*!< Frame Identifier. */  
        uint32_t PRIORITY: 3;       /*!< Local priority. */  
    };  
    union  
    {  
        struct  
        {  
            uint32_t WORD0; /*!< CAN Frame payload word0. */  
            uint32_t WORD1; /*!< CAN Frame payload word1. */  
        };  
        struct  
        {  
            /* The sequence refers to the little-endian-storage and big-endian transfer. */  
            uint8_t BYTE3; /*!< CAN Frame payload byte3. */  
            uint8_t BYTE2; /*!< CAN Frame payload byte2. */  
            uint8_t BYTE1; /*!< CAN Frame payload byte1. */  
            uint8_t BYTE0; /*!< CAN Frame payload byte0. */  
            uint8_t BYTE7; /*!< CAN Frame payload byte7. */  
            uint8_t BYTE6; /*!< CAN Frame payload byte6. */  
            uint8_t BYTE5; /*!< CAN Frame payload byte5. */  
            uint8_t BYTE4; /*!< CAN Frame payload byte4. */  
        };  
    };  
} FLEXCAN_Mb_Type;  

FLEXCAN_RxMbConf_Type类型的结构体,用于配置在MB中配置接收帧的部分属性,这相当于是FLEXCAN_Mb_Type`的轻量级版本,但不需要数据负载、数据长度、本地优先级等配置信息。

/*!  
 * @brief This type of structure instance is used to keep the settings when calling the @ref FLEXCAN_SetRxMb() to set the mask for buffer.  
 */  
typedef struct  
{  
    FLEXCAN_MbType_Type MbType; /*!< Data frame or Remote frame switcher. */  
    FLEXCAN_MbFormat_Type MbFormat; /*!< Extended or standard id switcher. */  
    uint32_t Id; /*!< Id value. */  
} FLEXCAN_RxMbConf_Type;  

FLEXCAN_MbCode_Type枚举类型指定了可用的CODE命令。

/*!  
 * @brief FLEXCAN Xfer MB frame code switcher.  
 */  
typedef enum  
{  
    /* rx. */  
    FLEXCAN_MbCode_RxInactive     = 0u,  /*!< Code for MB being not active. */  
    FLEXCAN_MbCode_RxFull         = 2u,  /*!< Code for MB being full. */  
    FLEXCAN_MbCode_RxEmpty        = 4u,  /*!< Code for MB being active and empty. */  
    FLEXCAN_MbCode_RxOverrun      = 6u,  /*!< Code for MB being over written without accessing the received frame. */  
    FLEXCAN_MbCode_RxRanswer      = 10u, /*!< Code for Rx waiting for remote frame. */  
    FLEXCAN_MbCode_RxBusy         = 15u, /*!< Code for Rx updating MB. */  
    /* rx. */  
    FLEXCAN_MbCode_TxInactive     = 8u,  /*!< Code for data response for Tx inactive. */  
    FLEXCAN_MbCode_TxAbort        = 9u,  /*!< Code for Tx abort after transmission. */  
    FLEXCAN_MbCode_TxDataOrRemote = 12u, /*!< Code for data frame or remote frame transmission. */  
    FLEXCAN_MbCode_TxTanswer      = 14u, /*!< Code for data response for remote frame. */  
} FLEXCAN_MbCode_Type;  

配置ID过滤器的结构体类型

FLEXCAN_RxMbMaskConf_Type可以用于配置全局ID过滤器和MB专属ID过滤器。

/*!  
 * @brief This type of structure instance is used to keep the settings when calling the @ref FLEXCAN_SetGlobalMbMaskConf() to set the mask for buffer.  
 */  
typedef struct  
{  
    FLEXCAN_MbType_Type MbType; /*!< Data frame or Remote frame switcher. */  
    FLEXCAN_MbFormat_Type MbFormat; /*!< Extended or standard id switcher. */  
    uint32_t IdMask; /*!< Id mask. */  
} FLEXCAN_RxMbMaskConf_Type;  

FIFO相关的结构体类型

FLEXCAN_RxFifoConf_Type类型用于在启用FIFO功能时,配置FIFO的属性。

/*!  
 * @brief This type of structure instance is used to keep the settings when calling the @ref FLEXCAN_EnableRxFifo() to initialize the fifo setting.  
 */  
typedef struct  
{  
    FLEXCAN_FifoIdFilterFormat_Type FilterFormat; /*!< fifo filter format which will decide how to filter the fifo reception. */  
    uint32_t IdFilterNum; /*!< The fifo filter element num. */  
    uint32_t * IdFilterTable; /*!< Filter array to be set for Rx fifo. */  
    FLEXCAN_FifoPriority_Type priority; /*!< Enable matching process start with fifo. */  
} FLEXCAN_RxFifoConf_Type;  

当使用FIFO时,需要使用FLEXCAN_RxFifoMaskConf_Type结构体类型的变量配置ID过滤器。

/*!  
 * @brief This type of structure instance is used to keep the settings when calling the @ref FLEXCAN_SetRxFifoGlobalMaskConf() to set the conf for fifo mask filter.  
 */  
typedef struct  
{  
    FLEXCAN_MbType_Type MbType; /*!< Data frame or Remote frame switcher. */  
    FLEXCAN_MbFormat_Type MbFormat; /*!< Extended or standard id switcher. */  
    FLEXCAN_FifoIdFilterFormat_Type FilterFormat; /*!< fifo filter format. */  
    union  
    {  
        uint32_t RxIdA;    /*!< The fifo Id setting for filter format A. */  
        uint16_t RxIdB[2]; /*!< The fifo Id setting for filter format B. */  
        uint8_t  RxIdC[4]; /*!< The fifo Id setting for filter format C. */  
    };  
} FLEXCAN_RxFifoMaskConf_Type;  

还有更多枚举类型和宏常量,可继续查阅hal_flexcan.h源文件。

API清单

FlexCAN驱动的API相对较多,这里做了个分类,便于快速索引。更详细的内容可见源码。

配置通信引擎的API

使用FlexCAN外设之前需要初始化驱动引擎。

void FLEXCAN_Enable(FLEXCAN_Type * FLEXCANx, bool enable);  
void FLEXCAN_DoSoftReset(FLEXCAN_Type * FLEXCANx);  
bool FLEXCAN_Init(FLEXCAN_Type * FLEXCANx, FLEXCAN_Init_Type * init);  
void FLEXCAN_SetTimingConf(FLEXCAN_Type * FLEXCANx, FLEXCAN_TimConf_Type * conf);  
void FLEXCAN_EnableFreezeMode(FLEXCAN_Type * FLEXCANx, bool enable); 

访问MB的API

这一组API中,第二个参数channel,对应的是MB列表中的索引,FlexCAN中有16个MB,对应channel的取值可以是0-15。FlexCAN外设中MB的内存区是ECC的,在使用之前必须通过FLEXCAN_ResetMb()函数复位选定的MB。

void FLEXCAN_ResetMb(FLEXCAN_Type * FLEXCANx, uint32_t channel);  
void FLEXCAN_SetMbCode(FLEXCAN_Type * FLEXCANx, uint32_t channel, FLEXCAN_MbCode_Type code);  
void FLEXCAN_SetRxMb(FLEXCAN_Type * FLEXCANx, uint32_t channel, FLEXCAN_RxMbConf_Type * conf);  
bool FLEXCAN_WriteTxMb(FLEXCAN_Type * FLEXCANx, uint32_t channel, FLEXCAN_Mb_Type * mb);  
bool FLEXCAN_ReadRxMb(FLEXCAN_Type * FLEXCANx, uint32_t channel, FLEXCAN_Mb_Type * mb);  

配置ID过滤器的API

ID过滤器分为全局过滤器和MB专属的过滤器,对应有各自的过滤码(掩码)。

void FLEXCAN_SetGlobalMbMaskConf(FLEXCAN_Type * FLEXCANx, FLEXCAN_RxMbMaskConf_Type * conf);  
void FLEXCAN_SetRxMbIndividualMaskConf(FLEXCAN_Type * FLEXCANx, uint32_t channel, FLEXCAN_RxMbMaskConf_Type * conf); 

中断和状态的API

FlexCAN的中断和状态标志位分别面向FlexCAN引擎和MB,另外还有一些属性状态,例如计数器、CRC值等。

void     FLEXCAN_EnableInterrupts(FLEXCAN_Type * FLEXCANx, uint32_t interrupts, bool enable);  
uint32_t FLEXCAN_GetStatus(FLEXCAN_Type * FLEXCANx);  
void     FLEXCAN_ClearStatus(FLEXCAN_Type * FLEXCANx, uint32_t flags);  
void     FLEXCAN_EnableMbInterrupts(FLEXCAN_Type * FLEXCANx, uint32_t interrupts, bool enable);  
uint32_t FLEXCAN_GetMbStatus(FLEXCAN_Type * FLEXCANx);  
void     FLEXCAN_ClearMbStatus(FLEXCAN_Type * FLEXCANx, uint32_t mbs);  
uint32_t FLEXCAN_GetTxError(FLEXCAN_Type * FLEXCANx);  
uint32_t FLEXCAN_GetRxError(FLEXCAN_Type * FLEXCANx);  
uint32_t FLEXCAN_GetMatchCrcValue(FLEXCAN_Type * FLEXCANx, uint32_t * channel);  

FIFO相关的API

在配置好FlexCAN引擎后,通过FLEXCAN_EnableRxFifo()函数启用FIFO功能,之后就可以以FIFO的方式访问MB。

bool     FLEXCAN_EnableRxFifo(FLEXCAN_Type * FLEXCANx, FLEXCAN_RxFifoConf_Type * conf);  
void     FLEXCAN_SetRxFifoGlobalMaskConf(FLEXCAN_Type * FLEXCANx, FLEXCAN_RxFifoMaskConf_Type * conf);  
bool     FLEXCAN_ReadRxFifo(FLEXCAN_Type * FLEXCANx, FLEXCAN_Mb_Type * mb);  
void     FLEXCAN_EnableFifoDMA(FLEXCAN_Type * FLEXCANx, bool enable);  
uint32_t FLEXCAN_GetFifoAddr(FLEXCAN_Type * FLEXCANx);  

样例工程

MindSDK为FlexCAN驱动设计了一些样例工程,用于演示在一些典型场景中使用FlexCAN的方法。这些样例工程也可以运行在搭载MM32F0140微控制器的POKT-F0140开发板上。见表x。

表x MindSDK中的FlexCAN驱动样例工程清单

image.png

回环通信 flexcan_loopback

FlexCAN外设的回环通信功能,就是将FlexCAN外设模块的Tx信号和Rx信号在模块内部,由软件配置电路连通,可用于验证在未接入CAN总线网络时,节点软件本身能否正常收发通信帧。当完成验证后,仅需要通过软件关闭回环通信功能,即可用已经验证过的收发过程同外部CAN总线网络对接。

flexcan_loopback样例工程中的源码,展示了使用FlexCAN模块回环通信的方法。除了启用了回环通信的功能之外,其余对CAN通信帧的发送操作和接收操作同正常对外通信无异,因此,本工程也是使用FlexCAN驱动收发CAN通信帧的最基础的用例。另外,由于使用了回环通信,不需要专门准备发送和接收两块运行不同程序的电路板,仅用一块开发板即可完成实验。

flxcan_loopback样例工程的main()函数中,除了必要的初始化微控制器的时钟、引脚和后台人机交互端口外,先初始化了FlexCAN模块,然后在while(1)循环中,先准备一组数据作为CAN通信帧的数据负载发送出去,等待发送完成后,再接收一个CAN通信帧,等待接收完成后打印到终端显示接收帧中的数据负载,周而复始。发送帧和接收帧的ID使用同一个 APP_FLEXCAN_XFER_ID,因此可以实现收发。

/*  
 * Variables.  
 */  
volatile bool app_flexcan_rx_flag = false; /* Flag the message buffer reception state. */  
FLEXCAN_Mb_Type app_flexcan_rx_mb; /* For message buffer rx frame storage. */  
uint8_t app_flexcan_tx_buf[APP_FLEXCAN_XFER_BUF_LEN]; /* The flexcan tx buffer for tx mb frame preparation. */  
uint8_t app_flexcan_rx_buf[APP_FLEXCAN_XFER_BUF_LEN]; /* The flexcan rx buffer for rx mb frame storage. */  
  
/*  
 * Declerations.  
 */  
void app_flexcan_init(void);            /* Setup flexcan. */  
void app_flexcan_tx(uint8_t *tx_buf);   /* Send frame. */  
void app_flexcan_read(uint8_t *rx_buf); /* Receive frame. */  
  
/*  
 * Functions.  
 */  
int main(void)  
{  
    BOARD_Init();  
    printf("\r\nflexcan_loopback example.\r\n");  
  
    /* Setup the flexcan module.*/  
    app_flexcan_init();  
    printf("press any key to send loop back frame with id 0x%x.\r\n", (unsigned)APP_FLEXCAN_XFER_ID);  
  
    while (1)  
    {  
        getchar();  
        /* Send a message through flexcan. */  
        for (uint8_t i = 0u; i < APP_FLEXCAN_XFER_BUF_LEN; i++)  
        {  
             app_flexcan_tx_buf[i] = ( app_flexcan_tx_buf[i] + i) % 256u;  
        }  
        app_flexcan_tx(app_flexcan_tx_buf);  
        printf("app_flexcan_tx() done.\r\n");  
  
        /* Wait for reception. */  
        while (!app_flexcan_rx_flag) /* This flag will be on when the Rx interrupt is asserted. */  
        {  
        }  
        app_flexcan_rx_flag = false;  
  
        printf("app_flexcan_read(): ");  
        for (uint8_t i = 0u; i < APP_FLEXCAN_XFER_BUF_LEN; i++)  
        {  
            printf("%u ", (unsigned)app_flexcan_rx_buf[i]);  
        }  
        printf("\r\n\r\n");  
    }  
}  

其中,初始化FlexCAN模块的函数 app_flexcan_init(),初始化了FlexCAN通信引擎,包括配置CAN总线通信的位时钟,配置好了发送MB和接收MB,分别使用两个不同的MB索引BOARD_FLEXCAN_TX_MB_CHBOARD_FLEXCAN_RX_MB_CH,并分别设定它们为有效的非激活状态。最后还启用了发送完成和接收到数据帧的中断。虽然这里也可以使用纯粹的轮询标志位实现流控制,但使用中断方式便于向后续用例中过渡。在中断服务程序中,当检测到有接收帧时,从接收帧的MB中搬运接收到的数据负载到内存变量app_flexcan_rx_buf中,然后清接收标志位。有实现代码如下:

/* Setup the flexcan module. */  
void app_flexcan_init(void)  
{  
    /* Set bit timing. */  
    FLEXCAN_TimConf_Type flexcan_tim_conf;  
    flexcan_tim_conf.EnableExtendedTime = true;  
    flexcan_tim_conf.PhaSegLen1 = 5u;  
    flexcan_tim_conf.PhaSegLen2 = 1u;  
    flexcan_tim_conf.PropSegLen = 2u;  
    flexcan_tim_conf.JumpWidth = 1u;  
  
    /* Setup flexcan. */  
    FLEXCAN_Init_Type flexcan_init;  
    flexcan_init.MaxXferNum = APP_FLEXCAN_XFER_MaxNum; /* The max mb number to be used. */  
    flexcan_init.ClockSource = FLEXCAN_ClockSource_Periph; /* Use peripheral clock. */  
    flexcan_init.BitRate = APP_FLEXCAN_XFER_BITRATE; /* 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_LoopBack; /* Normal workmode, can receive and transport. */  
    flexcan_init.Mask = FLEXCAN_Mask_Global; /* Use global mask for filtering. */  
    flexcan_init.EnableSelfReception = true; /* Must receive 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 = APP_FLEXCAN_XFER_ID; /* 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. */  
  
    /* Enable intterupts for rx mb. */  
    FLEXCAN_EnableMbInterrupts(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_RX_MB_INT, true);  
    NVIC_EnableIRQ(BOARD_FLEXCAN_IRQn);  
}  
  
/* Interrupt request handler. */  
void BOARD_FLEXCAN_IRQHandler(void)  
{  
    if (0u!= (FLEXCAN_GetMbStatus(BOARD_FLEXCAN_PORT) & BOARD_FLEXCAN_RX_MB_STATUS) )  
    {  
        /* Read the message. */  
        app_flexcan_read(app_flexcan_rx_buf);  
  
        /* Clear flexcan mb interrupt flag. */  
        FLEXCAN_ClearMbStatus(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_RX_MB_STATUS);  
        /* Update the flag. */  
        app_flexcan_rx_flag = true;  
    }  
}  

其中,flexcan_init.WorkMode = FLEXCAN_WorkMode_LoopBack;即指定启用了回环模式。

发送数据帧的操作被封装成app_flexcan_tx() 函数。在该函数中,将即将发送的数据填充到MB结构体的数据负载中,再将整个帧结构写入到FlexCAN硬件的MB内存区,最后过向发送MB的内存区写命令码,启动发送过程。注意,这里使用预分配的帧ID(而不是作为函数传参可配置),发送数据帧(而不是远程帧)。有源代码如下:

/* Send a message frame. */  
void app_flexcan_tx(uint8_t * tx_buf)  
{  
    /* Prepare the mb to be sent. */  
    FLEXCAN_Mb_Type mb;  
    mb.ID = APP_FLEXCAN_XFER_ID; /* Indicated ID number. */  
    mb.TYPE = FLEXCAN_MbType_Data; /* Data frame type. */  
    mb.FORMAT = FLEXCAN_MbFormat_Standard; /* STD frame format. */  
    mb.PRIORITY = APP_FLEXCAN_XFER_PRIORITY; /* The priority of the frame mb. */  
    mb.BYTE0 = tx_buf[0]; /* Set the data payload. */  
    mb.BYTE1 = tx_buf[1];  
    mb.BYTE2 = tx_buf[2];  
    mb.BYTE3 = tx_buf[3];  
    mb.BYTE4 = tx_buf[4];  
    mb.BYTE5 = tx_buf[5];  
    mb.BYTE6 = tx_buf[6];  
    mb.BYTE7 = tx_buf[7];  
    mb.LENGTH = APP_FLEXCAN_XFER_BUF_LEN;  /* Set the size of data payload. */  
    FLEXCAN_WriteTxMb(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_TX_MB_CH, &mb);  
    /* Write code to send. */  
    FLEXCAN_SetMbCode(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_TX_MB_CH, FLEXCAN_MbCode_TxDataOrRemote);   
}  

接收数据帧的操作被封装成app_flexcan_read()函数。这里执行的操作,仅仅是从预定的FlexCAN硬件的接收MB内存区中把整个MB读出来,然后从MB结构类型中提取数据负载,作为传参返回给函数调用者。有源代码如下:

/* Receive a message frame. */  
void app_flexcan_read(uint8_t *rx_buf)  
{  
    /* Read the info from mb and reconstruct for understanding. */  
    FLEXCAN_ReadRxMb(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_RX_MB_CH, &app_flexcan_rx_mb);  
    rx_buf[0] = app_flexcan_rx_mb.BYTE0;  
    rx_buf[1] = app_flexcan_rx_mb.BYTE1;  
    rx_buf[2] = app_flexcan_rx_mb.BYTE2;  
    rx_buf[3] = app_flexcan_rx_mb.BYTE3;  
    rx_buf[4] = app_flexcan_rx_mb.BYTE4;  
    rx_buf[5] = app_flexcan_rx_mb.BYTE5;  
    rx_buf[6] = app_flexcan_rx_mb.BYTE6;  
    rx_buf[7] = app_flexcan_rx_mb.BYTE7;  
}  

板对板直接通信  flexcan_b2b_tx & flexcan_b2b_rx

板对板直接通信的用例,需要两块开发板,一个作为接收方,另一个作为发送方,由发送方发送CAN通信帧到接收方,从而实现两块电路板通过CAN总线传输数据的过程。这个实验的样例工程同基本的flexcan_loopback工程非常相近,只是收发过程拆分成两个独立的工程。注意,收发两个工程中使用CAN通信帧的ID也是约定一致的。

相对于flexcan_loop工程,独立的flexcan_b2b_rxflexcan_b2b_tx工程中对FlexCAN模块的初始化过程,不再启用回环模式,而是常规模式。见源代码如下:

/* Set up the flexCAN module. */  
void app_flexcan_init(void)  
{  
    ...  
    /* Setup FlexCAN. */  
    FLEXCAN_Init_Type flexcan_init;  
    ...  
    flexcan_init.WorkMode = FLEXCAN_WorkMode_Normal; /* Normal workmode, can receive and transport. */  
    ...  
    FLEXCAN_Init(BOARD_FLEXCAN_PORT, &flexcan_init);  
    ...  
}  

接收方电路板运行flexcan_b2b_rx工程的程序:初始化FlexCAN通信引擎后,配置接收MB,开中断等待接收帧完成。在后台的FlexCAN中断服务程序中,一旦捕获到约定ID的通信帧,就将接收帧中的数据负载转存到内存中的app_flexcan_rx_buf变量中,并且通过标志变量app_flexcan_rx_flag告知前台程序,然后清除硬件标志位。在前台的while(1)循环中,一旦接收到约定ID的通信帧,就在终端界面打印出接收到帧的数据内容。

发送方电路板运行flexcan_b2b_tx工程的程序:初始化FlexCAN通信引擎后,配置发送MB,开中断等待发送帧完成。前台的while(1)循环中,由用户触发向发送MB填充数据负载,并发送预定帧数据的操作。在后台的FlexCAN中断服务程序中,一旦发送完成约定ID的通信帧,就通过标志变量app_flexcan_tx_flag告知前台程序,然后清除硬件标志位。

运行实验时,电脑同时接入发送方和接收方的两个终端界面,先在发送方的终端界面中输入任意字符启动发送帧过程,将有CAN通信帧从发送方上传CAN总线,接收方亦会从总线上捕获到约定同一ID的数据帧,解析出其中的数据负载再显示到接收方的终端界面上。

板对板请求远程帧通信 flexcan_b2b_req & flexcan_b2b_ack

板对板请求远程帧通信的用例,需要两块开发板,一个作为请求方,另一个作为响应方,由请求方发送CAN通信远程帧到应答方,应答方收到远程帧后,准备好数据帧,再将响应的数据帧发送至CAN总线,由请求方捕获,从而实现两块电路板通过CAN总线读数据的过程。相对于板对板直接通信的写数据过程,板对板请求远程帧通信过程实现的是读数据过程。这个实验的样例工程同基本的flexcan_loopback工程,以及板对板直接通信的两个工程非常相近,只是收发过程拆分成两个独立的工程,并且原来的发送方先发出远程帧再接收数据帧,而原来的接收方将先等待远程帧再发送数据帧。注意,收发两个工程中使用CAN通信帧的ID也是约定一致的。

请求方和响应方的两个工程,同flexcan_loopback工程对FlexCAN引擎的初始化过程完全相同,并且在各自的工程中,也需要处理发送和接收过程。不同之处仅在于其中的一个通信帧从数据帧变成的远程帧。

响应方电路板运行flexcan_b2b_ack工程的程序:初始化FlexCAN通信引擎后,配置接收MB,配置发送MB,开中断等待发送和接收帧完成。其中接收MB的帧类型为远程帧FLEXCAN_MbType_Remote

void app_flexcan_init(void)  
{  
    ...  
        /* Set rx mb. */  
    FLEXCAN_RxMbConf_Type flexcan_mb_conf;  
    flexcan_mb_conf.Id = APP_FLEXCAN_XFER_ID; /* Id for filtering with mask and receiving. */  
    flexcan_mb_conf.MbType = FLEXCAN_MbType_Remote; /* Only receive remote 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. */  
    ...  
}  

请求方电路板运行flexcan_b2b_req工程的程序:初始化FlexCAN通信引擎后,配置接收MB,配置发送MB,开中断等待发送和接收帧完成。在发送请求帧时,设定MB的帧类型为远程帧FLEXCAN_MbType_Remote

/* Send a message frame. */  
void app_flexcan_req()  
{  
    FLEXCAN_Mb_Type mb;  
    mb.ID = APP_FLEXCAN_XFER_ID; /* Indicated ID number. */  
    mb.TYPE = FLEXCAN_MbType_Remote; /* Setup remote frame. */  
    mb.FORMAT = FLEXCAN_MbFormat_Standard; /* STD frame format. */  
    mb.PRIORITY = APP_FLEXCAN_XFER_PRIORITY; /* The priority of the frame mb. */  
    mb.LENGTH = APP_FLEXCAN_REQ_BUF_LEN; /* Set the workload size. */  
    FLEXCAN_WriteTxMb(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_TX_MB_CH, &mb);     /* Send. */  
    /* Write code to send. */  
    FLEXCAN_SetMbCode(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_TX_MB_CH, FLEXCAN_MbCode_TxDataOrRemote);   
}  

当运行通信过程时:先启动应答方的程序,准备好响应过程;再请求方的程序,发出请求的远程帧到CAN总线上,应答方从CAN总线上捕获到请求的远程帧后,在本机生成数据负载组,使用同远程帧相同的ID,装成数据帧,再上传至CAN总线;请求方此时可以捕获到CAN总线上的同ID的数据帧,显示到终端界面。周而复始。

总结

MindSDK中设计的FlexCAN驱动程序,对FlexCAN外设进行了建模,创建了一系列数据结构和API,能够为软件开发者提供初始化FlexCAN通信引擎,通过MB的结构类型发送数据帧、远程帧等功能。MindSDK为FlexCAN驱动设计的一些样例工程,演示了在一些典型应用场景中(回环通信、板对板直接通信、板对板请求远程帧通信)使用FlexCAN驱动的方法。

参考文献

MindSDK在线发布网站,https://mindsdk.mindmotion.com.cn

作者:安德鲁苏
文章来源:安德鲁的设计笔记本

推荐阅读

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