文章目录
- 一、CAN总线协议
-
- 二、Linux下CAN的操作
- 1.硬件连接
-
- 2.查询 can 信息
- 3.开启/关闭 can
- 4.发送/接收 can 数据
- 5.设置 can 参数
- 三、CAN的回环测试
- 四、CAN的应用编程
-
一、CAN总线协议
1.简介
控制器局域网总线(CAN,Controller Area Network)是一种用于实时应用的串行通讯协议总线,它可以使用双绞线来传输信号,是世界上应用最广泛的现场总线之一,是 ISO 国际标准化的串行通信协议。
CAN协议用于汽车中各种不同元件之间的通信,以此取代昂贵而笨重的配电线束。该协议的健壮性使其用途延伸到其他自动化和工业应用。CAN协议的特性包括完整性的串行数据通讯、提供实时支持、传输速率高达1Mb/s、同时具有11位的寻址以及检错能力。
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 该层定义信号是如何实际地传输的,因此涉及到位时间,位编码,同步 |
CAN总线还具有多主控制、系统的柔软性、通信速度快、具有错误检测、故障封闭功能、连接节点多等众多优点。
2.电气属性
1)两根差分线,CAN_H 和 CAN_L
2)两个电平,显性电平和隐性电平
显性电平:逻辑 0,CAN_H 比 CAN_L 高,分别是 3.5v 和 1.5v,电位差为 2v
隐性电平:逻辑 1,CAN_H 和 CAN_L 都是 2.5v,电位差为 0v
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 总线的差分电平,在两条有差分电压的总线电缆上传输数据
3.通信原理
①数据帧的帧格式:
- 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,帧间隔,实际不属于帧内区域,必须等待帧间隔才能发送消息
②总线同步
- 首次同步由 sof 发起
- 同步的原因,因为没有单独的时钟线,编码形式是 NRZ,不带时钟同步
- 重同步,位填充机制(不允许发 6 个相同的极性,如果有则会插入一个相反的极性,因此用示波器查看时,会发现波形不对劲,需要人为修改,但用有 can 功能的示波器则可以获取正确序列),利用隐性位到显性位的边沿进行同步
③总线竞争
解决多个节点同一时间发送消息的问题,通过 id 来竞争每个节点在发送时,都会对总线电平进行监控
- send 0 总线上出现 1,则报错
- send 0 总线上出现 0,则继续竞争
- send 1 总线上出现 1,则继续竞争
- send 1 总线上出现 0,竞争失败,转为接收方
④数据保护
二、Linux下CAN的操作
1.硬件连接
①CAN电平转换器
转换器也需要5V供电:
②扩展板使用CAN
2.查询 can 信息
查询 can 的详细信息,包括波特率,标志设置等信息:
root@igkboard:~
查询 can 的工作状态:
root@igkboard:~
查询 can 的收发数据包情况,以及中断号:
root@igkboard:~
查询 can 的详细物理信息,包括电压,寄存器,中断等(需要启动can后才可以):
root@igkboard:~
3.开启/关闭 can
关闭 can:
root@igkboard:~
打开 can:
root@igkboard:~
打开 can 网络:
root@igkboard:~
关闭 can 网络:
root@igkboard:~
4.发送/接收 can 数据
发送默认 id 为 0x1 的 can 标准帧,数据为 0x11 22 33 44 55 66 77 88 每次最大 8 个 byte:
root@igkboard:~
-e 表示扩展帧,can_id 最大 29bit,标准帧 CAN_ID 最大 11bit,-I 表示 can_id:
root@igkboard:~
–loop 表示发送 20 个包:
root@igkboard:~
接收数据:
root@igkboard:~
发送数据,123 是发送到的 can 设备 id 号,后面接发送内容 :
root@igkboard:~
5.设置 can 参数
can 参数设置详解:
root@igkboard:~
设置 can0 的波特率为 800kbps,can 网络波特率最大值为 1mbps:
root@igkboard:~
设置回环模式,自发自收,用于测试是硬件是否正常,loopback 不一定支持:
root@igkboard:~
三、CAN的回环测试
首先通过ifconfig检查两路can:
can0: flags=193<UP,RUNNING,NOARP> mtu 16
unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 10 (UNSPEC)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
device interrupt 35
can1: flags=193<UP,RUNNING,NOARP> mtu 16
unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 10 (UNSPEC)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
device interrupt 36
使用 ip 命令对两路 can 进行参数配置,配置先必须先禁用该设备:
root@igkboard:~/40_pin_test
root@igkboard:~/40_pin_test
root@igkboard:~/40_pin_test
root@igkboard:~/40_pin_test
root@igkboard:~/40_pin_test
root@igkboard:~/40_pin_test
使用 candump 命令进行 can0 的数据接收,使用 cansend 命令进行 can1 的数据发送:
root@igkboard:~
root@igkboard:~
测试效果如下,can0 接收到 can1 发送的数据:
root@igkboard:~/40_pin_test
can0 123 [7] 01 02 03 04 05 06 07
四、CAN的应用编程
1.程序代码
sht20.h
#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
sht20.c
#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;
}
led.h
#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
led.c
#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);
}
can.h
#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
can.c
#include "can.h"
int can_set_controller(char *can_name)
{
struct ifreq ifr;
struct sockaddr_can can_addr;
int sock_fd = -1;
if(strcmp(can_name, "can0")==0)
{
system(CMD_CAN0_DOWN);
system(CMD_SET_CAN0_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;
}
strcpy(ifr.ifr_name, can_name);
ioctl(sock_fd, SIOCGIFINDEX, &ifr);
can_addr.can_family = AF_CAN;
can_addr.can_ifindex = ifr.ifr_ifindex;
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;
}
int can_write(int can_fd, struct can_frame tx_frame)
{
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;
}
}
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
#include "sht20.h"
#include "can.h"
#include "led.h"
struct thread_para
{
int can_fd;
struct can_frame rx_frame;
char* device_name;
};
void print_usage(char *arg)
{
printf("This is a can test program\n");
printf("usage: %s can0/can1 send/recieve sht20/led\n", arg);
}
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);
}
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;
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;
struct can_frame frame;
struct can_filter rfilter[1];
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_usage(argv[0]);
return 0;
}
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"))
{
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*)(¶));
if(ret != 0)
{
printf("fail to create sht20 recieve thread\n");
}
pthread_join(ntid, (void**)&thread_ret);
}
else if(0 == strcmp(argv[3], "led"))
{
ret = led_init(1, 10);
if(ret < 0)
{
printf("fail to excute led_init\n");
}
ret = pthread_create(&ntid, NULL, recieve_thread, (void*)(¶));
if(ret != 0)
{
printf("fail to create led recieve thread\n");
}
pthread_join(ntid, (void**)&thread_ret);
}
else
{
print_usage(argv[0]);
return 0;
}
}
else if(0 == strcmp(argv[2], "send"))
{
setsockopt(can_ctrl.can_fd, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
if(0 == strcmp(argv[3], "sht20"))
{
sht20_fd = sht2x_init();
if(sht20_fd < 0)
{
printf("fail to initializa sht20");
return -1;
}
frame.can_dlc = 6;
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);
}
}
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_usage(argv[0]);
return 0;
}
}
else
{
print_usage(argv[0]);
return 0;
}
}
2.makefile
CC = arm-linux-gnueabihf-gcc
APP_NAME = socketCAN
all:clean
@${CC} *.c -o ${APP_NAME} -lpthread -lgpiod
install:
@cp ${APP_NAME} ../tftpboot/
clean:
@rm -f ${APP_NAME}
3.程序测试
首先编译好我们的可执行程序:
然后通过tftp命令下载到开发板上,并赋予权限:
使用 can0 发送 sht20 数据
root@igkboard:~
[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:~
[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:~
[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:~
[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
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)