只针对收/发/配置进行说明
1 了解 CAN 的工作原理
2 了解相关 CAN 的控制语句及配置
3 用例 CAN1 连续发送和接收 在最后
1 注意: 官方配置可能有问题,需要自行配置
2 如果在测试的时候,请关注一下 CAN 的模式,如果不是测试模式,就很有可能再发送3次数据之后进入堵塞等待状态,这个也是我当时没注意遇到的问题
跟 485 一样 CAN 也是两线制的,利用差分信号传输,而为了避免信号的反射和干扰,一般会在 H 和 L 端接上 120 欧姆的电阻,这 120 欧姆的电阻是根据电缆线的特性阻抗决定(一般是 120 欧姆)
CAN_H - CAN_L < 0.5 V 是隐形的,逻辑 1 -> 高电平
CAN_H - CAN_L > 0.9 V 是显性的,逻辑 0 -> 低电平
cAN 总线采用“线与”进行总线仲裁,1 & 0 = 0,所以 0 为显性
只要有一个节点将总线拉倒低电平(0)即显性状态
CAN 通讯距离与波特率成反比,最远 1w 最快 1M
仲裁:在仲裁期间,每一个发送器都对发送的电平与被监控的总线电平进行比较。如果电平相同,则这个单元可以继续发送。如果发送的是一"隐性"电平而监视到的是一"显性"电平,那么这个节点失去了仲裁,必须退出发送状态。如果出现不匹配的位不是在仲裁期间则产生错误事件。
帧ID越小,优先级越高。由于数据帧的RTR位为显性电平,远程帧为隐性电平,所以帧格式和帧ID相同的情况下,数据帧优先于远程帧;由于标准帧的IDE位为显性电平,扩展帧的IDE位为隐形电平,对于前11位ID相同的标准帧和扩展帧,标准帧优先级比扩展帧高
CAN 采用广播类型,但是可以通过硬件芯片过滤掉一些与自己无关的数据
数据格式(不深入仅作了解)
数据帧/远程帧(通知发送方发送数据)/错误帧/过载帧(就是通知其他节点减缓发送速度)
在 STM32 官方库中
uint16_t CAN_Prescaler; // 1~1024
uint8_t CAN_SJW; // 1~4tq
uint8_t CAN_BS1; // 1~16tq
uint8_t CAN_BS2; // 1~8tq
STM32F4 系列计算波特率
baud = APB1 / ((sjw + bs1 + bs2) * pre);
Ex: 42M / ((1 + 7 + 6) * 6) = 500K bps
{CAN500KBaud, (CAN_SJW_1tq | CAN_BS1_7TQ | CAN_BS2_6TQ | 6)},
rt_device_t rt_device_find(const char* name);
#define CAN_DEV_NAME "can1" / "can2"
staic rt_device_t can_dev;
can_dev = rt_device_find(CAN_DEV_NAME);
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags);
如果设备注册时指定的参数中包括 RT_DEVICE_FLAG_STANDALONE 参数,此设备将不允许重复打开
#define RT_DEVICE_FLAG_INT_RX 0x100 // 中断接收模式
#define RT_DEVICE_FLAG_INT_TX 0x400 // 中断发送模式
rt_device_open(can_dev, RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX);
rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);
#define RT_DEVICE_CTRL_RESUME 0x01 /* 恢复设备 */
#define RT_DEVICE_CTRL_SUSPEND 0x02 /* 挂起设备 */
#define RT_DEVICE_CTRL_CONFIG 0x03 /* 配置设备 */
#define RT_CAN_CMD_SET_FILTER 0x13 /* 设置硬件过滤表 */
#define RT_CAN_CMD_SET_BAUD 0x14 /* 设置波特率 */
#define RT_CAN_CMD_SET_MODE 0x15 /* 设置 CAN 工作模式 */
#define RT_CAN_CMD_SET_PRIV 0x16 /* 设置发送优先级 */
#define RT_CAN_CMD_GET_STATUS 0x17 /* 获取 CAN 设备状态 */
#define RT_CAN_CMD_SET_STATUS_IND 0x18 /* 设置状态回调函数 */
#define RT_CAN_CMD_SET_BUS_HOOK 0x19 /* 设置 CAN 总线钩子函数 */
//设置 CAN 通信的波特率为 500kbit/s
rt_device_control(can_dev, RT_CAN_CMD_SET_BAUD, (void \*)CAN500kBaud);
//设置 CAN 工作模式
rt_device_control(can_dev, RT_CAN_CMD_SET_MODE, (void \*)RT_CAN_MODE_NORMAL);
//获取 CAN 状态
rt_device_control(can_dev, RT_CAN_CMD_GET_STATUS, &status);
4 设置硬件过滤表
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; /* 过滤表模式 */
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*/
};
RT-Thread 提供了默认的过滤表
//可使用这个宏定义设置默认过滤表
#define RT_CAN_FILTER_ITEM_INIT(id,ide,rtr,mode,mask,ind,args) \ {(id), (ide), (rtr), (mode), (mask), -1, (ind), (args)}
struct rt_can_filter_item filter; /* 报文 ID */
filter.id = 0x01; /* 标准格式 */
filter.ide = 0x00; /* 数据帧 */
filter.rtr = 0x00; /* 过滤表模式 */
filter.mode = 0x01; /* 匹配 ID */
filter.mask = 0x01; /* 使用默认过滤表 */
filter.hdr = -1;
RT_CAN_FILTER_ITEM_INIT(0x01, 0, 0, 1, 0x01, RT_NULL, RT_NULL);
使用过滤表还需要配置过滤表
struct rt_can_filter_config
{
rt_uint32_t count; //过滤表数量
rt_uint32_t actived; //过滤表激活选项, 1 表示已经初始化过, 0 表示需要初始化过滤表
strcut rt_can_filter_item *items; // 指向过滤表数组
};
struct rt_can_filter_item items[1] = {
RT_CAN_FILTER_ITEM_INIT(0x01, 0, 0, 1, 0x01, RT_NULL, RT_NULL),
/* 过滤 ID 为 0x01,match ID:0x100~0x1ff,hdr 为 - 1,设置默认过滤表 */
};
struct rt_can_filter_config cfg = {1, 1, items}; /* 一共有 1 个过滤表 */
/* 设置硬件过滤表 */
res = rt_device_control(can_dev, RT_CAN_CMD_SET_FILTER, &cfg);
5 发送数据
rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size);
struct rt_can_msg {
rt_uint32_t id : 29; /* CAN ID, 标志格式 11 位,扩展格式 29 位 */
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]; /* 数据段 */
};
msg.id = 0x78; /* ID 为 0x78 */
msg.ide = RT_CAN_STDID; /* 标准格式 */
msg.rtr = RT_CAN_DTR; /* 数据帧 */
msg.len = 8; /* 数据长度为 8 */
/* 待发送的 8 字节数据 */
msg.data[0] = 0x00;
msg.data[1] = 0x11;
msg.data[2] = 0x22;
msg.data[3] = 0x33;
msg.data[4] = 0x44;
msg.data[5] = 0x55;
msg.data[6] = 0x66;
msg.data[7] = 0x77;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &msg, sizeof(msg));
6 设置接收回调函数
rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t dev,rt_size_t size));
#define CAN_DEV_NAME "can1" /* CAN 设备名称 */
static rt_device_t can_dev; /* CAN 设备句柄 */
struct rt_can_msg msg = {0}; /* 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;
}
/* 设置接收回调函数 */
rt_device_set_rx_indicate(can_dev, can_rx_call);
7 接收数据
注意:CAN 消息中 hdr 必须有指定值默认写 -1 读取数据的时候也要写 -1 即可,可以赋值硬件过滤表号,表示从哪个硬件过滤表对应的消息链接中读取
rt_device_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size);
#define CAN_DEV_NAME "can1" /* CAN 设备名称 */
static rt_device_t can_dev; /* CAN 设备句柄 */
struct rt_can_msg rxmsg = {0}; /* CAN 接收消息缓冲区 */
/* hdr 值为 - 1,表示直接从 uselist 链表读取数据 */
rxmsg.hdr = -1;
/* 阻塞等待接收信号量 */
rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
/* 从 CAN 读取一帧数据 */
rt_device_read(can_dev, 0, &rxmsg, sizeof(rxmsg));
8 关闭设备
rt_device_close(rt_device_t dev);
用例: 注意官方配置的 can 参数存在问题,STM32F4 APB1 最高 42Mhz
所以
{CAN500KBaud, (CAN_SJW_1tq | CAN_BS1_7TQ | CAN_BS2_6TQ | 6)},
这串代码配置时错误的,因为 APB1 在 STM32F4 系列最高时 42MHz
重新配置500K
{CAN500KBaud, (CAN_SJW_1tq | CAN_BS1_7TQ | CAN_BS2_6TQ | 6)},
RT-Thread 4.0.2 版本
#elif defined (SOC_SERIES_STM32F4)/* APB1 45MHz(max) */
static const struct stm32_baud_rate_tab can_baud_rate_tab[] =
{
{CAN1MBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 3)},
{CAN800kBaud, (CAN_SJW_2TQ | CAN_BS1_8TQ | CAN_BS2_5TQ | 4)},
{CAN500kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 6)},
{CAN250kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 12)},
{CAN125kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 24)},
{CAN100kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 30)},
{CAN50kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 60)},
{CAN20kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 150)},
{CAN10kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 300)}
};
/*
* 程序清单:这是一个 CAN 设备使用例程
* 例程导出了 can_sample 命令到控制终端
* 命令调用格式:can_sample can1
* 命令解释:命令第二个参数是要使用的 CAN 设备名称,为空则使用默认的 CAN 设备
* 程序功能:通过 CAN 设备发送一帧,并创建一个线程接收数据然后打印输出。
*/
#include
#include "rtdevice.h"
#define CAN_DEV_NAME "can1" /* CAN 设备名称 */
static struct rt_semaphore rx_sem; /* 用于接收消息的信号量 */
static rt_device_t can_dev; /* CAN 设备句柄 */
#define THREAD_PRIORITY 25
#define THREAD_STACK_SIZE 512
#define THREAD_TIMESLICE 5
static rt_thread_t tid1 = RT_NULL;
/* 接收数据回调函数 */
static rt_err_t can_rx_call(rt_device_t dev, rt_size_t size) {
/* CAN 接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */
rt_sem_release(&rx_sem);
return RT_EOK;
}
static void can_rx_thread(void *parameter) {
int i;
rt_err_t res;
struct rt_can_msg rxmsg = {0};
/* 设置接收回调函数 */
rt_device_set_rx_indicate(can_dev, can_rx_call);
#ifdef RT_CAN_USING_HDR
struct rt_can_filter_item items[5] = {
RT_CAN_FILTER_ITEM_INIT(0x100, 0, 0, 1, 0x700, RT_NULL, RT_NULL), /* std,match ID:0x100~0x1ff,hdr 为 - 1,设置默认过滤表 */
RT_CAN_FILTER_ITEM_INIT(0x300, 0, 0, 1, 0x700, RT_NULL, RT_NULL), /* std,match ID:0x300~0x3ff,hdr 为 - 1 */
RT_CAN_FILTER_ITEM_INIT(0x211, 0, 0, 1, 0x7ff, RT_NULL, RT_NULL), /* std,match ID:0x211,hdr 为 - 1 */
RT_CAN_FILTER_STD_INIT(0x486, RT_NULL, RT_NULL), /* std,match ID:0x486,hdr 为 - 1 */
{0x555, 0, 0, 1, 0x7ff, 7,} /* std,match ID:0x555,hdr 为 7,指定设置 7 号过滤表 */
};
struct rt_can_filter_config cfg = {5, 1, items}; /* 一共有 5 个过滤表 */
/* 设置硬件过滤表 */
res = rt_device_control(can_dev, RT_CAN_CMD_SET_FILTER, &cfg);
RT_ASSERT(res == RT_EOK);
#endif
while (1) {
/* hdr 值为 - 1,表示直接从 uselist 链表读取数据 */
rxmsg.hdr = -1;
/* 阻塞等待接收信号量 */
rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
/* 从 CAN 读取一帧数据 */
rt_device_read(can_dev, 0, &rxmsg, sizeof(rxmsg));
/* 打印数据 ID 及内容 */
rt_kprintf("ID:%x", rxmsg.id);
for (i = 0; i < 8; i++) {
rt_kprintf("%2x", rxmsg.data[i]);
}
rt_kprintf("\n");
}
}
/* 线程 1 的入口函数 */
static void thread1_entry(void *parameter) {
struct rt_can_msg msg = {0};
unsigned long count = 0;
msg.id = 0x78; /* ID 为 0x78 */
msg.ide = RT_CAN_STDID; /* 标准格式 */
msg.rtr = RT_CAN_DTR; /* 数据帧 */
msg.len = 8; /* 数据长度为 3 */
/* 待发送的 3 字节数据 */
msg.data[0] = 0x00;
msg.data[1] = 0x11;
msg.data[2] = 0x22;
msg.data[3] = 0x00;
msg.data[4] = 0x11;
msg.data[5] = 0x00;
msg.data[6] = 0x11;
msg.data[7] = 0x22;
rt_kprintf("send %ld \n", ++count);
while (1) {
/* 线程 1 采用低优先级运行,一直打印计数值 */
rt_device_write(can_dev, 0, &msg, sizeof(msg));
rt_kprintf("send %ld \n", ++count);
rt_thread_mdelay(500);
}
}
int can_sample(int argc, char *argv[]) {
rt_err_t res;
rt_size_t size;
rt_thread_t thread;
char can_name[RT_NAME_MAX];
if (argc == 2) {
rt_strncpy(can_name, argv[1], RT_NAME_MAX);
} else {
rt_strncpy(can_name, CAN_DEV_NAME, RT_NAME_MAX);
}
/* 查找 CAN 设备 */
can_dev = rt_device_find(can_name);
if (!can_dev) {
rt_kprintf("find %s failed!\n", can_name);
return RT_ERROR;
}
/* 初始化 CAN 接收信号量 */
rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
/* 以中断接收及发送方式打开 CAN 设备 */
res = rt_device_open(can_dev, RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX);
/* 设置 CAN 的工作模式为正常工作模式 */
res = rt_device_control(can_dev, RT_CAN_CMD_SET_MODE, (void *)RT_CAN_MODE_NORMAL);
res = rt_device_control(can_dev, RT_CAN_CMD_SET_BAUD, (void *)CAN500kBaud);
RT_ASSERT(res == RT_EOK);
/* 创建数据接收线程 */
thread = rt_thread_create("can_rx", can_rx_thread, RT_NULL, 1024, 25, 10);
if (thread != RT_NULL) {
rt_thread_startup(thread);
} else {
rt_kprintf("create can_rx thread failed!\n");
}
if (size == 0) {
rt_kprintf("can dev write data failed!\n");
}
/* 创建线程 1,名称是 thread1,入口是 thread1_entry*/
tid1 = rt_thread_create("thread1",
thread1_entry, RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY, THREAD_TIMESLICE);
/* 如果获得线程控制块,启动这个线程 */
if (tid1 != RT_NULL)
rt_thread_startup(tid1);
else
rt_kprintf("start can send fail\n");
return res;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(can_sample, can device sample);