一、CAN
关于CAN总线,网上已经有很多文章详细介绍,以下是笔者的理解及具体项目的应用体会。
控制器局域网(Controller Area Network,CAN),是目前国际上应用最为广泛的现场总线之一。其特点是可拓展性好,可承受大量数据的高速通信,使用差分信号传输数据,高度稳定可靠,因此常应用于汽车电子领域、工业自动化、医疗设备等高要求环境。CAN分为高速和低速CAN。
低速CAN总线为开环,高速CAN总线为闭环,总线由CAN_H和CAN_L两根线组成,总线上可以挂多个节点设备。每个节点设备由CAN控制器和CAN收发器组成,CAN控制器通常作为外设集成在MPU/MCU上,而CAN收发器则需要外围添加芯片电路。所以我们在设计板子时,要根据我们的需求选择收发器。在学习CAN之前,我们先大致了解CAN的特点(可与下文的RS485进行对比),相信回过头来看这些特点会有更深刻的理解。
CAN的特点如下:
1、双向半双工。CAN总线上的节点既可以发送数据又可以接收数据,没有主从之分。但是在同一个时刻,只能由一个节点发送数据,其他节点只能接收数据(半双工)。因为CAN数据的收发不能同时进行,故其是半双工,但从应用层的角度来看,CAN又可以认为是伪全双工,因为CAN存在仲裁,各应用单元可以随心所欲收发数据,不必关心当前实际正在接收还是发送。
2、多主机系统: CAN支持多主机系统,多个节点可以发送和接收数据。单条总线最多可接110个节点,并可方便的扩充节点数。
3、高速率超远传输。传输距离远(最远10Km),传输速率快(最高1MHz bps)。
4、抗干扰能力强: CAN使用差分信号传输,通过两个线路(CAN_H和CAN_L)之间的电压差来传递信息。差分传输提供了良好的抗干扰性能,使得CAN总线适用于工业等环境恶劣的场合。
5、灵活性: CAN支持不同的波特率和通信速率,硬件报文滤波功能,只接收必要信息,减轻cpu负担,简化软件编制。
6、实时性: CAN总线采用非破坏性仲裁机制,以发送的ID号自动进行仲裁,确保了总线上数据传输的有序性,避免了冲突的同时省去了主机的询问,提高了总线利用率。是否存在仲裁是485和CAN的区别之一。
7、安全性:CAN总线具有CRC检查和其他错误检测手段,CAN能够识别和处理传输过程中可能发生的错误。拥有错误处理机制,避免错误设备干扰总线。
总结:实时性,安全性,超远距离传输是CAN最大的优势。
1、CAN——硬件部分
高速CAN电路如下图,其规定:
电压差为0V时表示逻辑1(隐性电平);电压差为2V时表示逻辑0(显性电平)
低速CAN电路如下图,其规定:
电压差为-1.5V时表示逻辑1(隐性电平);电压差为3V时表示逻辑0(显性电平)
CAN总线由两根信号线,即CANH和CANL,没有时钟同步信号。所以CAN是一种异步通信方式,与UART的异步通信方式类似,而SPI、I2C是以时钟信号同步的同步通信方式。
2、CAN——帧格式
CAN总线以“帧”(Frame)的形式进行通信。CAN 总线协议规定了5种帧,分别是数据帧、远程帧、错误帧、超载帧以及帧间隔,各种帧的作用如下,笔者在项目中使用了最常用的数据帧。
数据帧 | 发送设备主动发送数据(广播式) |
遥控帧 | 接收设备主动请求数据(请求式) |
错误帧 | 某个设备检测出错误时向其他设备通知错误 |
过载帧 | 接收设备通知其尚未做好接收准备 |
帧间隔 | 用于将数据帧及遥控帧与前面的帧分离开 |
数据帧包括以下这些部分,
SOF(Start of Frame):帧起始位,1bit,为显性信号0,其拉低总线,表示数据帧的开始,即后面一段波形为传输的数据位。
ID(Identify):标识符,决定优先级,ID又分为标准帧的ID和拓展帧的ID,标准帧ID范围是11位,即0x000~0x7FF,而拓展帧的ID为29位。
RTR(Remote Transmission Request ):远程请求位,区分数据帧和遥控帧,数据帧RTR为显性电平0,遥控帧RTR为隐性电平1。ID和RTR两者构成仲裁段。
IDE(Identifier Extension):扩展标志位,区分标准帧和拓展帧,标准帧的IDE为0,拓展帧的IDE为1。
SRR(Substitute Remote Request):标准格式SRR=0,扩展格式SRR=1。
r0/r1(Reserve):保留位,为后续协议升级留下空间。
DLC(Data Length Code):数据长度编码位,表示数据段有几个字节。
Data:数据段的1~8个字节有效数据。
CRC(Cyclic Redundancy Check):循环冗余校验,校验数据是否正确。
ACK(Acknowledgement):应答位,判断数据有没有被接收方接收。
CRC/ACK界定符:为应答位前后发送方和接收方释放总线留下时间。
EOF(End of Frame ):帧结束,7位长度,隐性信号0,表示帧的结束。
3、CAN——位填充:
在数据传输过程中,有位填充规则,当发送方每发送5个相同电平后,自动追加一个相反电平的填充位,接收方检测到填充位时,会自动移除填充位,恢复原始数据。位填充的作用是:
1、增加波形的定时信息,利于接收方执行“再同步”,防止波形长时间无变化,导致接收方不能精确掌握数据采样时机
2、将正常数据流与“错误帧”和“过载帧”区分开,
3、保持CAN总线在发送正常数据流时的活跃状态,防止被误认为总线空闲。
4、CAN——位时序:
由于CAN是收发双方约定波特率,故存在采样时机的问题。为了灵活调整每个采样点的位置,使采样点对齐数据位中心附近,CAN总线对每一个数据位的时长进行了更细的划分,分为同步段(SS)、传播时间段(PTS)、相位缓冲段1(PBS1)和相位缓冲段2(PBS2),每个段又由若干个最小时间单位(Tq)构成。各段的时间如下:
SS = 1Tq | PTS = 1~8Tq | PBS1 = 1~8Tq | PBS2 = 2~8Tq |
如下图:其中SS段长度为1Tq,PTS段长度为2Tq,PSB1段长度为3Tq,PSB2段长度为3Tq。采样点在PSB1和PSB2之间,调整各段的长度,即可对采样点位置进行调整,实现准确采样。
为了更好地避免采样问题,CAN还有一个再同步机制:
若发送方或接收方的时钟有误差,随着误差积累,数据位边沿逐渐偏离SS段,则此时接收方根据再同步补偿宽度值(SJW)通过加长PBS1段,或缩短PBS2段,以调整同步。再同步可以发生在第一个下降沿之后的每个数据位跳变边沿
SJW=1~4Tq
5、CAN——波特率
了解了位时序,只要我们知道了CAN中Tq的值,再知道各段为多少个Tq,就能很容易计算出波特率。要注意的是,为STM32,GD系列的CAN控制器位时序,和标准CAN协议的位时序略有不同。STM32,GD系列的位时序只有三段,分别为:
同步段,长度为1Tq;标准CAN协议中的PTS段和PSB1合并为位段1(范围为1-16Tq);标准CAN协议中的PSB2段对应位段2(范围为1-8Tq)。那么波特率就为:
具体的波特率配置在后面代码处会详解。
5、CAN——总线仲裁
CAN总线上可挂载着多个设备,哪一个设备优先发送是由总线仲裁决定的。CAN的总线仲裁来自于以下两个规则。
1、先占先得,任何设备检测到连续11个隐性电平,即认为总线空闲,只有在总线空闲时,设备才能发送数据帧/遥控帧。
2、非破坏性仲裁,包括线与特性,回读机制。设备发送数据时,发送设备会读回自己发出的数据。当有一个设备发0时,总线势必被拉低到0,发送设备检测读回的数据与发出的数据是否一致,不一致则退出仲裁,该设备仲裁失利,等待下一次发送(这就是为什么ID号越小优先级越高),这便是非破坏性仲裁。
6、CAN——错误处理
CAN中的错误共有5种: 位错误、填充错误、CRC错误、格式错误、应答错误。每个设备内部管理一个TEC和REC,根据TEC和REC的值确定自己的状态,分为主动错误状态,被动错误状态,总线关闭状态。主动错误状态的设备正常参与通信并在检测到错误时发出主动错误帧;被动错误状态的设备正常参与通信但检测到错误时只能发出被动错误帧;总线关闭状态的设备不能参与通信。
错误处理转换图如下:
——————————————————————————————————————————————————————————————————————————————————————
理解完上述CAN协议的各个部分,下面就进入GD32F303的CAN学习吧(STM32同理),熟悉配置CAN外设的整个过程,再上手其他单片机也是很简单的。
1、CAN硬件设计
电路图如下所示,其中STB引脚是收发器的模式配置引脚,在代码中要进行初始化,其中两个60欧姆电阻即120欧姆起终端匹配和钳制作用,VCC是5V供电,需要注意电压,否则可能会因为压差不够而导致传输错误,CANH、CANL接到USB转CAN的对应的接口上。
2、CAN软件设计
在写代码之前,我们来熟悉一下GD32CAN外设。
GD32有多个CAN,每个CAN外设里面又有14个过滤器组(互联性是28个过滤器组),每个过滤器组有两个32位过滤器寄存器。CAN的过滤器可过滤报文,过滤器有列表模式和掩码模式,由于其ID号至少为11位,所以两个32位寄存器可分为16和32的多种组合。
综上其有:16位列表模式、16位屏蔽模式、32位列表模式、32位屏蔽模式
在列表模式中,写入ID即是要接收的ID值;而在掩码模式中,则是先写入ID号,然后设置屏蔽位的值,对应位为0则不一样也可以通过,1则是一样的才能通过,例如想接收ID号为0x10~0x1F的数据,则对应屏蔽位应该设置为为0xF0(0x10=1000 0000 ;0x1F= 1000 1111,则高四位必须一样,低四位随意)
代码的整体流程如下:
1、开启时钟,初始化引脚
2、配置CAN外设。使用结构体can_parameter进行CAN初始化,因为CAN0是挂载在APB1上,其操作速度限制在60MHz,配置分频系数为60,segment_1和segment_2分别为4和3。由上述介绍波特率部分我们可知,波特率为60MHZ/60/(4+3+1)=125k。具体解释看以下代码,有详细的注释。
3、配置过滤器。由于项目无要求,这里直接选择最方便的全通模式,即接收任意ID的报文
4、发送函数。编写发送函数,具体解释看以下代码,都是比较简单的。
5、接收函数。编写接收函数,具体解释看以下代码,都是比较简单的。
#include "gd32f30x.h"
#include "can.h"
void PCB_CAN_Init(void)
{
can_deinit(CAN0);
rcu_periph_clock_enable(RCU_CAN0);
rcu_periph_clock_enable(RCU_GPIOA);//使能时钟
// CAN,不休眠,初始化GPIO引脚,使能引脚,速度为50MHz,模式为推挽输出
gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
gpio_init(GPIOA, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ,GPIO_PIN_11);
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_12);
gpio_bit_write(GPIOA, GPIO_PIN_10, RESET);
can_parameter_struct can_parameter; //配置CAN外设参数
/* baudrate 250Kbps */
can_parameter.prescaler = 60;
can_parameter.time_segment_1 = CAN_BT_BS1_4TQ; // PSB1
can_parameter.time_segment_2 = CAN_BT_BS2_3TQ; // PSB2
can_parameter.working_mode = CAN_NORMAL_MODE; //正常工作模式
can_parameter.resync_jump_width = CAN_BT_SJW_1TQ; //1TQ
can_parameter.auto_retrans = DISABLE; //失能不自动重传功能
can_parameter.auto_bus_off_recovery = DISABLE; //离线手动恢复
can_parameter.auto_wake_up = DISABLE; //手动唤醒
can_parameter.rec_fifo_overwrite = DISABLE; //溢出报文被新报文覆盖(ENABLE丢弃)
can_parameter.time_triggered = DISABLE; //关闭时间触发通信模式
can_parameter.trans_fifo_order = DISABLE; //ID号小先发送(ENABLE先请求先发送)
can_init(CAN0, &can_parameter);
can_filter_parameter_struct can_filter; //配置过滤器参数
/*
以下四个参数是两个32位寄存器的值,在GD32中,可以分为
1、16位列表模式——分别写入一组ID
2、16位屏蔽模式——list_high存入第一组ID,mask_high存入对应屏蔽位
——list_low存入第二组ID,mask_low存入对应屏蔽位
3、32位列表模式——list_high,list_low组合存入第一组ID
——mask_high,mask_low组合存入第二组ID
4、32位屏蔽模式——list_high,list_low组合存入第一组ID
——mask_high,mask_low组合存入对应屏蔽位
*/
can_filter.filter_list_high =0x0000; //ID号0
can_filter.filter_list_low =0x0000; //ID号0
can_filter.filter_mask_high =0x0000; //屏蔽位0,即全通
can_filter.filter_mask_low =0x0000; //屏蔽位0,即全通
can_filter.filter_fifo_number = CAN_FIFO0; //报文进入FIFO 0排队
can_filter.filter_number = 0; //初始化过滤器0
can_filter.filter_mode =CAN_FILTERMODE_MASK; //过滤器模式,屏蔽模式
can_filter.filter_bits =CAN_FILTERBITS_32BIT; //过滤器位宽,32位
can_filter.filter_enable = ENABLE; //使能过滤器
can_filter_init(&can_filter);
}
void PCB_CAN_Transmit(uint32_t ID, uint8_t Length, uint8_t *Data) //CAN报文发送
{
can_trasnmit_message_struct can_frame;
can_frame.tx_sfid = ID ; //要发送的ID——标准格式
can_frame.tx_efid = ID ; //要发送的ID——拓展格式
can_frame.tx_ff = CAN_FF_STANDARD ; //标准帧
can_frame.tx_ft = CAN_FT_DATA ; //数据帧
can_frame.tx_dlen = Length ; //报文长度
for (uint8_t i = 0; i < Length; i ++)
{
can_frame.tx_data[i] = Data[i]; //逐个写入
}
//返回发送邮箱号
uint8_t TransmitMailbox = can_message_transmit(CAN0, &can_frame);
//检测邮箱状态,是否发送完毕
while (can_transmit_states(CAN0, TransmitMailbox) != CAN_TRANSMIT_OK);
}
uint8_t PCB_CAN_ReceiveFlag(void) //获取接收状态
{
if (can_receive_message_length_get(CAN0, CAN_FIFO0) > 0) //检测FIFO 0状态
{
return 1; //有报文返回1
}
return 0; //无报文返回0
}
void PCB_CAN_Receive(uint32_t *ID, uint8_t *Length, uint8_t *Data) //报文接收函数
{
can_receive_message_struct RxMessage; //定义报文接收的结构体
can_message_receive(CAN0, CAN_FIFO0, &RxMessage); //接收的报文存入
if (RxMessage.rx_ff == CAN_FF_STANDARD) //判断是否为标准帧
{
*ID = RxMessage.rx_sfid; //使用指针传值
}
else
{
//暂不做处理
}
if (RxMessage.rx_ft == CAN_FT_DATA ) //判断是否是数据帧
{
*Length = RxMessage.rx_dlen;
for (uint8_t i = 0; i < *Length; i ++)
{
Data[i] = RxMessage.rx_data[i]; //使用数组传值
}
}
else
{
//暂不做处理
}
}
在main函数中进行简单调用测试,至此已完成CAN通信。
#include "gd32f30x.h"
#include "Can.h"
uint32_t TxID = 0x555; //发送的ID号
uint8_t TxLength = 4; //长度为4
uint8_t TxData[] = {0x13, 0x14, 0x52, 0x00};//发送的数据
uint32_t RxID; //接收到的ID号
uint8_t RxLength; //接收到数据的长度
uint8_t RxData[8]; //接收到的数据
int main()
{
PCB_CAN_Init(); //CAN初始化
while(1)
{
PCB_CAN_Transmit(TxID, TxLength, TxData); //发送
if (PCB_CAN_ReceiveFlag())
PCB_CAN_Receive(&RxID, &RxLength, RxData); //接收
}
}
设备:GD32F303、TJA1050收发器、USB—CAN适配器、上位机软件(笔者使用的是创芯的USB_CAN TOOL),即可实现CAN通信。余下部分等待以后有机会继续完善更新!!