MM32F0140学习笔记——FlexCAN 控制器局域网

cathy的头像
cathy 发布于:周一, 05/16/2022 - 10:57 ,关键词:

1、FlexCAN 简介

FlexCAN 控制器局域网模块是符合 ISO 11898-1 标准和 CAN 2.0B 规范的通信控制器,支持 CAN 总线协议。FlexCAN 模块框图如 图1 所示。FlexCAN 模块包括 CAN 收发器(CAN Transceiver)、协议引擎(Protocol Engine)、控制接口(Controller Host Interface)、总线接口单元(Bus Interface Unit),以及多个支持独立收发功能的信息缓冲区(Message Buffer)。其中 CAN 收发器负责将收发引脚的 TTL 信号转换为 CAN 总线的电平信号,协议引擎负责处理对信息缓冲区的读写请求,控制接口负责传输帧的发送仲裁以及接收匹配,总线接口单元处理 FlexCAN 和内部总线的交互,信息缓冲区用于存储 FlexCAN 的传输帧。

“图
图 1 MM32F0140 FlexCAN 模块框图

MM32F0140 的 FlexCAN 模块支持标准帧和扩展帧两种帧格式,支持数据帧和远程请求帧两种帧类型,其中数据帧的最大有效数据长度可达8比特。FlexCAN 模块支持最高可达 1Mps 的可编程比特率,支持对前 16 个信息缓冲区的中断,支持局部和全局的接收帧过滤机制,支持可选择的 FIFO 接收功能。

FlexCAN 模块支持三种工作模式:回环模式、只听模式和正常工作模式。因为在回环模式下,FlexCAN 的传输帧并不会通过其收发引脚发送到 CAN 总线,所以通常用于测试单块芯片的 FlexCAN 模块是否工作正常。只听模式下,FlexCAN 模块将只接收总线上的帧,而不能发送帧,并且也无法发送接收应答。正常工作模式下,FlexCAN 模块既可发送帧,也可接收帧。

2、FlexCAN 帧格式

FlexCAN 帧传输过程

“图
图 2 CAN 帧发送流程

图2 是 CAN 帧的发送流程,首先是帧起始位 SOF,SOF 值规定为 1,标识此帧的开始。在 CAN 总线协议中约定,值 0 为显性位,值 1 为隐性位。FlexCAN 采取多主机、基于优先级的总线传输方式,总线帧的收发顺序在 Arbitration field 仲裁段确定。仲裁成功的节点会继续发送帧,仲裁失败的节点会转为接收状态。如果不设置使用帧的优先级进行仲裁,则使用帧的 ID 号进行仲裁比较,越小优先级越高。结合 图2 CAN 帧发送流程 和 图3 CAN 标准数据帧格式可知,Arbitration field 仲裁段存放 CAN 报文的 ID 号以及 RTR 标识,RTR 用于表示帧类型,在下文讲述 FlexCAN 使用信息缓冲区发送报文时,会进一步解释其含义。Control filed 控制段在 MM32F0140 的 FlexCAN 中,主要涉及两个部分,IDE 字段和 DLE 字段。IDE 字段用于标识帧格式,为数据帧还是远程帧。DLC 字段用于标识此帧的有效字节数,DLC 字段值将影响后续的 Data field 字段长度。FDF 字段表示此帧是否为 CAN FD 帧,在MM32F0140 的 FlexCAN 模块中暂时未使用到。Data field 有效传输数据负载段,如果此帧为远程帧,将不携带任何数据,所以 DLC 字段对应为 0;如果此帧为数据帧,此段长度由 DLC 字段控制。CRC field 循环冗余码段,用于在发送和接收流程中检查此帧是否出现比特错误。CRC 后,为 ACK field 检测段,发送方会发送隐性位 1,然后在此段期间回读总线上信号。如果读到为显性位 0,则说明有其他 CAN 接收器接收到此帧,发送方发送成功。最后是 EOF 结束位,标识发送结束。

“图
图 3 CAN 标准数据帧格式

以下是 MM32F0140 FlexCAN 在上述 CAN 发送流程要求下,使用信息缓冲区寄存器进行 CAN 帧的收发的详细操作流程。信息缓冲区寄存器的结构如图 4 所示。

“图
图 4 FlexCAN 信息缓冲区结构图

FlexCAN 发送帧

FlexCAN 发送帧时,首先将帧相关信息写入预计使用的信息缓冲区寄存器中。如果此帧的 ID 号可用 11 个比特位进行表示,则将此帧的 ID 号填入 ID 段,并将 IDE 位置 0,标识为标准帧。如果此帧的 ID 号超过 11 位,则将剩余低位部分填入 ID(Extended) 段,并将 IDE 位置 1,标识为扩展帧。FlexCAN 的帧 ID 长度不能超过 29 位。

然后根据此帧的帧类型,如果是携带数据的数据帧,则将 SRR 替代远程请求位和 RTR 远程请求位都设置为 0,并将对应需要发送的数据写入 DataByte 区,将数据长度写入 DLC 字段;如果是不携带数据的远程请求帧,则将上述的 SRR 位和 RTR 位都设置为 1。

如果 MCR[LPRIOEN] 位被设置为 1,则在发送前还需要将此帧的优先级写入 PRIO 位,PRIO 位值越小,优先级越高。

当上述各字段都填充完毕后,将 CODE 值 12 填入 CODE 区,此 CODE 值表示将要发送此帧,发送完毕后 CODE 值变为 8,表示发送完毕。回读 TIME STAMP 段,可以得到发送成功时的时间戳。

FlexCAN 接收帧

FlexCAN 接收帧前,需要设置此接收信息缓冲区的 ID 号、帧格式 IDE 和帧类型 SRR 和 RTR,参考上述发送过程的设置。然后将 CODE 区值设置为值 4,表示等待接收。如果需要帧过滤,还需要设置局部帧过滤器 RXIMR 和全局帧过滤器 RXMGMASK、RX14MASK和RX15MASK。当 CAN 总线上有其他 FlexCAN 模块发送的帧时,信息缓冲区会将总线上的帧 ID 与自己的 ID 段以及掩码值进行匹配,接收帧,此时 TIME STAMP 段的值为接收时的时间戳。

在传输中会常使用到的 CODE 值如下表 1 所示。

表格 1 信息缓冲区 CODE 值含义

“表格

FlexCAN 功能测试流程

首先使用 FlexCAN 的回环模式测试开发板上的 FlexCAN 是否工作正常。回环测试成功以后可以进行 FlexCAN 正常工作模式的测试。如果使用的是两块开发板或使用 CAN 分析仪进行测试,需要注意总线两端必须连接 120Ω 终端电阻。在配置 FlexCAN 过程中,要保证收发方的波特率都配置成相同值。测试 FlexCAN 正常工作模式下收发的实验过程中,要确保至少有一个接收节点是正常工作模式,而非只听模式。

3、FlexCAN 配置

配置时钟

首先需要使能 FlexCAN 时钟,根据所使用的外设对 RCC 的 RCC_APB1ENR 寄存器进行赋值,将对应外设位置 1 即可使能时钟,详细外设如图 5 所示。

“图
图 5 MM32F0140 APB1总线外设

使能并初始化模块

使能 FlexCAN 模块,通过配置 FLEXCAN_MCR[MDIS] 为 0,可以使能 FlexCAN 模块。使能后,FlexCAN 模块会自动进入冻结模式。在冻结模式下,设置 FLEXCAN_MCR_SOFTRST 为 1,软件重置 FlexCAN 模块寄存器。然后将所有的信息缓冲区的独立掩码寄存器和全局掩码寄存器的掩码值都设置为 1,表示接收信息缓冲区将只接收 ID 号和缓冲区提前配置的 ID 号完全相同的报文,同时将每个信息缓冲区寄存器也全部清 0。

配置工作模式

如果 FlexCAN 需要进入正常工作模式,仅需在使能 FlexCAN、初始化信息缓冲区后退出冻结模式即可进入,即将 FLEXCAN_MCR[FRZ] 设置为 0。因为退出冻结模式需要一定时间,通过轮询 FLEXCAN_MCR[FRZACK] 是否等于 1 判断是否退出成功。如果 FlexCAN 需要配置为只读模式,则需要将 FLEXCAN_CTRL1[LOM] 置为 1;如果 FlexCAN 需要配置为回环模式,则需要将 FLEXCAN_CTRL1[LPB] 设置为 1。值得注意的是,当配置为回环模式时,FlexCAN 的 FLEXCAN_MCR[SRXDIS] 不能被设置为 1,否则 FlexCAN 将无法收到回环报文。

配置波特率

首先通过设置 FLEXCAN_CTRL1[CLKSRC] 指定 FlexCAN 协议引擎的时钟源,设置为 0 时使用振荡器时钟,设置为 1 时使用外设时钟。可详见图 6 FlexCAN 模块协议引擎时钟框图。

“图
图 6 FlexCAN模块协议引擎时钟框图

对 FlexCAN 引擎时钟频率进行可编程分频后得到 FlexCAN 串行时钟频率,FlexCAN 串行时钟频率的倒数被称为时间份额“图,用于衡量 FlexCAN 传输过程中各个阶段所需的时间长度,详见图 7 1 bit time 下 FlexCAN 传输段。

“图
图 7 1 bit time下FlexCAN传输段

SYNC_SEG 表示使用 1 个时间份额用于同步;Time Segment 1 段包括 PROPSEG 段和 PSEG1 段,前者用于补偿实际网络传输延时,后者和 PSEG2 都将用于补偿在 FlexCAN 传输过程中潜在的边缘相位误差。上述三项可以分别通过 FLEXCAN_CTRL1[PROPSEG]、FLEXCAN_CTRL1[PSEG1] 和 FLEXCAN_CTRL1[PSEG2] 进行设置。下图 8 是 Bosch CAN 2.0B 标准中对于 PSEG1 和PSEG2 的设置表。

“图
图 8 Bosch CAN 2.0B 标准中 PSEG1 和 PSEG2 的建议设置

在上述定义下,假定此时引擎时钟源的时钟频率为 ClockFreqHz,PROPSEG 中指定值为 PropSegLen,PSEG1 和PSEG2 中指定值分别为 PhaSegLen1 和 PhaSegLen2,所需波特率为 BaudRate 时,我们可以得到预期的分频数值 Div 为:

“MM32F0140学习笔记——FlexCAN

得到的 Div 值通过配置 FLEXCAN_CTRL1[PreDiv] 实现。这里尤其要注意各项和 ClockFreqHz 之间是否满足整除关系。

配置发送帧

首先需要将发送帧的内容填入等待发送的 i 号信息缓冲区的寄存器 FLEXCAN_MB[i] 的 CS、ID、WORD0 和WORD1 寄存器。然后将发送 CODE 值 (12) 填入 FLEXCAN_MB[i].CS[CODE] 中,即可发送。此时,如果将 FLEXCAN_ IMASK1 中对应中断位设置为 1,则发送成功后将会唤起中断。

配置接收帧格式

接收匹配需要设置对应的 i 号信息缓冲区 FLEXCAN_MB[i].CS[CODE] 值为 4,表示当前信息缓冲区接收为空,可以接收信息并安全存储,配置 FLEXCAN_MB[i].ID 为预期接收的帧 ID 号。设置 FLEXCAN_MB[i].CS[IDE] 配置接收扩展帧或标准帧,接收扩展帧则配置为 1,接收标准帧则配置为 0。设置 FLEXCAN_MB[i].CS[RTR] 配置接收远程帧或数据帧,接收远程帧则此位配置为 1,否则配置为 0。

如果需要对帧进行过滤接收,则需额外考虑配置全局帧过滤器或局部帧过滤器。配置 FLEXCAN_MCR[IRMQ] 为 1 则采用局部帧过滤,配置为 0 则采用全局帧过滤。当使用局部帧过滤方法时,掩码需要通过 FLEXCAN_RXIMRN[i] 寄存器配置,全局则通过 FLEXCAN_RXMGMASK、FLEXCAN_RX14MASK 和FLEXCAN_RX15MASK 寄存器进行配置。

4、FlexCAN 实验

在 SDK 中已有支持的 pokt-f0140 开发板上,在 driver example 下的 flexcan_loopback 样例中,使用 FlexCAN 模块进行回环测试,演示 FlexCAN 的初始化设置、接收和发送设置以及中断处理。

初始化外设时钟

FlexCAN 模块在 APB1 总线上,因此对 RCC_ APB1ENR 寄存器的 FLEXCAN 对应位设置为 1。
RCC_EnableAPB1Periphs(RCC_APB1_PERIPH_FLEXCAN, true);

初始化FlexCAN

使用外设时钟作为 FlexCAN 的时钟源,则 FlexCAN 的时钟频率为 pokt-f0140 上 APB1 总线时钟频率,为48MHz。此时将传输阶段的时钟配置 PhaseSegLen1,PhaseSegLen2,PropSegLen 分别配置为 2, 1, 1;将波特率APP_FLEXCAN_XFER_BAUDRATE 设置为 1MHz。则根据先前所述的波特率计算公式,可以满足整除关系。在实际调整波特率,需要考虑传输段的时钟设置是否满足整除关系。

/* Setup the flexcan module. */

void app_flexcan_init(void)
{
    /* Set the baudrate and bit time. */
    FLEXCAN_TimConf_Type flexcan_tim_conf;
    flexcan_tim_conf.EnableExtendedTime = false;/* No need to use extended time setting register. */
    flexcan_tim_conf.PhaSegLen1 = 2u;
    flexcan_tim_conf.PhaSegLen2 = 1u;
    flexcan_tim_conf.PropSegLen = 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.BaudRate = APP_FLEXCAN_XFER_BAUDRATE; /* Set baudrate. */
    flexcan_init.ClockFreqHz = CLOCK_APB1_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. */
    /* Enable FlexCAN. */
    FLEXCAN_Init(BOARD_FLEXCAN_PORT, &flexcan_init);
}

配置接收信息缓冲区

配置接收 MB 的接收帧类型为标准数据帧,ID 为 APP_FLEXCAN_XFER_ID。通过配置接收 MB 的 CODE 区,将其配置为接收为空的状态。

/* Set rx mb. */
FLEXCAN_RxMbConf_Type flexcan_mb_conf;
flexcan_mb_conf.Id = APP_FLEXCAN_XFER_ID; /* Id for 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);

/* Set for receiving frames. */
FLEXCAN_SetMbCode(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_RX_MB_CH, FLEXCAN_MbCode_RxEmpty);

配置发送信息缓冲区

将发送 MB 的寄存器清空。通过设置该 MB 的 CODE 区,将该 MB 设置为发送空闲。

/* Reset 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);

配置接收中断

使能 FlexCAN 的接收中断,设置 NVIC。

/* 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)
{
    /* Check mb status. If received. */
    if (0u!= (FLEXCAN_GetMbStatus(BOARD_FLEXCAN_PORT) & BOARD_FLEXCAN_RX_MB_STATUS) )
    {
        /* Clear flexcan mb interrupt flag. */
        FLEXCAN_ClearMbStatus(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_RX_MB_STATUS);
        /* Update the flag. */
        app_flexcan_rx_flag = true;
    }
}

发送数据

设置标准数据帧的 ID 号,帧类型和帧格式以及优先级;然后填入数据;设置数据长度。将上述配置写入 MB 相关寄存器,然后修改 MB 的 CODE 区进行发送。

/* Send a message frame. */
void app_flexcan_tx(uint8_t * tx_buf)
{
    /* Prepare sending mb frame. */
    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; /* Standard frame format. */
    mb.PRIORITY = APP_FLEXCAN_XFER_PRIORITY; /* The priority of the frame mb. */

    /* Set the information. */
    mb.BYTE0 = tx_buf[0];
    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];

    /* Set the workload size. */
    mb.LENGTH = APP_FLEXCAN_XFER_BUF_LEN;

    /* Send. */
    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. */
}

读取数据

读取对应的 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;
}

main() 函数

主函数会在键入之后,准备好数据并通过发送 MB 发送;等待中断处理函数将全局标志位设置为真后,解析接收MB 收到的帧,并打印。然后再等待下一次键入。

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 %u.\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;

        /* Read the message. */
        app_flexcan_read(app_flexcan_rx_buf); /* Send frame. */
        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");
    }
}

实验结果

“图
图 9 实验结果

来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 582