STM32F103_LL库+寄存器学习笔记13 - 梳理外设CAN与如何发送CAN报文(串行发送)

news2025/7/18 5:43:23

导言


CAN总线因其高速稳定的数据传输与卓越抗干扰性能,在汽车、机器人及工业自动化中被广泛应用。它采用分布式网络结构,实现多节点间实时通信,确保各控制模块精准协同。在汽车领域,CAN总线连接发动机、制动、车身系统,保障车辆安全;在机器人和工业控制中,传感器与执行器间信息传递迅速,使其成为智能制造与自动化控制不可或缺的重要技术。

遗憾的是CubeMX不支持生成CAN总线的LL库代码。所以,梳理完HAL库的实现方式后,继续梳理寄存器方式的实现。
以下是本章节的效果,开发板每隔100ms往CAN总线发送一个报文。CANID是0x0123,数据帧,标准帧,长度0x08,内容是0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08。
在这里插入图片描述
项目地址:

  • (HAL库):https://github.com/q164129345/MCU_Develop/tree/main/stm32f103_hal_library13_Can_Send
  • (寄存器方式):https://github.com/q164129345/MCU_Develop/tree/main/stm32f103_ll_library13_Can_Send

一、CubeMX


1.1、Clock Configuration

在这里插入图片描述

1.2、Parameter Settings

在这里插入图片描述
采样点的设置是CAN稳定通讯的前提!
采样点的设置是CAN稳定通讯的前提!
采样点的设置是CAN稳定通讯的前提!
如上所示,配置的重点是4分频、Tq1 = 14Times、Tq2 = 3Times。此时采样点的计算如下:
采样点位置,采样点在同步段后的 Tq1 处,即 1 + 14 = 15 Tq,采样点比例 = 15 / 18 ≈ 83.3%。所以,Prescaler = 4, Tq1 = 14, Tq2 = 3 这种配置可以精确得到 500 kbps,并且采样点位于约 83.3% 的位置,这是一个较为合理的采样位置,既能保证波特率精确,又能提供足够的采样稳定性。参考我的另外一篇博文《CAN总线技术 | 物理层03 - 采样点》
在这里插入图片描述

二、代码(HAL库)


2.1、main.c

在这里插入图片描述
在这里插入图片描述
如上所示,通过HAL库来实现CAN发送的代码实在简单。

2.2、编译、下载代码

在这里插入图片描述
如上所示,编译通过。下载代码到开发板后,效果如下:
在这里插入图片描述

三、梳理CAN发送


3.1、发送邮箱

CAN发送比CAN接收要简单许多,所以先从简单的CAN发送开始梳理。
在这里插入图片描述
在这里插入图片描述
如上所示,CAN一共有三个发送邮箱,所以为什么函数Test_CAN_Send_Msg()里需要用HAL_CAN_GetTxMailboxesFreeLevel()去判断有没有空的发送邮箱,才能将需要发送的CAN报文丢进去发送邮箱。CAN的发送邮箱相当于串口的发送数据寄存器USART_TDR。只是CAN的发送邮箱一共有3个,而串口的发送数据寄存器USART_TDR只有一个。显然,CAN的发送缓存比串口的发送缓存要大得多。
请记住,软件只能将需要发送的CAN消息放到发送邮箱,并不是将CAN消息发送到总线。

3.2、发送优先级

在这里插入图片描述
如上所示,从《STM32F1参考手册》的章节22.7.1-发送处理看到,CAN消息的发送优先级有两种设置,通过寄存器CAN_MCR的位TXFP置1,开启FIFO模式。值得注意的是,寄存器CAN_MCR的位TXFP默认是置0,即标识符决定发送优先级。另外,标识符越小,优先级越高。
在这里插入图片描述

3.3、标识符是什么?

如下图所示,标识符就是CANID。
在这里插入图片描述

3.4、终止发送

在这里插入图片描述

3.5、禁止自动重传模式

在这里插入图片描述
默认是禁止自动重传模式的,禁止CAN自动重传功能的影响主要体现在以下几点:

  1. 消息丢失风险增大
    当一帧CAN报文发送失败(例如由于总线干扰或仲裁错误)时,如果自动重传被禁用,硬件不会重新尝试发送这帧报文,导致该报文直接丢失。此时,如果没有额外的软件层重传机制,就可能影响通信的可靠性。
  2. 降低传输延时
    在一些实时性要求高的应用中,自动重传可能会引入额外延时。禁用自动重传可以使得一旦检测到发送失败就立即放弃,从而避免因连续重传而延迟后续消息的发送。
  3. 简化错误处理逻辑
    禁用自动重传后,发送操作完成后,邮箱状态总会变为空置(无论是成功发送或发送失败),这对某些系统来说可以简化对邮箱状态的监控和管理。不过,这也意味着应用层需要增加判断和处理发送失败的逻辑,以决定是否进行重发或者采取其他补救措施。
  4. 总线负载和资源利用
    自动重传在错误发生时可能导致总线负载增加,尤其在总线故障或者干扰情况下频繁重传会占用大量资源。禁用自动重传可以防止这种情况,但同时需要确保上层有机制去检测和响应通信错误。总之,禁用自动重传功能是一种在追求低延时和精细控制CAN通信时的取舍,但需要权衡消息可靠性和实时性,通常需要在应用层增加适当的错误检测和补救机制。
    在这里插入图片描述

3.6、500K波特率,发送8个字节的CAN标准帧消息,需要多长时间?

理论上,我们可以计算标准CAN数据帧的位数,再乘以每位的传输时间。对于标准CAN数据帧(CAN 2.0A),各部分位数通常为:

  • 起始位 (SOF):1位
  • 仲裁段:11位标识符 + 1位远程传输请求(RTR) = 12位
  • 控制段:6位(包括IDE、保留位以及4位数据长度代码 DLC)
  • 数据段:8字节 × 8 = 64位
  • CRC段:15位CRC + 1位CRC分隔符 = 16位
  • 确认段 (ACK):2位
  • 结束位 (EOF):7位
    总计:1 + 12 + 6 + 64 + 16 + 2 + 7 = 108位

在500k波特率下,每位传输时间为:1 / 500,000 = 2微秒。因此,理论上传输108位的CAN报文所需时间为:108 × 2μs = 216微秒。注意:这只是理论传输时间,实际情况还可能受到位填充、总线仲裁和间隔等因素影响,但理论上就是216微秒

四、寄存器梳理


4.1、配置时钟

4.1.1、开启CAN时钟

在这里插入图片描述
在这里插入图片描述

RCC->APB1ENR |= (1UL << 25UL); // 开启CAN时钟

4.1.2、开启GPIOA时钟

在这里插入图片描述
在这里插入图片描述

RCC->APB2ENR |= (1UL << 2UL); // 开启GPIOA时钟,因为CAN使用PA11与PA12端口

4.2、配置GPIO

4.2.1、CAN_REMAP决定使用哪个GPIO口

在这里插入图片描述
在这里插入图片描述
如上所示,CAN_REMAP默认状态就是00,所以CAN1使用PA11与PA12。如需复用其他GPIO口,按照表格去修改GPIO_REMAP即可。

4.2.2、GPIO配置

在这里插入图片描述
如上图所示,CAN_TX与CAN_RX有对应的GPIO口模式

    /* 2. 配置PA11(CAN_RX)为上拉输入、PA12(CAN_TX)为复用推挽输出 */
    // PA11: CRH[15:12], MODE=00, CNF=10(上拉输入)
    GPIOA->CRH &= ~(0xF << 12);
    GPIOA->CRH |=  (0x8 << 12);
    GPIOA->ODR |=  (1UL << 11); // 上拉
    // PA12: CRH[19:16], MODE=11(50MHz), CNF=10(复用推挽)
    GPIOA->CRH &= ~(0xF << 16);
    GPIOA->CRH |=  (0xB << 16);

4.3、配置CAN

4.3.1、退出睡眠模式

在这里插入图片描述
如上所示,进入初始化之前必须先退出睡眠模式,避免进入初始化模式失败。

if (CAN1->MSR & (1UL << 1)) { // 检查 MSR.SLAK 是否为 1
	CAN1->MCR &= ~(1UL << 1);  // 清除 SLEEP 位
	while (CAN1->MSR & (1UL << 1));  // 等待 MSR.SLAK 变 0
}

4.3.2、进入初始化模式

在这里插入图片描述
在这里插入图片描述

/* 3. 进入初始化模式 */
CAN1->MCR |= (1UL << 0);            // 请求进入INIT (MCR.INRQ=1)
while (!(CAN1->MSR & (1UL << 0)));  // 等待INAK=1 (MSR.INAK=1)

4.3.3、关闭时间触发模式

在这里插入图片描述

CAN1->MCR &= ~(1UL << 7);  // 清除TTCM位(时间触发模式)

4.3.4、自动离线管理模式

在这里插入图片描述

CAN1->MCR &= ~(1UL << 6);  // 清除ABOM位(自动离线管理模式)

4.3.5、自动唤醒

在这里插入图片描述

CAN1->MCR &= ~(1UL << 5);  // 清除AWUM位(软件自动唤醒)

4.3.6、接收FIFO锁定模式

在这里插入图片描述

CAN1->MCR &= ~(1UL << 3);  // 清除RFLM位(接收FIFO设置新报文覆盖旧报文)

4.3.7、发送FIFO优先级

在这里插入图片描述
在这里插入图片描述

CAN1->MCR &= ~(1UL << 2);  // 清除TXFP位(发送FIFO优先级由标识符来决定)

4.3.8、禁止报文自动重传

在这里插入图片描述
在这里插入图片描述

CAN1->MCR |= (1UL << 4); // CAN报文只发送一次

4.3.9、位时序(重点!!!!)

位时序设置规则参考《CAN总线技术 | 物理层03 - 采样点》
在这里插入图片描述
如上所示,相当于CubeMX里的Bit Timings Parameters。
在这里插入图片描述
在这里插入图片描述

    4. 设置BTR=0x002D0003
       - SJW=0  => 1Tq
       - TS2=0x02 => 2 => 3Tq
       - TS1=0x0D => 13 => 14Tq
       - BRP=0x03 => 3 => 分频=4
    */
    CAN1->BTR = (0x00 << 24) |  // SILM(31) | LBKM(30) = 0
            (0x00 << 22) |  // SJW(23:22) = 0 (SJW = 1Tq)
            (0x02 << 20) |  // TS2(22:20) = 2 (TS2 = 3Tq)
            (0x0D << 16) |  // TS1(19:16) = 13 (TS1 = 14Tq)
            (0x0003);       // BRP(9:0) = 3 (Prescaler = 4)

4.3.10、退出初始化模式、进入正常模式

在这里插入图片描述
如上所示,软件对寄存器CAN_MCR的位INRQ清0时,会退出初始化模式,进入工作模式。

CAN1->MCR &= ~(1UL << 0);  // 清除 INRQ (进入正常模式)
while (CAN1->MSR & (1UL << 0)); // 等待 MSR.INAK 变 0

4.3.11、设置过滤器0

在这里插入图片描述
如上所示,STM32F103ZET6一共有14个过滤器,但CAN要能正常收发,必须至少要设置一个过滤器。

所有过滤器进入初始化模式

在这里插入图片描述
如上所示,通过寄存器CAN_FMR的位0-FINIT置1,让所有过滤器组进入初始化模式。

CAN1->FMR |= (1UL << 0);   // 进入过滤器初始化模式
设置过滤器组0通过所有标识符(CANID),即不过滤
CAN1->sFilterRegister[0].FR1 = 0x00000000;
CAN1->sFilterRegister[0].FR2 = 0x00000000;

如上所示,数组0代表过滤器组0。当FR1与FR2都设置0x00000000时,代表不过滤任何CANID,即所有CANID都会被接收。此时,如果CAN总线上有很多高频的CAN消息的话,CAN中断会非常频繁地进入,极大地浪费MCU的资源。
后续,会弄一篇笔记,讲讲怎样设置过滤器组,让开发板只接收感兴趣的CANID,而不是所有的CANID。

过滤器组0匹配FIFO0

在这里插入图片描述

CAN1->FFA1R &= ~(1UL << 0);  // 过滤器组0 分配到 FIFO0
激活过滤器组0

在这里插入图片描述

CAN1->FA1R  |=  (1UL << 0);  // 激活过滤器 0
所有过滤器组退出初始化模式

在这里插入图片描述

CAN1->FMR   &= ~(1UL << 0);  // 退出过滤器初始化模式

4.4、发送CAN报文

4.4.1、确认邮箱是不是空闲

在这里插入图片描述
如上所示,通过判断寄存器CAN_TIxR的bit0-TXRQ是不是等于0,来确认发送邮箱是不是空闲的。

/* 寻找空闲邮箱 */
for(mailbox = 0; mailbox < 3; mailbox++) {
  if((CAN1->sTxMailBox[mailbox].TIR & (1UL << 0)) == 0)
	 break;
}
if(mailbox >= 3)
	return 1; // 无空闲邮箱

4.4.2、清空某个发送邮箱

在这里插入图片描述
将发送邮箱的4个寄存器都清0即可,包括TIR、TDTR、TDLR、TDHR。

/* 清空该邮箱 */
CAN1->sTxMailBox[mailbox].TIR  = 0;
CAN1->sTxMailBox[mailbox].TDTR = 0;
CAN1->sTxMailBox[mailbox].TDLR = 0;
CAN1->sTxMailBox[mailbox].TDHR = 0;

4.4.3、设置将要发送的CANID、CAN帧类型

在这里插入图片描述

CAN1->sTxMailBox[mailbox].TIR |= (stdId << 21);  // 标准ID写入TIR的[31:21]、IDE=0相当于标准帧、RTR=0相当于数据帧

4.4.4、设置CAN报文长度

在这里插入图片描述

CAN1->sTxMailBox[mailbox].TDTR = (DLC & 0x0F); // 设置CAN报文的长度。使用&运算的目的是保证只有变量DLC的低四位写入TDTR寄存器,不会干涉到其他位。

4.4.5、将要发送的数据放入发送邮箱

在这里插入图片描述

/* 填充数据 */
if(DLC <= 4) {
	for(uint8_t i = 0; i < DLC; i++) {
	  CAN1->sTxMailBox[mailbox].TDLR |= ((uint32_t)data[i]) << (8 * i);
	}
} else {
	for(uint8_t i = 0; i < 4; i++) {
	  CAN1->sTxMailBox[mailbox].TDLR |= ((uint32_t)data[i]) << (8 * i);
	}
	for(uint8_t i = 4; i < DLC; i++) {
	  CAN1->sTxMailBox[mailbox].TDHR |= ((uint32_t)data[i]) << (8 * (i-4));
	}
}

4.4.6、请求发送数据

在这里插入图片描述

CAN1->sTxMailBox[mailbox].TIR |= CAN_TI0R_TXRQ;

4.4.7、等待邮箱的CAN消息被成功发送(可选)

在这里插入图片描述

/* 轮询等待TXRQ清零或超时 */
while((CAN1->sTxMailBox[mailbox].TIR & CAN_TI0R_TXRQ) && --timeout);
if(timeout == 0) {
	// 发送失败(无ACK或位错误), 返回2
	return 2;
}
为什么要等待TXRQ清零?

TXRQ 位的作用是在发送请求时置 1,并在以下情况下自动清零:

  1. 报文成功发送(总线空闲时成功仲裁,并收到 ACK)。
  2. 发送失败(无 ACK 或仲裁失败):
    • 若 NART=0(自动重传开启),CAN 硬件会自动重试,直到发送成功。
    • 若 NART=1(自动重传关闭),发送失败时 TXRQ 也会清零,并可能产生错误标志(TERR、ALST、REC/TEC 递增等)。
  3. 软件手动中止发送(通过设置 ABRQ 置 1 来取消发送)。

如果不等待 TXRQ 清零,可能发生:

  • 报文未成功发送(因无ACK、错误等原因),但代码并不知道,导致误以为报文已经发送成功;
  • CAN 总线忙碌,报文未立即发送,但代码已经继续执行其他任务,可能影响数据完整性。
什么时候可以不等 TXRQ 清零?
  1. 如果程序不关心发送是否成功(只管发,不管 ACK),可以不等 TXRQ 清零。
  2. 如果使用中断模式(而非轮询),可以不在此等待 TXRQ,而是注册 CAN 发送完成中断(最常用!!!!!!)。
  3. 如果应用层通过 TSR(Transmit Status Register)等方式定期检查发送状态,而不依赖 TXRQ 位轮询。
什么时候必须等 TXRQ 清零?
  1. 需要确认报文已发送完毕(尤其是 NART=1 时,若无 ACK 会导致发送失败)。
  2. 要确保 FIFO 发送顺序正确(如果多个报文依次发送,等待 TXRQ 清零可确保当前报文已经结束)。
  3. 应用层需要可靠的反馈(如果 TXRQ 持续置位,说明发送失败,应触发错误处理机制)。

五、代码(寄存器方式)


5.1、main.c

在这里插入图片描述
在这里插入图片描述
如上所示,函数CAN_Config()将CAN设置好,并进入正常工作模式。波特率500K,其他小功能全部关闭,且不过滤任何CANID。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.2、编译、调试

在这里插入图片描述
编译OK,下载程序到开发板,效果如下所示:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2328735.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux系统调用编程

文章目录 一、进程和线程二、Linux的虚拟内存管理和stm32的真实物理内存**Linux虚拟内存管理**STM32物理内存映射2. 主要区别 三、Linux系统调用函数 fork()、wait()、exec()1. fork()&#xff1a;创建子进程2. wait()&#xff1a;等待子进程状态改变3. exec()&#xff1a;替换…

游戏引擎学习第203天

回顾当前情况 在这里我将直播完成整个游戏的制作。我们现在面临一些技术上的困难&#xff0c;确实如此。我的笔记本电脑的电源接口坏了&#xff0c;所以我不得不准备了这台备用笔记本&#xff0c;希望它能够正常工作。我所以希望一切都还好&#xff0c;尽管我不完全确定是否一…

深度学习数据集划分比例多少合适

在机器学习和深度学习中&#xff0c;测试集的划分比例需要根据数据量、任务类型和领域需求灵活调整。 1. 常规划分比例 通用场景 训练集 : 验证集 : 测试集 60% : 20% : 20% 适用于大多数中等规模数据集&#xff08;如数万到数十万样本&#xff09;&#xff0c;平衡了训练数…

CExercise_1_5 水仙花数

题目&#xff1a; 经典循环案例&#xff1a;请求出所有的水仙花数&#xff0c;并统计总共有几个。 所谓的水仙花数是指一个三位数&#xff0c;其各位数字的立方和等于该数本身。 举例&#xff1a;153就是一个水仙花数&#xff0c;153 1 * 1 * 1 5 * 5 * 5 3 * 3 * 3 1 125…

哈密尔顿路径(Hamiltonian Path)及相关算法题目

哈密尔顿路径要求访问图中每个顶点恰好一次&#xff0c;通常用于解决旅行商问题&#xff08;TSP&#xff09;或状态压缩DP问题。 哈密尔顿路径&#xff08;Hamiltonian Path&#xff09;是指在一个图中经过每个顶点恰好一次的路径。如果这条路径的起点和终点相同&#xff08;即…

MINIQMT学习课程Day10

开始获取股票数据课程的学习&#xff1a; 获取qmt账号的持仓情况后&#xff0c;我们进入下一步&#xff0c;如何获得当前账号的委托状况 还是之前的步骤&#xff0c;打开qmt&#xff0c;选择独立交易&#xff0c; 之后使用pycharm&#xff0c;编写py文件 导入包&#xff1a…

JAVA实战开源项目:智慧图书管理系统(Vue+SpringBoot) 附源码

本文项目编号 T 152 &#xff0c;文末自助获取源码 \color{red}{T152&#xff0c;文末自助获取源码} T152&#xff0c;文末自助获取源码 目录 一、系统介绍二、数据库设计三、配套教程3.1 启动教程3.2 讲解视频3.3 二次开发教程 四、功能截图五、文案资料5.1 选题背景5.2 国内…

Linux 系统管理综合实训 —— 基于 NAT 模式的多 IP 配置、Nginx 服务部署及存储管理

1. 虚拟机网络配置&#xff1a;NAT模式与多IP地址设置 将你的虚拟机的网卡模式设置为nat模式&#xff0c;给虚拟机网卡配置三个主机位分别为100、200、168的ip地址 设置静态IP [rootlocalhost ~]# nmcli c modify ens160 ipv4.method manual ipv4.addresses 192.168.2.100/2…

如何在windows 环境、且没有显卡的情况下用python跑通从ModelScope下载的大模型的调用

文章目录 背景介绍源代码&#xff1a;安装调试过程1.设置第三方镜像源2.预先安装&#xff1a;3.在python中创建代码&#xff1a;4.最终修改程序,将device_map从“cuda”改成“auto”&#xff0c;大模型调用1.5B&#xff08;1___5B)的5.最终跑出结果解释&#xff1a;示例&#x…

黑马点评redis改 part 1

本篇将主要阐述短信登录的相关知识&#xff0c;感谢黑马程序员开源&#xff0c;感谢提供初始源文件&#xff08;给到的是实战第7集开始的代码&#xff09;【Redis实战篇】黑马点评学习笔记&#xff08;16万字超详细、Redis实战项目学习必看、欢迎点赞⭐收藏&#xff09;-CSDN博…

【Ragflow】11. 文件解析流程分析/批量解析实现

概述 本文继续对ragflow文档解析部分进行分析&#xff0c;并通过脚本的方式实现对文件的批量上传解析。 文件解析流程 文件解析的请求处理流程大致如下&#xff1a; 1.前端上传文件&#xff0c;通过v1/document/run接口&#xff0c;发起文件解析请求 2.后端api\apps\docum…

第三期:深入理解 Spring Web MVC [特殊字符](数据传参+ 特殊字符处理 + 编码问题解析)

✨前言&#xff1a;传参和状态管理&#xff0c;看似简单其实门道不少 在 Web 开发中&#xff0c;前端和后端最核心的交流方式就是“传参”&#xff0c;而“传参”除了涉及如何写代码获取参数&#xff0c;还藏着很多开发者容易忽略的细节&#xff1a; 为什么 URL 带了中文&…

Everything 安装教程与使用教程(附安装包)

文章目录 前言一、Everything 介绍二、Everything 安装教程1.Everything 安装包下载2.选择安装文件3.选择安装语言4.接受许可协议5.选择安装位置6.配置安装选项7.完成安装 三、Everything 使用教程1.启动软件2.简单关键词搜索3.按类型搜索 前言 在日常使用电脑时&#xff0c;随…

SQL语句(三)—— DQL

目录 基本语法 一、基础查询 1、查询多个字段 2、字段设置别名 3、去除重复记录 4、示例代码 二、条件查询 1、语法 2、条件列表常用的运算符 3、示例代码 三、分组查询 &#xff08;一&#xff09;聚合函数 1、介绍 2、常见的聚合函数 3、语法 4、示例代码 &…

Opencv计算机视觉编程攻略-第九节 描述和匹配兴趣点

一般而言&#xff0c;如果一个物体在一幅图像中被检测到关键点&#xff0c;那么同一个物体在其他图像中也会检测到同一个关键点。图像匹配是关键点的常用功能之一&#xff0c;它的作用包括关联同一场景的两幅图像、检测图像中事物的发生地点等等。 1.局部模板匹配 凭单个像素就…

汇编学习之《push , pop指令》

学习本章前线了解ESP, EBP 指令 汇编学习之《指针寄存器&大小端学习》-CSDN博客 栈的特点&#xff1a; 好比一个垂直容器&#xff0c;可以陆续放入物体&#xff0c;但是先放的物体通常会被后面放的物体压着&#xff0c;只有等上面后放的物品拿出来后&#xff0c;才能…

Python循环控制语句

1. 循环类型概述 Python提供两种主要的循环结构&#xff1a; while循环 - 在条件为真时重复执行for循环 - 遍历序列中的元素 2. while循环 基本语法 while 条件表达式:循环体代码示例 count 0 while count < 5:print(f"这是第{count1}次循环")count 13. f…

微信小程序(下)

目录 在事件处理函数中为 data 中的数据赋值 事件传参 bindinput 的语法格式 实现文本框和 data 之间的数据同步 条件渲染 结合 使用 wx:if hidden wx:if与 hidden 的对比 wx:for 手动指定索引和当前项的变量名 wx:key 的使用 WXSS 和 CSS 的关系 什么是 rpx 尺寸…