版权声明

本文档所有内容文字资料由凌云实验室郭工编著,主要用于凌云嵌入式Linux教学内部使用,版权归属作者个人所有。任何媒体、网站、或个人未经本人协议授权不得转载、链接、转帖或以其他方式复制发布/发表。已经授权的媒体、网站,在下载使用时必须注明来源,违者本人将依法追究责任。

  • Copyright (C) 2021 凌云物网智科实验室·郭工

  • Author: GuoWenxue <guowenxue@gmail.com> QQ: 281143292

wechat_pub

2.11 Linux 下 SocketCAN 通信

2.11.1 can 总线介绍和应用

2.11.1.1 can 总线协议简介

can(controller area network)是目前应用非常广泛的现场总线之一,控制器局域网络, 是 ISO 国际标准化的串行通信协议,主要用于汽车电子和工业领域 can 是一种多主方式的串行通讯总线,各个单元通过 can 总线连接在一起,每个单元都是独立的 can 节点,同一 can 网络中所有单元的通信速度必须一致,不同网络之间通信速度可以不同 can 总线协议参考了 osi 七层模型,但是实际上 can 协议只定义了两层物理层和数据链路层

序号

层次

描述

7

应用层

主要定义CAN应用层

2

数据链路层

数据链路层分为逻辑链接控制子层 LLC 和介质访问控制子层MAC,MAC 子层是 CAN 协议的核心,它把接收到的报文提供给 LLC 子层,并接收来自 LLC 子层的报文
MAC 子层负责报文分帧,仲裁,应答,错误检测和标定,MAC 子层也被称作故障界定的管理实体监管
LLC 子层涉及报文滤波,过载通知,以及恢复管理
LLC = Logical Link Control
MAC = Medium Access Control

1

物理层

物理层,为物理编码子层PCS 该层定义信号是如何实际地传输的,因此涉及到位时间,位编码,同步

2.11.1.2 can 总线的优点

多主控制,在 can 总线空闲时,所有节点都可以发送消息,总线根据节点的 id 进行仲裁,然后,确定谁先发送消息 系统的柔软性,没有类似于地址的信息,因此在同一总线的其他节点,软硬件和应用都不需要改变 通信速度快,距离远,最快 10mbos,最远 10km 具有错误检测,错误通知和错误恢复功能,所有单元都可以检测错误(错误检测功能),检测出错误的单元会立即同时通知其他所有单元(错误通知功能),正在发送消息的单元一旦检测出错误,会强制结束当前的发送,强制结束发送的单元会不断反复地重新发送此消息直到成功发送为止(错误恢复功能) 故障封闭功能,当总线上发生持续数据错误时,可将引起此故障的单元从总线上隔离出去 连接节点多,can 总线是可同时连接多个单元的总线,可连接的单元总数理论上是没有限制的,但实际上可连接的单元数受总线上的时间延迟及电气负载的限制,降低通信速度,可连接的单元数增加,提高通信速度,则可连接的单元数减少

2.11.1.3 电气属性

can_phycisal_structure 1)两根差分线,CAN_H 和 CAN_L 2)两个电平,显性电平和隐性电平 显性电平:逻辑 0,CAN_H 比 CAN_L 高,分别是 3.5v 和 1.5v,电位差为 2v 隐性电平:逻辑 1,CAN_H 和 CAN_L 都是 2.5v,电位差为 0v can_level 3)途中所有的节点单元都采用 CAN_H 和 CAN_L 这两根线连接在一起, CAN_H 接 CAN_H、 CAN_L 接 CAN_L, CAN 总线两端要各接一个 120Ω 的端接电阻,用于匹配总线阻抗,吸收信号反射及回拨,提高数据通信的抗干扰能力以及可靠性 4)can 控制器,控制器用于将欲收发的消息,转换为符合 can 规范的 can 帧,通过 can 收发器,在 can 总线上上交换信息 5)can 收发器是 can 控制器和物理总线之间的接口,将 can 控制器的逻辑电平转换为 can 总线的差分电平,在两条有差分电压的总线电缆上传输数据

2.11.1.4 通信部分

can 协议遵循,去中心化,分布式的原则,总线空闲的时间任何一个节点均可以竞争发送消息,消息将被广播,由节点自己决定是否过滤

2.11.1.4.1 帧格式

数据帧的帧格式如下图 can_frame sof 1bit,发出一个显性位边沿,网络节点以此开始同步 id 11bit,定义消息的优先级/总线竞争力,数字越低优先级越高 rtr 1bit,显性表示数据帧,隐性表示远程帧 ide 1bit,扩展帧标识符,扩展帧的 id 可以是 29 位,如下所示(扩展帧和标准帧格式不同,不能存在于同一 can 网络 r 1bit,保留位 dlc 4bit,表示数据场的长度,最大表示 8,因此 data field 最多 9 字节 data field 64 bit crc field 16 bit,含 1 bit 隐性位的 crc 界定符,crc 是从 sof 开始计算的 ack field 2 bit,由 ack 和 del(ack 界定符) 组成,由接收方进行确认,收到消息给出一个显性位,如果一个节点都没有确认收到消息,发送方监听此位为隐性位就会报错 eof 7bit,结束标识符,7bit 隐性位即结束 itm 3bit,帧间隔,实际不属于帧内区域,必须等待帧间隔才能发送消息

2.11.1.4.2 总线同步

首次同步由 sof 发起 同步的原因,因为没有单独的时钟线,编码形式是 NRZ,不带时钟同步 重同步,位填充机制(不允许发 6 个相同的极性,如果有则会插入一个相反的极性,因此用示波器查看时,会发现波形不对劲,需要人为修改,但用有 can 功能的示波器则可以获取正确序列),利用隐性位到显性位的边沿进行同步

2.11.1.4.3 总线竞争

解决多个节点同一时间发送消息的问题,通过 id 来竞争 每个节点在发送时,都会对总线电平进行监控 send 0 总线上出现 1,则报错 send 0 总线上出现 0,则继续竞争 send 1 总线上出现 1,则继续竞争 send 1 总线上出现 0,竞争失败,转为接收方 can_competition_process

2.11.1.4.4 数据保护

2.11.1.4.4.1 在物理设计上

NRZ 编码可以减小干扰发射 双绞线 canh 和 canl 差分信号可以消除共模干扰 终端电阻可以消除电缆线高频传输时,传输末端的反射

2.11.1.4.4.2 总线逻辑错误检查机制

1)stuffcheck 接受点检测,检测是否有位填充错误,出现 6 个同极性就报错 2)formcheck 接受点检测,检测 crc 界定符和 ack 界定符以及 eof 区域是否出现显性位 3)bit monitoring 发送节点检测,发送显性位,但总线时隐性位则报错 4)crc 接受点检测,接受方会快速生成一个值,与发送方的 crc 比对,不一致就报错 5)ack check 发送方检测,接收方会在收到消息后在 ack 应答位给出一个显性电平,如果发送方检测到该位为隐性,则报错

2.11.2 linux 下 can 的使用

在测试之前硬件连接如下,需要注意的是 can 电平转换器也需要供电且需要 5V 供电

image-can_hardware_connection

实物图如下

img-can_profile_display

2.11.2.1 查询 can 信息

查询 can 的详细信息,包括波特率,标志设置等信息

root@igkboard:~# ip -details link show can0

查询 can 的工作状态

root@igkboard:~# ip -details -statistics link show can0

查询 can 的收发数据包情况,以及中断号

root@igkboard:~# ifconfig -a

查询 can 的详细物理信息,包括电压,寄存器,中断等

root@igkboard:~# dmesg | grep “can”

2.11.2.2 开启/关闭 can

关闭 can

root@igkboard:~# ifconfig can0 down

打开 can

root@igkboard:~# ifconfig can0 up

打开 can 网络

root@igkboard:~# ip link set can0 up type can

关闭 can 网络

root@igkboard:~# ip link set can0 down

2.11.2.3 发送/接收 can 数据

发送默认 id 为 0x1 的 can 标准帧,数据为 0x11 22 33 44 55 66 77 88 每次最大 8 个 byte

root@igkboard:~# cansend can0 0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88

-e 表示扩展帧,can_id 最大 29bit,标准帧 CAN_ID 最大 11bit,-I 表示 can_id

root@igkboard:~# cansend can0 -i 0x800 0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88 -e

–loop 表示发送 20 个包

root@igkboard:~# cansend can0 -i 0x02 0x11 0x12 --loop=20

接收数据

root@igkboard:~# candump can0

发送数据,123 是发送到的 can 设备 id 号,后面接发送内容

root@igkboard:~# cansend can0 123#1122334455667788

2.11.2.4 设置 can 参数

can 参数设置详解

root@igkboard:~# ip link set can0 type can --help

设置 can0 的波特率为 800kbps,can 网络波特率最大值为 1mbps

root@igkboard:~# ip link set can0 up type can bitrate 800000

设置回环模式,自发自收,用于测试是硬件是否正常,loopback 不一定支持

root@igkboard:~# ip link set can0 up type can bitrate 800000 loopback on

2.11.3 linux 下 can 回环测试

开发板的 rx 连接收发器的 rx,tx 同理,收发器的 CANH 也是连接另一个的 CANH,CANL 同理,电源 VCC 按照厂家要求连接,有的需要 5V

先查看是否存在两路 can

root@igkboard:~# ifconfig 
can0      Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  
          inet addr:169.254.185.103  Mask:255.255.0.0
          UP RUNNING NOARP  MTU:16  Metric:1
          RX packets:5 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:2 overruns:0 carrier:2
          collisions:0 txqueuelen:10 
          RX bytes:39 (39.0 B)  TX bytes:0 (0.0 B)
          Interrupt:35 

can1      Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  
          inet addr:169.254.96.198  Mask:255.255.0.0
          UP RUNNING NOARP  MTU:16  Metric:1
          RX packets:7 errors:0 dropped:0 overruns:0 frame:0
          TX packets:1 errors:0 dropped:3 overruns:0 carrier:3
          collisions:0 txqueuelen:10 
          RX bytes:55 (55.0 B)  TX bytes:7 (7.0 B)
          Interrupt:36 

使用 ip 命令对两路 can 进行参数配置,配置先必须先禁用改设备

root@igkboard:~/40_pin_test# ip link set can0 down
root@igkboard:~/40_pin_test# ip link set can0 type can bitrate 500000
root@igkboard:~/40_pin_test# ip link set can0 up

root@igkboard:~/40_pin_test# ip link set can1 down
root@igkboard:~/40_pin_test# ip link set can1 type can bitrate 500000
root@igkboard:~/40_pin_test# ip link set can1 up

使用 candump 命令进行 can0 的数据接收,使用 cansend 命令进行 can1 的数据发送

root@igkboard:~# candump can0
root@igkboard:~# cansend can1 123#01020304050607

测试效果如下,can0 接收到 can1 发送的数据

root@igkboard:~/40_pin_test# candump can0
  can0  123   [7]  01 02 03 04 05 06 07

2.11.4 linux 下 can 应用编程

2.11.4.1 can 应用编程代码

linux 提供了 socket can 应用编程接口,使得 can 总线通信近似于和以太网的通信,socket can 应用编程接口在头文件 linux/can.h 中有定义 接下来的应用,实现了利用 can 总线接收 sht20 温湿度,以及发送指令控制 led 亮灭

2.11.4.1.1 led 部分代码

led 部分,主要有两个文件 led.h 和 led.c,主要实现了初始化 led 对应的 gpio,并通过接受到的命令,来控制 led 亮灭,代码如下

#ifndef LED_H
#define LED_H

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <string.h>
#include <gpiod.h>

#define LED_ON          1
#define LED_OFF         0
#define LED_OFFSET	    10

struct gpiod_chip *chip;
struct gpiod_line *line_led;

int led_init(unsigned char gpio_chip, unsigned char gpio_offset);
int led_control(unsigned char cmd);
int led_release(void);

#endif
/* 简单的利用 gpiod 控制 led 亮灭 */
#include "led.h"

int led_init(unsigned char gpio_chip, unsigned char gpio_offset)
{
    int             ret;
    unsigned char   buf[16];

    if(gpio_chip<0 || gpio_chip>5 || gpio_offset>32)
    {
        printf("%s line [%d] %s() get invalid input arguments\n", __FILE__, __LINE__, __func__ );
        return -1;
    }

    memset(buf, 0x0, sizeof(buf));
    snprintf(buf, sizeof(buf), "/dev/gpiochip%d", gpio_chip-1);

    chip = gpiod_chip_open(buf);
    if(!chip)
    {
        printf("fail to open chip0\n");
        return -1;
    }

    line_led = gpiod_chip_get_line(chip, LED_OFFSET);
    if(!line_led)
    {
        printf("fail to get line_led\n");
        return -2;
    }

	ret = gpiod_line_request_output(line_led, "led_out", 1);
    if(ret < 0)
    {
        printf("fail to request line_led for output mode\n");
        return -3;
    }

    return ret;
}

int led_control(unsigned char cmd)
{
    int ret; 

    if(cmd == LED_OFF)
    {
        ret = gpiod_line_set_value(line_led, 0);
    }
    else if(cmd == LED_ON)
    {
        ret = gpiod_line_set_value(line_led, 1);
    }

    if(ret < 0)
    {
        printf("fail to set line_led value\n");
        return -1;
    }

    return ret;
}

int led_release(void)
{
    gpiod_line_release(line_led);
    gpiod_chip_close(chip);
}

2.11.4.1.2 sht20 部分代码

sht20 部分,有 sht20.h 和 sht20.c,包含通过 i2c 协议获取温湿度的函数

#ifndef SHT20_H
#define SHT20_H

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <sys/stat.h>
#include <linux/i2c-dev.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#include <errno.h>
#include <string.h>

#define SOFTRESET                       0xFE
#define TRIGGER_TEMPERATURE_NO_HOLD     0xF3
#define TRIGGER_HUMIDITY_NO_HOLD        0xF5

#define SHT20_PATH                      "/dev/i2c-0"

static inline void msleep(unsigned long ms);
int sht2x_init(void);
int sht2x_get_temp_humidity(int fd, unsigned char *buf_temp, unsigned char *buf_rh, float *temp, float *rh);

#endif
/* i2c 的应用编程相关内容 */
#include "sht20.h"

static inline void msleep(unsigned long ms)
{
    struct timespec cSleep;
    unsigned long ulTmp;

    cSleep.tv_sec = ms / 1000;
    if (cSleep.tv_sec == 0)
    {
        ulTmp = ms * 10000;
        cSleep.tv_nsec = ulTmp * 100;
    }
    else
    {
       cSleep.tv_nsec = 0;
    }

    nanosleep(&cSleep, 0);
}

int sht2x_softreset(int fd)
{
    uint8_t           buf[4];

    if(fd < 0)
    {
        printf("%s line [%d] %s() get invalid input arguments\n", __FILE__, __LINE__, __func__ );
        return -1;
    }

    memset(buf, 0, sizeof(buf));

    buf[0] = SOFTRESET;
    write(fd, buf, 1);

    msleep(50);

    return 0;
}

int sht2x_init(void)
{
    int          fd;

    if((fd=open(SHT20_PATH, O_RDWR)) < 0)
    {
        printf("fail to open sht20\n");
        return -1;
    }

    ioctl(fd, I2C_TENBIT, 0);    
    ioctl(fd, I2C_SLAVE, 0x40); 

    if( sht2x_softreset(fd) < 0 )
    {
        printf("fail to softreset sht20\n");
        return -2;
    }

    return fd;
}

int sht2x_get_temp_humidity(int fd, unsigned char *buf_temp, unsigned char *buf_rh, float *temp, float *rh)
{
    uint8_t           buf[4];

    if(fd<0 || !temp || !rh)
    {
        printf("%s line [%d] %s() get invalid input arguments\n", __FILE__, __LINE__, __func__ );
        return -1;
    }

    memset(buf, 0, sizeof(buf));
    buf[0]=TRIGGER_TEMPERATURE_NO_HOLD;
    write(fd, buf, 1);

    msleep(85); 

    memset(buf, 0, sizeof(buf));
    read(fd, buf, 3);
    *buf_temp = *buf;
    *(buf_temp+1) = *(buf+1);
    *(buf_temp+2) = *(buf+2);
    *temp = 175.72 * (((((int) buf[0]) << 8) + buf[1]) / 65536.0) - 46.85;

    memset(buf, 0, sizeof(buf));
    buf[0] = TRIGGER_HUMIDITY_NO_HOLD;
    write(fd, buf, 1);

    msleep(29); 
    memset(buf, 0, sizeof(buf));

    read(fd, buf, 3);
    *buf_rh = *buf;
    *(buf_rh+1) = *(buf+1);
    *(buf_rh+2) = *(buf+2);
    *rh = 125 * (((((int) buf[0]) << 8) + buf[1]) / 65536.0) - 6;

    return 0;
}

2.11.4.1.3 can 部分代码

can 部分,由 can.h 和 can.c 组成,主要定义了通过 can 总线发送数据和接受数据的函数,以及初始化 can 控制器函数 can_set_controller 函数,通过 socket 函数创建套接字文件,然后构建 ifr 结构体,该结构体用于记录网络接口信息(can 设备中,主要传入 ifr_name 为 can0 或 can1 即可),然后配置 sockaddr_can 结构体,主要是指定地址族和 can 设备索引,最后使用 bind 函数绑定套接字文件和 sockaddr_can can_write 和 can_read,主要调用内核提供的 write 和 read 函数,进行数据传输

#ifndef CAN_H
#define CAN_H

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>
#include <pthread.h>

#define CMD_SET_CAN0_BITRATE     "ip link set can0 type can bitrate 500000 triple-sampling on"
#define CMD_CAN0_UP              "ifconfig can0 up"      
#define CMD_CAN0_DOWN            "ifconfig can0 down"  

#define CMD_SET_CAN1_BITRATE     "ip link set can1 type can bitrate 500000 triple-sampling on"
#define CMD_CAN1_UP              "ifconfig can1 up"      
#define CMD_CAN1_DOWN            "ifconfig can1 down" 

struct can_controller
{
    char    *can_name;
    int     can_fd;                                
};

int can_set_controller(char *can_name);
int can_write(int can_fd, struct can_frame tx_frame);
int can_read(int can_fd, struct can_frame *rx_frame);

#endif
#include "can.h"

/* 设置 can 控制器的函数 */
int can_set_controller(char *can_name)
{
   	struct ifreq 		   ifr;		     // 网络接口信息结构体,可以用 ioctrl 来指定 can 设备,主要是通过指定 ifreq 结构体中的 ifr_name 成员
   	struct sockaddr_can     can_addr;	  // 用于将套接字和 can 设备进行绑定
    int                     sock_fd = -1; // 用 socket 函数创建 can 的套接字文件,返回文件描述符
	
    /* 通过参数判断使能 can0 还是 can1

   * 利用 system 函数执行 shell 命令 
     * CMD_SET_CAN0_BITRATE 用于设置波特率 
      * CMD_CAN0_UP 和 CMD_CAN0_DOWN 用于使能和禁用 can 
        */
        if(strcmp(can_name, "can0") == 0)
        {
        system(CMD_CAN0_DOWN);               
        system(CMD_SET_CAN0_BITRATE);
        system(CMD_CAN0_UP);
        }

    if(strcmp(can_name, "can1") == 0)
    {
        system(CMD_CAN1_DOWN);               
        system(CMD_SET_CAN1_BITRATE);
        system(CMD_CAN1_UP);
    }
	
    /* 创建套接字文件 */ 
    sock_fd = socket(AF_CAN, SOCK_RAW, CAN_RAW);
    if(sock_fd < 0)
    {
        printf("fail to create socket for %s\n", can_name);
        return -1;
    }

    /* 指定设备为名 can_name 的 can 设备 */
    strcpy(ifr.ifr_name, can_name);
    
    /* 获取网络接口 */
    ioctl(sock_fd, SIOCGIFINDEX, &ifr); 
	
    /* 向 can_addr 中填充数据 */
    can_addr.can_family = AF_CAN;
    can_addr.can_ifindex = ifr.ifr_ifindex;
	
    /* 将套接字与 can0 进行绑定 */
    if(bind(sock_fd, (struct sockaddr *)&can_addr, sizeof(can_addr)) < 0)
    {
        printf("fail to bind %s to socket\n", can_name);
        return -1;
    }

    return sock_fd;
}

/* 通过 can 发送数据的函数 */
int can_write(int can_fd, struct can_frame tx_frame)
{
    /* 调用 write 函数,向 can_fd 写入数据 */
    int ret = write(can_fd, &tx_frame, sizeof(struct can_frame));
	
    /* 打印信息,用于查看发出的数据 */
    if(ret > 0)
    {
        printf("[send data]\n");
        printf("id=%03x dlc=%d ", tx_frame.can_id, tx_frame.can_dlc);
        printf("data=");
        for(int i=0; i<tx_frame.can_dlc; i++)
        {
            printf("0x%02x ", tx_frame.data[i]);
        }
        printf("\n");

        return 0;
    }
    else
    {
        return ret;
    }
}

/* 通过 can 接受数据的函数 */
int can_read(int can_fd, struct can_frame *rx_frame)
{ 
    int ret = read(can_fd, rx_frame, sizeof(struct can_frame));

    if(ret > 0)
    {
        printf("[recieve data]\n");
        if (rx_frame->can_id & CAN_ERR_FLAG) 
        {
            printf("error frame\n");
        }

        /* 校验帧类型,数据帧还是远程帧 */
        if (rx_frame->can_id & CAN_RTR_FLAG) 
        {
            printf("remote request\n");
        }

        /* 校验帧格式,判断是扩展帧还是标准帧 */
        if (rx_frame->can_id & CAN_EFF_FLAG)	
        {
            printf("extened frame id=0x%08x ", rx_frame->can_id & CAN_EFF_MASK);
        }
        else	
        {
            printf("standard frame id=0x%03x ", rx_frame->can_id & CAN_SFF_MASK);
        }							
        
        /* 打印信息,用于查看接受的数据 */
        printf("dlc=%d ", rx_frame->can_dlc);
        printf("data=");
        for(int i=0; i<rx_frame->can_dlc; i++)
        {
            printf("0x%02x ", rx_frame->data[i]);
        }
        printf("\n");

        return 0;
    } 
    else
    {
        return ret;
    }
}

最后在主函数,main.c 中通过 can0/can1,recieve/send 参数, led/sht20 参数选项可以进行不同数据收发 在接受数据时,创建了线程,这样就可以在同一主机上同时进行数据收发,代码如下

#include "sht20.h"
#include "can.h"
#include "led.h"

/* thread_para 结构体,用于之后接受数据时,为创建的线程传入参数*/
struct thread_para
{
    int can_fd;
    struct can_frame rx_frame;
    char* device_name;
};

void print_help(char *arg)
{
    printf("this is a can test program\n");
    printf("usage: %s can0/can1 send/recieve sht20/led\n", arg);
}

/* 解析 sht20 数据,并打印 */
int parse_sht20_data(struct can_frame rx_frame)
{
    float       			temp;
    float       			rh;

    temp = 175.72 * (((((int) rx_frame.data[0]) << 8) + rx_frame.data[1]) / 65536.0) - 46.85;
    rh = 125 * (((((int) rx_frame.data[3]) << 8) + rx_frame.data[4]) / 65536.0) - 6;
    printf("temperature=%lf relative humidity=%lf\n", temp, rh);
}

/* 解析 led 数据,并控制 led 灯 */
int parse_led_data(struct can_frame rx_frame)
{
    if(rx_frame.data[0] == 0x0)
    {
        led_control(0);
    }
    else if(rx_frame.data[0] == 0x1)
    {
        led_control(1);
    }
}

/* 接受数据时创建的线程 */
void *recieve_thread(void *arg)
{
    int                     can_fd;
    struct                  can_frame rx_frame;
    char                    *device_name;

    struct thread_para      *para = (struct thread_para *)arg;

    can_fd = para->can_fd;
    rx_frame = para->rx_frame;
    device_name = para->device_name;
	
    /* 判断传入的参数,并解析,执行 sht20 和 led 对应的解析函数 */
    for ( ; ; ) 
    {
        if(can_read(can_fd, &rx_frame) == 0)
        {
            if(strcmp(device_name, "sht20") == 0)
            {
                if(rx_frame.can_dlc < 6)
                {
                    printf("the length of data is incorrect\n\n");
                    continue;
                }
                parse_sht20_data(rx_frame);
            }
            else if(strcmp(device_name, "led") == 0)
            {
                parse_led_data(rx_frame);
            }
        }

        printf("\n");
    }
}

int main(int argc, char **argv)
{
    struct can_controller   can_ctrl;		// can 控制器结构体
    struct can_frame 		frame;		   // can 帧数据,用于数据传输
    struct can_filter       rfilter[1];	    // can 过滤器
    int                     ret;

    int         			sht20_fd;
    float       			temp;
    float       			rh;
	unsigned char 			buf_temp[3];
    unsigned char 			buf_rh[3];
	
    /* 创建线程所需要的参数 */
    pthread_t               ntid;
    struct thread_para      para;
    int                     *thread_ret = NULL;
	
    if( argc<3 || (strcmp(argv[1], "can0") !=0 && strcmp(argv[1], "can1") !=0))
    {
        print_help(argv[0]);
        return 0;
    }
	
    /* 通过传入的参数,来使能 can 设备,并设置 can 控制器*/
    can_ctrl.can_name = argv[1]; 
    can_ctrl.can_fd = can_set_controller(can_ctrl.can_name);
    if(can_ctrl.can_fd < 0)
    {
        printf("fail to set can controller");
        return -1;
    }
	
   	/* 如果应用程序用于接受数据 */
    if(0 == strcmp(argv[2], "recieve"))
    {
        /* 设置 can 过滤器,这里表示 只接受 id 为 0x123 can 从设备发出的数据*/
        rfilter[0].can_id = 0x123;
        rfilter[0].can_mask = 0x7ff;
        setsockopt(can_ctrl.can_fd, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
		
        /* 设置创建的新线程所需的参数 */
        para.can_fd = can_ctrl.can_fd;
        para.rx_frame = frame;
        para.device_name = argv[3];
		
        /* 创建新线程,并开始接受数据 */
        if(0 == strcmp(argv[3], "sht20"))
        {
            ret = pthread_create(&ntid, NULL, recieve_thread, (void*)(&para));
            if(ret != 0)
            {
                printf("fail to create sht20 recieve thread\n");
            }

            pthread_join(ntid, (void**)&thread_ret);
        }
        else if(0 == strcmp(argv[3], "led"))
        {
            /* 测试时,使用的是 gpio1_10 进行 led 测试 */
            ret = led_init(1, 10);
            if(ret < 0)
            {
                printf("fail to excute led_init\n");
            }

            ret = pthread_create(&ntid, NULL, recieve_thread, (void*)(&para));
            if(ret != 0)
            {
                printf("fail to create sht20 recieve thread\n");
            }

            pthread_join(ntid, (void**)&thread_ret);
        }
        else
        {
            print_help(argv[0]);
            return 0;
        }
    }
    /* 如果应用程序用于发送数据 */
    else if(0 == strcmp(argv[2], "send"))
    {
        /* 设置 socket 为只接受模式 */
        setsockopt(can_ctrl.can_fd, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0); 

        /* 如果要发送 sht20 数据,则初始化 sht20,然后获取数据,并使用 can_write 发送数据*/
        if(0 == strcmp(argv[3], "sht20"))
        {
            sht20_fd = sht2x_init();
            if(sht20_fd < 0)
            {
                printf("fail to  initialize sht20\n");
                return -1;
            }

            frame.can_dlc = 6;	
            /* 测试时,设置接受中使用过滤器,只接受 id 为 0x123 的设备发出的数据,因此此处设置 can_id 为 0x123*/
            frame.can_id = 0x123;

            for ( ; ; ) 
            {
                if(sht2x_get_temp_humidity(sht20_fd, buf_temp, buf_rh, &temp, &rh) < 0)
                {
                    printf("fail to get data from sht2x\n");
                    return -1;
                }
                frame.data[0] = buf_temp[0];
                frame.data[1] = buf_temp[1];
                frame.data[2] = buf_temp[2];
                frame.data[3] = buf_rh[0];
                frame.data[4] = buf_rh[1];
                frame.data[5] = buf_rh[2];

                can_write(can_ctrl.can_fd, frame);
                printf("\n");

                sleep(1);		
            }
        }
        /* 同理,如果发送 led 数据如下 */
        else if(0 == strcmp(argv[3], "led"))
        {
            frame.can_dlc = 1;		
            frame.can_id = 0x123;
            
            while(1)
            {
                frame.data[0] = 0x1;
                can_write(can_ctrl.can_fd, frame);
                sleep(1);

                frame.data[0] = 0x0;
                can_write(can_ctrl.can_fd, frame);
                sleep(1);

                printf("\n");
            }
        }
        else
        {
            print_help(argv[0]);
            return 0;
        }
    }
    else
    {
        print_help(argv[0]);
        return 0;
    }
}

2.11.4.2 代码测试

使用 can0 发送 sht20 数据

root@igkboard:~# ./can_test can0 send sht20       
[send data]
id=111 dlc=6 data=0x6b 0x34 0x4e 0x8b 0xe2 0x9a 

[send data]
id=111 dlc=6 data=0x6b 0x34 0x4e 0x8b 0xe2 0x9a 

[send data]
id=111 dlc=6 data=0x6b 0x34 0x4e 0x8b 0xe2 0x9a 

[send data]
id=111 dlc=6 data=0x6b 0x34 0x4e 0x8b 0xe2 0x9a

在另一端看到的接受数据

root@igkboard:~# ./can_test can0 recieve sht20
[recieve data]
standard frame id=0x111 dlc=6 data=0x6b 0x34 0x4e 0x8b 0xe2 0x9a 
temperature=26.734896 relative humidity=62.302155

[recieve data]
standard frame id=0x111 dlc=6 data=0x6b 0x34 0x4e 0x8b 0xe2 0x9a 
temperature=26.734896 relative humidity=62.302155

[recieve data]
standard frame id=0x111 dlc=6 data=0x6b 0x34 0x4e 0x8b 0xe2 0x9a 
temperature=26.734896 relative humidity=62.302155

[recieve data]
standard frame id=0x111 dlc=6 data=0x6b 0x34 0x4e 0x8b 0xe2 0x9a 
temperature=26.734896 relative humidity=62.302155

同理,使用 can0 控制 led 灯的亮灭

root@igkboard:~# ./can_test can0 send led       
[send data]
id=222 dlc=1 data=0x01 

[send data]
id=222 dlc=1 data=0x00 

[send data]
id=222 dlc=1 data=0x01 

[send data]
id=222 dlc=1 data=0x00 

在另一端可以看到 led 灯闪烁,并且终端可以接受到如下信息

root@igkboard:~# ./can_test can0 recieve led  
[recieve data]
standard frame id=0x222 dlc=1 data=0x01 

[recieve data]
standard frame id=0x222 dlc=1 data=0x00 

[recieve data]
standard frame id=0x222 dlc=1 data=0x01 

[recieve data[]
standard frame id=0x222 dlc=1 data=0x00