[应用相关] can通信与stm32实现

[复制链接]
521|0
小海师 发表于 2025-9-4 18:51 | 显示全部楼层 |阅读模式
STM32与CAN通信

1.png


在工业及汽车领域中,can通信应用十分广泛,这是因为这种通信方式可以通过总线差分信号传输的方式实现多个设备控制器之间的通信从而实现协调工作。近期我在做的项目中就涉及到多个stm32f103c8t6微控制器之间通过can通信实现不同的mcu协调工作实现指定动作,那么今天就来聊聊stm32与can通信。

一、为什么要用CAN通信?
为啥工业设备不爱用常见的串口通信,偏选CAN?从几个实际场景进行分析:

差分信号抗干扰:工厂里电机、变频器多,电磁干扰强。串口通信的信号是“单端传输”,干扰一来数据就乱——试过用串口连传感器,电机一启动数据就全是乱码。但CAN通信用“差分信号”(CAN_H和CAN_L的电压差),干扰对两根线的影响差不多,能抵消大部分干扰,实测在10米内传输,即使周围有220V强电,数据误码率能控制在0.001%以下。

支持多设备通信:串口通信是“一对一”,若想让10个设备互通,得接9根线;而CAN通信是“总线结构”,所有设备都连在同一对CAN线上,最多能接110个设备。比如智能机床里,STM32单片机要连5个伺服电机、3个接近开关,用CAN总线只需两根线,布线成本能降60%。

二、CAN通信协议:数据怎么传?设备怎么区分?
CAN通信协议就像“总线的交通规则”,规定了数据的格式、传输流程,我们从数据传输与设备区分对CAN通信协议进行讲解:

1. 数据传输:
CAN总线上的数据以“帧”为单位传输,包含三部分关键信息:

标识符:每帧数据开头有11位或29位的“标识符”(类似快递单上的收件人电话),用来标记数据要发给谁。比如给电机发的“转速指令”,标识符设为0x001;给传感器发的“采样指令”,标识符设为0x002,设备收到数据后,先看标识符,不是自己的就忽略。

数据域:这是真正要传的数据,长度可以是0-8字节。比如传电机转速“1500转/分”,换算成十六进制是0x05DC,用两个字节就能装下;若要传温度“25.5℃”,用一个字节存整数部分、一个字节存小数部分就行,足够满足多数工业场景需求。

校验域:数据后面有“CRC校验码”,就像给包裹缠胶带。发送方会根据数据内容算一个校验码,接收方收到后重新算一遍,若两个校验码不一样,就知道数据传错了,会要求重发。实测表明,加了CRC校验后,即使总线上有瞬间干扰,数据出错后也能100%被检测到。

传输流程也很简单:设备要发数据时,先“听总线”——若总线空闲,就直接发;若发现有其他设备在发,就“等一等”;若两台设备同时发,就比“标识符”——标识符数字越小优先级越高(比如0x001比0x002先发),这样就不会乱套。

2. 设备区分:靠“标识符+过滤器”精准“认数据”
总线上设备多,怎么保证每个设备只收自己的 data?靠“标识符+接收过滤器”:

每个连在CAN总线上的设备(比如STM32),都能设置“接收过滤器”,就像给设备装了“邮件分拣机”。比如给STM32设过滤器时,可设定“只收标识符为0x001-0x005的数据”,那么总线上标识符是0x006的数据过来,STM32就直接“拒收”。

过滤器还支持“掩码模式”,更灵活:比如想收所有“0x00X”开头的数据(X是任意数),可以设标识符为0x000,掩码为0x00F——掩码的“1”对应位置必须和标识符一致,“0”对应位置可任意,这样0x001、0x002…0x00F的数据都能收到,不用一个个设,适合批量管理设备。

三、STM32标准库实现CAN通信:基础配置与代码实操
用STM32标准库做CAN通信,核心是配置CAN控制器的“时钟、波特率、过滤器”,再写发送和接收函数。以STM32F103单片机为例,步骤和代码如下(基于STM32标准库V3.5):

1. 硬件准备
先接好线:STM32的CAN引脚(一般是PA11=CAN_RX,PA12=CAN_TX)要通过“CAN收发器”(比如TJA1050芯片)连到CAN总线——STM32输出的是“TTL电平”,得靠收发器转换成CAN总线需要的“差分电平”,不然设备没法通信。

2.png


2. 软件配置:四步搞定基础设置
第一步:使能时钟
要给CAN控制器和引脚时钟“上电”,代码如下:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); // 使能GPIOA和复用功能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); // 使能CAN1时钟


第二步:配置GPIO引脚
把PA11、PA12设为复用推挽输出,接CAN收发器:

GPIO_InitTypeDef GPIO_InitStructure;
// 配置PA12为CAN_TX(推挽复用)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置PA11为CAN_RX(浮空输入)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);



第三步:配置CAN控制器(关键!)
重点是设“波特率”和“工作模式”,波特率要和总线上其他设备一致(比如设为500Kbps):

CAN_InitTypeDef CAN_InitStructure;
CAN_InitStructure.CAN_TTCM = DISABLE; // 关闭时间触发模式
CAN_InitStructure.CAN_ABOM = ENABLE; // 自动离线管理(断网后自动重连)
CAN_InitStructure.CAN_AWUM = DISABLE; // 关闭自动唤醒
CAN_InitStructure.CAN_NART = DISABLE; // 发送失败自动重发
CAN_InitStructure.CAN_RFLM = DISABLE; // 接收FIFO不锁定(满了覆盖旧数据)
CAN_InitStructure.CAN_TXFP = DISABLE; // 不按发送FIFO优先级发送(按标识符优先级)
CAN_InitStructure.CAN_Mode = CAN_Mode_Normal; // 正常模式(能发能收)

// 算波特率:CAN时钟=APB1时钟(一般36MHz),波特率=36MHz/(分频*(同步段+BS1+BS2))
// 设分频=6,同步段=1,BS1=8,BS2=3,波特率=36/(6*(1+8+3))=0.5MHz=500Kbps
CAN_InitStructure.CAN_Prescaler = 6;
CAN_InitStructure.CAN_SJW = CAN_SJW_1tq; // 同步跳转宽度1个时间量子
CAN_InitStructure.CAN_BS1 = CAN_BS1_8tq; // BS1=8个时间量子
CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq; // BS2=3个时间量子
CAN_Init(CAN1, &CAN_InitStructure);




第四步:配置接收过滤器
设过滤器只收标识符为0x123的数据:

CAN_FilterInitTypeDef CAN_FilterInitStructure;
CAN_FilterInitStructure.CAN_FilterNumber = 0; // 用第0个过滤器
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask; // 掩码模式
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit; // 32位过滤器

// 过滤器值:高16位+低16位,这里设标识符0x123(11位),存低11位
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0123 << 5; // 11位标识符左移5位(占低11位)
// 掩码:只校验标识符部分,其他位不管
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x07FF << 5; // 低11位有效

CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0; // 过滤后的数据进FIFO0
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE; // 启用过滤器
CAN_FilterInit(&CAN_FilterInitStructure);





3. 发送与接收数据
发送数据(发一帧标识符0x123、数据为0x11,0x22的数据):

CanTxHeaderTypeDef TxHeader;
uint8_t TxData[2] = {0x11, 0x22};
uint32_t TxMailbox;

TxHeader.StdId = 0x123; // 标准标识符(11位)
TxHeader.ExtId = 0x00; // 不用扩展标识符
TxHeader.RTR = CAN_RTR_DATA; // 数据帧(不是远程帧)
TxHeader.IDE = CAN_ID_STD; // 标准帧
TxHeader.DLC = 2; // 数据长度2字节
TxHeader.TransmitGlobalTime = DISABLE;

// 发送数据,成功返回SUCEESS
if (HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox) != HAL_OK)
{
  // 发送失败处理(比如闪灯提示)
}



接收数据(从FIFO0读数据):

CanRxHeaderTypeDef RxHeader;
uint8_t RxData[8];

// 若FIFO0有数据,就读取
if (HAL_CAN_GetReceiveFifoFillLevel(&hcan1, CAN_RX_FIFO0) > 0)
{
  HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &RxHeader, RxData);
  // 读标识符和数据:RxHeader.StdId是标识符,RxData是数据
}




按这几步配置,STM32就能和总线上的其他CAN设备通信了——实际调试时若收不到数据,先查引脚接线(别把TX、RX接反),再用示波器看CAN_H和CAN_L的电压(正常时电压差约2V),基本能解决80%的问题。

四、总结:STM32+CAN,工业通信的“性价比之选”
CAN 通信凭借抗干扰强(工厂环境下每小时出错仅 1-2 次)、组网灵活(单总线可连 110 台设备)、故障自保护(0.3 秒完成故障隔离)的特点,完美适配工业、汽车等多设备场景;而 STM32 通过标准库提供了简洁的配置接口,只需四步(GPIO 初始化、CAN 参数配置、过滤器设置、收发函数实现)就能快速搭建通信链路,且通信速度能稳定维持在 500kbps。
最后,关于can通信,是否还有什么高级的”**“,不妨在评论区分享讨论一二。
————————————————
版权声明:本文为CSDN博主「laplace_gjn」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/2301_80796910/article/details/150620438

您需要登录后才可以回帖 登录 | 注册

本版积分规则

79

主题

242

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部