CAN 总线协议原理与嵌入式实现全攻略
CAN 总线协议原理与嵌入式实现全攻略
一、CAN 总线基本概念、历史与典型应用场景
1.1 CAN 总线基本概念
CAN(Controller Area Network,控制器局域网络)是一种多主从、多节点、差分传输的通信协议,主要用于汽车、工业、医疗等领域。它具有高可靠性、实时性、灵活性和扩展性等特点。
1.2 CAN 总线历史
CAN 总线由德国博世公司于1983年提出,最初用于汽车行业。经过多年的发展,CAN 总线已成为汽车电子领域的事实标准。
1.3 典型应用场景
- 汽车:发动机控制、车身控制、安全系统等。
- 工业:生产线自动化、机器人控制、工业控制等。
- 医疗:医疗设备、监护系统等。
二、物理层
2.1 差分信号
CAN 总线采用差分信号传输,即 CAN_H(高电平线)和 CAN_L(低电平线)两根双绞线。差分信号具有抗干扰能力强、传输距离远(最长约 1km)等优点。
2.2 显性电平与隐性电平
CAN 用两根线的电压差来表示逻辑电平:
- **隐性(Recessive)**:CAN_H 和 CAN_L 都约为 2.5V,差分电压 Vdiff ≈ 0V,对应逻辑 1。
- **显性(Dominant)**:CAN_H 拉高(约 3.5V)、CAN_L 拉低(约 1.5V),差分电压 Vdiff ≈ 2V,对应逻辑 0。
显性电平会覆盖隐性电平,这正是 CAN 无损仲裁(按位仲裁)的物理基础。
2.3 终端电阻120欧姆
CAN 总线两端需要连接120欧姆的终端电阻,用于抑制信号反射。
2.4 总线拓扑
CAN 总线采用总线拓扑,所有节点连接在同一条总线上。
三、数据链路层
3.1 帧格式
CAN 总线帧格式包括标准帧和扩展帧。标准帧ID为11位,扩展帧ID为29位。
3.2 数据帧/远程帧/错误帧/过载帧
- 数据帧:用于传输数据。
- 远程帧:请求发送数据帧。
- 错误帧:用于标识错误。
- 过载帧:用于请求总线空闲。
3.3 仲裁机制(CSMA/CA)
CAN 总线采用 CSMA/CA(载波侦听多址访问/碰撞避免)仲裁机制,确保数据传输的可靠性。
3.4 位填充
CAN 总线采用位填充机制,防止数据帧中出现连续的多个相同电平位。
四、波特率与位时序
4.1 位时序:SYNC_SEG、PROP_SEG、PHASE_SEG1/2 与 SJW
一个位时间被划分为若干时间量子(TQ,Time Quantum):
- **同步段(SYNC_SEG)**:固定 1 个 TQ,用于总线电平跳变的同步。
- **传播段(PROP_SEG)**:补偿收发器与总线上的信号传播延迟。
- **相位缓冲段 1/2(PHASE_SEG1/2)**:用于采样点定位,可被 SJW 调整以容忍时钟漂移。
- **SJW(同步跳转宽度)**:重同步时可缩短/延长相位段的 TQ 数,SJW 越大抗时钟漂移能力越强,但会降低容错精度。
4.2 采样点计算
采样点(Sample Point)是位时间中真正读取电平的时刻,工程上推荐落在 75% ~ 87.5% 区间。计算公式为:
采样点 = (SYNC_SEG + PROP_SEG + PHASE_SEG1) / (SYNC_SEG + PROP_SEG + PHASE_SEG1 + PHASE_SEG2) × 100%
以 STM32 常见配置为例:SYNC_SEG=1TQ, TSEG1=7TQ, TSEG2=2TQ,则总位时间为 10 TQ,采样点 = (1+7)/10 = 80%,处于推荐区间。波特率由分频系数决定:波特率 = APB 时钟 / (Prescaler × 总 TQ 数),例如 36MHz/(16 × 10)= 225kbps。
五、CAN 2.0A/B 与 CAN FD、CANopen、J1939 协议栈简介
5.1 CAN 2.0A/B
CAN 2.0A/B 是 CAN 总线的两个版本,分别支持11位和29位ID。
5.2 CAN FD
CAN FD(Flexible Data-Rate)是 CAN 总线的扩展版本,支持更高的数据传输速率。
5.3 CANopen
CANopen 是一种基于 CAN 总线的通信协议,广泛应用于工业自动化领域。
5.4 J1939
J1939 是一种用于汽车行业的通信协议,基于 CAN 总线。
六、嵌入式实现
6.1 STM32 bxCAN/FDCAN 外设配置
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
/** Configure the main internal clock source
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
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();
}
/** Initialize 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();
}
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_CAN1;
PeriphClkInitStruct.Can1ClockSelection = RCC_CAN1CLKSOURCE_PCLK1;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
{
Error_Handler();
}
}
void MX_CAN1_Init(void)
{
CAN_HandleTypeDef hcan1;
hcan1.Instance = CAN1;
hcan1.Init.Prescaler = 16;
hcan1.Init.Mode = CAN_MODE_NORMAL;
hcan1.Init.SyncJumpWidth = CAN SJW_1TQ;
hcan1.Init.TimeSeg1 = CAN_TSEG1_7TQ;
hcan1.Init.TimeSeg2 = CAN_TSEG2_2TQ;
hcan1.Init.TimeTriggeredMode = DISABLE;
hcan1.Init.AutoBusOff = DISABLE;
hcan1.Init.AutoWakeUp = DISABLE;
hcan1.Init.AutoRetransmission = DISABLE;
hcan1.Init.ReceiveFifoLocked = DISABLE;
hcan1.Init.TransmitFifoPriority = DISABLE;
if (HAL_CAN_Init(&hcan1) != HAL_OK)
{
Error_Handler();
}
}
6.2 收发流程
void CAN_Send(void)
{
CAN_TxHeaderTypeDef TxHeader;
uint8_t TxData[8];
TxHeader.DLC = 8;
TxHeader.IDE = CAN_ID_STD;
TxHeader.RTR = CAN_RTR_DATA;
TxHeader.StdId = 0x123;
TxHeader.TransmitFifo = CAN_TX_FIFO0;
for (int i = 0; i < 8; i++)
{
TxData[i] = i;
}
if (HAL_CAN_Transmit(&hcan1, 10, &TxHeader, TxData) != HAL_OK)
{
// Error handling
}
}
void CAN_Receive(void)
{
CAN_RxHeaderTypeDef RxHeader;
uint8_t RxData[8];
if (HAL_CAN_Receive(&hcan1, CAN_RX_FIFO0, &RxHeader, RxData, 10) != HAL_OK)
{
// Error handling
}
}
6.3 过滤器配置
CAN_FilterTypeDef sFilterConfig;
sFilterConfig.FilterNumber = 0;
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
sFilterConfig.FilterIdHigh = 0x0000;
sFilterConfig.FilterIdLow = 0x0000;
sFilterConfig.FilterMaskIdHigh = 0x0000;
sFilterConfig.FilterMaskIdLow = 0x0000;
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
sFilterConfig.FilterActivation = ENABLE;
sFilterConfig.Bank = CAN_FILTER_BANK_0;
if (HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK)
{
// Error handling
}
6.4 中断处理
void CAN_IRQHandler(void)
{
HAL_CAN_IRQHandler(&hcan1);
}
void HAL_CAN_RxCpltCallback(CAN_HandleTypeDef *hcan)
{
// Callback function for receiving data
}
void HAL_CAN_TxCpltCallback(CAN_HandleTypeDef *hcan)
{
// Callback function for transmitting data
}
七、实际踩坑经验
7.1 终端电阻忘接
在实际项目中,曾因忘记连接终端电阻而导致 CAN 总线通信不稳定。解决方法是重新连接终端电阻,并检查总线拓扑。
7.2 波特率匹配
波特率不匹配会导致通信失败。解决方法是确保所有节点使用相同的波特率。
7.3 错误状态管理
CAN 总线存在多种错误状态,如仲裁丢失、错误帧接收等。解决方法是监控错误状态,并采取相应措施。
7.4 总线隔离(隔离收发器)
在恶劣环境下,使用隔离收发器可以提高 CAN 总线的可靠性。
7.5 EMC
电磁兼容性(EMC)是 CAN 总线设计中的重要因素。解决方法是采取屏蔽、接地等措施。
八、调试工具
8.1 CAN 分析仪
CAN 分析仪可以实时显示 CAN 总线上的数据,方便调试。
8.2 逻辑分析仪抓包
逻辑分析仪可以抓取 CAN 总线上的数据,并进行分析。
8.3 回环模式测试
回环模式测试可以验证 CAN 总线的通信功能。
九、总结
本文详细介绍了 CAN 总线协议原理与嵌入式实现,包括物理层、数据链路层、波特率与位时序、CAN 2.0A/B 与 CAN FD、CANopen、J1939 协议栈、嵌入式实现、实际踩坑经验和调试工具等内容。希望对嵌入式工程师在 CAN 总线设计和开发过程中有所帮助。