20191119 (36) RT-Thread 下 CAN 驱动模块的使用和测试 不含过滤表说明(NXP JTA1050)

目的

只针对收/发/配置进行说明
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 采用广播类型,但是可以通过硬件芯片过滤掉一些与自己无关的数据
数据格式(不深入仅作了解)
数据帧/远程帧(通知发送方发送数据)/错误帧/过载帧(就是通知其他节点减缓发送速度)

CAN 必要配置(对照 STM32 库)
1 模式 共 4 种主要使用两种
  1. 正常模式 RT_CAN_MODE_NORMAL
    就是正常的收发数据
  2. 回环模式 RT_CAN_MODE_LOOPBACK
    用于自测试
2 波特率

在 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)},

RTOS 操作

1 查找 CAN 设备
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);

2 打开 CAN 设备
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);

3 设置参数
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 <rtthread.h>
#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);


发布了120 篇原创文章 · 获赞 27 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/qq_24890953/article/details/103144010