文章目录
一、CAN总线协议
1.简介
控制器局域网络(Controller Area Network,简称CAN)是一种为实时应用精心设计的串行通信协议总线。它基于双绞线传输机制,已在全球范围内得到广泛认可,并在众多场合中得到应用,成为现场总线通信技术的国际标准化方案。
CAN协议最初为汽车工业所设计,目的是在车辆内部实现不同电子控制单元(ECUs)之间的高效通信,它有效地取代了成本高昂、体积庞大的传统布线系统。得益于其卓越的健壮性和可靠性,CAN协议已经成功地扩展到自动化和工业控制等多个领域。该协议的核心特点包括完备的串行数据通信功能、实时性支持、最高1Mbps的数据传输速率、11位的地址空间,以及先进的错误检测机制。
CAN作为一种多主体串行通信总线,支持多个单元通过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,竞争失败,转为接收方
④数据保护
CAN总线(Controller Area Network)在设计时特别强调了数据的安全性和可靠性,采取了多种机制来保护传输数据的完整性和准确性。以下是一些关键的数据保护特性:
错误检测:CAN总线集成了循环冗余校验(CRC),这使得CAN节点能够检测数据包中的任何错误。CRC校验确保了数据包的完整性,任何不符合CRC校验的数据包将被丢弃,从而避免了错误数据的传播。
优先级编码:CAN总线的每个数据帧都有一个优先级字段,允许高优先级消息优先传输,确保了关键任务数据的及时传递。
仲裁机制:在多个节点同时发送数据时,通过仲裁机制确保了数据传输的有序性,防止了数据冲突。
确认机制:发送节点发送数据后,接收节点将发送确认,确保数据被正确接收。
重发机制:如果数据帧未被成功接收,发送节点将自动重发数据,增强了数据传输的可靠性。
位填充:CAN协议使用位填充技术,通过在数据帧中插入额外的位以确保数据帧的同步。
总线监控:CAN总线节点可以处于监听模式,实时监控总线状态,及时发现并响应总线错误。
错误计数器:CAN控制器包含错误计数器,用于跟踪总线错误,如果错误超过阈值,节点将进入错误被动模式或总线关闭,防止错误扩散。
物理层保护:CAN总线的物理层设计包括了对电磁干扰的防护,确保了数据在恶劣电磁环境下的稳定传输。
数据加密:虽然不是CAN协议本身的一部分,但可以通过上层应用层实现数据加密,增加数据传输的安全性。
通过这些机制,CAN总线确保了在汽车、工业自动化和其他实时系统中数据传输的高可靠性和安全性。
二、Linux下CAN的操作
1.硬件连接
①CAN电平转换器
转换器也需要5V供电:
②扩展板使用CAN
2.查询 can 信息
查询 can 的详细信息,包括波特率,标志设置等信息:
root@igkboard:~# ip -details link show can0
查询 can 的工作状态:
root@igkboard:~# ip -details -statistics link show can0
查询 can 的收发数据包情况,以及中断号:
root@igkboard:~# ifconfig -a
查询 can 的详细物理信息,包括电压,寄存器,中断等(需要启动can后才可以):
root@igkboard:~# dmesg | grep “can”
3.开启/关闭 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
4.发送/接收 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
5.设置 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
三、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# 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
四、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
/* 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);
}
/*sht20命令复位*/
int sht2x_softreset(int fd)
{
uint8_t buf[4];
if(fd < 0)
{
printf("%s line [%d] %s() get invalid input arguments\n", __FILE__