基于RT-Thread的STM32开发第十讲——CAN通讯
基于RT-Thread的STM32开发第十讲——CAN通讯
文章目录
- 基于RT-Thread的STM32开发第十讲——CAN通讯
- 前言
- 一、工程创建
- 二、代码开发
- 1.user_can.c
- 2.user_can.h
- 3.main.c
- 三、效果展示
前言
本章是基于RT-Thread studio实现CAN通讯,使用的MCU是STM32F103C8T6,使用的RT-Thread驱动是5.1.0。
首先还是需要得到5.1.0的不报错工程,这部分有疑问可以去看我的往期文章,也可以直接使用4系列的稳定驱动。关于CAN的介绍的配置可以看这篇文章CAN的构成介绍及CubeMX配置说明
强烈建议先看这篇文章在看本文,看完这篇文章后对本文很多配置就非常容易理解
一、工程创建
STM32F103C8T6是有一对CAN通讯的引脚,但是创建的工程不管是驱动5还是驱动4都没有把CAN的配置文件添加进去,我又试了一些F4系类的,发现能够自动添加,看样子像是疏漏了。所以我们需要自行添加。
在RT-ThreadStudio的安装目录找到HAL_Drivers文件夹,类似地址如下
F:\RT-ThreadStudio\repo\Extract\RT-Thread_Source_Code\RT-Thread\5.1.0\bsp\stm32\libraries\HAL_Drivers
其中5.1.0可换成你用的版本,在HAL_Drivers文件夹下包含了所以驱动文件,我们把drv_can.c
和drv_can.h
复制到自己创建的工程内,
drv_can.c
放在工程drivers文件夹下,drv_can.h
放在工程drivers\include文件夹下,然后重新构建项目。CAN的驱动文件就添加进来了。
同样,在board.c中也没有CAN的宏定义,也许自行添加,如图
然后去setting中开启CAN的使能
CANFD是传统CAN总线协议的扩展版本,主要改进在于:
更高传输速率更高:仲裁段(控制信号)保持传统 CAN 的速率(最高 1Mbps),但数据段速率可大幅提升(最高 8Mbps 甚至更高)
数据帧更长:传统 CAN 数据长度最大 8 字节,而 CANFD 可支持最大 64 字节
无特殊说明,一半不开启
接着去CubeMX配置好CAN的功能,并将void HAL_CAN_MspInit(CAN_HandleTypeDef* canHandle)
和void HAL_CAN_MspDeInit(CAN_HandleTypeDef* canHandle)
复制到board.c下方
到这CAN的工程就配置完成了,下面就可以写通讯函数了
二、代码开发
因为CAN的数据包构成比较复杂,比其他一般外设配置复杂,强烈建议仔细阅读官方文档的CAN设备讲解。
1.user_can.c
#include "ads_can.h"
#define DBG_TAG "ads_can"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>#define CAN_name "can1"static rt_device_t can_dev;
static rt_sem_t can_sem;static void can_rx_entry(void *parameter);
int can_init(void)
{can_dev = rt_device_find(CAN_name);if(can_dev == RT_NULL){LOG_D("failed to fine %s device\n",CAN_name);return -1;}can_sem = rt_sem_create("can_sem", 0, RT_IPC_FLAG_FIFO);if(can_sem == RT_NULL){LOG_D("failed to create can sem\n");return -1;}rt_err_t can_dev_flag;can_dev_flag = rt_device_open(can_dev, RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_INT_TX);RT_ASSERT(can_dev_flag == RT_EOK);rt_thread_t can_rx_thread;can_rx_thread = rt_thread_create("can_rx", can_rx_entry, RT_NULL, 1024, 20, 1);if(can_rx_thread != RT_NULL){rt_thread_startup(can_rx_thread);}return 0;
}
rt_err_t can_rx_call(rt_device_t dev,rt_size_t size)
{rt_sem_release(can_sem);return 0;
}
static void can_rx_entry(void *parameter)
{struct rt_can_msg rxmsg = {0};rt_device_set_rx_indicate(can_dev,can_rx_call);
#ifdef RT_CAN_USING_HDRrt_device_control(can_dev, RT_CAN_CMD_SET_BAUD,(void *)CAN500kBaud);//波特率设置为500Krt_device_control(can_dev, RT_CAN_CMD_SET_MODE,(void *)RT_CAN_MODE_LOOPBACKANLISTEN);//设置为环回静默模式struct rt_can_filter_item items[1] ={RT_CAN_FILTER_ITEM_INIT(0x00f8,0,0,0,0x0030,RT_NULL,RT_NULL),};struct rt_can_filter_config cfg = {1,1,items};rt_err_t res;res = rt_device_control(can_dev, RT_CAN_CMD_SET_FILTER, &cfg);RT_ASSERT(res == RT_EOK);
#endifwhile(1){rxmsg.hdr_index = -1;rt_sem_take(can_sem, RT_WAITING_FOREVER);rt_thread_mdelay(10);rt_device_read(can_dev, 0, &rxmsg, sizeof(rxmsg));rt_kprintf("ID:%x\n",rxmsg.id);for (int var = 0; var < 8; ++var) {rt_kprintf("%x ",rxmsg.data[var]);}rt_kprintf("\n");}
}
void can_sample(int argc,char **argv)
{struct rt_can_msg txmsg = {0};txmsg.id = strtol(argv[1], 0, 16);txmsg.ide = 0;txmsg.rtr = 0;txmsg.len = 4;txmsg.data[0] = 0x01;txmsg.data[1] = 0x02;txmsg.data[2] = 0x04;txmsg.data[3] = 0x08;int size;size = rt_device_write(can_dev, 0, &txmsg, sizeof(txmsg));if(size == sizeof(txmsg)){rt_kprintf("can dev write data success\n");}
}
MSH_CMD_EXPORT(can_sample,can device sample);
int can_init(void)
里面创建了一个线程、一个信号量。找到CAN设备再打开CAN设备,开启接收中断的发送中断。信号量用于CAN设备收到了一个数据进来提醒线程进行数据解析的作用。
static void can_rx_entry(void *parameter)
这是线程的入口函数,在里面设置了CAN接收回调函数,一旦有信息进来,则进入回调函数,类似中断,在回调函数来释放信号量,从而使其他线程能够获得信号量。
然后设置了CAN通讯的波特率和工作模式,波特率按实际情况而定,不宜太高。我这里工作模式设置成了回环静默模式,如果没有CAN转USB模块,很难验证通讯效果,而设置成回环静默模式就没问题了,能够自发自收。一般工作下设置成普通模式。
下面这一步就是对CAN数据包的配置了,按官方模板来就行
struct rt_can_filter_item items[1] ={RT_CAN_FILTER_ITEM_INIT(0x00f8,0,0,0,0x0030,RT_NULL,RT_NULL),};struct rt_can_filter_config cfg = {1,1,items};
RT_CAN_FILTER_ITEM_INIT
函数的定义如下
#define RT_CAN_FILTER_ITEM_INIT(id,ide,rtr,mode,mask,ind,args) \{(id), (ide), (rtr), (mode),(mask), -1, CAN_RX_FIFO0,(ind), (args)}/*0:CAN_RX_FIFO0*/
id = 0x00f8
标识符设置
ide = 0
IDE 位为标准和扩展选择位,0:标准,1:扩展。这里使用标准数据帧
rtr = 0
RTR用标注该帧是数据帧还是远程帧,0:标准;1:远程。这里使用数据帧,远程帧也就是遥控帧
mode = 0
过滤表模式,0 表示标识符屏蔽模式,1 表示标识符扩展位模式,这里使用屏蔽模式。
(2025年10月5日,当前官方文章关于这一步的注释有误,屏蔽模式和扩展位模式弄反了)
mask = 0x0030
ID掩码,0表示对应的位不关心,1表示对应的位必须匹配,仅在标识符屏蔽模式下有效
在标识符扩展位模式下,ID掩码和ID码是唯二能够接收的标识符。
while循环就是将接收到的数据输出到屏幕上,很好理解
void can_sample(int argc,char **argv)
这是一个自定义finsh命令的语句,用来主动发送CAN数据包,前四位数据设置为1,2,3,4了。自定义ID
2.user_can.h
代码如下(示例):
#ifndef APPLICATIONS_ADS_CAN_H_
#define APPLICATIONS_ADS_CAN_H_#include <board.h>
#include <rtdevice.h>int can_init(void);#endif /* APPLICATIONS_ADS_CAN_H_ */
3.main.c
int main(void)
{can_init();while(1){rt_thread_mdelay(800);}return RT_EOK;
}
三、效果展示
下面是标识符扩展位模式,只允许0xf8和0x30通过,其他ID的数据包会被过滤掉
下面是标识符屏蔽模式,只允许xx11 xxxx类ID通过,其他ID的数据包会被过滤掉