显性电平:0 隐性电平:1
一、帧结构
1.帧类型
1)数据帧:发送设备主动发送数据(广播式)
2)请求帧:接收设备主动请求数据(请求式)
2.帧结构
1)标准帧
1位SOF+11位ID+1位RTR+1位IDE+1位r0+4位DLC+0~64位Data+15位CRC效验码+1位CRC界定符+1位ACK槽+1位ACK界定符+7位结束符
- 1位 SOF:帧起始,表示后面一段波形为传输的数据位,显性0
- 11位 ID:标识符,区分功能,同时决定优先级
- 1位 RTR:远程请求位,区分数据帧(显性0)和遥控帧(隐性1)
- 1位 IDE:扩展标志位,区分标准格式(0)和扩展格式(1)
- 1位 r0/r1:保留位,为后续协议升级留下空间(显性0)
- 4位 DLC:数据长度,指示数据段Data有几个字节
- 0~64位 Data:数据段的1~8个字节有效数据
- 15位 CRC:循环冗余校验,校验数据是否正确
- 1位 CRC界定符:隐性1
- 1位 ACK:应答位,判断数据有没有被接收方接收(显性0收到数据,隐形1没收到数据)
- 1位 ACK界定符:为应答位前后发送方和接收方释放总线留下时间(隐性1)
- 7位 EOF:帧结束,表示数据位已经传输完毕(隐性电平1)
2)扩展帧
11位ID结束后,加1位SRR和1位IDE。其中SRR是代替RTR,协议升级时留下的无意义位。IDE是隐性电平1,表示这是扩展帧。其余与标准帧设定一致。
4.关于位填充
位填充规则:发送方每发送5个相同电平后,自动追加一个相反电平的填充位,接收方检测到填充位时,会自动移除填充位,恢复原始数据
位填充例子:
二、仲裁
1.先占先得
2.非破坏性仲裁
仲裁段就是ID号+RTR位,ID号越小,仲裁的优先级越高,填充位不会影响仲裁,如果ID号一致,数据帧的优先级高于遥控桢
如下图所示,单元一和单元二同时开始波形, 根据线与特性到标红的位置,总线呈现显性0,单元二回读为0,与发出0一致,单元一回读为0,与发出的1不一致。此时,单元一仲裁失败,转为接收状态。
三、STM32的CAN外设
1.STM32的默认CAN复用端口:
PA11——CAN_RX
PA12——CAN_TX
2.CAN收发报文
- CAN_RX写入指令到控制器,接收过滤器过滤所接收到的指令,再放入指定的FIFO队列(队列遵循先进先出),接收的队列一共有两个,可以指定两个队列的优先级。每一个队列共有三个邮箱,邮箱如果满了,接收的数据就会溢出。接收流程:接收到一个报文→匹配过滤器后进入FIFO 0或FIFO 1→CPU读取
- CAN_TX发送指令时,也有三个发送邮箱,三个发送邮箱可以按照指定顺序进行发送,如先进先出或者ID优先级发送。ID优先级一般是ID号小的先发送。发送流程:选择一个空置邮箱——写入报文——请求发送
- NART:置1,关闭自动重传,CAN报文只被发送1次;置0,自动重传,直到发送成功
- TXFP:置1,优先级由发送请求的顺序来决定,先请求的先发送;置0,优先级由报文标识符(报文ID)来决定,标识符值小的先发送(标识符值相等时,邮箱号小的报文先发送)
- RFLM:置1,接收FIFO锁定,FIFO溢出时,新收到的报文会被丢弃;置0,禁用FIFO锁定,FIFO溢出时,FIFO中最后收到的报文被新报文覆盖
3.标识符过滤器
-
FSCx:位宽设置,置0,16位;置1,32位
-
FBMx:模式设置,置0,屏蔽模式;置1,列表模式
-
FFAx:关联设置,置0,FIFO 0;置1,FIFO 1
-
FACTx:激活设置,置0,禁用;置1,启用
4.测试模式:(CAN_MODE)
静默模式、环回模式、静默环回模式
/* CAN_Mode_Normal ((uint8_t)0x00) 正常模式CAN_Mode_LoopBack ((uint8_t)0x01) 环回模式CAN_Mode_Silent ((uint8_t)0x02) 静默模式CAN_Mode_Silent_LoopBack ((uint8_t)0x03) 环回静默模式*/
5.工作模式:
初始化模式、正常模式、睡眠模式、AWUM(置1自动唤醒,置0手动唤醒)
6.位时间特性
- 波特率 = APB1时钟频率 / 分频系数 / 一位的Tq数量
= 36MHz / (BRP[9:0]+1) / (1 + (TS1[3:0]+1) + (TS2[2:0]+1))
-
7.四个中断
- 发送中断:发送邮箱空时产生
- FIFO 0中断:收到一个报文/FIFO 0满/FIFO 0溢出时产生
- FIFO 1中断:收到一个报文/FIFO 1满/FIFO 1溢出时产生
- 状态改变错误中断:出错/唤醒/进入睡眠时产生
-
8.时间触发通信
-
TTCM:置1,开启时间触发通信功能;置0,关闭时间触发通信功能(CAN外设内部有一个16位计数器,用于记录时间戳)
-
9.离线恢复
-
ABOM:置1,开启离线自动恢复,进入离线状态后,就自动开启恢复过程;置0,关闭离线自动恢复,软件必须先请求进入然后再退出初始化模式,随后恢复过程才被开启
-
四、具体代码使用
-
1.CAN初始化函数调用
-
void MyCAN_Init(void) {RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//启用GPIOA时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);//启用CAN1时钟//初始化GPIOA的12号引脚GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//模式为复用推挽输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;//12号引脚GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//引脚速度GPIO_Init(GPIOA, &GPIO_InitStructure);//引脚初始化//初始化GPIO的11号引脚GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化CAN1控制器CAN_InitTypeDef CAN_InitStructure;//定义一个CAN的结构体/* CAN_Mode_Normal ((uint8_t)0x00) 正常模式CAN_Mode_LoopBack ((uint8_t)0x01) 环回模式CAN_Mode_Silent ((uint8_t)0x02) 静默模式CAN_Mode_Silent_LoopBack ((uint8_t)0x03) 环回静默模式*/CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack;//CAN工作模式为环回模式,用于自收自发测试用//波特率 = 36M(时钟频率) / 48 (预分频器的值)/ (1 + 2(BS1)+ 3(BS2)) = 125KCAN_InitStructure.CAN_Prescaler = 48; //预分频器的值为48 CAN_InitStructure.CAN_BS1 = CAN_BS1_2tq;//BS1的时间长度,1-16tqCAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;//BS2的时间长度CAN_InitStructure.CAN_SJW = CAN_SJW_2tq;//SJW的时间长度,1-4tqCAN_InitStructure.CAN_NART = DISABLE;//DISABLE,表示寄存器置0,自动重传CAN_InitStructure.CAN_TXFP = DISABLE;//发送邮箱优先级,DISABLE置零,ID小的先发送,如果是ENABLE置一,先进先出CAN_InitStructure.CAN_RFLM = DISABLE;//禁用FIOF锁定,溢出后,新报文覆盖最后一个报文CAN_InitStructure.CAN_AWUM = DISABLE;//DISABLE 手动唤醒,ENABLE 自动唤醒CAN_InitStructure.CAN_TTCM = DISABLE;//关闭时间触发通信CAN_InitStructure.CAN_ABOM = DISABLE;//DISABLE 手动恢复,ENABLE 自动恢复CAN_Init(CAN1, &CAN_InitStructure);//初始化CAN//初始化CAN过滤器CAN_FilterInitTypeDef CAN_FilterInitStructure;//定义CAN过滤器结构体CAN_FilterInitStructure.CAN_FilterNumber = 0;//过滤器编号,0-13/*16位列表模式,四个参数分别存入一组ID即可,共四个16位ID列表屏蔽模式:IDHIGH存入第一组ID,MaskIDHIGH存入对应的屏蔽位,共两组16位ID和两组屏蔽位32位列表模式:IDHIGH和IDLOW组合成一个32位ID,MaskIDHIGH和MaskIDLOW组成第二组32位ID32位频闭模式:IDHIGH和IDLOW组合成一个32位ID,MaskIDHIGH和MaskIDLOW组成第二组32位屏蔽位*/CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;//高16位CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;//低16位CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;//选则相应模式为32位屏蔽模式CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;//过滤器位宽,32位或者16位,这里是32位CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;//过滤器模式,CAN_FilterMode_IdMask 屏蔽模式 CAN_FilterMode_IdList 列表模式CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;//配置过滤器关联,这里有两个CAN_Filter_FIFO0,CAN_Filter_FIFO1CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;//激活过滤器CAN_FilterInit(&CAN_FilterInitStructure);//初始化过滤器 }
2.发送报文
/*
Name:MyCAN_Transmit CAN 发送报文
Param: ID Length 数据长度*Data 数据指针
*/
void MyCAN_Transmit(uint32_t ID, uint8_t Length, uint8_t *Data)
{CanTxMsg TxMessage; //定义CanTxMsg结构体变量TxMessage.StdId = ID;//标准IDTxMessage.ExtId = ID;//扩展IDTxMessage.IDE = CAN_Id_Standard; //扩展标志位,CAN_Id_Standard 标准ID ,CAN_Id_Extended扩展IDTxMessage.RTR = CAN_RTR_Data; //遥控标志位,CAN_RTR_Remote 遥控帧, CAN_RTR_Data数据帧TxMessage.DLC = Length;//数据段长度,传入的参数//把形参DATA传过来的数组赋值给TxMessage.Datafor (uint8_t i = 0; i < Length; i ++){TxMessage.Data[i] = Data[i];//Data为8字节的数组}/*CAN_Transmit的原理:选择空发送邮箱——如果邮箱有空位,则将报文写入指定寄存器——TXRQ置1,请求发送*/uint8_t TransmitMailbox = CAN_Transmit(CAN1, &TxMessage);//请求发送结构体指向的报文,返回值是邮箱编号//uint32_t Timeout = 0;//等待函数返回OK,当CAN1的邮箱状态为CAN_TxStatus_Ok表示发送成功,如果不成功则进入循环while (CAN_TransmitStatus(CAN1, TransmitMailbox) != CAN_TxStatus_Ok){Timeout ++;//如果大于超时时间,则跳出循环if (Timeout > 100000){break;}}
}
3.接收报文
/*
@brief: recieve CAN message
@param: *ID the ID*Length the length of DATA*DATA the Data of CAN massage
*/
void MyCAN_Receive(uint32_t *ID, uint8_t *Length, uint8_t *Data)
{CanRxMsg RxMessage;//定义一个CanRxMsg结构体CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);//判断接收的报文是标准ID还是扩展IDif (RxMessage.IDE == CAN_Id_Standard){*ID = RxMessage.StdId;//标准ID}else{*ID = RxMessage.ExtId; //扩展ID}if (RxMessage.RTR == CAN_RTR_Data)//是否为数据帧{*Length = RxMessage.DLC;//数据长度//数据内容for (uint8_t i = 0; i < *Length; i ++){Data[i] = RxMessage.Data[i];}}else{//遥控帧,暂时不做处理}
}