从小白的视角了解并实现简单的STM32F103的CAN通信,直接上手。
一、CAN协议简介
CAN总线上传输的信息称为报文,当总线空闲时任何连接的单元都可以开始发送新的报文,有5种类型的帧:数据帧、遥控帧、错误帧、过载帧、帧间隔。数据帧有两种格式:标准格式和扩展格式主要区别在于ID长度。
二、参数含义和设置
STM32CubeIDE关于CAN的参数设置
1、位时序参数说明和设置
Prescaler:预分频系数,定义CAN单位时钟周期=APB1外设时钟频率/分频系数,取值范围:1 ~ 1024,上述APB频率 = 36MHz。
用打电话的方式,表示设置参数:
步骤 | CAN总线对应 | 参数作用 |
1. 你说:"今~天~晚~上~7~点~" | TimeSeg1(匀速发送数据) | 放慢语速,确保对方听清每个字(信号稳定) |
2. 停顿时间 | TimeSeg2(等待ACK) | 留时间让对方回应(同步采样点) |
3. 对方可能在停顿内回应: | ||
- ✅ "好!"(ACK) | 正常接收,继续通信 | 同步成功,无需调整 |
- ❌ 没回应(信号延迟) | SyncJumpWidth 发挥作用! | |
4. 你决定最多再等一等 | SyncJumpWidth 等待xTq,x个单位时间 | 允许对方稍晚回应(时钟容错) |
- 如果多等的时间内对方回应 | 同步成功 | 通信继续 |
- 如果多等的时间内仍无回应 | 判定不同步,重传数据 | 自动重传或报错 |
参数确定:
1、优先确定Prescaler:
计算 Prescaler = CAN_CLK / (波特率 × (1 + TimeSeg1 + TimeSeg2)),取最接近的整数值。
例如:36MHz目标1Mbps,假设 1+6+2=9Tq → Prescaler = 36MHz/(1MHz×9) = 4。
2、TimeSeg1/TimeSeg2分配:
短距离:TimeSeg1可小(如5~6Tq),TimeSeg2=1~2Tq。
长距离:TimeSeg1需大(如8~12Tq),TimeSeg2=2~4Tq。
规则:TimeSeg1 ≥ TimeSeg2 + SyncJumpWidth。
3、SyncJumpWidth选择:
高速CAN(≥500kbps):1Tq(严格同步)。
低速/工业CAN(≤250kbps):2Tq(增强容错)。
4、抗干扰优化:
增大TimeSeg1和SyncJumpWidth可提升稳定性,但会降低波特率。
若通信不稳定,逐步增加TimeSeg1(每次+1Tq)并测试。
2、基本参数说明和设置
参数说明:
基本参数 | 含义 |
Time Triggered Communication Mode (时间触发通信模式) | 作用:为接收帧添加时间戳(用于时间同步协议) ✅ ENABLE:硬件记录帧的接收时间戳(存储在CAN_RDTxR寄存器) ❌ DISABLE:不记录时间戳(默认) |
Automatic Bus-Off Management (自动总线关闭管理) | 作用:节点错误计数器超限后是否自动恢复 ✅ ENABLE:节点进入"Bus-Off"状态后,自动等待128次11位隐性位后恢复通信 ❌ DISABLE:需手动调用HAL_CAN_ResetError()恢复,适合需要人工干预的调试场景 |
Automatic Wake-up Mode (自动唤醒模式) | 作用:CAN从休眠模式唤醒的条件 ✅ ENABLE:检测到总线活动时自动唤醒(适合低功耗设备) ❌ DISABLE:必须通过软件或硬件信号唤醒(如按键触发) |
Automatic Retransmission (自动重传) | 作用:控制发送失败时是否自动重传 ✅ ENABLE:发送失败后自动重传(默认),确保数据可靠性,适合实时性要求高的场景(如汽车ECU) ❌ DISABLE:仅发送一次,失败不重试。用于严格时序控制(如CAN FD协议中避免报文堆积) |
Receive Fifo Locked Mode (接收FIFO锁定模式) | 接收FIFO满时的处理策略 ✅ ENABLE:FIFO满时丢弃新数据(避免覆盖旧数据) ❌ DISABLE:新数据覆盖最旧数据(默认) |
Transmit Fifo Priority (发送FIFO优先级) | 作用:发送邮箱的调度策略 ✅ ENABLE:按报文ID优先级发送(低ID优先) ❌ DISABLE:按FIFO顺序发送(默认 |
3、高级参数设置和说明
测试模式说明
Test Mode 测试模式 | CAN工作模式: Normal:正常模式(向总线发送或从总线接收数据) Loopback:环回模式(自发自收,无需硬件连接,用于本地回环测试) Silent:静默模式(监听总线,不响应,),只监听不干扰,适合诊断 Loopback combined with Silent:静默环回模式(本地回环+总线监听),混合用途,调试复杂场景 |
4、开启中断
可以在System Core --> NVIC中设置中断优先等级
三、实际例程
1、CAN初始化
增加接收滤波器,可以用于筛选接收数据,硬件级过滤,可以
降低CPU负担(避免处理无关报文)
提高实时性(仅处理目标数据)
必须配置(否则无法接收数据)
灵活可控(支持掩码/列表两种过滤模式)
/*** @brief CAN Initialization Function* @param None* @retval None*/
static void MX_CAN_Init(void)
{/* USER CODE BEGIN CAN_Init 0 *//* USER CODE END CAN_Init 0 *//* USER CODE BEGIN CAN_Init 1 *//* USER CODE END CAN_Init 1 */hcan.Instance = CAN1;hcan.Init.Prescaler = 4;hcan.Init.Mode = CAN_MODE_LOOPBACK;hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;hcan.Init.TimeSeg1 = CAN_BS1_5TQ;hcan.Init.TimeSeg2 = CAN_BS2_3TQ;hcan.Init.TimeTriggeredMode = DISABLE;hcan.Init.AutoBusOff = DISABLE;hcan.Init.AutoWakeUp = ENABLE;hcan.Init.AutoRetransmission = DISABLE;hcan.Init.ReceiveFifoLocked = DISABLE;hcan.Init.TransmitFifoPriority = DISABLE;if (HAL_CAN_Init(&hcan) != HAL_OK){Error_Handler();}/* USER CODE BEGIN CAN_Init 2 *//* USER CODE BEGIN CAN_Init 2 */CAN_FilterTypeDef sFilterConfig;sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;sFilterConfig.FilterIdHigh = 0x0000; // 可以保持为0接收所有IDsFilterConfig.FilterIdLow = 0x0000;sFilterConfig.FilterMaskIdHigh = 0x0000;sFilterConfig.FilterMaskIdLow = 0x0000;sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;sFilterConfig.FilterBank = 0; // 添加过滤器组号sFilterConfig.SlaveStartFilterBank = 14;sFilterConfig.FilterActivation = ENABLE;HAL_StatusTypeDef state = HAL_CAN_ConfigFilter(&hcan, &sFilterConfig);if (state != HAL_OK){Error_Handler();}state = HAL_CAN_Start(&hcan);if (state != HAL_OK){Error_Handler();}state = HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);if (state != HAL_OK){Error_Handler();}/* USER CODE END CAN_Init 2 *//* USER CODE END CAN_Init 2 */
}
关于接收滤波器说明与设置:
参数 | 说明 |
FilterMode (过滤模式) | 可选值: CAN_FILTERMODE_IDMASK(掩码模式) CAN_FILTERMODE_IDLIST(列表模式) 作用: 掩码模式:通过FilterId和FilterMask组合定义ID范围(类似通配符规则) 示例:FilterId=0x100, FilterMask=0xFF00 → 接收ID范围0x100~0x1FF 列表模式:精确匹配FilterId和FilterMask中指定的ID(最多2个32位ID或4个16位ID) 示例:FilterId=0x123, FilterMask=0x456 → 仅接收ID为0x123或0x456的帧 |
FilterScale (过滤器尺度) | 可选值: CAN_FILTERSCALE_32BIT(32位宽过滤器) CAN_FILTERSCALE_16BIT(16位宽过滤器) 作用: 32位模式:单个过滤器可处理1个完整扩展帧ID(29位)或2个标准帧ID(11位)。 16位模式:单个过滤器可处理2个标准帧ID(11位)或部分扩展帧ID(需组合使用)。 选择建议: 扩展帧或复杂过滤规则 → 32位模式。 仅标准帧且需更多过滤组 → 16位模式。 |
FilterIdHigh FilterIdLow (过滤器ID值) | 作用:定义待匹配的基准ID值(具体解释取决于FilterMode和FilterScale) 32位模式下的存储规则 标准帧ID(11位): FilterIdHigh = (StdId << 5); // 左移5位对齐到寄存器位[15:5] FilterIdLow = 0x0000; // 低16位未使用 扩展帧ID(29位): FilterIdHigh = (ExtId >> 13) & 0xFFFF; // 高16位(bit28~bit13) FilterIdLow = ((ExtId << 3) & 0xFFF8) | 0x04; // 低13位(bit12~bit0)+ IDE=1 |
FilterMaskIdHigh FilterMaskIdLow (过滤器掩码) | 作用: 掩码模式:定义ID中哪些位必须匹配(1=必须匹配,0=不关心)。 示例:FilterId=0x100, FilterMask=0xFF00 → 匹配所有0x1XX的ID。 列表模式:作为第二个ID值(与FilterId组成精确匹配列表)。 特殊值: 全0x0000:接收所有ID(关闭过滤)。 全0xFFFF:精确匹配FilterId |
FilterFIFOAssignment (FIFO分配) | 可选值: CAN_RX_FIFO0 或 CAN_RX_FIFO1 作用: 指定匹配的报文存入哪个接收FIFO(STM32 CAN控制器有2个接收FIFO)。 推荐配置: 通常使用FIFO0,FIFO1可用于优先级更高的报文 |
FilterBank (过滤器组编号) | 取值范围: 单CAN设备:0~13(如STM32F103) 双CAN设备:0~27(如STM32F105) 作用: 选择具体的硬件过滤器组(STM32提供多个独立过滤器组,可并行工作) |
SlaveStartFilterBank (从CAN过滤器起始组) | 作用: 在双CAN模式(如CAN1+CAN2)下,定义从CAN实例(CAN2)的起始过滤器组编号。 示例:SlaveStartFilterBank=14 → CAN1用组0~13,CAN2用组14~27。 单CAN模式:此参数无效,但需保留(通常设为14) |
FilterActivation (过滤器激活) | 可选值: ENABLE:立即激活过滤器。 DISABLE:配置但不启用(需后续手动激活)。 关键点: 必须至少有一个激活的过滤器,否则CAN控制器会丢弃所有报文! |
2、添加中断回调函数
STM32 HAL库中CAN接收中断的回调函数,当CAN控制器的接收FIFO0(接收缓冲区0)中有新消息到达时,会自动触发此函数。
/* USER CODE BEGIN 4 */void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *CanHandle)
{/* Get RX message */if (HAL_CAN_GetRxMessage(CanHandle, CAN_RX_FIFO0, &RxHeader, RxData) != HAL_OK){/* Reception Error */Error_Handler();}// 接收到的数据:RxData[8]// 接收到的ID值:RxHeader.StdId
}/* USER CODE END 4 */
3、发送数据
将CAN报文放入发送邮箱,由硬件自动发送到总线上
非阻塞式发送:函数仅将数据存入CAN控制器的发送邮箱,实际发送由硬件完成,无需CPU持续等待。
if (HAL_CAN_AddTxMessage(&hcan, &txHeader, TxData, &TxMailbox) != HAL_OK){Error_Handler();}
4、整个例程
可以通过调试查看接收的数据是否对应发送数据。
/* USER CODE BEGIN Header */
/********************************************************************************* @file : main.c* @brief : Main program body******************************************************************************* @attention** Copyright (c) 2025 STMicroelectronics.* All rights reserved.** This software is licensed under terms that can be found in the LICENSE file* in the root directory of this software component.* If no LICENSE file comes with this software, it is provided AS-IS.********************************************************************************/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes *//* USER CODE END Includes *//* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD *//* USER CODE END PTD *//* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD *//* USER CODE END PD *//* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM *//* USER CODE END PM *//* Private variables ---------------------------------------------------------*/
CAN_HandleTypeDef hcan;/* USER CODE BEGIN PV *//* USER CODE END PV *//* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_CAN_Init(void);
/* USER CODE BEGIN PFP *//* USER CODE END PFP *//* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */CAN_TxHeaderTypeDef txHeader ={.StdId = 0x123,.ExtId = 0x00,.IDE = CAN_ID_STD,.RTR = CAN_RTR_DATA,.DLC = 8,.TransmitGlobalTime = DISABLE};
CAN_RxHeaderTypeDef RxHeader;
uint8_t TxData[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; // 定义数据
uint8_t RxData[8];
uint32_t TxMailbox;/* USER CODE END 0 *//*** @brief The application entry point.* @retval int*/
int main(void)
{/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();MX_CAN_Init();/* USER CODE BEGIN 2 *//* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */if (HAL_CAN_AddTxMessage(&hcan, &txHeader, TxData, &TxMailbox) != HAL_OK){Error_Handler();}HAL_Delay(2000);HAL_GPIO_TogglePin(SYS_LED_GPIO_Port, SYS_LED_Pin);}/* USER CODE END 3 */
}/*** @brief System Clock Configuration* @retval None*/
void SystemClock_Config(void)
{RCC_OscInitTypeDef RCC_OscInitStruct = {0};RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};/** Initializes the RCC Oscillators according to the specified parameters* in the RCC_OscInitTypeDef structure.*/RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;RCC_OscInitStruct.HSEState = RCC_HSE_ON;RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;RCC_OscInitStruct.HSIState = RCC_HSI_ON;RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK){Error_Handler();}/** Initializes the CPU, AHB and APB buses clocks*/RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK){Error_Handler();}
}/*** @brief CAN Initialization Function* @param None* @retval None*/
static void MX_CAN_Init(void)
{/* USER CODE BEGIN CAN_Init 0 *//* USER CODE END CAN_Init 0 *//* USER CODE BEGIN CAN_Init 1 *//* USER CODE END CAN_Init 1 */hcan.Instance = CAN1;hcan.Init.Prescaler = 4;hcan.Init.Mode = CAN_MODE_LOOPBACK;hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;hcan.Init.TimeSeg1 = CAN_BS1_5TQ;hcan.Init.TimeSeg2 = CAN_BS2_3TQ;hcan.Init.TimeTriggeredMode = DISABLE;hcan.Init.AutoBusOff = DISABLE;hcan.Init.AutoWakeUp = ENABLE;hcan.Init.AutoRetransmission = DISABLE;hcan.Init.ReceiveFifoLocked = DISABLE;hcan.Init.TransmitFifoPriority = DISABLE;if (HAL_CAN_Init(&hcan) != HAL_OK){Error_Handler();}/* USER CODE BEGIN CAN_Init 2 *//* USER CODE BEGIN CAN_Init 2 */CAN_FilterTypeDef sFilterConfig;sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;sFilterConfig.FilterIdHigh = 0x0000; // 可以保持为0接收所有IDsFilterConfig.FilterIdLow = 0x0000;sFilterConfig.FilterMaskIdHigh = 0x0000;sFilterConfig.FilterMaskIdLow = 0x0000;sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;sFilterConfig.FilterBank = 0; // 添加过滤器组号sFilterConfig.SlaveStartFilterBank = 14;sFilterConfig.FilterActivation = ENABLE;HAL_StatusTypeDef state = HAL_CAN_ConfigFilter(&hcan, &sFilterConfig);if (state != HAL_OK){Error_Handler();}state = HAL_CAN_Start(&hcan);if (state != HAL_OK){Error_Handler();}state = HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);if (state != HAL_OK){Error_Handler();}/* USER CODE END CAN_Init 2 *//* USER CODE END CAN_Init 2 */
}/*** @brief GPIO Initialization Function* @param None* @retval None*/
static void MX_GPIO_Init(void)
{GPIO_InitTypeDef GPIO_InitStruct = {0};/* USER CODE BEGIN MX_GPIO_Init_1 *//* USER CODE END MX_GPIO_Init_1 *//* GPIO Ports Clock Enable */__HAL_RCC_GPIOC_CLK_ENABLE();__HAL_RCC_GPIOD_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();/*Configure GPIO pin Output Level */HAL_GPIO_WritePin(SYS_LED_GPIO_Port, SYS_LED_Pin, GPIO_PIN_SET);/*Configure GPIO pin : SYS_LED_Pin */GPIO_InitStruct.Pin = SYS_LED_Pin;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(SYS_LED_GPIO_Port, &GPIO_InitStruct);/* USER CODE BEGIN MX_GPIO_Init_2 *//* USER CODE END MX_GPIO_Init_2 */
}/* USER CODE BEGIN 4 */void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *CanHandle)
{/* Get RX message */if (HAL_CAN_GetRxMessage(CanHandle, CAN_RX_FIFO0, &RxHeader, RxData) != HAL_OK){/* Reception Error */Error_Handler();}// 接收到的数据:RxData[8]// 接收到的ID值:RxHeader.StdId
}/* USER CODE END 4 *//*** @brief This function is executed in case of error occurrence.* @retval None*/
void Error_Handler(void)
{/* USER CODE BEGIN Error_Handler_Debug *//* User can add his own implementation to report the HAL error return state */__disable_irq();while (1){}/* USER CODE END Error_Handler_Debug */
}#ifdef USE_FULL_ASSERT
/*** @brief Reports the name of the source file and the source line number* where the assert_param error has occurred.* @param file: pointer to the source file name* @param line: assert_param error line source number* @retval None*/
void assert_failed(uint8_t *file, uint32_t line)
{/* USER CODE BEGIN 6 *//* User can add his own implementation to report the file name and line number,ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) *//* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
四、整个通信逻辑
CAN通信关键环节详解
1、发送端逻辑
步骤 | 操作 | 硬件/软件 | 校验机制 |
---|---|---|---|
组帧 | 填充ID、数据、帧类型(标准/扩展) | 软件 | 无 |
CRC计算 | 硬件自动计算15位CRC,附加到帧尾 | 硬件 | CRC校验字段 |
发送仲裁 | 竞争总线:低ID优先发送 | 硬件 | 无 |
2. 总线传输
广播特性:所有节点同时收到数据,但只有通过过滤的节点会处理。
物理层校验:
差分信号(CAN_H/CAN_L)抗干扰
显性电平(0)覆盖隐性电平(1)实现仲裁
3. 接收端逻辑
步骤 | 操作 | 硬件/软件 | 校验机制 |
---|---|---|---|
硬件过滤 | 比较报文ID与过滤器规则 | 硬件 | ID匹配 |
CRC校验 | 硬件自动验证CRC,错误则丢弃 | 硬件 | CRC校验 |
ACK响应 | 接收节点发送ACK位(显性0) | 硬件 | ACK确认 |
数据存储 | 通过校验的帧存入FIFO | 硬件 | 无 |
软件校验 | 检查DLC、数据有效性等 | 软件 | 自定义校验(如和校验) |
4. 硬件级校验
校验类型 | 层 | 实现方式 | 失败处理 |
---|---|---|---|
CRC校验 | 数据链路层 | 硬件计算15位CRC | 自动丢弃错误帧 |
ACK响应 | 数据链路层 | 接收节点发送ACK位 | 发送端重传 |
格式校验 | 数据链路层 | 检查帧格式(如EOF) | 触发错误帧 |
5、错误处理机制
错误类型 | 检测方式 | 处理动作 |
---|---|---|
CRC错误 | 硬件自动检测 | 丢弃帧,错误计数器+1 |
ACK缺失 | 发送节点未检测到ACK位 | 自动重传(若AutoRetransmission=ENABLE) |
总线离线 | 错误计数器>255 | 进入Bus-Off状态,需复位或等待恢复 |
6、应用场景建议
汽车ECU:严格ID过滤 + CRC校验 + AutoRetransmission
工业控制:自定义软件校验 + 长TimeSeg1抗干扰
诊断工具:接收所有ID(FilterMask=0) + 软件解析
通过上述机制,CAN总线实现高可靠、实时的分布式通信。