STM32H7双路CAN踩坑记录
1 问题描述
STM32的CAN1和CAN2无法同时使用。
注:MCU使用的是STM32H743,其他型号不确定是否一样,本文只以STM32H743举例说明。
2 原因分析
经过测试分析,双路CAN无法同时使用与CAN的消息RAM配置有关。
其中问题就出现在上图的RAM配置,STM32H7系列的两路CAN是共用同一块RAM的,并且把这块内存空间的分配交给了用户,也没有检查内存分配是否合理的操作,这点其实挺坑的,稍不注意就掉进去了。
我之前用过STM32其他的系列,它们芯片内部就定义好了哪部分RAM给哪个CAN,是使用不允许用户自由分配的,因此就吃了这个亏。我原本两路CAN的配置基本都是一致的,其中也包括消息RAM这一点,因此,在初始化之后,先初始化的那路CAN就会被覆盖导致不能使用。
3 解决办法
CAN1的消息RAM从地址偏移0开始,而CAN2则从CAN1消息RAN结束的位置开始,这样就先保证了CAN1有足够的RAM空间使用,也保证了CAN2的RAM不会跟CAN1冲突。具体代码配置参考如下:
FDCAN2_Handler.Init.MessageRAMOffset = FDCAN1_Handler.msgRam.EndAddress - SRAMCAN_BASE; // CAN1消息RAM的结束地址 - 消息RAM的起始地址 = CAN1实际使用的内存大小,把这个作为CAN2的内存偏移地址
注:当然,你也可以先计算一下或用串口打印一下CAN1具体使用了多少内存空间,然后在这个基础上做偏移,只要保证两路CAN有足够的内存并且内存不重叠就可以了。
4 CAN配置参考代码
/**
* @brief FDCAN初始化
* @param presc : 分频值,取值范围1~512;
* @param tsjw : 重新同步跳跃时间单元.范围:1~128;
* @param ntsg1 : 时间段1的时间单元.取值范围2~256;
* @param ntsg2 : 时间段2的时间单元.取值范围2~128;
* @param mode : FDCAN_MODE_NORMAL,普通模式;
FDCAN_MODE_INTERNAL_LOOPBACK,内部回环模式;
FDCAN_MODE_EXTERNAL_LOOPBACK,外部回环模式;
FDCAN_MODE_RESTRICTED_OPERATION,限制操作模式
FDCAN_MODE_BUS_MONITORING,总线监控模式
* @note 以上5个参数, 除了模式选择其余的参数在函数内部会减1, 所以, 任何一个参数都不能等于0
* FDCAN其输入时钟频率为 Fpclk1 = 20Mhz
* 波特率 = Fpclk1 / ((ntsg1 + ntsg2 + 1) * presc);
* 我们设置 fdcan_init(10, 8, 31, 8, 1), 则CAN波特率为:
* 20M / ((31 + 8 + 1) * 10) = 500Kbps
* @retval 0, 初始化成功; 其他, 初始化失败;
*/
u8 FDCAN1_Mode_Init(uint16_t presc, uint8_t tsjw, uint16_t ntsg1, uint8_t ntsg2, uint32_t mode)
{
FDCAN_FilterTypeDef fdcan_filterconfig;
HAL_FDCAN_DeInit(&FDCAN1_Handler); /* 先清除以前的设置 */
FDCAN1_Handler.Instance = FDCAN1;
FDCAN1_Handler.Init.FrameFormat = FDCAN_FRAME_FD_BRS; /* 位速率变换FDCAN模式 */
FDCAN1_Handler.Init.Mode = mode; /* 模式设置 */
FDCAN1_Handler.Init.AutoRetransmission = ENABLE; /* 使能自动重传!传统模式下一定要关闭!!! */
FDCAN1_Handler.Init.TransmitPause = ENABLE; /* 使能传输暂停 */
FDCAN1_Handler.Init.ProtocolException = DISABLE; /* 关闭协议异常处理 */
/* FDCAN中仲裁段位速率最高1Mbit/s, 数据段位速率最高8Mbit/s */
/* 数据段通信速率(仅FDCAN模式需配置) = 20M / (1 + dseg1 + dseg2) = 20M / (2 + 1 + 1) = 5 Mbit/s */
FDCAN1_Handler.Init.DataPrescaler = 10; /* 数据段分频系数范围:1~32 */
FDCAN1_Handler.Init.DataSyncJumpWidth = 16; /* 数据段重新同步跳跃宽度1~16 */
FDCAN1_Handler.Init.DataTimeSeg1 = 2; /* 数据段dsg1范围:1~32 5 */
FDCAN1_Handler.Init.DataTimeSeg2 = 1; /* 数据段dsg2范围:1~16 1 */
/* 仲裁段通信速率(FDCAN与传统CAN均需配置) = 20M / (1 + ntsg1 + ntsg2) = 20M / (31 + 8 + 1) = 500Kbit/s */
FDCAN1_Handler.Init.NominalPrescaler = presc; /* 分频系数 */
FDCAN1_Handler.Init.NominalSyncJumpWidth = tsjw; /* 重新同步跳跃宽度 */
FDCAN1_Handler.Init.NominalTimeSeg1 = ntsg1; /* tsg1范围:2~256 */
FDCAN1_Handler.Init.NominalTimeSeg2 = ntsg2; /* tsg2范围:2~128 */
FDCAN1_Handler.Init.MessageRAMOffset = 0; /* 信息RAM偏移(使用2路CAN时非常重要) */
FDCAN1_Handler.Init.StdFiltersNbr = 28; /* 标准信息ID滤波器编号 */
FDCAN1_Handler.Init.ExtFiltersNbr = 8; /* 扩展信息ID滤波器编号 */
FDCAN1_Handler.Init.RxFifo0ElmtsNbr = 1; /* 接收FIFO0元素编号 */
FDCAN1_Handler.Init.RxFifo0ElmtSize = FDCAN_DATA_BYTES_64; /* 接收FIFO0元素大小:最大64字节 */
FDCAN1_Handler.Init.RxBuffersNbr = 0; /* 接收FIFO0元素编号 */
FDCAN1_Handler.Init.TxEventsNbr = 0; /* 发送事件编号 */
FDCAN1_Handler.Init.TxBuffersNbr = 0; /* 发送缓冲编号 */
FDCAN1_Handler.Init.TxFifoQueueElmtsNbr = 1; /* 发送FIFO序列元素编号 */
FDCAN1_Handler.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION; /* 发送FIFO序列模式 */
FDCAN1_Handler.Init.TxElmtSize = FDCAN_DATA_BYTES_64; /* 发送大小:最大64字节 */
if (HAL_FDCAN_Init(&FDCAN1_Handler) != HAL_OK)
{
return 1; /* 初始化 */
}
/* 配置CAN过滤器 */
fdcan_filterconfig.IdType = FDCAN_STANDARD_ID; /* 标准ID */
fdcan_filterconfig.FilterIndex = 0; /* 滤波器索引 */
fdcan_filterconfig.FilterType = FDCAN_FILTER_MASK; /* 滤波器类型 */
fdcan_filterconfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; /* 过滤器0关联到FIFO0 */
fdcan_filterconfig.FilterID1 = 0x0000; /* 32位ID */
fdcan_filterconfig.FilterID2 = 0x0000; /* 如果FDCAN配置为传统模式的话,这里是32位掩码 */
/* 过滤器配置 */
if (HAL_FDCAN_ConfigFilter(&FDCAN1_Handler, &fdcan_filterconfig) != HAL_OK)
{
return 2; /* 滤波器初始化 */
}
/* 配置全局过滤器,拒收所有不匹配的标准帧或扩展帧 */
if (HAL_FDCAN_ConfigGlobalFilter(&FDCAN1_Handler, FDCAN_REJECT, FDCAN_REJECT, FDCAN_FILTER_REMOTE, FDCAN_FILTER_REMOTE) != HAL_OK)
{
return 3;
}
/* 启动CAN外围设备 */
if (HAL_FDCAN_Start(&FDCAN1_Handler) != HAL_OK)
{
return 4;
}
HAL_FDCAN_ActivateNotification(&FDCAN1_Handler, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0);
return 0;
}
/**
* @brief FDCAN初始化
* @param presc : 分频值,取值范围1~512;
* @param tsjw : 重新同步跳跃时间单元.范围:1~128;
* @param ntsg1 : 时间段1的时间单元.取值范围2~256;
* @param ntsg2 : 时间段2的时间单元.取值范围2~128;
* @param mode : FDCAN_MODE_NORMAL,普通模式;
FDCAN_MODE_INTERNAL_LOOPBACK,内部回环模式;
FDCAN_MODE_EXTERNAL_LOOPBACK,外部回环模式;
FDCAN_MODE_RESTRICTED_OPERATION,限制操作模式
FDCAN_MODE_BUS_MONITORING,总线监控模式
* @note 以上5个参数, 除了模式选择其余的参数在函数内部会减1, 所以, 任何一个参数都不能等于0
* FDCAN其输入时钟频率为 Fpclk1 = 20Mhz
* 波特率 = Fpclk1 / ((ntsg1 + ntsg2 + 1) * presc);
* 我们设置 fdcan_init(10, 8, 31, 8, 1), 则CAN波特率为:
* 20M / ((31 + 8 + 1) * 10) = 500Kbps
* @retval 0, 初始化成功; 其他, 初始化失败;
*/
u8 FDCAN2_Mode_Init(uint16_t presc, uint8_t tsjw, uint16_t ntsg1, uint8_t ntsg2, uint32_t mode)
{
FDCAN_FilterTypeDef fdcan_filterconfig;
HAL_FDCAN_DeInit(&FDCAN2_Handler); /* 先清除以前的设置 */
FDCAN2_Handler.Instance = FDCAN2;
FDCAN2_Handler.Init.FrameFormat = FDCAN_FRAME_FD_BRS; /* 位速率变换FDCAN模式 */
FDCAN2_Handler.Init.Mode = mode; /* 模式设置 */
FDCAN2_Handler.Init.AutoRetransmission = ENABLE; /* 使能自动重传!传统模式下一定要关闭!!! */
FDCAN2_Handler.Init.TransmitPause = ENABLE; /* 使能传输暂停 */
FDCAN2_Handler.Init.ProtocolException = DISABLE; /* 关闭协议异常处理 */
/* FDCAN中仲裁段位速率最高1Mbit/s, 数据段位速率最高8Mbit/s */
/* 数据段通信速率(仅FDCAN模式需配置) = 20M / (1 + dseg1 + dseg2) = 20M / (2 + 1 + 1) = 5 Mbit/s */
FDCAN2_Handler.Init.DataPrescaler = 10; /* 数据段分频系数范围:1~32 */
FDCAN2_Handler.Init.DataSyncJumpWidth = 16; /* 数据段重新同步跳跃宽度1~16 */
FDCAN2_Handler.Init.DataTimeSeg1 = 2; /* 数据段dsg1范围:1~32 5 */
FDCAN2_Handler.Init.DataTimeSeg2 = 1; /* 数据段dsg2范围:1~16 1 */
/* 仲裁段通信速率(FDCAN与传统CAN均需配置) = 20M / (1 + ntsg1 + ntsg2) = 20M / (31 + 8 + 1) = 500Kbit/s */
FDCAN2_Handler.Init.NominalPrescaler = presc; /* 分频系数 */
FDCAN2_Handler.Init.NominalSyncJumpWidth = tsjw; /* 重新同步跳跃宽度 */
FDCAN2_Handler.Init.NominalTimeSeg1 = ntsg1; /* tsg1范围:2~256 */
FDCAN2_Handler.Init.NominalTimeSeg2 = ntsg2; /* tsg2范围:2~128 */
FDCAN2_Handler.Init.MessageRAMOffset = FDCAN1_Handler.msgRam.EndAddress - SRAMCAN_BASE;/* 信息RAM偏移(使用2路CAN时非常重要) */
FDCAN2_Handler.Init.StdFiltersNbr = 32; /* 标准信息ID滤波器编号 */
FDCAN2_Handler.Init.ExtFiltersNbr = 1; /* 扩展信息ID滤波器编号 */
FDCAN2_Handler.Init.RxFifo1ElmtsNbr = 1; /* 接收FIFO1元素编号 */
FDCAN2_Handler.Init.RxFifo1ElmtSize = FDCAN_DATA_BYTES_64; /* 接收FIFO1元素大小:最大64字节 */
FDCAN2_Handler.Init.RxBuffersNbr = 1; /* 接收FIFO1元素编号 */
FDCAN2_Handler.Init.TxEventsNbr = 1; /* 发送事件编号 */
FDCAN2_Handler.Init.TxBuffersNbr = 1; /* 发送缓冲编号 */
FDCAN2_Handler.Init.TxFifoQueueElmtsNbr = 2; /* 发送FIFO序列元素编号 */
FDCAN2_Handler.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION; /* 发送FIFO序列模式 */
FDCAN2_Handler.Init.TxElmtSize = FDCAN_DATA_BYTES_64; /* 发送大小:最大64字节 */
if (HAL_FDCAN_Init(&FDCAN2_Handler) != HAL_OK)
{
return 1; /* 初始化 */
}
/* 配置CAN过滤器 */
fdcan_filterconfig.IdType = FDCAN_STANDARD_ID; /* 标准ID */
fdcan_filterconfig.FilterIndex = 1; /* 滤波器索引 */
fdcan_filterconfig.FilterType = FDCAN_FILTER_MASK; /* 滤波器类型 */
fdcan_filterconfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO1; /* 过滤器1关联到FIFO1 */
fdcan_filterconfig.FilterID1 = 0x0000; /* 32位ID */
fdcan_filterconfig.FilterID2 = 0x0000; /* 如果FDCAN配置为传统模式的话,这里是32位掩码 */
/* 过滤器配置 */
if (HAL_FDCAN_ConfigFilter(&FDCAN2_Handler, &fdcan_filterconfig) != HAL_OK)
{
return 2; /* 滤波器初始化 */
}
/* 配置全局过滤器,拒收所有不匹配的标准帧或扩展帧 */
if (HAL_FDCAN_ConfigGlobalFilter(&FDCAN2_Handler, FDCAN_REJECT, FDCAN_REJECT, FDCAN_FILTER_REMOTE, FDCAN_FILTER_REMOTE) != HAL_OK)
{
return 3;
}
/* 启动CAN外围设备 */
if (HAL_FDCAN_Start(&FDCAN2_Handler) != HAL_OK)
{
return 4;
}
HAL_FDCAN_ActivateNotification(&FDCAN2_Handler, FDCAN_IT_RX_FIFO1_NEW_MESSAGE, 0);
return 0;
}
static uint32_t HAL_RCC_FDCAN_CLK_ENABLED=0;
//FDCAN底层驱动,引脚配置,时钟使能
//HAL_FDCAN_Init()调用
//hsdram:FDCAN1句柄
void HAL_FDCAN_MspInit(FDCAN_HandleTypeDef* hfdcan)
{
GPIO_InitTypeDef GPIO_Initure;
RCC_PeriphCLKInitTypeDef FDCAN_PeriphClk;
if(hfdcan->Instance==FDCAN1)
{
//FDCAN时钟源配置为PLL1Q
FDCAN_PeriphClk.PeriphClockSelection=RCC_PERIPHCLK_FDCAN;
FDCAN_PeriphClk.FdcanClockSelection=RCC_FDCANCLKSOURCE_PLL;
if (HAL_RCCEx_PeriphCLKConfig(&FDCAN_PeriphClk) != HAL_OK)
{
printf("can1 init failed.\n");
}
/* Peripheral clock enable */
HAL_RCC_FDCAN_CLK_ENABLED++;
if(HAL_RCC_FDCAN_CLK_ENABLED==1)
{
__HAL_RCC_FDCAN_CLK_ENABLE();
}
// FDCAN1
__HAL_RCC_GPIOD_CLK_ENABLE(); //开启GPIOD时钟
GPIO_Initure.Pin=GPIO_PIN_0|GPIO_PIN_1;
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //推挽复用
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_MEDIUM; //超高速
GPIO_Initure.Alternate=GPIO_AF9_FDCAN1; //复用为CAN1
HAL_GPIO_Init(GPIOD,&GPIO_Initure); //初始化
#if FDCAN1_RX0_INT_ENABLE
HAL_NVIC_SetPriority(FDCAN1_IT0_IRQn,1,2);
HAL_NVIC_EnableIRQ(FDCAN1_IT0_IRQn);
#endif
}
else if(hfdcan->Instance==FDCAN2)
{
FDCAN_PeriphClk.PeriphClockSelection = RCC_PERIPHCLK_FDCAN;
FDCAN_PeriphClk.FdcanClockSelection = RCC_FDCANCLKSOURCE_PLL;
if (HAL_RCCEx_PeriphCLKConfig(&FDCAN_PeriphClk) != HAL_OK)
{
printf("can2 init failed.\n");
}
/* Peripheral clock enable */
HAL_RCC_FDCAN_CLK_ENABLED++;
if(HAL_RCC_FDCAN_CLK_ENABLED==1)
{
__HAL_RCC_FDCAN_CLK_ENABLE();
}
// FDCAN2
__HAL_RCC_GPIOB_CLK_ENABLE(); //开启GPIOB时钟
GPIO_Initure.Pin=GPIO_PIN_5|GPIO_PIN_6;
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //推挽复用
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_MEDIUM; //超高速
GPIO_Initure.Alternate=GPIO_AF9_FDCAN2; //复用为CAN2
HAL_GPIO_Init(GPIOB,&GPIO_Initure); //初始化
#if FDCAN2_RX0_INT_ENABLE
HAL_NVIC_SetPriority(FDCAN2_IT0_IRQn,1,2);
HAL_NVIC_EnableIRQ(FDCAN2_IT0_IRQn);
#endif
}
}
//此函数会被HAL_FDCAN_DeInit调用
//hfdcan:fdcan句柄
void HAL_FDCAN_MspDeInit(FDCAN_HandleTypeDef* hfdcan)
{
/* Peripheral clock disable */
if(hfdcan->Instance==FDCAN1)
{
HAL_RCC_FDCAN_CLK_ENABLED--;
if(HAL_RCC_FDCAN_CLK_ENABLED==0)
{
__HAL_RCC_FDCAN_CLK_DISABLE();
}
HAL_GPIO_DeInit(GPIOD, GPIO_PIN_0|GPIO_PIN_1);
#if FDCAN1_RX0_INT_ENABLE
HAL_NVIC_DisableIRQ(FDCAN1_IT0_IRQn);
#endif
}
else if(hfdcan->Instance==FDCAN2)
{
/* Peripheral clock disable */
HAL_RCC_FDCAN_CLK_ENABLED--;
if(HAL_RCC_FDCAN_CLK_ENABLED==0)
{
__HAL_RCC_FDCAN_CLK_DISABLE();
}
HAL_GPIO_DeInit(GPIOB, GPIO_PIN_5|GPIO_PIN_6);
#if FDCAN2_RX0_INT_ENABLE
HAL_NVIC_DisableIRQ(FDCAN2_IT0_IRQn);
#endif
}
}