CAN设备用户开发
一、基础BSP配置--以STM32为例
建立工程文件
如果在提供的OneOS源码中已经有适配好的DEMO工程,则可以直接使用;如果没有请参照快速上手中的操作指南,新建一个适合的工程。
使用STM32CUBEMX配置硬件
- 打开 oneos\projects\xxxxx(project文件夹)\board\CubeMX_Config 下 的 CUBE 工程文件;
- 在 CUBE 工程中进行 CAN 配置,如下图所示,红色框选中的地方依次选择CAN、勾选Master Mode,注意检查此时自动选择的CAN引脚线是否与硬件电路一致,然后修改[3]中的Time Quanta,保证软件不提醒冲突,接着在Clock Configuration检查时钟配置是否正确;然后检查引脚配置和中断配置如[5]和[6],需要注意,如果外接了CAN控制器则可以直接使用默认的引脚配置,否则必须配置为pull-up。
- 最后点击GENARATE CODE 生成代码;
使用 Menuconfig 配置工程选项
- 在对应的 oneos\projects\xxxxx(project文件夹) 目录下打开OneOS-Cube工具,在命令行输入 menuconfig 打开可视化配置界面;
- 通过[↑]、[↓]按键、空格、enter键或向右方向键选择 Drivers->CAN 下的 Using CAN drivers选项,如下所示:
(Top) → Drivers→ CAN
[*] Using CAN drivers
3.Esc键退出menuconfig,注意保存所修改的设置。
使用 Scons 构建工程
在上一步打开的OneOS-Cube 工具命令行中输入 scons --ide=mdk5重新构建keil工程;
二、工程编译及实现
- 打开对应的 oneos\projects\xxxxx(project文件夹) 目录下的 project.uvprojx 工程文件,此时即可在工程中添加相应的应用程序。
- 在工程中将原有或自己编写的can_test.c文件加入到application子文件夹中;
- 编译并下载工程,运行程序;
- 打开串口工具如xshell、SecureCRT等,通信成功后,即可使用can_start命令设置CAN的工作模式并开启一个接受任务,使用can_send命令即可通过串口查看到CAN接收任务打印的接收数据;
- 通过修改can_test.c中can的工作模式、发送数据值等,查看CAN的接收数据效果。
- 如不再使用CAN设备,可再次使用OneOS-Cube工具取消Drivers->CAN 下的Using CAN drivers的选择,保存设置后重新生成工程即可。
三、CAN设备API介绍
API 列表
接口 | 说明 |
---|---|
os_device_find | 查找设备 |
os_device_open | 打开设备 |
os_device_read_block | 读取数据(阻塞) |
os_device_read_nonblock | 读取数据(非阻塞) |
os_device_write_block | 写入数据(阻塞) |
os_device_write_nonblock | 写入数据(非阻塞) |
os_device_control | 控制设备 |
os_device_close | 关闭设备 |
os_device_find
该函数用于查找 CAN 设备,应用程序根据 CAN设备名称通过该函数查找 CAN 设备并获取设备指针,进而可以操作 CAN设备,函数原型如下:
os_device_t *os_device_find(const char *name);
参数 | 说明 |
---|---|
name | CAN设备名称 |
返回 | 说明 |
设备指针 | 查找到CAN设备将返回CAN设备的指针 |
OS_NULL | 没有找到CAN设备 |
os_device_open
该函数用于开启 CAN 设备,通过CAN设备指针,应用程序可以打开和关闭设备,打开设备时,会检测设备是否已经初始化,没有初始化则会默认调用初始化接口进行初始化,函数原型如下:
os_err_t os_device_open(os_device_t *dev);
参数 | 说明 |
---|---|
dev | CAN设备名称 |
返回 | 说明 |
OS_EOK | 设备打开成功 |
其他错误码 | 设备打开失败 |
os_device_read_block
该函数用于阻塞式接收 CAN 数据,函数原型如下:
os_size_t os_device_read_block(os_device_t *dev, os_off_t pos, void *buffer, os_size_t size);
参数 | 说明 |
---|---|
dev | CAN设备指针 |
pos | 读取数据偏移量,此参数 CAN 设备默认为0(未使用) |
buffer | CAN 消息类型的指针 |
size | CAN 消息大小 |
返回 | 说明 |
大于0 | 接收的CAN消息量大小 |
小于0 | 错误码 |
如果设备有数据可读,函数立即返回,数据被读取到 buffer 中,返回值为实际读取的数据量。如果设备没有准备好数据,读线程会睡眠阻塞,直到数据可读才被唤醒、返回。
os_device_read_nonblock
该函数用于非阻塞式接收 CAN 数据,函数原型如下:
os_size_t os_device_read_nonblock(os_device_t *dev, os_off_t pos, void *buffer, os_size_t size);
参数 | 说明 |
---|---|
dev | CAN设备指针 |
pos | 读取数据偏移量,此参数 CAN 设备默认为0(未使用) |
buffer | CAN 消息类型的指针 |
size | CAN 消息大小 |
返回 | 说明 |
大于等于0 | 接收的CAN消息量大小 |
小于0 | 错误码 |
如果设备有数据可读,函数立即返回,数据被读取到 buffer 中,返回值为实际读取的数据量。如果设备没有准备好数据,函数立即返回,返回值为0。
os_device_write_block
该函数用于阻塞式发送 CAN 数据,函数原型如下:
os_size_t os_device_write_block(os_device_t *dev, os_off_t pos, const void *buffer, os_size_t size);
参数 | 说明 |
---|---|
dev | CAN设备指针 |
pos | 写入数据偏移量,此参数 CAN 设备默认为0(未使用) |
buffer | CAN 消息类型的指针 |
size | CAN 消息大小 |
返回 | 说明 |
大于0 | 发送的CAN消息量大小 |
小于0 | 错误码 |
如果设备有空间可写,buffer 中的数据写入设备,函数立即返回,返回值为实际写入的数据量。如果设备没有空间可写,写线程会睡眠阻塞,直到有空间可写才被唤醒,返回实际写入的数据量。
其中buffer一般指向CAN消息结构体,其原型如下:
struct os_can_msg
{
os_uint32_t id : 29; /* can_id:为适配扩展长度29位,标准格式时11位 */
os_uint32_t ide : 1; /* 标识扩展帧位 */
os_uint32_t rtr : 1; /* 标识远程帧位 */
os_uint32_t rsv : 1; /* 保留位 */
os_uint32_t len : 8; /* 数据段长度 */
os_uint32_t priv : 8; /* 报文发送优先级 */
os_int32_t hdr : 8; /* 硬件过滤表号 */
os_uint32_t reserved : 8; /* 保留位 */
os_uint8_t data[8]; /* can数据 */
};
os_device_write_nonblock
该函数用于非阻塞式发送 CAN 数据,函数原型如下:
os_size_t os_device_write_nonblock(os_device_t *dev, os_off_t pos, const void *buffer, os_size_t size);
参数 | 说明 |
---|---|
dev | CAN设备指针 |
pos | 写入数据偏移量,此参数 CAN 设备默认为0(未使用) |
buffer | CAN 消息类型的指针 |
size | CAN 消息大小 |
返回 | 说明 |
大于等于0 | 发送的CAN消息量大小 |
小于0 | 错误码 |
如果设备有空间可写,buffer 中的数据写入设备,函数立即返回,返回值为实际写入的数据量。如果设备没有空间可写,函数立即返回,返回值为0。
os_device_control
该函数用于CAN 设备参数修改,通过命令控制字,应用程序可以对 CAN 设备参数进行配置,函数原型如下:
os_err_t os_device_control(os_device_t *dev, int cmd, void *arg);
参数 | 说明 |
---|---|
dev | CAN设备指针 |
cmd | 控制命令 |
arg | 控制参数 |
返回 | 说明 |
OS_EOK | 函数执行成功 |
OS_ENOSYS | 执行失败,设备不支持 control 接口 |
其他错误码 | 执行失败 |
arg(控制参数)根据命令不同而不同,cmd(控制命令)可取以下值:
#define OS_DEVICE_CTRL_CONFIG IOC_UNKNOWN(7) /* configure device */#define OS_DEVICE_CTRL_SET_CB IOC_UNKNOWN(3) /* 设置rx/tx回调函数 */#define OS_CAN_CMD_SET_FILTER IOC_CAN(1) /* 设置硬件过滤表 */#define OS_CAN_CMD_SET_BAUD IOC_CAN(2) /* 设置波特率 */#define OS_CAN_CMD_SET_MODE IOC_CAN(3) /* 设置 CAN 工作模式 */#define OS_CAN_CMD_SET_PRIV IOC_CAN(4) /* 设置发送优先级 */#define OS_CAN_CMD_GET_STATUS IOC_CAN(5) /* 获取 CAN 设备状态 */#define OS_CAN_CMD_SET_STATUS_IND IOC_CAN(6) /* 设置状态回调函数 */#define OS_CAN_CMD_SET_BUS_HOOK IOC_CAN(7) /* 设置 CAN 总线钩子函数 */
os_device_close
该函数用于关闭 CAN 设备,当不再使用CAN设备时,可以关闭 CAN 设备(关闭、打开设备接口需配对使用),函数原型如下:
os_err_t os_device_close(os_device_t *dev);
参数 | 说明 |
---|---|
dev | CAN设备指针 |
返回 | 说明 |
OS_EOK | 关闭设备成功 |
OS_ERROR | 设备已经完全关闭,不能重复关闭设备 |
其他错误码 | 关闭设备失败 |
四、CAN示例介绍
CAN参数配置示例:
参数设置主要通过os_device_control函数实现。各项参数的具体设置如下所示:
/ OS_CAN_CMD_SET_BAUD:设置CAN设备的波特率 /
res = os_device_control(can_dev, OS_CAN_CMD_SET_BAUD, (void *)CAN500kBaud);
/ OS_CAN_CMD_SET_MODE:设置CAN的工作模式,本例设置为回环模式 /
res = os_device_control(can_dev, OS_CAN_CMD_SET_MODE, (void *)OS_CAN_MODE_LOOPBACK);
/ OS_CAN_CMD_GET_STATUS:读取CAN设备的状态 /
res = os_device_control(can_dev, OS_CAN_CMD_GET_STATUS, &status);
/ OS_CAN_CMD_SET_FILTER:设置CAN硬件过滤表 /
struct os_can_filter_item item[1] ={ OS_CAN_FILTER_ITEM_INIT(0x01, 0, 0, 1, 0x01, OS_NULL, OS_NULL),};struct os_can_filter_config cfg = {1, 1, item};res = os_device_control(can_dev, OS_CAN_CMD_SET_FILTER, &cfg);
/ OS_DEVICE_CTRL_SET_CB:设置rx或tx回调函数 /
struct os_device_cb_info rx_cb_info = { .type = OS_DEVICE_CB_TYPE_RX, .cb = can_rx_call,/* 回调函数指针 */};os_device_control(can_dev, OS_DEVICE_CTRL_SET_CB, &rx_cb_info); struct os_device_cb_info tx_cb_info = { .type = OS_DEVICE_CB_TYPE_TX, .cb = can_tx_done,/* 回调函数指针 */};os_device_control(can_dev, OS_DEVICE_CTRL_SET_CB, &tx_cb_info);
其中CAN的过滤设置主要通过结构体实现:
/* filter结构体 */struct os_can_filter_item{ os_uint32_t id : 29; /* CAN message ID */ os_uint32_t ide : 1; /* Extended frame flag */ os_uint32_t rtr : 1; /* Remote frame flag */ os_uint32_t mode : 1; /* can Filter table mode */ os_uint32_t mask; /* ID 掩码,0 表示对应的位不关心,1 表示对应的位必须匹配 */ os_int32_t hdr; /* -1 表示不指定过滤表号,对应的过滤表控制块也不会被初始化,正数为过滤表号,对应的过滤表控制块会被初始化 */#ifdef OS_CAN_USING_HDR /* 过滤表回调函数 */ os_err_t (*ind)(os_device_t dev, void *args , os_int32_t hdr, os_size_t size); /* 回调函数参数 */ void *args;#endif /*OS_CAN_USING_HDR*/};/* 为方便初始化过滤表,开发了对应的过滤表宏 */#define OS_CAN_FILTER_ITEM_INIT(id,ide,rtr,mode,mask,ind,args) \ {(id), (ide), (rtr), (mode), (mask), -1, (ind), (args)}/* 过滤表配置前还需指定过滤表配置控制块 */struct os_can_filter_config{ os_uint32_t count; /* 过滤表数量 */ os_uint32_t actived; /* 过滤表激活选项,1 表示初始化过滤表控制块,0 表示不初始化过滤表控制块 */ struct os_can_filter_item *item; /* 过滤表指针,可指向一个过滤表数组 */};
上述有关CAN的设置均可通过os_device_control的返回值判断是否设置成功。
CAN收发数据示例:
#include <os_task.h>#include <device.h>#include <os_sem.h>#include <os_errno.h>#include <os_clock.h>#include <os_assert.h>#include <stdint.h>#include <string.h>#include <stdlib.h>#include <shell.h>#include <driver.h>#include <can/can.h>#define CAN_DATA_DUMP#define CAN_TEST_WRITE_NONBLOCK#define CAN_TEST_READ_NONBLOCKstatic struct os_semaphore rx_sem;static os_device_t *can_dev;static int rx_count = 0;static int tx_count = 0;static uint16_t tx_crc = 0;static uint16_t rx_crc = 0;OS_USED static os_err_t can_rx_call(os_device_t *dev, struct os_device_cb_info *info){ os_sem_post(&rx_sem); rx_count++; return OS_EOK;}static void can_rx_task(void *parameter){ struct os_can_msg rxmsg = {0};#ifdef CAN_TEST_READ_NONBLOCK struct os_device_cb_info cb_info = { .type = OS_DEVICE_CB_TYPE_RX, .cb = can_rx_call, }; os_device_control(can_dev, OS_DEVICE_CTRL_SET_CB, &cb_info);#endif while (1) { rxmsg.hdr = -1;#ifdef CAN_TEST_READ_NONBLOCK os_sem_wait(&rx_sem, OS_WAIT_FOREVER); if (os_device_read_nonblock(can_dev, 0, &rxmsg, sizeof(rxmsg)) <= 0)#else if (os_device_read_block(can_dev, 0, &rxmsg, sizeof(rxmsg)) <= 0)#endif continue; rx_crc = crc16(rx_crc, rxmsg.data, 8);#ifdef CAN_DATA_DUMP os_kprintf("recv(%d) id:%x ", rx_count, rxmsg.id); hex_dump(rxmsg.data, 8);#endif }}int can_start(int argc, char *argv[]){ os_err_t res; os_task_t *can_task; os_uint32_t baud = CAN500kBaud; os_uint8_t mode = OS_CAN_MODE_NORMAL; char *can_name; if (argc < 2) { os_kprintf("usage: can_start <dev> [baud] [mode]\r\n"); os_kprintf(" can_start can1 [500000] [mode]\r\n"); os_kprintf(" can_start can1 500000 0(normal)\r\n"); os_kprintf(" can_start can1 500000 1(listen)\r\n"); os_kprintf(" can_start can1 500000 2(loopback)\r\n"); os_kprintf(" can_start can1 500000 3(listen loopback)\r\n"); return -1; } can_name = argv[1]; if (argc > 2) { baud = strtol(argv[2], OS_NULL, 0); } if (argc > 3) { mode = strtol(argv[3], OS_NULL, 0); } os_kprintf("can:%s, baud:%d.\r\n", can_name, baud); can_dev = os_device_find(can_name); if (!can_dev) { os_kprintf("find %s failed!\r\n", can_name); return OS_ERROR; } os_sem_init(&rx_sem, "rx_sem", 0, OS_SEM_MAX_VALUE); res = os_device_open(can_dev); OS_ASSERT(res == OS_EOK); res = os_device_control(can_dev, OS_CAN_CMD_SET_BAUD, (void *)baud); res = os_device_control(can_dev, OS_CAN_CMD_SET_MODE, (void *)mode); can_task = os_task_create("can_rx", can_rx_task, OS_NULL, 1024, 25); if (can_task != OS_NULL) { os_task_startup(can_task); } else { os_kprintf("create can_rx task failed!\r\n"); } return res;}SH_CMD_EXPORT(can_start, can_start, "can device sample");OS_USED static os_err_t can_tx_done(os_device_t *uart, struct os_device_cb_info *info){ //os_kprintf("<%d>tx done\r\n", os_tick_get()); tx_count++; return 0;}void can_send(int argc, char *argv[]){ struct os_can_msg msg = {0}; os_size_t size; int loops = 1; if (argc == 2) { loops = strtol(argv[1], OS_NULL, 0); } msg.id = 0x68; msg.ide = OS_CAN_STDID; msg.rtr = OS_CAN_DTR; msg.len = 8; rx_count = 0; tx_crc = 0; rx_crc = 0; tx_count = 0;#ifdef CAN_TEST_WRITE_NONBLOCK struct os_device_cb_info cb_info = { .type = OS_DEVICE_CB_TYPE_TX, .cb = can_tx_done, }; os_device_control(can_dev, OS_DEVICE_CTRL_SET_CB, &cb_info);#endif while (loops--) { msg.data[0] = rand(); msg.data[1] = rand(); msg.data[2] = rand(); msg.data[3] = rand(); msg.data[4] = rand(); msg.data[5] = rand(); msg.data[6] = rand(); msg.data[7] = rand(); tx_crc = crc16(tx_crc, msg.data, 8);#ifdef CAN_DATA_DUMP os_kprintf("send id:%x ", msg.id); hex_dump(msg.data, 8);#endif#ifdef CAN_TEST_WRITE_NONBLOCK size = os_device_write_nonblock(can_dev, 0, &msg, sizeof(msg));#else size = os_device_write_block(can_dev, 0, &msg, sizeof(msg));#endif if (size == 0) { os_kprintf("can dev write data failed!\r\n"); } } os_task_msleep(3000); os_kprintf("rx_count: %d\r\n", rx_count); os_kprintf("tx_count: %d\r\n", tx_count); os_kprintf(" %s tx_crc:%04x, rx_crc:%04x\r\n", (tx_crc == rx_crc) ? "success" : "failed", tx_crc, rx_crc);}SH_CMD_EXPORT(can_send, can_send, "send can data");
例程主要由can_start和send_can_data组成,并将两个函数导入到SH命令行中。其中send_can_data主要发送一组CAN数据,can_start实现的功能如下图所示:
在串口的命令行中输入"can_start canX"启动CAN设备后,再输入can_send即可发送数据。