rt thread中的can通信 学习记录
本文介绍了基于STM32F429的CAN总线通信实现方法,重点分析了CAN协议的特点和RT-Thread操作系统下的开发流程。主要内容包括:1)CAN总线采用双线差分传输,支持多设备挂载和ID优先级仲裁机制;2)详细解析了5种CAN帧类型及其应用场景;3)阐述了RT-Thread中CAN设备驱动架构,包括接收中断处理、信号量同步和线程处理机制;4)提供了完整的CAN回环测试代码示例,涵盖设备初始化
can通信特点:两根线CANH+CANL,多设备挂在同一根线上,靠id识别数据
本历程采用:正点原子阿波罗stm32f429+can回环模式
回环模式:不需要经过具体的引脚
can的基础知识点:
1:优先级
两个以上的单元同时开始发送消息时,根据标识符 ID 决定优先级。ID 表示访问总线的消息的优先级。
理解:CAN 的数据帧里有一个 ID。在 CAN 里,ID 越小,优先级越高。例如,ID 为 0x01 的消息优先级高于 ID 为 0x10 的消息。ID 不仅用来表示优先级,在实际应用中通常用来表示“这条消息包含什么内容”(比如是发动机转速,还是车门状态)。
2:仲裁比较
两个以上的单元同时开始发送消息时,对各消息 ID 的每个位进行逐个仲裁比较。仲裁获胜...继续发送,仲裁失利...立刻停止并接收。
理解:这叫做线与机制。在物理电平上,逻辑 0 是显性电平,逻辑 1 是隐性电平。显性电平可以覆盖隐性电平。一边发送,一边回读。发送隐性电平(1)却读回显性电平(0)的节点,就知道自己输了(失去仲裁),立刻转为接收模式。
3:5种帧
数据帧:用于发送节点向接收节点传输实际的数据。在 RT-Thread 中,你配置 rt_can_msg 结构体,填入 id、设置 rtr = RT_CAN_DTR(表示数据帧),并填入你的业务数据(比如温度传感器的值),然后发送。
遥控帧:遥控帧没有数据段。它的结构和数据帧很像,但它的 RTR(远程传输请求)位是隐性电平(1)。当某个节点收到与自己 ID 匹配的遥控帧时,它应该立刻回复一个包含实际数据的数据帧。
错误帧:用于在接收或发送消息时检测出错误,并通知总线上的所有其他节点。CAN 总线有极其严格的自我诊断机制(CRC 校验、位填充检查等)。任何一个节点一旦发现总线上的信号不符合规则,就会立刻发送一串连续的“破坏性”电平(6个显性位)。这会主动破坏当前正在传输的帧,迫使所有节点丢弃这个坏消息。
STM32 内部的 CAN 控制器硬件会自动检测错误并发送错误帧。你需要做的只是在 RT-Thread 中检查错误中断或错误计数器,看看总线是不是物理上出问题了(比如线缆短路、干扰太大)。
过载帧:格式和错误帧非常相似,但它是在帧间隔期间发送的,用来强行延长两帧之间的等待时间。
帧间隔:它是由连续的 3 个隐性位(逻辑 1)组成的。无论是谁刚发完一帧,都必须等待这 3 个位的时间过去,总线才算真正进入“空闲”状态,大家才能开始新一轮的抢夺(仲裁)。如果此时硬件还在处理上一帧的收尾工作,就会用到前面提到的“过载帧”来延长这个间隔。
stm32中纯硬件行为,STM32 会严格遵守这个规则,确保总线节奏有条不紊。你不需要、也无法用代码去干预帧间隔。
4:接收中断与发送中断
接收中断:目的是收,总线信号进入stm32,经过硬件滤波后,存入FIFO中,这时候fifo非空,产生rx中断信号,进入isr中断服务函数,在中断里读取fifo的数据,可以通过释放信号量,来唤醒接收线程
发送中断:目的是发,软件把第一帧信息放入空闲的硬件邮箱,触发硬件发送,发送成功后,邮箱变成空闲,产生tx中断,在中断函数里:从软件的发送队列中提取下一帧信息,放入空邮箱,再次请求发送。
5:rt_can_filter_item过滤器
struct rt_can_filter_item
{
rt_uint32_t id : 29; /* 报文 ID */
rt_uint32_t ide : 1; /* 扩展帧标识位 */
rt_uint32_t rtr : 1; /* 远程帧标识位 */
rt_uint32_t mode : 1; /* 过滤表模式,0 表示标识符屏蔽位模式,1 表示标识符列表模式 */
rt_uint32_t mask; /* ID 掩码,0 表示对应的位不关心,1 表示对应的位必须匹配 */
rt_int32_t hdr; /* -1 表示不指定过滤表号,对应的过滤表控制块也不会被初始化,正数为过滤表号,对应的过滤表控制块会被初始化 */
#ifdef RT_CAN_USING_HDR
/* 过滤表回调函数 */
rt_err_t (*ind)(rt_device_t dev, void *args , rt_int32_t hdr, rt_size_t size);
/* 回调函数参数 */
void *args;
#endif /*RT_CAN_USING_HDR*/
};
1. id:你想接收的 ID
这是你期望收到的 CAN 帧 ID。
-
标准帧范围:
0x000~0x7FF -
扩展帧范围:
0x00000000~0x1FFFFFFF
2. ide:标准帧还是扩展帧
-
0(RT_CAN_STDID):只接收标准帧 -
1(RT_CAN_EXTID):只接收扩展帧
3. rtr:数据帧还是远程帧
-
0(RT_CAN_DTR):只接收数据帧(最常用,电机发回来的数据都是这个) -
1(RT_CAN_RTR):只接收远程帧(很少用)
4. mode:两种过滤模式
模式 0:掩码模式 (RT_CAN_FILTER_MODE_MASK)
意思:设定 “哪些位必须严格等于 id,哪些位可以随便变”。
-
就像相亲:
id是 “要求”,mask是 “哪些要求必须满足”。
模式 1:列表模式 (RT_CAN_FILTER_MODE_LIST)
意思:只接收完全等于 id 的那一帧。
-
就像点名:只点
id这一个号。
5. mask:掩码(配合模式 0 使用)
这是一个 32 位的数。
-
某一位为
1:收到的数据的这一位 必须 和id的这一位 一样。 -
某一位为
0:收到的数据的这一位 无所谓,是 0 是 1 都可以。
6. hdr:过滤器硬件编号
STM32F429 的 CAN1 有 14 个硬件过滤器(0~13)。
-
-1:让 RT-Thread 自动帮你分配一个空闲的过滤器。 -
0~13:你手动指定用第几个过滤器。
建议初学者直接填 -1。
7.
#define RT_CAN_FILTER_ITEM_INIT(id,ide,rtr,mode,mask,ind,args) \
{(id), (ide), (rtr), (mode), (mask), -1, (ind), (args)}
这是一个 C 语言宏,它会自动生成一个结构体的初始化列表。
| 宏参数 | 对应结构体成员 | 含义 | 初学者建议填什么 |
|---|---|---|---|
id |
id |
你想接收的 CAN ID | 比如 0x123 |
ide |
ide |
标准帧 / 扩展帧 | RT_CAN_STDID |
rtr |
rtr |
数据帧 / 远程帧 | RT_CAN_DTR |
mode |
mode |
列表模式 / 掩码模式 | RT_CAN_FILTER_MODE_LIST |
mask |
mask |
掩码 | 0x000 (列表模式下无效) |
ind |
ind |
回调函数 | RT_NULL (不用管) |
args |
args |
回调参数 | RT_NULL (不用管) |
实战操作:快速填好结构体
// 定义并初始化
struct rt_can_filter_item filter = RT_CAN_FILTER_ITEM_INIT(
0x123, // id
RT_CAN_STDID, // ide (标准帧)
RT_CAN_DTR, // rtr (数据帧)
RT_CAN_FILTER_MODE_LIST, // mode (列表模式)
0x000, // mask (列表模式下无效,填0)
RT_NULL, // ind (无回调)
RT_NULL // args (无参数)
);
// 直接调用 API 设置即可
rt_device_control(can_dev, RT_CAN_CMD_SET_FILTER, &filter);
过滤器例子1
总线上只有 ID=0x123 的数据能进来,其他全屏蔽。
struct rt_can_filter_item filter;
// 1. 清空结构体
rt_memset(&filter, 0, sizeof(filter));
// 2. 配置参数
filter.id = 0x123; // 只接收 ID 为 0x123 的数据
filter.ide = RT_CAN_STDID; // 标准帧
filter.rtr = RT_CAN_DTR; // 数据帧
filter.mode = RT_CAN_FILTER_MODE_LIST; // 列表模式:精确匹配
filter.hdr = -1; // 自动分配过滤器编号
// 3. 调用 API 设置过滤器
rt_device_control(can_dev, RT_CAN_CMD_SET_FILTER, &filter);
过滤器例子2
需求:我要控制 8 个电机,它们的 ID 是 0x100, 0x101 ... 0x107。
分析:ID 的二进制是 0001 0000 0xxx,最后 3 位是电机编号。
struct rt_can_filter_item filter;
rt_memset(&filter, 0, sizeof(filter));
filter.id = 0x100; // 基准 ID
filter.ide = RT_CAN_STDID;
filter.rtr = RT_CAN_DTR;
filter.mode = RT_CAN_FILTER_MODE_MASK; // 掩码模式
// 【关键】掩码设置:
// 二进制:1111 1111 1000 (0x7F8)
// 意思:前 8 位必须是 0x100,后 3 位随便 (0~7)
filter.mask = 0x7F8;
filter.hdr = -1;
rt_device_control(can_dev, RT_CAN_CMD_SET_FILTER, &filter);
6:rt_can_msg信息结构
struct rt_can_msg
{
rt_uint32_t id : 29; /* 【必学】CAN ID */
rt_uint32_t ide : 1; /* 【必学】标准帧/扩展帧 */
rt_uint32_t rtr : 1; /* 【必学】数据帧/远程帧 */
rt_uint32_t rsv : 1; /* 【忽略】保留位 */
rt_uint32_t len : 8; /* 【必学】数据长度 */
rt_uint32_t priv : 8; /* 【了解】发送优先级 */
rt_uint32_t hdr : 8; /* 【了解】是被哪个过滤器收到的 */
rt_uint32_t reserved : 8;/* 【忽略】保留 */
rt_uint8_t data[8]; /* 【必学】真正的数据内容 */
};
1. id:CAN 帧 ID
-
如果是标准帧 (
ide=0):0x000~0x7FF(11 位) -
如果是扩展帧 (
ide=1):0x00000000~0x1FFFFFFF(29 位)
2. ide:标准帧还是扩展帧
-
含义:ID 是 11 位的还是 29 位的。
-
可选值(RT-Thread 已经定义好了宏,直接用):
-
RT_CAN_STDID(0):标准帧 (11 位 ID) —— 最常用,电机、工业控制一般用这个。 -
RT_CAN_EXTID(1):扩展帧 (29 位 ID)。
-
3. rtr:数据帧还是远程帧
-
含义:这帧是用来发数据的,还是用来 “请求” 别人发数据的。
-
RT_CAN_DTR(0):数据帧 —— 99.9% 用这个,你发数据、电机回传数据都是这个。 -
RT_CAN_RTR(1):远程帧 (Remote Transmission Request) —— 几乎不用。
-
4. len:数据长度
-
含义:
data[8]数组里有几个字节是有效的。 -
范围:
0~8。 -
注意:CAN 一帧最多只能发 8 个字节!
-
实战填写:
-
发送时:你发几个字节就填几(比如发 8 个就填
8)。 -
接收时:读这个变量,知道收到了几个字节。
-
5. data[8]:真正的数据负载
-
含义:你要传的具体内容。
-
大小:8 个字节。
-
实战填写:
-
发送时:把你要发的数填进去。
-
接收时:从这里面读数据。
-
6. priv:发送优先级
-
含义:当多个发送邮箱都有数据时,优先级高的先发。
-
范围:
0~255。 -
实战填写:
-
直接填 0 或者不管它,初始化时清 0 即可。
-
一般简单应用用不到硬件优先级调度。
-
7. hdr:硬件过滤器表号
-
含义:(只读)这帧数据是通过第几个过滤器进来的。
-
实战用法:
-
比如你设了过滤器 0 收电机 1,过滤器 1 收电机 2。
-
收到数据后看一眼
hdr,如果是 0,就是电机 1 发的;如果是 1,就是电机 2 发的。 -
初学者可以忽略,直接看
id更直观。
-
8. rsv 和 reserved
-
含义:保留位,给未来扩展用的。
-
实战:不要去写它,也不用读它。
7:MSH_CMD_EXPORT()注册命令
MSH_CMD_EXPORT(can_test, start can loopback test);
把 can_test 这个函数注册成命令。如果用户在终端敲 help,请显示这行描述:start can loopback test。
:用户现在可以直接命令行运行这个函数


8:rt_device_set_rx_indicate()
rt_err_t rt_device_set_rx_indicate(
rt_device_t dev,
rt_err_t (*rx_ind)(rt_device_t dev, rt_size_t size)
);
rt_device_t dev:给哪个设备设置回调函数
参数二:
| 部分 | 含义 |
|---|---|
(*rx_ind) |
这是一个指针,名字叫 rx_ind |
(rt_device_t dev, rt_size_t size) |
它指向的函数,必须接收这两个参数 |
rt_err_t |
它指向的函数,返回值必须是 rt_err_t |
自己写的回调函数必须是这个样子
// 返回值必须是 rt_err_t
// 参数必须是 (rt_device_t, rt_size_t)
rt_err_t 你的函数名(rt_device_t dev, rt_size_t size)
{
// 做你想做的事
return RT_EOK; // 必须返回一个值
}
8:rt_device_write();
rt_device_write(can_dev, 0, &txmsg, sizeof(txmsg));
| 概念 | 代码中的位置 | 含义 | 数值 |
|---|---|---|---|
| 1. CAN 有效数据长度 | txmsg.len |
CAN 总线真正发出去几个字节 | 你自己设的 0~8 |
| 2. 驱动缓冲区大小 | sizeof(txmsg) |
你传给 RT-Thread 驱动的这包数据有多大 | 整个结构体的大小(通常是 16 或 20 字节) |
sizeof(txmsg)告诉 RT-Thread 设备驱动:“我给你传了一个结构体,它的大小是这么多字节,你要完整地把它读进去解析。”
9:rt_device_read(can_dev, 0, &rxmsg, sizeof(rxmsg))
-
can_dev(设备句柄):-
作用:告诉系统你要从哪个具体的设备里读数据。在这里,它代表我们之前打开的
"can1"外设。
-
-
0(读取位置/偏移量 pos):-
作用:对于像 SD 卡或 EEPROM 这样的存储设备,这个参数决定了从第几个扇区或字节开始读。
-
为什么是 0:CAN 总线是一种数据流/数据包设备,新来的数据总是按顺序排在硬件的 FIFO(先进先出队列)里。我们只能“从头往外拿”,没有“偏移”的概念,所以这里固定填
0或-1都可以。
-
-
&rxmsg(数据存放地址):-
作用:这是你准备好的“空箱子”的地址。你告诉底层驱动:“请把读到的 CAN 数据原封不动地塞到这块内存里”。
-
-
sizeof(rxmsg)(期望读取的大小):-
作用:告诉底层你想读多大的数据。
rxmsg是一个struct rt_can_msg类型的结构体(包含了帧 ID、数据长度、8个字节的具体数据等信息)。这里就是要求底层完整地读取一帧报文的结构体大小。
-
-
函数返回:实际上读取多少个字节
具体例子
基于 RT-Thread 设备驱动框架的 CAN 回环(自己发自己收)通信测试。
它的核心架构采用了经典的 “中断通知 + 线程处理” 模式:
-
底层(硬件):CAN 硬件接收到数据后,触发底层的接收中断。
-
中间层(回调):中断触发我们的回调函数,回调函数什么复杂业务都不做,只负责“释放一个信号量”(相当于拉响警报),然后立刻退出,保证中断极速响应。
-
上层(线程):专门有一个接收线程在后台“死等”这个信号量。平时它处于挂起(休眠)状态,不占用 CPU;一旦信号量被释放,它立刻苏醒,把 CAN 硬件里的数据读出来并打印,读完继续休眠。
如何初始化can通信:
1:系统配置好环境后,系统里挂载了很多外设:can1,usart等,通过rt_device_find("can1")
2:拿到句柄后,打开设备rt_device_open()
3:配置参数:波特率,工作模式,硬件过滤。rt_device_control()
4:注册回调函数,触发接收中断,对信息进行接收。rt_device_set_rx_indicate()
5:填好信息结构体后,发送信息rt_device_write()
#include <rtthread.h> // RT-Thread 内核头文件,包含基础数据类型、线程、信号量等API
#include <rtdevice.h> // RT-Thread 设备驱动框架头文件(CAN设备API属于这里)
#include <board.h> // 板级支持包头文件,包含底层硬件相关定义
#define CAN_DEV_NAME "can1" // 定义要操作的 CAN 设备名称
static rt_device_t can_dev = RT_NULL; // 定义一个设备句柄指针,用于后续操作该 CAN 设备
static struct rt_semaphore rx_sem; // 定义一个信号量结构体,用于在中断和接收线程之间同步数据到达的事件
/* CAN 接收硬件中断回调函数
* 注意:此函数在硬件中断上下文中运行,应当秉持“快进快出”原则,绝不能在此处包含任何会导致阻塞的代码。
*/
static rt_err_t can_rx_call(rt_device_t dev, rt_size_t size)
{
/* 当底层 CAN 硬件接收到数据触发中断时,释放信号量,唤醒正在等待的接收线程 */
rt_sem_release(&rx_sem);
return RT_EOK;
}
/* CAN 数据接收处理线程的入口函数
* 该线程在后台独立运行,负责将底层接收到的数据读取出来并打印。
*/
static void can_rx_thread_entry(void *parameter)
{
struct rt_can_msg rxmsg = {0}; // 定义一个 CAN 消息结构体,用于存放读取到的数据,并初始化为 0
while (1) // 线程主循环
{
/* 挂起当前线程,无限期阻塞等待信号量(RT_WAITING_FOREVER)。
只有当中断回调函数释放了 rx_sem 信号量时,该线程才会被唤醒并往下执行。 */
if (rt_sem_take(&rx_sem, RT_WAITING_FOREVER) == RT_EOK)
{
/* 使用 while 循环不断读取硬件 FIFO 中的数据,防止中断触发太快导致数据堆积遗漏。
rt_device_read 返回实际读取的字节数,当返回值等于一帧报文大小(sizeof(rxmsg))时说明成功读到完整一帧。 */
while (rt_device_read(can_dev, 0, &rxmsg, sizeof(rxmsg)) == sizeof(rxmsg))
{
/* 打印接收到的报文 ID */
rt_kprintf("\n[CAN RX] ID: 0x%x, Data: ", rxmsg.id);
/* 循环打印报文中的具体数据段,%02X 表示以至少2位宽度的十六进制大写打印,不足补0,方便对齐查看 */
for (int i = 0; i < rxmsg.len; i++) rt_kprintf("%02X ", rxmsg.data[i]);
rt_kprintf("\n");
}
}
}
}
/* CAN 综合测试函数:负责设备初始化、线程创建以及发送测试数据 */
int can_test(void)
{
struct rt_can_msg txmsg = {0}; // 定义用于发送的 CAN 消息结构体
rt_err_t res; // 用于保存函数返回的状态码
rt_thread_t thread; // 定义线程句柄
static uint8_t is_initialized = 0; // 定义静态标志位,用于防止用户在终端多次输入命令导致的重复初始化
/* 1. 根据设备名称在系统中查找对应的 CAN 设备 */
can_dev = rt_device_find(CAN_DEV_NAME);
if (!can_dev)
{
rt_kprintf("[ERR] Find %s failed!\n", CAN_DEV_NAME);
return -RT_ERROR; // 找不到设备则退出测试
}
rt_kprintf("[OK] Found %s device.\n", CAN_DEV_NAME);
/* 2. 防呆保护:如果之前已经打开并初始化过,先尝试关闭它 (主要为解决重复打开导致的报错问题) */
if (is_initialized == 1)
{
rt_kprintf("[WARN] Device already init, trying to close and re-open...\n");
rt_device_close(can_dev);
// 注意:这里为了简单,不做完全的反初始化(如删除线程、删除信号量等),仅通过 close 解决 open 报错
}
/* 如果是首次运行该命令(或者说尚未完成初始化过程),则执行核心初始化动作 */
if (is_initialized == 0)
{
/* 3. 初始化接收信号量,名字设为 "rx_sem",初始值为0,采用先进先出(FIFO)的唤醒模式 */
rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
/* 4. 打开 CAN 设备 (关键步骤)
以 中断接收(RT_DEVICE_FLAG_INT_RX) 和 中断发送(RT_DEVICE_FLAG_INT_TX) 的模式打开 */
rt_kprintf("[INFO] Opening CAN device...\n");
res = rt_device_open(can_dev, RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_INT_TX);
if (res != RT_EOK)
{
rt_kprintf("[ERR] Open failed! Error code: %d\n", res);
return -RT_ERROR; // 打开失败退出
}
rt_kprintf("[OK] CAN device opened.\n");
/* 5. 配置 CAN 底层工作参数 */
rt_kprintf("[INFO] Setting baudrate to 500kbps...\n");
/* 通过 rt_device_control 接口下发命令,将波特率设为 500k */
rt_device_control(can_dev, RT_CAN_CMD_SET_BAUD, (void *)CAN500kBaud);
rt_kprintf("[INFO] Setting Loopback mode...\n");
/* 设为 回环模式(Loopback),即自己发自己收,不经过外部物理总线,便于单板验证代码逻辑 */
rt_device_control(can_dev, RT_CAN_CMD_SET_MODE, (void *)RT_CAN_MODE_LOOPBACK);
/* 将我们上面编写好的 can_rx_call 注册为底层 CAN 外设的接收中断通知回调函数 */
rt_device_set_rx_indicate(can_dev, can_rx_call);
/* 6. 创建并启动接收线程
线程名"can_rx",入口函数 can_rx_thread_entry,无参数传入,栈大小1024字节,优先级15,时间片10个tick */
thread = rt_thread_create("can_rx", can_rx_thread_entry, RT_NULL, 1024, 15, 10);
if (thread) rt_thread_startup(thread); // 创建成功后,立刻启动该线程让其进入就绪态等待信号量
/* 标记初始化流程已完成,后续再次执行 can_test 命令时将跳过上述 3~6 步 */
is_initialized = 1;
rt_kprintf("[OK] CAN Init Complete!\n");
}
/* 7. 构造并发送测试数据 */
txmsg.id = 0x123; // 设置本次发送帧的 ID 为 0x123
txmsg.ide = RT_CAN_STDID; // 设置为标准帧 (Standard ID, 11位)
txmsg.rtr = RT_CAN_DTR; // 设置为数据帧 (Data Frame)
txmsg.len = 8; // 规定本次数据长度为 8 个字节
/* 利用 for 循环填充具体的 8 字节测试数据内容:0x0A, 0x0B, 0x0C... */
for (int i = 0; i < 8; i++) txmsg.data[i] = i + 0x0A;
rt_kprintf("\n[CAN TX] Sending...\n");
/* 调用标准的设备写入接口,把准备好的 txmsg 结构体扔给底层 CAN 硬件发送出去 */
rt_device_write(can_dev, 0, &txmsg, sizeof(txmsg));
return RT_EOK;
}
/* 将 can_test 函数导出为 MSH 控制台命令,可在串口终端里敲 "can_test" 触发执行该函数 */
MSH_CMD_EXPORT(can_test, start can loopback test);
/* 应用层的入口主函数,由于我们的业务逻辑挂载在了终端命令上,所以此处直接返回即可 */
int main(void)
{
return RT_EOK;
}
1.can_rx_call (接收中断回调函数)
-
触发时机:当 CAN 硬件 FIFO 接收到一帧完整的数据时,底层 HAL 库的中断会自动调用这个函数。
-
作用:执行
rt_sem_release(&rx_sem);,释放一次信号量。它的作用就像是传达室大爷,收到快递(CAN 数据)后,按一下呼叫铃(释放信号量),通知楼上的接件员(接收线程)来拿,绝不在传达室里拆快递。
2. can_rx_thread_entry (接收处理线程)
-
触发时机:系统启动后就会创建并在后台独立运行。
-
作用:真正的“接件员”。
-
rt_sem_take:这是一个阻塞函数,如果信号量没被释放,这行代码就会卡住(挂起线程),出让 CPU 权限给其他任务。 -
rt_device_read:一旦拿到信号量苏醒,就进入while循环,把 CAN 接收 FIFO 里积压的数据全部读出来(防止中断太快导致数据堆积),并打印出 ID 和具体的字节数据。
-
3. can_test (初始化与发送测试函数)
-
触发时机:在串口控制台输入
can_test命令并回车时触发。 -
作用:这是整个流程的“总导演”,做了三件大事:
-
安全初始化:通过
is_initialized标志位判断,如果是第一次运行,就去查找外设、初始化信号量、打开 CAN 设备、设置 500K 波特率和回环模式(Loopback),最后把接收线程创建并跑起来。 -
防爆破机制:如果你在控制台连续多次输入该命令,它会检测到已经初始化过,不再重复建线程,而是利用
rt_device_close做简单的规避,然后直接发信息rt_device_write。 -
发送数据:打包一帧 ID 为
0x123的标准数据帧(内容为0A 0B 0C 0D 0E 0F 10 11),调用rt_device_write把数据塞给 CAN 硬件发出去。
-

更多推荐



所有评论(0)