CAN的简单概述
CAN-BUS即CAN总线技术,全称为“控制器局域网总线技术(Controller Area Network-BUS)”。Can-Bus总线技术最早被用于飞机、坦克等武器电子系统的通讯联络上。将这种技术用于民用汽车最早起源于欧洲,在汽车上这种总线网络用于车上各种传感器数据的传递。
大家知道当今车辆的电控系统是越来越多,例如电子燃油喷射装置、ABS装置、安全气囊装置、电动门窗、主动悬架等等。同时遍布于车身的各种传感器实时的监测车辆的状态信息,并将此信息发送至相对应的控制单元内。CAN 控制器根据两根线上的电位差来判断总线电平。总线电平分为显性电平和隐性电平,二者必居其一。发送方通过使总线电平发生变化,将消息发送给接收方。
CAN 协议具有一下特点:
1) 多主控制。 在总线空闲时,所有单元都可以发送消息(多主控制),而两个以上的单元
同时开始发送消息时,根据标识符(Identifier 以下称为 ID)决定优先级。 ID 并不是
表示发送的目的地址,而是表示访问总线的消息的优先级。两个以上的单元同时开始
发送消息时,对各消息 ID 的每个位进行逐个仲裁比较。仲裁获胜(被判定为优先级
最高)的单元可继续发送消息,仲裁失利的单元则立刻停止发送而进行接收工作。
2) 系统的柔软性。 与总线相连的单元没有类似于“地址”的信息。因此在总线上增加单
元时,连接在总线上的其它单元的软硬件及应用层都不需要改变。
3) 通信速度较快,通信距离远。 最高 1Mbps(距离小于 40M),最远可达 10KM(速率低
于 5Kbps)。
4) 具有错误检测、错误通知和错误恢复功能。 所有单元都可以检测错误(错误检测功能),
检测出错误的单元会立即同时通知其他所有单元(错误通知功能),正在发送消息的单
元一旦检测出错误,会强制结束当前的发送。强制结束发送的单元会不断反复地重新
发送此消息直到成功发送为止(错误恢复功能)。
5) 故障封闭功能。CAN 可以判断出错误的类型是总线上暂时的数据错误(如外部噪声等)
还是持续的数据错误(如单元内部故障、驱动器故障、断线等)。由此功能,当总线上
发生持续数据错误时,可将引起此故障的单元从总线上隔离出去。
6) 连接节点多。 CAN 总线是可同时连接多个单元的总线。可连接的单元总数理论上是没
有限制的。但实际上可连接的单元数受总线上的时间延迟及电气负载的限制。降低通
信速度,可连接的单元数增加;提高通信速度,则可连接的单元数减少。
ISO11898标准 物理层特性如下图(是针对通信速率为125Kbps~1Mbps 的高速通信标准)
注意:终端电阻别丢!由于用CAN_H跟CAN_L的电平差来表示显性位和隐性位,所以整个总线上电压基本为0,也减小了总线上的消耗,这也是汽车要用CAN通信的原因之一。
此次要讲解的是用STM32的CAN2.0B来访问现今新能源汽车上电池内部的BMS(电池管理系统)的CAN报文,根据协议手册来解码信息。
先简单介绍一下STM32的CAN外设
需要详细了解CAN数据帧类型的去问度娘!!!
CAN 协议是通过以下 5 种类型的帧进行的:
- 数据帧
- 要控帧
- 错误帧
- 过载帧
- 帧间隔
我们主要讲解数据帧,下图是数据帧的构成
(1) 帧起始。表示数据帧开始的段。
(2) 仲裁段。表示该帧优先级的段。
(3) 控制段。表示数据的字节数及保留位的段。
(4) 数据段。数据的内容,一帧可发送 0~8 个字节的数据。
(5) CRC 段。检查帧的传输错误的段。
(6) ACK 段。表示确认正常接收的段。
(7) 帧结束。表示数据帧结束的段。
标准格式ID为11位,而扩展格式ID为29位。在汽车通信中数据种类的多样化,11位ID是不能够满足需求的。所以汽车CAN通信国标必将引用29位ID扩展格式。(后边将讲解汽车通信协议:国标SAE J1939-21)
几个概念:
同步段(SS)
传播时间段(PTS)
相位缓冲段 1(PBS1)
相位缓冲段 2(PBS2)
Tq:最小时间单元
这几个概念不懂的去百度!!!
我用的STM32F103, 250Kbps(BMS方式250Kbps,只能是这个)。
波特率=36000/[(9+8+1)*8]=250Kbps。
该寄存器在库中对应的结构体如下
typedef struct
{
uint32_t StdId; /*!< Specifies the standard identifier.
This parameter can be a value between 0 to 0x7FF. */
uint32_t ExtId; /*!< Specifies the extended identifier.
This parameter can be a value between 0 to 0x1FFFFFFF. */
uint8_t IDE; /*!< Specifies the type of identifier for the message that
will be received. This parameter can be a value of
@ref CAN_identifier_type */
uint8_t RTR; /*!< Specifies the type of frame for the received message.
This parameter can be a value of
@ref CAN_remote_transmission_request */
uint8_t DLC; /*!< Specifies the length of the frame that will be received.
This parameter can be a value between 0 to 8 */
uint8_t Data[8]; /*!< Contains the data to be received. It ranges from 0 to
0xFF. */
uint8_t FMI; /*!< Specifies the index of the filter the message stored in
the mailbox passes through. This parameter can be a
value between 0 to 0xFF */
} CanRxMsg;
结构体成员 | 作用 |
---|---|
StdId | 标准标识符或扩展标识符,MSB取决于IDE位的值 |
ExtId | 扩展标识符,扩展标识符的LSB |
IDE | 标识符扩展,此位用于定义邮箱中信息的标识符类型。0:标准标识符;1:扩展标识符 |
RTR | 远程发送请求 。 0:数据帧;1:遥控帧 |
TXRQ | 发送邮箱请求,由软件置1,用于请求发送相应邮箱的内容。邮箱变为空后,此位由硬件清零。 |
SAE J1939- - SAE 标准
由卡车及客车电子电气委员会所属的卡车及客车控制及通信小组委员会制定,用于公路设备的
控制及通信网络推荐操作规程。
网络拓扑图
图上很明确的标明了汽车内部控制器跟BMS(电池管理系统)的连接关系!
这个也就是上边STM32内部CAN外设里边说的那个扩展格式
帧格式图如下:
每一帧数据包含了:优先级、数据页、ID、数据。
直接看可能不好理解,我直接举例说明;
ID:0x0C019ED0 用二进制表示为:0 1100 0000 0001 1001 1110 1101 0000(ID位29位)
按照PDU要求的对以上二进制数进行拆分;
以上是对ID的解析,参照PDU格式表,应该很容易理解。
ID后边紧跟着就是8bit数据,数据格式每个厂家都不一样(由于保密原因无法公开数据格式),需要根据厂家提供的解析方式进行解析(无非也就是一个数乘上一个比例,在加上一个偏移0.0)
这一帧数据是BMS发送到CAN总线上的,我们的目的就是为了得到这个ID里边的数据
我们只介绍对报文接收,发送报文道理也是一样的
void CAN_Receive(CAN_TypeDef* CANx, uint8_t FIFONumber, CanRxMsg* RxMessage)
{
/* Check the parameters */
assert_param(IS_CAN_ALL_PERIPH(CANx));
assert_param(IS_CAN_FIFO(FIFONumber));
/* 获取ID数值*/
RxMessage->IDE = (uint8_t)0x04 & CANx->sFIFOMailBox[FIFONumber].RIR;
if (RxMessage->IDE == CAN_Id_Standard)//判断是标准格式,还是扩展格式,并赋值给结构体变量
{
RxMessage->StdId = (uint32_t)0x000007FF & (CANx->sFIFOMailBox[FIFONumber].RIR >> 21);
}
else
{
RxMessage->ExtId = (uint32_t)0x1FFFFFFF & (CANx->sFIFOMailBox[FIFONumber].RIR >> 3);
}
//因为BMS里都是扩展帧格式,下面直接判断
if((RxMessage->ExtId & (uint32_t)0x0000000F)==0x0000000E)//总线上数据帧很多,筛选结尾为E的ID串口打印ID
{
printf("ExtId=%08X\r\n",RxMessage->ExtId);
RxMessage->RTR = (uint8_t)0x02 & CANx->sFIFOMailBox[FIFONumber].RIR;
/* 获取DLC数值 */
RxMessage->DLC = (uint8_t)0x0F & CANx->sFIFOMailBox[FIFONumber].RDTR;
/* 获取FMI数值 */
RxMessage->FMI = (uint8_t)0xFF & (CANx->sFIFOMailBox[FIFONumber].RDTR >> 8);
/* 获取数据区并赋值给结构体数组,调试时直接打印RxMessage.Data即可*/
RxMessage->Data[0] = (uint8_t)0xFF & CANx->sFIFOMailBox[FIFONumber].RDLR;
RxMessage->Data[1] = (uint8_t)0xFF & (CANx->sFIFOMailBox[FIFONumber].RDLR >> 8);
RxMessage->Data[2] = (uint8_t)0xFF & (CANx->sFIFOMailBox[FIFONumber].RDLR >> 16);
RxMessage->Data[3] = (uint8_t)0xFF & (CANx->sFIFOMailBox[FIFONumber].RDLR >> 24);
RxMessage->Data[4] = (uint8_t)0xFF & CANx->sFIFOMailBox[FIFONumber].RDHR;
RxMessage->Data[5] = (uint8_t)0xFF & (CANx->sFIFOMailBox[FIFONumber].RDHR >> 8);
RxMessage->Data[6] = (uint8_t)0xFF & (CANx->sFIFOMailBox[FIFONumber].RDHR >> 16);
RxMessage->Data[7] = (uint8_t)0xFF & (CANx->sFIFOMailBox[FIFONumber].RDHR >> 24);
}
/* Release the FIFO */
/* Release FIFO0 */
if (FIFONumber == CAN_FIFO0)
{
CANx->RF0R |= CAN_RF0R_RFOM0;
}
/* Release FIFO1 */
else /* FIFONumber == CAN_FIFO1 */
{
CANx->RF1R |= CAN_RF1R_RFOM1;
}
}
判断FIFO0是否有数据,接收到数据是返回非零数值,存入u8 buff[8].
//CAN口接收数据
//buf:数据缓冲区,存放8bit数据
//返回值:0,无数据返回;
// 非0,接收数据长度,即为DLC值;
u8 Can_Receive_Msg(u8 *buf)
{
u32 i;
CanRxMsg RxMessage;
if( CAN_MessagePending(CAN1,CAN_FIFO0)==0)return 0; //ûÓнÓÊÕµ½Êý¾Ý,Ö±½ÓÍ˳ö
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);//¶ÁÈ¡Êý¾Ý
for(i=0;i<8;i++)
buf[i]=RxMessage.Data[i];
return RxMessage.DLC;
}
main函数打印数据区:
(省略一些初始化,数据解析等等一些操作)
CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,8,0);//CAN普通模式初始化,波特率250kbps
while(1)
{
//printf("XX-21 CAN2BMS test program;\r\n");
flag=Can_Receive_Msg(canbuf);
if(flag)//非0即为接收到数据
{
for(i=0;iprintf("%02X ",canbuf[i]);//打印数据
}
printf("\r\n");
}
}