STM32CubeMX | STM32 HAL库CAN总线收发、中断方式接收示例教程

平台:战舰mini板,STM32F103RB
STM32CUBEMX V5.3
TrueSTUDIO V9.3


配置CAN

先了解几个关键词简称:

  • 最小时间单位(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 HAL库CAN总线收发、中断方式接收示例教程_STM32

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

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

除去这几个参数,还需要设置分频,STM32F1系列一般是跑到72M主频,CAN是挂在36M的APB1时钟上,设置CAN的分频就是分的这个36M。

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

STM32CubeMX | STM32 HAL库CAN总线收发、中断方式接收示例教程_STM32CUBEMX_02

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

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

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

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

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

stm32f103的CAN的时钟主频是36M,分9频就是4M,设置tq1=5,tq2=2,ss恒等于1,那么:波特率 = 36MHz / 9分频 /(5 + 2 + 1) = 500KHz

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

​注意:stm32cubemx生成的CAN代码是不带过滤器的,需要自己手动添加​​。

STM32CubeMX | STM32 HAL库CAN总线收发、中断方式接收示例教程_寄存器_03


STM32CubeMX | STM32 HAL库CAN总线收发、中断方式接收示例教程_HAL_04

代码修改

STM32CUBEMX生成的CAN配置代码是没有过滤器设置的,需要手动添加。

typedef struct
{
uint32_t mailbox;
CAN_TxHeaderTypeDef hdr;
uint8_t payload[8];
}CAN_TxPacketTypeDef;

typedef struct
{
CAN_RxHeaderTypeDef hdr;
uint8_t payload[8];
}CAN_RxPacketTypeDef;
/// 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;


#define CAN_BASE_ID 0 ///< CAN标准ID,最大11位,也就是0x7FF

#define CAN_FILTER_MODE_MASK_ENABLE 1 ///< CAN过滤器模式选择:=0:列表模式 =1:屏蔽模式

#define CAN_ID_TYPE_STD_ENABLE 1 ///< CAN过滤ID类型选择:=1:标准ID,=0:扩展ID

void CAN_Filter_Config(void)
{
CAN_FilterTypeDef sFilterConfig;
CAN_FilterRegTypeDef IDH = {0};
CAN_FilterRegTypeDef IDL = {0};

#if CAN_ID_TYPE_STD_ENABLE
IDH.Sub.STID = (CAN_BASE_ID >> 16) & 0xFFFF; // 标准ID高16位
IDL.Sub.STID = (CAN_BASE_ID & 0xFFFF); // 标准ID低16位
#else
IDH.Sub.EXID = (CAN_BASE_ID >> 16) & 0xFFFF; // 扩展ID高16位
IDL.Sub.EXID = (CAN_BASE_ID & 0xFFFF); // 扩展ID低16位
IDL.Sub.IDE = 1; // 扩展帧标志位置位
#endif
sFilterConfig.FilterBank = 0; // 设置过滤器组编号
#if CAN_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 = 14; // 设置从CAN的起始过滤器编号,本单片机只有一个CAN,顾此参数无效
if (HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) != HAL_OK)
{
Error_Handler();
}
}

uint8_t CAN_Transmit(CAN_TxPacketTypeDef* packet)
{
if(HAL_CAN_AddTxMessage(&hcan, &packet->hdr, packet->payload, &packet->mailbox) != HAL_OK)
return 1;
return 0;
}

void CAN_Init(void)
{
MX_CAN_Init();
CAN_Filter_Config();
HAL_CAN_Start(&hcan);
HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING); // 使能CAN接收中断
}

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *canHandle)
{
static CAN_RxPacketTypeDef packet;

// CAN数据接收
if (canHandle->Instance == hcan.Instance)
{
if (HAL_CAN_GetRxMessage(canHandle, CAN_RX_FIFO0, &packet.hdr, packet.payload) == HAL_OK) // 获得接收到的数据头和数据
{
printf("\r\n\r\n\r\n################### CAN RECV ###################\r\n");
printf("STID:0x%X\r\n",packet.hdr.StdId);
printf("EXID:0x%X\r\n",packet.hdr.ExtId);
printf("DLC :%d\r\n", packet.hdr.DLC);
printf("DATA:");
for(int i = 0; i < packet.hdr.DLC; i++)
{
printf("0x%02X ", packet.payload[i]);
}
HAL_CAN_ActivateNotification(canHandle, CAN_IT_RX_FIFO0_MSG_PENDING); // 再次使能FIFO0接收中断
}
}
}
CAN_TxPacketTypeDef g_CanTxPacket;

void CAN_SetTxPacket(void)
{
g_CanTxPacket.hdr.StdId = 0x321; // 标准ID
// g_CanTxPacket.hdr.ExtId = 0x10F01234; // 扩展ID
g_CanTxPacket.hdr.IDE = CAN_ID_STD; // 标准ID类型
// g_CanTxPacket.hdr.IDE = CAN_ID_EXT; // 扩展ID类型
g_CanTxPacket.hdr.DLC = 8; // 数据长度
g_CanTxPacket.hdr.RTR = CAN_RTR_DATA; // 数据帧
// g_CanTxPacket.hdr.RTR = CAN_RTR_REMOTE; // 远程帧
g_CanTxPacket.hdr.TransmitGlobalTime = DISABLE;

for(int i = 0; i < 8; i++)
{
g_CanTxPacket.payload[i] = i;
}
}

int main()
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
CAN_Init();
printf("----------------------------------------\r\n");

CAN_SetTxPacket();

while(1)
{
if(CAN_Transmit(&g_CanTxPacket) != 0)
printf("failed\r\n");
HAL_Delay(1000);
}
}