STM32CubeMX | STM32 F4系列HAL库使用双CAN配置及注意事项


主控芯片:STM32F406ZGT6
软件版本:STM32CUBEMX 5.4
HAL库版本:STM32Cube FW_F4 V1.24.2

STM32CubeMX | STM32 F4系列HAL库使用双CAN配置及注意事项_STM32CUBEMX对于本实验芯片,该单片机具有两个CAN接口,CAN1为主机CAN,CAN2为从机CAN;​​​CAN2是和CAN1有关联的,要想使用CAN2必须先使能CAN1的时钟!​


扫描以下二维码,关注公众号雍正不秃头获取更多STM32资源及干货!

STM32CubeMX | STM32 F4系列HAL库使用双CAN配置及注意事项_STM32_02


先了解几个关键词简称:

  • 最小时间单位(Tq,Time Quantum)
  • 同步段(SS,Synchronization Segment)1tq
  • 传播时间段(PTS,Propagation Time Segment)1~8tq
  • 相位缓冲段1(PBS1,Phase Buffer Segment1)1~8tq
  • 相位缓冲段2(PBS2,Phase Buffer Segment2)2~8tq
  • 再同步补偿宽度(SJW,reSynchronization Jump Width)1~4tq
  • 波特率分频器(BRP,Baud Rate Prescaler)

STM32把传播时间段(PTS)和相位缓冲段1(PBS1)合并了,形成了时间段1(TS1)。

CAN位时序寄存器(CAN_BTR)用于设置TS1、TS2、BRP、SJW等参数,这些参数直接决定CAN的波特率。

STM32CubeMX | STM32 F4系列HAL库使用双CAN配置及注意事项_STM32_03

  • SJW[1:0]再同步补偿宽度
  • TS1[3:0]时间段1
  • TS2[2:0]时间段2
  • BRP[9:0]波特率分频器

可以看到没有同步段(SS段),这是因为STM32已经将SS段固化为1。

下面这张图是波特率计算公式:

STM32CubeMX | STM32 F4系列HAL库使用双CAN配置及注意事项_STM32_04

整合一下波特率计算公式就是这样的:

​波特率 = APB1 / [(1 + (TS1+1) + (TS2+1)) * (BRP+1)]​

在简化就是:​​波特率 = 时钟主频 / 分频 / (tq1 + tq2 + ss)​

其中SS就是同步段,已经恒为1,所以:​​波特率 = 时钟主频 / 分频 / (tq1 + tq2 + 1)​

下面我们开始实际设置波特率,这里要注意,CAN的波特率最大为1Mbps。

另外还有一个参数是:再同步补偿宽度(reSynchronization Jump Width) 这个参数,其实就是一个由数个Tq组成的一个段,用来对同步误差进行补偿,可以简单理解为为了提高精准度的,例如两个CAN进行通讯时由于两个板子的晶振可能存在误差从而导致CAN的波特率没有那么精准,所以就需要设置一个补偿参数去修正,这个参数就需要根据你实际的板子情况去调整了。


时钟配置

STM32CubeMX | STM32 F4系列HAL库使用双CAN配置及注意事项_CAN_05


CAN配置

STM32CubeMX | STM32 F4系列HAL库使用双CAN配置及注意事项_STM32CUBEMXCAN外设是挂载在APB1_PCLK1时钟上的,APB1_PCLK1是42M。CAN的最大速率是1MHZ,本实验配置为500KHZ。波特率配置计算方法:

  • ​CAN波特率 = APB1_PCLK1/分频/(tq1 + tq2 + ss)​​,其中SS是SS段,在STM32中已经固定为1个tq
  • ​本实验波特率 = 42MHZ/4分频/(14 + 6 + 1) = 0.5MHZ = 500KHZ​
  • STM32CubeMX | STM32 F4系列HAL库使用双CAN配置及注意事项_CAN_07

  • 使能CAN接收中断
  • STM32CubeMX | STM32 F4系列HAL库使用双CAN配置及注意事项_双CAN_08

  • 配置CAN的IO,此步骤必须!!要不然HAL库函数MX_CAN_Init初始化会失败!!
  • STM32CubeMX | STM32 F4系列HAL库使用双CAN配置及注意事项_双CAN_09

CAN2和CAN1是同样的配置,这里不再贴CAN2的配置图了。


STM32外设CAN过滤器说明

STM32CUBEMX生成的代码默认是没有设置ID筛选器的,所以需要手动添加过滤器代码。下面一张图,STM32的过滤器组:

STM32CubeMX | STM32 F4系列HAL库使用双CAN配置及注意事项_CAN_10

STM32F407ZG有28组筛选器,一组筛选器有两个32位的寄存器,筛选器组可配置为四种模式:

  • 1个32位筛选器-标识符掩码模式,这时候筛选器组的两个32位寄存器一个用来存放ID,另一个用来存放ID的掩码
  • 两个32位筛选器-标识符列表模式,这时候筛选器组的两个32位寄存器都用来存放ID
  • 两个16位筛选器-标识符掩码模式,这时候筛选器组被分成了4个16位的寄存器,分别存放ID高16位+掩码高16位,ID低16位+掩码低16位
  • 四个16位筛选-标识符列表模式,这时候筛选器组被分成了4个16位的寄存器,都存放ID的高16位和低16位

贴上我的配置代码,:

#define CAN1_FILTER_MODE_MASK_ENABLE 1  ///< CAN1过滤器模式选择:=1:屏蔽位模式  =0:屏蔽列表模式
#define CAN2_FILTER_MODE_MASK_ENABLE 1 ///< CAN2过滤器模式选择:=1:屏蔽位模式 =0:屏蔽列表模式

#define CAN1_BASE_ID 0x10F00266 ///< 主CAN过滤ID
#define CAN2_BASE_ID 0x10F0F126 ///< 从CAN过滤ID

#define CAN1_FILTER_BANK 0 ///< 主CAN过滤器组编号
#define CAN2_FILTER_BANK 14 ///< 从CAN过滤器组编号

/// CAN过滤器寄存器位宽类型定义
typedef union
{
__IO uint32_t value;
struct
{
uint8_t REV : 1; ///< [0] :未使用
uint8_t RTR : 1; ///< [1] : RTR(数据帧或远程帧标志位)
uint8_t IDE : 1; ///< [2] : IDE(标准帧或扩展帧标志位)
uint32_t EXID : 18; ///< [21:3] : 存放扩展帧ID
uint16_t STID : 11; ///< [31:22]: 存放标准帧ID
} Sub;
} CAN_FilterRegTypeDef;

// CAN_FMR寄存器位宽类型定义
typedef union
{
__IO uint32_t value;
struct
{
uint8_t FINIT : 1;
uint8_t RESERVER_0 : 7;
uint8_t CAN2SB : 6;
uint32_t RESERVER_1 : 18;
}Sub;
}FMR_TypeDef;

/// 设置CAN1的过滤器(主CAN)
static void CAN1_Filter_Config(void)
{
CAN_FilterTypeDef sFilterConfig;
CAN_FilterRegTypeDef IDH = {0};
CAN_FilterRegTypeDef IDL = {0};

IDH.Sub.IDE = 0; // 标准帧
IDH.Sub.STID = 0; // 标准帧ID值
IDH.Sub.EXID = (CAN1_BASE_ID >> 16) & 0xFFFF; // 扩展帧高16位ID值

IDL.Sub.IDE = 1; // 扩展帧
IDL.Sub.STID = 0; // 标准帧ID值
IDL.Sub.EXID = (CAN1_BASE_ID & 0xFFFF); // 扩展帧低16位ID值

sFilterConfig.FilterBank = CAN1_FILTER_BANK; // 设置过滤器组编号
#if CAN1_FILTER_MODE_MASK_ENABLE
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; // 屏蔽位模式
#else
sFilterConfig.FilterMode = CAN_FILTERMODE_IDLIST; // 列表模式
#endif
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // 32位宽
sFilterConfig.FilterIdHigh = IDH.value; // 标识符寄存器一ID高十六位,放入扩展帧位
sFilterConfig.FilterIdLow = IDL.value; // 标识符寄存器一ID低十六位,放入扩展帧位
sFilterConfig.FilterMaskIdHigh = IDH.value; // 标识符寄存器二ID高十六位,放入扩展帧位
sFilterConfig.FilterMaskIdLow = IDL.value; // 标识符寄存器二ID低十六位,放入扩展帧位
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; // 过滤器组关联到FIFO0
sFilterConfig.FilterActivation = ENABLE; // 激活过滤器
sFilterConfig.SlaveStartFilterBank = CAN2_FILTER_BANK; // 设置CAN2的起始过滤器组(对于单CAN的CPU或从CAN此参数无效;对于双CAN的CPU此参数为从CAN的起始过滤器组编号)
if (HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK)
{
Error_Handler();
}
{
FMR_TypeDef regval = {0};
regval.value = hcan1.Instance->FMR;
printf("------ CAN1:> FMR:0x%0X CAN2SB:0x%X \r\n", regval.value, regval.Sub.CAN2SB);
}
}

/// 设置CAN2的过滤器(从CAN)
static void CAN2_Filter_Config(void)
{
CAN_FilterTypeDef sFilterConfig;
CAN_FilterRegTypeDef IDH = {0};
CAN_FilterRegTypeDef IDL = {0};

IDH.Sub.IDE = 0;
IDH.Sub.STID = 0;
IDH.Sub.EXID = (CAN2_BASE_ID >> 16) & 0xFFFF;

IDL.Sub.IDE = 1;
IDL.Sub.STID = 0;
IDL.Sub.EXID = (CAN2_BASE_ID & 0xFFFF);

sFilterConfig.FilterBank = CAN2_FILTER_BANK; // 设置过滤器组编号
#if CAN2_FILTER_MODE_MASK_ENABLE
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; // 屏蔽位模式
#else
sFilterConfig.FilterMode = CAN_FILTERMODE_IDLIST; // 列表模式
#endif
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // 32位宽
sFilterConfig.FilterIdHigh = IDH.value; // 标识符寄存器一ID高十六位,放入扩展帧位
sFilterConfig.FilterIdLow = IDL.value; // 标识符寄存器一ID低十六位,放入扩展帧位
sFilterConfig.FilterMaskIdHigh = IDH.value; // 标识符寄存器二ID高十六位,放入扩展帧位
sFilterConfig.FilterMaskIdLow = IDL.value; // 标识符寄存器二ID低十六位,放入扩展帧位
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; // 过滤器组关联到FIFO0
sFilterConfig.FilterActivation = ENABLE; // 激活过滤器
sFilterConfig.SlaveStartFilterBank = 28; // 无效
if (HAL_CAN_ConfigFilter(&hcan2, &sFilterConfig) != HAL_OK)
{
Error_Handler();
}
{
FMR_TypeDef regval = {0};
regval.value = hcan2.Instance->FMR;
printf("------ CAN2:> FMR:0x%0X CAN2SB:0x%X \r\n", regval.value, regval.Sub.CAN2SB);
}
}

/// CAN初始化
void CAN_Init(void)
{
MX_CAN1_Init(); // 初始化CNA1
CAN1_Filter_Config(); // 初始化CNA1过滤器
HAL_CAN_Start(&hcan1); // 启动CAN1
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING); // 激活CAN1 FIFO0

MX_CAN2_Init();
CAN2_Filter_Config();
HAL_CAN_Start(&hcan2);
HAL_CAN_ActivateNotification(&hcan2, CAN_IT_RX_FIFO0_MSG_PENDING);
}

/**
* CAN数据传输
* @param buf 待发送的数据
* @param len 数据长度
* @param number CAN编号,=0:CAN1,=1:CAN2
* @return 0:成功 other:失败
*/
uint8_t CAN_Transmit(const void* buf, uint32_t len, uint8_t number)
{
uint32_t txmailbox = 0;
uint32_t offset = 0;
CAN_TxHeaderTypeDef hdr;

hdr.IDE = CAN_ID_EXT; // ID类型:扩展帧
hdr.RTR = CAN_RTR_DATA; // 帧类型:数据帧
hdr.StdId = 0; // 标准帧ID,最大11位,也就是0x7FF
hdr.ExtId = number == 0 ? CAN1_BASE_ID : CAN2_BASE_ID; // 扩展帧ID,最大29位,也就是0x1FFFFFFF
hdr.TransmitGlobalTime = DISABLE;

while (len != 0)
{
hdr.DLC = len > 8 ? 8 : len; // 数据长度
if (HAL_CAN_AddTxMessage(number == 0 ? &hcan1 : &hcan2, &hdr, ((uint8_t *)buf) + offset, &txmailbox) != HAL_OK)
return 1;
offset += hdr.DLC;
len -= hdr.DLC;
}
return 0;
}

uint8_t CAN1_RX_STA = 0; ///< CAN1数据接收标志:[7]:数据 [6:0]:未使用
uint8_t CAN2_RX_STA = 0; ///< CAN2数据接收标志:[7]:数据 [6:0]:未使用

uint8_t CAN1_RX_BUF[8]; ///< CAN1数据接收缓存
uint8_t CAN2_RX_BUF[8]; ///< CAN2数据接收缓存

uint8_t CAN1_TX_BUF[8]; ///< CAN1数据发送缓存
uint8_t CAN2_TX_BUF[8]; ///< CAN2数据发送缓存

/**
* CAN FIFO0 数据接收中断回调函数
* @param hcan CAN句柄
*/
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
static CAN_RxHeaderTypeDef CAN_RX_HDR;

// CAN1数据接收
if (hcan->Instance == hcan1.Instance)
{
// 数据已经处理
if ((CAN1_RX_STA & 0x80) == 0)
{
// 清空缓存
memset(CAN1_RX_BUF, 0, 8);

// 接收数据
if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &CAN_RX_HDR, CAN1_RX_BUF) == HAL_OK) // 获得接收到的数据头和数据
{
CAN1_RX_STA |= 0x80; // 标记接收到数据
HAL_CAN_ActivateNotification(hcan, CAN_IT_RX_FIFO0_MSG_PENDING); // 再次使能FIFO0接收中断
}
}
}

// CAN2数据接收
else if (hcan->Instance == hcan2.Instance)
{
// 数据已经处理
if ((CAN2_RX_STA & 0x80) == 0)
{
// 清空缓存
memset(CAN2_RX_BUF, 0, 8);

// 接收数据
if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &CAN_RX_HDR, CAN2_RX_BUF) == HAL_OK) // 获得接收到的数据头和数据
{
CAN2_RX_STA |= 0x80; // 标记接收到数据
HAL_CAN_ActivateNotification(hcan, CAN_IT_RX_FIFO0_MSG_PENDING); // 再次使能FIFO0接收中断
}
}
}
}

///< CAN数据处理函数
inline void CAN_RecvHandler(void)
{
// CAN1有数据收到
if (CAN1_RX_STA & 0x80)
{
int i = 0;
memcpy(CAN1_TX_BUF, CAN1_RX_BUF, sizeof(CAN1_RX_BUF)); // 拷贝出数据
CAN1_RX_STA = 0; // 重置CAN1接收状态
for(i = 0; i != 8; i++)
{
printf("CAN1_TX_BUF[%d]:0x%X\r\n", i, CAN1_TX_BUF[i]);
}
printf("\r\n\r\n");
}

// CAN2有数据收到
if (CAN2_RX_STA & 0x80)
{
int i = 0;
memcpy(CAN2_TX_BUF, CAN2_RX_BUF, sizeof(CAN2_RX_BUF)); // 拷贝出数据
CAN2_RX_STA = 0; // 重置CAN1接收状态
for(i = 0; i != 8; i++)
{
printf("CAN2_TX_BUF[%d]:0x%X\r\n", i, CAN2_TX_BUF[i]);
}
printf("\r\n\r\n");
}
}