产品文档

物联网操作系统

CAN 设备

CAN 简介

CAN 是控制器局域网络 (Controller Area Network, CAN) 的简称,是由以研发和生产汽车电子产品著称的德国 BOSCH 公司开发的,并最终成为国际标准(ISO 11898),是国际上应用最广泛的现场总线之一。

CAN 控制器根据两根线上的电位差来判断总线电平。总线电平分为显性电平和隐性电平,二者必居其一。发送方通过使总线电平发生变化,将消息发送给接收方。 CAN 的连接示意图如下图所示:

CAN 连接图

CAN 连接图

CAN 总线有如下特点:

  • CAN 总线是可同时连接多个单元的总线。可连接的单元总数理论上是没有限制的。但实际上可连接的单元数受总线上的时间延迟及电气负载的限制。降低通信速度,可连接的单元数增加;提高通信速度,则可连接的单元数减少。

  • 多主控制。在总线空闲时,所有的单元都可开始发送消息(多主控制)。多个单元同时开始发送时,发送高优先级 ID 消息的单元可获得发送权。

  • 消息的发送。在 CAN 协议中,所有的消息都以固定的格式发送。总线空闲时,所有与总线相连的单元都可以开始发送新消息。两个以上的单元同时开始发送消息时,根据标识符 ID 决定优先级。ID 表示访问总线的消息的优先级。两个以上的单元同时开始发送消息时,对各消息 ID 的每个位进行逐个仲裁比较。仲裁获胜(被判定为优先级最高)的单元可继续发送消息,仲裁失利的单元则立刻停止发送而进行接收工作。

  • 根据整个网络的规模,可设定适合的通信速度。在同一网络中,所有单元必须设定成统一的通信速度。即使有一个单元的通信速度与其它的不一样,此单元也会输出错误信号,妨碍整个网络的通信。不同网络间则可以有不同的通信速度。

CAN 协议包括 5 种类型的帧:

  • 数据帧

  • 遥控帧

  • 错误帧

  • 过载帧

  • 帧间隔

数据帧和遥控帧有标准格式和扩展格式两种格式。标准格式有 11 个位的 ID,扩展格式有 29 个位的 ID。

各种帧的用途如下表所示:

帧用途
数据帧 用于发送单元向接收单元传送数据的帧
遥控帧 用于接收单元向具有相同 ID 的发送单元请求数据的帧
错误帧 用于当检测出错误时向其它单元通知错误的帧
过载帧 用于接收单元通知其尚未做好接收准备的帧
帧间隔 用于将数据帧及遥控帧与前面的帧分离开来的帧

访问 CAN 设备

应用程序通过 RT-Thread 提供的 I/O 设备管理接口来访问 CAN 硬件控制器,相关接口如下所示:

函数 描述
rt_device_find 查找设备
rt_device_open 打开设备
rt_device_read 读取数据
rt_device_write 写入数据
rt_device_control 控制设备
rt_device_set_rx_indicate 设置接收回调函数
rt_device_close 关闭设备

查找 CAN 设备

应用程序根据 CAN 设备名称查找设备获取设备句柄,进而可以操作 CAN 设备,查找设备函数如下所示,

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ rt_device_t rt_device_find(const char* name);

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

参数 描述
name 设备名称
返回 ——
设备句柄 查找到对应设备将返回相应的设备句柄
RT_NULL 没有找到相应的设备对象

一般情况下,注册到系统的 CAN 设备名称为 can1,can2 等,使用示例如下所示:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

define CAN_DEV_NAME "can1" /* CAN 设备名称 */

static rt_device_t can_dev; /* CAN 设备句柄 / / 查找 CAN 设备 */ can_dev = rt_device_find(CAN_DEV_NAME);

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

打开 CAN 设备

通过设备句柄,应用程序可以打开和关闭设备,打开设备时,会检测设备是否已经初始化,没有初始化则会默认调用初始化接口初始化设备。通过如下函数打开设备:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags);

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

参数 描述
dev 设备句柄
oflags 打开设备模式标志
返回 ——
RT_EOK 设备打开成功
-RT_EBUSY 如果设备注册时指定的参数中包括 RT_DEVICE_FLAG_STANDALONE 参数,此设备将不允许重复打开
其他错误码 设备打开失败

目前 RT-Thread CAN 设备驱动框架支持中断接收和中断发送模式。oflags 参数支持下列取值 (可以采用或的方式支持多种取值):

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

define RT_DEVICE_FLAG_INT_RX 0x100 /* 中断接收模式 */

define RT_DEVICE_FLAG_INT_TX 0x400 /* 中断发送模式 */

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

以中断接收及发送模式打开 CAN 设备的示例如下所示:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

define CAN_DEV_NAME "can1" /* CAN 设备名称 */

static rt_device_t can_dev; /* CAN 设备句柄 / / 查找 CAN 设备 / can_dev = rt_device_find(CAN_DEV_NAME); / 以中断接收及发送模式打开 CAN 设备 */ rt_device_open(can_dev, RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX);

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

控制 CAN 设备

通过命令控制字,应用程序可以对 CAN 设备进行配置,通过如下函数完成:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

参数 描述
dev 设备句柄
cmd 控制命令
arg 控制参数
返回 ——
RT_EOK 函数执行成功
其他错误码 执行失败

arg(控制参数)根据命令不同而不同,cmd(控制命令)可取以下值:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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 总线钩子函数 */

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

设置波特率

设置波特率的示例代码如下所示:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

define CAN_DEV_NAME "can1" /* CAN 设备名称 */

static rt_device_t can_dev; /* CAN 设备句柄 */

/* 查找 CAN 设备 / can_dev = rt_device_find(CAN_DEV_NAME); / 以中断接收及发送方式打开 CAN 设备 / res = rt_device_open(can_dev, RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX); / 设置 CAN 通信的波特率为 500kbit/s*/ res = rt_device_control(can_dev, RT_CAN_CMD_SET_BAUD, (void *)CAN500kBaud);

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

设置工作模式

设置工作模式的示例代码如下所示:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

define CAN_DEV_NAME "can1" /* CAN 设备名称 */

static rt_device_t can_dev; /* CAN 设备句柄 */

/* 查找 CAN 设备 / can_dev = rt_device_find(CAN_DEV_NAME); / 以中断接收及发送方式打开 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);

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

获取 CAN 设备状态

获取 CAN 设备状态的示例代码如下所示:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

define CAN_DEV_NAME "can1" /* CAN 设备名称 */

static rt_device_t can_dev; /* CAN 设备句柄 / static struct rt_can_status status; / 获取到的 CAN 总线状态 */

/* 查找 CAN 设备 / can_dev = rt_device_find(CAN_DEV_NAME); / 以中断接收及发送方式打开 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_GET_STATUS, &status);

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

设置硬件过滤表

过滤表控制块各成员描述如下所示:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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*/

};

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

如果需要过滤的报文 ID 为 0x01 的标准数据帧,使用默认过滤表,则过滤表各个成员设置如下:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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-Thread 系统提供了匹配过滤表的宏

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

define RT_CAN_FILTER_ITEM_INIT(id,ide,rtr,mode,mask,ind,args) \

 {(id), (ide), (rtr), (mode), (mask), -1, (ind), (args)}

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

过滤表宏中各个位分别和过滤表结构体成员变量一一对应,只是使用的过滤表是默认的过滤表。

则上述过滤信息使用过滤表的宏可以表示为

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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 表示去初始化过滤表控制块 */ struct rt_can_filter_item items; / 过滤表指针,可指向一个过滤表数组 */ };

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

设置硬件过滤表示例代码如下所示:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

define CAN_DEV_NAME "can1" /* CAN 设备名称 */

static rt_device_t can_dev; /* CAN 设备句柄 */

can_dev = rt_device_find(CAN_DEV_NAME);

/* 以中断接收及发送模式打开 CAN 设备 */ rt_device_open(can_dev, RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX);

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);

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

发送数据

使用 CAN 设备发送数据,可以通过如下函数完成:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size);

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

参数 描述
dev 设备句柄
pos 写入数据偏移量,此参数 CAN 设备未使用
buffer CAN 消息指针
size CAN 消息大小
返回 ——
不为 0 实际发送的 CAN 消息大小
0 发送失败

CAN 消息原型如下所示:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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]; / 数据段 */ };

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

使用 CAN 设备发送数据示例程序如下所示:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

define CAN_DEV_NAME "can1" /* CAN 设备名称 */

static rt_device_t can_dev; /* CAN 设备句柄 / struct rt_can_msg msg = {0}; / CAN 消息 */

can_dev = rt_device_find(CAN_DEV_NAME);

/* 以中断接收及发送模式打开 CAN 设备 */ rt_device_open(can_dev, RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX);

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));

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

设置接收回调函数

可以通过如下函数来设置数据接收指示,当 CAN 收到数据时,通知上层应用线程有数据到达 :

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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));

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

参数 描述
dev 设备句柄
rx_ind 回调函数指针
dev 设备句柄(回调函数参数)
size 缓冲区数据大小(回调函数参数)
返回 ——
RT_EOK 设置成功

该函数的回调函数由调用者提供。CAN 设备在中断接收模式下,当 CAN 接收到一帧数据产生中断时,就会调用回调函数,并且会把此时缓冲区的数据大小放在 size 参数里,把 CAN 设备句柄放在 dev 参数里供调用者获取。

一般情况下接收回调函数可以发送一个信号量或者事件通知 CAN 数据处理线程有数据到达。使用示例如下所示:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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);

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

接收数据

可调用如下函数读取 CAN 设备接收到的数据:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size);

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

参数 描述
dev 设备句柄
pos 读取数据偏移量,此参数 CAN 设备未使用
buffer CAN 消息指针,读取的数据将会被保存在缓冲区中
size CAN 消息大小
返回 ——
不为 0 CAN 消息大小
0 失败

!!! note "注意事项" 接收数据时 CAN 消息的 hdr 参数必须要指定值,默认指定为 -1 就可以,表示从接收数据的 uselist 链表读取数据。也可以指定为硬件过滤表号的值,表示此次读取数据从哪一个硬件过滤表对应的消息链接读取数据,此时需要设置硬件过滤表的时候 hdr 有指定正确的过滤表号。如果设置硬件过滤表的时候 hdr 都为 -1,则读取数据的时候也要赋值为-1。

CAN 使用中断接收模式并配合接收回调函数的使用示例如下所示:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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));

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

关闭 CAN 设备

当应用程序完成 CAN 操作后,可以关闭 CAN 设备,通过如下函数完成:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ rt_err_t rt_device_close(rt_device_t dev);

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

参数 描述
dev 设备句柄
返回 ——
RT_EOK 关闭设备成功
-RT_ERROR 设备已经完全关闭,不能重复关闭设备
其他错误码 关闭设备失败

关闭设备接口和打开设备接口需配对使用,打开一次设备对应要关闭一次设备,这样设备才会被完全关闭,否则设备仍处于未关闭状态。

CAN 设备使用示例

示例代码的主要步骤如下所示:

  1. 首先查找 CAN 设备获取设备句柄。

  2. 初始化信号量,然后以中断接收及中断发送方式打开 CAN 设备。

  3. 创建读取数据线程。

  4. 发送一帧 CAN 数据。

  • 读取数据线程首先会设置接收回调函数,然后设置硬件过滤表,之后会等待信号量。当 CAN 设备接收到一帧数据时会触发中断并调用接收回调函数,此函数会发送信号量唤醒线程,此时线程会马上读取接收到的数据。

  • 此示例代码不局限于特定的 BSP,根据 BSP 注册的 CAN 设备,修改示例代码宏定义 CAN_DEV_NAME 对应的 CAN 设备名称即可运行。

运行序列图如下图所示:

CAN 中断接收及发送序列图

CAN 中断接收及发送序列图

程序运行起来后在命令行输入 can_sample 即可运行示例代码,后面数据为 CAN 设备接收到的数据:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ \ | / - RT - Thread Operating System / | \ 4.0.1 build Jun 24 2019 2006 - 2019 Copyright by rt-thread team msh >can_sample ID:486 0 11 22 33 0 23 4 86 ID:111 0 11 22 33 0 23 1 11 ID:555 0 11 22 33 0 23 5 55 ID:211 0 11 22 33 0 23 2 11 ID:344 0 11 22 33 0 23 3 44

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

可以使用 CAN 分析工具连接对应 CAN 设备收发数据,第一帧数据为 CAN 示例代码发送的 ID 为 0X78的数据, 效果如下图所示:

CAN 分析工具数据收发过程

CAN 分析工具数据收发过程

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /* * 程序清单:这是一个 CAN 设备使用例程 * 例程导出了 can_sample 命令到控制终端 * 命令调用格式:can_sample can1 * 命令解释:命令第二个参数是要使用的 CAN 设备名称,为空则使用默认的 CAN 设备 * 程序功能:通过 CAN 设备发送一帧,并创建一个线程接收数据然后打印输出。 */

include <rtthread.h>

include "rtdevice.h"

define CAN_DEV_NAME "can1" /* CAN 设备名称 */

static struct rt_semaphore rx_sem; /* 用于接收消息的信号量 / static rt_device_t can_dev; / 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;

}

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");
}

}

int can_sample(int argc, char *argv[ ]) { struct rt_can_msg msg = {0}; 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);
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");
}

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));
if (size == 0)
{
    rt_kprintf("can dev write data failed!\n");
}

return res;

} /* 导出到 msh 命令列表中 */ MSH_CMD_EXPORT(can_sample, can device sample); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

更新时间:2020-04-09 19:30:37
文档反馈 docs feedback