【STC32】CAN协议

九、CAN协议

(一)认识CAN协议

今天我们要来学习CAN协议,这跟我们上一节学到的SPI相比有什么优势呢?

(1)铜线个数

根据SPI的内容我们可以知道:主机和从机是点对点连接的。通信的两端仅有一个发送方和一个接收方,数据直接从发送方传输到接收方,中间没有其他设备干扰或参与。

像上图这种多从机连接时,主机需要通过很多铜线去连接不同的从机。

而CAN总线呢?区别于SPI,是多节点连接。多个设备可以同时连接到同一个总线或介质上,并通过协议规定的方式进行数据交换和通信。每个设备可以是发送方,也可以是接收方,数据通过总线广播或定向传输到目标设备。

而非这种使用数倍长且杂乱无章的总线

(2)抗干扰能力

SPI是采用是的是单端信号传输。单端信号是在一根信号线上传输电压信号,参考电压通常是地(GND)。

由于信号每一点的电压都是相对于地而言的。而在长距离传输时,不同位置地平面的电位可能会有差别,这就导致了信号的误差,即不稳定因素。加之可能会有干扰信号,这就导致了单端信号的抗干扰能力很差。

CAN协议使用差分信号传输。差分信号通过两根电平相反的信号线传输数据,分别是CAN_H(CAN High)和CAN_L(CAN Low)。

如果在某一点地平面电位有浮动,或者有共模干扰信号加入,这些误差会同时同方向地作用在两根信号线上。但由于最后差分信号会进行相减的操作,因此这些误差会被减操作抵消掉,也就是很好地抵抗了干扰。至于这个减操作,就是差分放大器要做的事情了。

(二)CAN 协议简介

CAN 是控制器局域网络 (Controller Area Network) 的简称,它是由研发和生产汽车电子产品著称的德国 BOSCH 公司开发的,并最终成为国际标准(ISO11519以及ISO11898),是国际上应用最广泛的现场总线之一。CAN 总线协议已经成为汽车计算机控制系统和嵌入式工业控制局域网的标准总线,并且拥有以CAN 为底层协议专为大型货车和重工机械车辆设计的 J1939 协议。近年来,它具有的高可靠性和良好的错误检测能力受到重视,被广泛应用于汽车计算机控制系统和环境温度恶劣、电磁辐射强及振动大的工业环境。

之前我们也讲过它的由来, 就是为适应“减少线束的数量”、“通过多个 LAN,进行大量数据的高速通信”的需要而创建出来的一种协议。

单片机想实现CAN协议就得连接一个CAN收发器。

CAN收发器采用的是差分信号线CAN_HIGH和CAN_LOW。

差分信号其实使用CAN_HIGH减去CAN_LOW的到的,

逻辑信号中:

5V--------->逻辑1,

0V--------->逻辑0

差分信号中: 0V---------逻辑1------隐形电平 2.0V--------逻辑0-----显性电平

解释一下就是:

当单片机给CAN收发器高电平(逻辑1)时,收发器CAN_H、CAN_L输出2.5V,CAN_H-CAN_L=0V,这称为隐形电平

单片机给CAN收发器低电平也(逻辑0)时,收发器CAN_H输出3.5V,CAN_L输出1.5V,CAN_H-CAN_L=2V,这称为显性电平

(1)CAN物理层

不同于 I2C、SPI 等具有时钟信号的同步通讯方式,CAN 通讯并不是以时钟信号来进行同步在的,它是一种异步通讯,只具有 CAN_High 和 CAN_Low 两条信号线,共同构成一组差分信号线,以差分信号的形式进行通讯。

CAN 物理层的形式主要有两种:

一种是CAN 通讯网络一种遵循着 ISO11898 标准的高速短距离“闭环网络”,它的总线最大长度为 40m,通信速度最高为 1Mbps,总线的两端各要求有一个“120 欧”的电阻。

image.png

另一种遵循 ISO11519-2 标准的低速远距离“开环网络”,它的最大传输距离为 1km,最高通讯速率为 125kbps,两根总线是独立的、不形成闭环,要求每根总线上各串联有一个“2.2千欧”的电阻。

image.png

(2)CAN报文

以下是CAN报文的种类,所谓的帧就是CAN报文(本章只讲标准的数据帧

img

什么是报文?

简单来说就跟写信一样。例如A给B写信,需要称呼、问候、正文、祝颂语、署名、日期。

而帧也是一样,A发送一串连续的(几-几十位数据的)帧给B,前面几位代表xxxx,中间几位代表xxxx,后几位代表xxxx(这些位包含各种特定的信息,比如ID,数据位之类的),B接收后对其进行解析就好了。

(三)标准数据帧

(1)起始位(1位):

表示数据传输开始的意思,告诉一声数据要来了,并且是显性电平

(2)识别位(11位):

根据这11位的识别码,就可以知道这一整串的数据帧是发给哪个设备的了。

(3)RTR位(1位):

用于区分数据帧与远程帧,显性电平表示数据帧,隐形表示远程帧。

(4)控制码(6位):

用于控制接下来要发的数据的位个数。

其中的第一位IDE位(1位):用于区分是标准帧还是扩展帧。标准帧和扩展帧的区别在于识别码个数,扩展帧有29位识别码(前面提到过标准帧是11位的识别码)

第二位空闲位(1位):是逻辑0

后四位预留位(4位):代表着接下来实际发送有效数据的位数(这个有效数据指的就是信的主体内容而非格式)。比如说这四位数分别为1000也就是10进制“8”,则代表有效数据的总字长为8字节->64位

(5)数据码(0~64位):

实际发送有效数据的位数(这个有效数据指的就是信的主体内容而非格式)。

(6)CRC冗余校验码(15位):

算出来的这15位校验码和接收到的校验码相同,表示正确,如果出现错位,则会通过错误帧返回,请求重新发送。

CRC界定符:分界线,代表着跟后面数据隔离开

(7)ACK位(2位):

表应答的意思。ACK 段包括一个 ACK 槽位,和 ACK 界定符位。类似 I2C 总线,在 ACK 槽位中,发送节点发送的是隐性位,而接收节点则在这一位中发送显性位以示应答。在 ACK 槽和帧结束之间由 ACK 界定符间隔开。

(8)结束符(7位)

表示该传输的结束,这里用七位显性电平表示。

其它帧我就不一一解释了,如下:

image.png

image.png

(四)代码实现

由于STC8H8K64U没有CAN模块,所以这里用的是STC32G12K128,发送ID为0x0345,每隔500ms发送0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,主频12MHZ

这个紫色的是CAN协议收发模块

这里我们使用的是创芯科技的CAN分析仪,该分析仪使用方法可看http://t.csdnimg.cn/vYQfP

在这里插入图片描述

CAN.H

#ifndef __CAN_H_
#define __CAN_H_
​
#include "main.h"
​
#define MAIN_Fosc 24000000UL//主频
​
// CAN总线波特率=FCLK/((1+(TSG1+1)+(TSG+2))*(BPR+1)*2)
​
#define TSG1 2 // 0~15
#define TSG2 1 // 0~7
#define BRP 3  // 0~63
​
// 24000000/((1+2+3)*4*2)=500KHZ
​
#define SJW 1 // 重新同步跳跃宽度
​
typedef struct {
    uint16_t CAN_ID;    // 目的ID号
    uint8_t RX_BUF[8];  // 接收的一个字节
    uint8_t TX_BUF[8];  // 发送的一个字节
} CAN_ObjectTypedef;
​
extern CAN_ObjectTypedef hCANx; // 声明全局变量
​
extern void CAN_Handle_Init(CAN_ObjectTypedef *hCANx,uint16_t CAN_ID, uint8_t *RX_BUF, uint8_t *TX_BUF);
​
extern uint8_t CANReadReg(uint8_t addr);
extern void CANWriteReg(uint8_t addr, uint8_t dat);
extern void CANReadFifo(uint8_t *pdat);
extern uint16_t CANReadMsg(uint8_t *pdat);
extern void CANSendMsg(uint16_t canid, uint8_t *pdat);
extern void CANSetBaudrate(void);
extern void CANInit(void);
​
​
#endif

CAN.c

#include "CAN.h"
​
CAN_ObjectTypedef hCANx; // 声明全局变量
​
​
void CAN_Handle_Init(CAN_ObjectTypedef *hCANx,
                                         uint16_t  CAN_ID,
                               uint8_t   *RX_BUF, 
                                         uint8_t   *TX_BUF)
{
    int i;
    hCANx->CAN_ID = CAN_ID;
    
    for (i = 0; i < 8; i++) {
        hCANx->RX_BUF[i] = RX_BUF[i];
        hCANx->TX_BUF[i] = TX_BUF[i];
    }
}
​
uint8_t CANReadReg(uint8_t addr)
{
    uint8_t dat;
    CANAR = addr;
    dat = CANDR;
    return dat;
}
​
void CANWriteReg(uint8_t addr, uint8_t dat)
{
    CANAR = addr;
    CANDR = dat;
}
​
void CANReadFifo(uint8_t *pdat)
{
    pdat[0] = CANReadReg(RX_BUF0);
    pdat[1] = CANReadReg(RX_BUF1);
    pdat[2] = CANReadReg(RX_BUF2);
    pdat[3] = CANReadReg(RX_BUF3);
    pdat[4] = CANReadReg(RX_BUF0);
    pdat[5] = CANReadReg(RX_BUF1);
    pdat[6] = CANReadReg(RX_BUF2);
    pdat[7] = CANReadReg(RX_BUF3);
     pdat[8] = CANReadReg(RX_BUF0);
     pdat[9] = CANReadReg(RX_BUF1);
     pdat[10] = CANReadReg(RX_BUF2);
     pdat[11] = CANReadReg(RX_BUF3);
    
     pdat[12] = CANReadReg(RX_BUF0);
     pdat[13] = CANReadReg(RX_BUF1);
     pdat[14] = CANReadReg(RX_BUF2);
     pdat[15] = CANReadReg(RX_BUF3);
}
​
uint16_t CANReadMsg(uint8_t *pdat)
{
    uint8_t i;
    uint16_t CANID;
    uint8_t buffer[16];
    CANReadFifo(buffer);
    CANID = ((buffer[1] << 8) + buffer[2]) >> 5;
    for (i = 0; i < 8; i++)
    {
        pdat[i] = buffer[i + 3];
    }
    return CANID;
}
​
void CANSendMsg(uint16_t canid, uint8_t *pdat)
{
    uint16_t CANID;
    CANID = canid << 5;
    CANWriteReg(TX_BUF0, 0x08);
    CANWriteReg(TX_BUF1, (uint8_t)(CANID >> 8));
    CANWriteReg(TX_BUF2, (uint8_t)CANID);
    CANWriteReg(TX_BUF3, pdat[0]);
    CANWriteReg(TX_BUF0, pdat[1]);
    CANWriteReg(TX_BUF1, pdat[2]);
    CANWriteReg(TX_BUF2, pdat[3]);
    CANWriteReg(TX_BUF3, pdat[4]);
    CANWriteReg(TX_BUF0, pdat[5]);
    CANWriteReg(TX_BUF1, pdat[6]);
    CANWriteReg(TX_BUF2, pdat[7]);
    CANWriteReg(TX_BUF3, 0x00);
    CANWriteReg(CMR, 0x04); // 发起一次帧传输
}
​
void CANSetBaudrate(void)
{
    CANWriteReg(BTR0, ((SJW << 6) + BRP));
    CANWriteReg(BTR1, ((TSG2 << 4) + TSG1));
}
​
void CANInit(void)
{
    CANSetBaudrate();
    CANWriteReg(ACR0, 0x00);
    CANWriteReg(ACR1, 0x00);
    CANWriteReg(ACR2, 0x00);
    CANWriteReg(ACR3, 0x00);
    CANWriteReg(AMR0, 0xFF);
    CANWriteReg(AMR1, 0xFF);
    CANWriteReg(AMR2, 0xFF);
    CANWriteReg(AMR3, 0xFF);
    CANWriteReg(IMR, 0xff);
    CANWriteReg(MR, 0x00);
    P_SW1 = 0;//引脚
    CANICR = 0x02; // CAN使能中断
    AUXR2 |= 0x02; // CAN模块被使能
}
​
void CANBUS_Interrupt(void) interrupt 28
{
    uint8_t isr;
     uint8_t CAN_ID;
    isr = CANReadReg(ISR);
     
    if ((isr & 0x04) == 0x04)//发送中断
    {
        CANAR = 0x03; //清除发送中断信号
        CANDR = 0x04;
    }
    if ((isr & 0x08) == 0x08)//接收中断
    {
        CANAR = 0x03; // 清除接收中断信号
        CANDR = 0x08;
        CAN_ID = CANReadMsg(hCANx.RX_BUF); // 接收CAN总线数据
        CANSendMsg(CAN_ID+1 ,hCANx.RX_BUF); // CANID加1,原样发送CAN总线数据
    }
}
​

main.C

#include "main.h"
​
void STC32G_IOinit(void)
{
    P0M1 = 0;   P0M0 = 0;   //设置为准双向口   参考用户手册387页
    P1M1 = 0;   P1M0 = 0;   //设置为准双向口   参考用户手册387页
    P2M1 = 0;   P2M0 = 0;   //设置为准双向口   参考用户手册387页
    P3M1 = 0;   P3M0 = 0;   //设置为准双向口   参考用户手册387页
    P4M1 = 0;   P4M0 = 0;   //设置为准双向口   参考用户手册387页
    P5M1 = 0;   P5M0 = 0;   //设置为准双向口   参考用户手册387页
    P6M1 = 0;   P6M0 = 0;   //设置为准双向口   参考用户手册387页
    P7M1 = 0;   P7M0 = 0;   //设置为准双向口   参考用户手册387页  
    P0 = 0XFF;  P1 = 0XFF;  P2 = 0XFF; 
    P3 = 0XFF;  P4 = 0XFF;  P5 = 0XFF; 
    P6 = 0XFF;  P7 = 0XFF;                      //设置P0~P7所有I/O口为高电平
}
​
void Delay500ms(void) //@24.000MHz
{
    unsigned long i;
​
    _nop_();
    _nop_();
    i = 2999998UL;
    while (i) i--;
}
​
void main(void)
{    
​
    uint16_t CAN_ID;
   uint8_t RX_BUF[8] = {0}; // 初始化接收缓冲区
   uint8_t TX_BUF[8] = {0}; // 初始化发送缓冲区
    
    EAXFR=1;
    CKCON=0x00;
    WTST=0x00;
    
    STC32G_IOinit();
    CANInit();
   CAN_Handle_Init(&hCANx, CAN_ID, RX_BUF, TX_BUF);
​
    EA = 1; // 打开总中断
        
     hCANx.CAN_ID = 0x0345;
        
    // 初始化发送缓冲区数据
    hCANx.TX_BUF[0] = 0x11;
    hCANx.TX_BUF[1] = 0x22;
    hCANx.TX_BUF[2] = 0x33;
    hCANx.TX_BUF[3] = 0x44;
    hCANx.TX_BUF[4] = 0x55;
    hCANx.TX_BUF[5] = 0x66;
    hCANx.TX_BUF[6] = 0x77;
    hCANx.TX_BUF[7] = 0x88;
​
    while (1)
    {
        CANSendMsg(hCANx.CAN_ID, hCANx.TX_BUF);
        Delay500ms();     
    }
}

(5)实验现象

CAN波特率是500kps

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值