本文描述的FlexCan主要指的是NXP公司的I.MX RT系列提供的FlexCan低速总线,主要内容是个人根据在调试FlexCan接口时碰到的一些问题而总结出的调试心得,希望对后续使用到此类接口的朋友有所帮助。
其中比较重要的结构就是提供Message Buffers去临时存储发送或者接收到的CAN Frame数据,这也是后续会使用到的很重要的一个功能。CAN收发数据时,MBs会存储有仲裁域、数据域的数据(含ID),控制器将从这一块的空间去取得收发的数据,进而完成收发功能。
此处不做详细介绍,感兴趣的朋友直接参照手册即可,下面直接进入正题,关于FlexCan的初始化和不同的功能实现。
**
**
第一步,配置CAN的总线时钟,配置如下:
/*Clock setting for FLEXCAN*/
clock_root_config_t rootCfg = {0};
rootCfg.mux = kCLOCK_CAN1_ClockRoot_MuxSysPll2Pfd3; //FLEXCAN_CLOCK_SOURCE_SELECT
rootCfg.div = 5; //CAN ROOT CLK:60M
CLOCK_SetRootClock(kCLOCK_Root_Can1, &rootCfg);
此处我配置的总线时钟为60M,主要是为了后续配置CANFD总线波特率方便分频,如数据域的几个常用波特率:1Mbps,2 Mbps,3 Mbps,4 Mbps,5Mbps等,具体波特率可以参照产品的实际需求去配置。
第二步,在配置完时钟之后,配置波特率的实际分频参数,注意这里为了更贴合CAN总线对Bit数据采样的需求(实际测试过程中也发现如果使用默认配置,板子当前的硬件链路高波特率下无法完成正常传输:3Mbps以上,收发器支持5Mbps),下图是1BIT的CAN数据的组成:
代码如下:
if (FLEXCAN_FDCalculateImprovedTimingValues(flexcanConfig.baudRate, flexcanConfig.baudRateFD, CAN_CLK_FREQ,
&timing_config))
{
/* Update the improved timing configuration*/
memcpy(&(flexcanConfig.timingConfig), &timing_config, sizeof(flexcan_timing_config_t));
}
else
{
PRINTF("No found Improved Timing Configuration. Just used default configuration\r\n\r\n");
}
第三步,设置完波特率之后,就是初始化CAN控制器,使用的函数如下:
/*!
* brief Initializes a FlexCAN instance.
*
* This function initializes the FlexCAN module with user-defined settings.
* This example shows how to set up the flexcan_config_t parameters and how
* to call the FLEXCAN_FDInit function by passing in these parameters.
* code
* flexcan_config_t flexcanConfig;
* flexcanConfig.clkSrc = kFLEXCAN_ClkSrc0;
* flexcanConfig.baudRate = 1000000U;
* flexcanConfig.baudRateFD = 2000000U;
* flexcanConfig.maxMbNum = 16;
* flexcanConfig.enableLoopBack = false;
* flexcanConfig.enableSelfWakeup = false;
* flexcanConfig.enableIndividMask = false;
* flexcanConfig.disableSelfReception = false;
* flexcanConfig.enableListenOnlyMode = false;
* flexcanConfig.enableDoze = false;
* flexcanConfig.timingConfig = timingConfig;
* FLEXCAN_FDInit(CAN0, &flexcanConfig, 60000000UL, kFLEXCAN_8BperMB, false);
* endcode
*
* param base FlexCAN peripheral base address.
* param pConfig Pointer to the user-defined configuration structure.
* param sourceClock_Hz FlexCAN Protocol Engine clock source frequency in Hz.
* param dataSize FlexCAN FD frame payload size.
* param brs If bitrate switch is enabled in FD mode.
*/
void FLEXCAN_FDInit(
CAN_Type *base, const flexcan_config_t *pConfig, uint32_t sourceClock_Hz, flexcan_mb_size_t dataSize, bool brs)
这里有两个和MBs相关的参数,需要格外注意,分别是flexcanConfig.enableIndividMask和flexcanConfig.maxMbNum,enableIndividMask决定使用global mask还是使用individual mask,后面会有详细介绍,此处不细讲,maxMbNum决定最大的Message Buffer数量,由处理器决定,我们使用的处理器最大支持64个。
之后创建用户自定义的中断回调函数,并将用户中断回调注册到中断处理函数中,此处也是直接调用底层提供的API,如下:
/* Create FlexCAN handle structure and set call back function. */
FLEXCAN_TransferCreateHandle(CAN, &flexcanHandle, flexcan_callback, (flexcan_frame_t *)&rxframe);
NVIC_SetPriority(CAN1_IRQn, 3);
用户中断回调函数的函数原型:
/*! @brief FlexCAN transfer callback function.
*
* The FlexCAN transfer callback returns a value from the underlying layer.
* If the status equals to kStatus_FLEXCAN_ErrorStatus, the result parameter is the Content of
* FlexCAN status register which can be used to get the working status(or error status) of FlexCAN module.
* If the status equals to other FlexCAN Message Buffer transfer status, the result is the index of
* Message Buffer that generate transfer event.
* If the status equals to other FlexCAN Message Buffer transfer status, the result is meaningless and should be
* Ignored.
*/
typedef void (*flexcan_transfer_callback_t)(
CAN_Type *base, flexcan_handle_t *handle, status_t status, uint32_t result, void *userData);
第四步,配置Message Buffers和对应的掩码,掩码有两种形式,其一是全局掩码,对全局的MBs有效,另一个是私有掩码,只对对应的MB有效,决定哪一个掩码有效就由上面描述的flexcanConfig.enableIndividMask = false/true决定。
全局掩码有三个,分别是RXMGMASK、RX14MASK、RX15MASK(RX14MASK只对MB 14有效,RX15MASK同理),全局掩码具体的设置函数如下:
/* Set Rx Masking mechanism. */
FLEXCAN_SetRxMbGlobalMask(CAN, FLEXCAN_RX_MB_STD_MASK(MASK, 0, 0));
私有掩码则有64个,RXIMR0 - RXIMR63,对应MB0 – MB63,是一一对应的关系,所以私有掩码和对应的MB可以放到一起配置,这样看起来更方便,如下:
/* Setup Rx Message Buffer. */
mbConfig.format = kFLEXCAN_FrameFormatStandard;
mbConfig.type = kFLEXCAN_FrameTypeData;
rxframe.format = (uint8_t)kFLEXCAN_FrameFormatStandard;
rxframe.type = (uint8_t)kFLEXCAN_FrameTypeData;
rxframe.length = (uint8_t)DLC;
for (uint8_t i = 0; i < MB_COUNT; i++)
{
if(idtable[i].id != 0x0U)
{
FLEXCAN_SetRxIndividualMask(CAN, i, FLEXCAN_RX_MB_STD_MASK(idtable[i].mask,0,0));
mbConfig.id = FLEXCAN_ID_STD(idtable[i].id);
#if (defined(USE_CANFD) && USE_CANFD)
FLEXCAN_SetFDRxMbConfig(CAN, i , &mbConfig, true);
#else
FLEXCAN_SetRxMbConfig(CAN, i, &mbConfig, true);
#endif
rxframe.id = FLEXCAN_ID_STD(idtable[i].id);
rxXfer.mbIdx = (uint8_t)i;
#if (defined(USE_CANFD) && USE_CANFD)
rxXfer.framefd = &rxframe;
(void)FLEXCAN_TransferFDReceiveNonBlocking(CAN, &flexcanHandle, &rxXfer);
#else
rxXfer.frame = &rxframe;
(void)FLEXCAN_TransferReceiveNonBlocking(EXAMPLE_CAN, &flexcanHandle, &rxXfer);
#endif
}
}
这里的核心思想就是对于每一个MB都要配置对应的IndividualMask,同时启用该MB的接收函数,我们正常使用会让CAN接收函数常驻,在接收到数据时再通知应用,所以也要在中断回调中再次启用对应result(即MB index)的CAN接收函数,如下:
/*!
* @brief FlexCAN Call Back function
*/
static void flexcan_callback(CAN_Type *base, flexcan_handle_t *handle, status_t status, uint32_t result, void *userdata)
{
static BaseType_t xHigherPriorityTaskWoken = pdFALSE;
static BaseType_t outcome;
switch (status)
{
case kStatus_FLEXCAN_RxIdle:
outcome = xEventGroupSetBitsFromISR(flexcan_evengroup, BIT_1, &xHigherPriorityTaskWoken);
if (outcome != pdFAIL)
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
rxXfer.mbIdx = result;
#if (defined(USE_CANFD) && USE_CANFD)
(void)FLEXCAN_TransferFDReceiveNonBlocking(base, &flexcanHandle, &rxXfer);
#else
(void)FLEXCAN_TransferReceiveNonBlocking(base, &flexcanHandle, &rxXfer);
#endif
break;
case kStatus_FLEXCAN_TxIdle:
if (TX_MESSAGE_BUFFER_NUM == result)
{
outcome = xEventGroupSetBitsFromISR(flexcan_evengroup, BIT_0, &xHigherPriorityTaskWoken);
if (outcome != pdFAIL)
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
break;
case kStatus_FLEXCAN_WakeUp:
break;
default:
break;
}
}
设置的MB ID和对应MASK的关系,如果设置MB[1]的ID是0x100,设置对应的RXIMR[1]的MASK为0x7FF,这说明MB[1]只会接收ID为0x100的CAN口数据,同理如果MASK设置为0x7FC,即最后两bit都为0,不会check,那此时MB[1]会接收ID范围为0x100-0x103的数据。
上述流程都是针对CANFD的,但如果使用CAN Classical,此时CAN将能支持CAN RXFIFO。
当RXFIFO enable 后, RXFIFO 会占用前面几个MB, 首先MB[0:5]用来作为FIFO 缓存, 但是MB[6]之后的MB 可以用来定义 filter, 具体占用几个MB, 是通过CTRL2[RFFN] 来定义的, 那最少的定义(RFFN=0)MB6, 7 会被用做filter 的设置。
MB 6-7的空间能容纳8个 filter(4Byte存储一个filter), 这样RXIMR[0…7] 也有8个mask 与之对应, 这样 Rx FIFO global mask 就没有用途。
那么如果 RFFN = 1, MB6-9 用作filter, format C 格式的话, 那么会有16个filter, 但是 0-9 只有10个RXIMR 与之对应, 这样还有6个filter (10…15)没有mask 与其对应, 那么这样的换RX FIFO global mask 就跟这6个filter 合作,过滤收到的ID。
CANFIFO的filtertable配置idFilterTable对应的数组即可,然后再配置对应的MASK,如下:
/*! @brief FlexCAN Rx FIFO configuration structure. */
typedef struct _flexcan_rx_fifo_config
{
uint32_t *idFilterTable; /*!< Pointer to the FlexCAN Rx FIFO identifier filter table. */
uint8_t idFilterNum; /*!< The quantity of filter elements. */
flexcan_rx_fifo_filter_type_t idFilterType; /*!< The FlexCAN Rx FIFO Filter type. */
flexcan_rx_fifo_priority_t priority; /*!< The FlexCAN Rx FIFO receive priority. */
} flexcan_rx_fifo_config_t;
/*!
* @brief Configures the FlexCAN Rx FIFO.
*
* This function configures the Rx FIFO with given Rx FIFO configuration.
*
* @param base FlexCAN peripheral base address.
* @param pRxFifoConfig Pointer to the FlexCAN Rx FIFO configuration structure.
* @param enable Enable/disable Rx FIFO.
* - true: Enable Rx FIFO.
* - false: Disable Rx FIFO.
*/
void FLEXCAN_SetRxFifoConfig(CAN_Type *base, const flexcan_rx_fifo_config_t *pRxFifoConfig, bool enable);
至此,CAN的初始化基本完成,同时CAN接收函数保持常驻后台,如有不足欢迎补充。
调试过程中实际碰到的问题有,CANFD的通信在自己的测试板上正常,但和标准CANFD模块通信测试无法通过,一直显示错误,后面确认是CANFD的CRC域未配置为ISO标准模式,配置成ISO标准模式即正常,寄存器CAN->CTRL2如下: