STM32 CAN编程详解

1.差分信号

1.概述

差分信号,又叫差模信号,通过两根信号线上的电压差来表示逻辑0和逻辑1。
当表示逻辑时,这两根线上的电压信号的振幅相同,相位相反。见下图:
STM32 CAN编程详解_第1张图片

2.差分信号的优点

  1. 抗干扰能力强
    差分信号传输数据只与电压差有关,外界的噪声等干扰会同时耦合到两根信号线上使得影响变相抵消
  2. 有效抑制对外EMI
    同上,由于两根信号的极性相反,因此对外辐射的点磁场可以相互抵消,信号线间耦合的越紧密,泄放到外界的电磁能量越少。因此一般将两根线相互绞合使用,俗称双绞线。
  3. 时序定位精确
    差分信号的开关变化是位于两个信号的交点,不同于普通信号依靠高低两个阈值电压判断,因而受工艺、温度的影响小,能降低时序上的误差,同时也更适合于低幅度信号。

由于差分信号的这些优点,在 USB 协议、485 协议、以太网协议及 CAN 协议的物理层中,都使用了差分信号传输

2.CAN协议

1. 简述

  • CAN 中文翻译控制器局域网络。最初由德国BOSCH公司开发。ISO有两个标准ISO11898ISO11519,他们区别在仅在于物理层。ISO11898后来又被拆分为ISO11898-1(仅涉及数据链路层)和ISO11898-2(仅涉及物理层)。
  • J1939协议是以CAN为底层协议专为大型货车和重工机械车量设计的协议。

2.CAN物理层

CAN的物理层是通过差分信号来实现的,和串口一样的是通信的波特率需要通信双方提前约定好。
CAN按通信距离分为以下两种:

  • 闭环网络
    也叫高速CAN,遵循ISO11898标准,适用于高速、短距离网络的通信,传输限制为40M,最高速度为1Mbps。
    总线两端要求各并联一个120欧的电阻,见下图:
    STM32 CAN编程详解_第2张图片

    • 闭环网络中的逻辑1叫做隐形电平,两根信号线的电压值都是2.5V,它们的差值为0V
      闭环网络中的逻辑0叫做显性电平,其中高电平信号线的电压值为3.5V,低电平信号线的电压值1.5V,它们的差值为2V
    • 类似于I2C总线中的线与特性,当CAN总线上不同的两个节点同时输出逻辑1和逻辑0,逻辑0的电平将覆盖逻辑1的电平,因此可以认为逻辑0的优先级大于逻辑1,逻辑0显性电平的名称也是这样来的。

    上述同时也表明了CAN通信是一个半双工通信。

  • 开环网络
    也叫低速CAN,遵循ISO11519-2标准,适用于低速、远距离网络的通信,传输距离限制为1KM,速率为125kbps。
    总线的两根信号线彼此独立,但要求每根总线串联一个2.2k欧的电阻,见下图: STM32 CAN编程详解_第3张图片

    • 开环网络中的逻辑1的电平分别为1.75V和3.75V,它们的差值为-1.5V。
    • 开环网络中的逻辑0的电平分别为4.0V和1.0V,它们的差值为3.0V。

CAN总线的节点不同于I2C节点需要为每一个节点分配地址,CAN是直接在总线上进行广播通信,每个CAN节点通过通信报文的ID来决定是否接收。
总线上的节点个数理论上不受限制,可通过中继器增强负载。

3.CAN协议层

1.CAN位同步

CAN协议通过将传输的每一个位的分解成SS段、PTS段、PBS1段、PBS2段来实现总线电平的正确采样。这样分解采样的方式叫做位同步,协议中的最小时间单位叫TQ,一个完整的位由8-25TQ组成,见下图:
STM32 CAN编程详解_第4张图片

  • SS段
    同步段,大小固定1TQ,总线信号同步起始信号
  • PTS段
    传播时间段,大小为1-8TQ, 用来补偿网络的物理延时时间。
    一般随便给值,确保采样点在整个时序70%的位置就差不多了
  • PBS1
    前相位缓冲段,大小为1-8TQ,用于确定采样点位置
  • PBS2
    后相位缓冲区, 大小为2-8TQ,用于确定采样点位置

确定各个段的时间,即可确定CAN的波特率,比如1tq=1us,则1s / 20tq = 50kbps

2.CAN报文

报文就是人为规定在数据前后加上各种标识,用于数据分类的一种方法。
根据标识的值的不同,该报文的种类就不同,CAN报文分为以下五种:

报文类型 作用
数据帧 用于节点向外传送数据
遥控帧 用于向远端节点请求数据
错误帧 用于向远端节点通知校验错误,请求重新发送上一个数据
过载帧 用于通知远端节点:本节点尚未做好接收准备
帧间隔 用于将数据帧及遥控帧与前面的帧分离开来

1.数据帧

数据帧以显性位作为帧起始,7个连续的隐形位作为帧结束,这之间包含仲裁段、控制段、数据段、CRC段和ACK段,注意仲裁段的RTR位必须为显性电平。
具体见下图:
STM32 CAN编程详解_第5张图片

  • 帧起始
    包含一个显性电平,用于通知各节点进行硬同步。
  • 仲裁段
    包含报文ID,ID有11位的标准格式和29位的扩展格式两种,通过IDE位进行标识。
    IDE为显性位则ID为为11位,IDE位隐形位则ID为29位,由此可以看到标准格式的优先级大于扩展格式
    RTR位为显性是表示数据帧,隐形位表示遥控帧,由此可以看到数据帧的优先级大于遥控帧。
    SRR位仅仅用于扩展格式中代替RTR而存在的,是一个隐形电平。
  • 控制段
    包含6个数据位,其中R0和R1是显性保留位,DLR用于表示数据段个长度,数值范围为0-8,对饮数据段0-8个字节。
  • 数据段
    包含待发数据,MSB先行。
  • CRC段
    包含15位的校验码,检验出错时将会根据最大重发数来向发送节点反馈一个错误帧来请求重新发送。
    CRC部分一般由CAN控制器硬件来完成,出错时的处理则由软件来控制。
    CRC界定符用于间隔CRC段和ACK段,是一个隐形位,同ACK界定符。
  • ACK段
    包含一个用于应答的ACK槽位和用于间隔的隐性的ACK界定符。
  • 帧结束
    包含7个隐形位,用于表示一帧数据的结束

2. 遥控帧

STM32 CAN编程详解_第6张图片

3. 错误帧

STM32 CAN编程详解_第7张图片

4. 过载帧

STM32 CAN编程详解_第8张图片

5. 间隔帧

STM32 CAN编程详解_第9张图片

3. STM32中的CAN外设

  • CAN外设功能
    STM32中提供支持2.0A和2.0B版本CAN协议的bxCAN控制器,通过配置对应寄存器,可完成自动收发报文的功能,该报文支持标准ID和扩展ID,通信最大速率为1Mb/s
    CAN外设有3个发送邮箱用于缓存待发送的报文,发送邮箱可配置发送优先级、发送时间戳和自动重传等一系列功能。
    CAN外设还有2个3级深度的接收FIFO用于缓存接收到的报文,通过过滤器可过滤指定ID的报文
    CAN外设不支持DMA来进行缓冲区的读写。
    CAN外设支持主从模式,但只有主模式下可以控制存储器访问控制器,所以当作为从设备的工作的时候要打开主设备的时钟

  • 工作模式
    STM32提供了4种工作模式,正常模式、静默模式、回环模式、回环静默模式,通过BTR寄存器的SILM和LBKM位进行进行配置。
    回环就是自发自收数据,不对外接收。
    静默就是发送的显性电平将直接送到接收端,隐形电平正常输出。
    所以上述四种模式在功能上可以理解为正常、自收收、自收发,仅自收。

  • 位时序段与再同步
    CAN标准协议中的位时序段分为4段,但CAN外设位时序段只支持3段,分别是占用1tq的同步段、BS1和BS2段,BS1和BS2的延时长度可通过BTR寄存器的TS1和TS2位进行的配置。
    CAN外设还可进行采样点的再同步的配置,也就是根据设置好的位长自动自动调整最佳的采样点,通过BTR寄存器的SJW位进行配置。

  • CAN波特率
    计算方法在前面已经给出,这里直接复述公式。
    公式:(1 + TS1 + TS2) * BRP * PCLK
    其中 BRP 分频因子,通过BTR寄存器的BRP位进行配置。PCLK是CAN所挂载的总线的频率。

  • CAN筛选器
    CAN外设有28组筛选器,每组有2个32位的寄存器,每个寄存器按地址可以配置为16位或32位模式,按功能可以配置为列表和掩码模式。
    掩码模式文字说明可能比较麻烦,以16位+掩码模式给出如下示例,其中x代表任意:

    寄存器1高16位:1111 0000 1111 0000
    寄存器1低16位:1111 0000 1111 0000
    实际过滤的报文:1111 xxxx 1111 xxxx
    tips1:这里有个坑,按理说如果配置为16位+掩码模式,那筛选器功能的顺序应该为 地址1、掩码1、地址2、掩码2,但实际测试好像是地址1、地址2、掩码1、掩码2的情况
    tips2:尽管手册上表明是28组筛选器是CAN1和CAN2进行公用的,但实际好像是前14组给CAN1用的,后14组给CAN2用的

  • CAN 其他说明
    CAN 的当前缓存情况可通过RFx寄存器进行查看,实际接收的数据存储在RI寄存器中
    CAN 报文的发送ID和接收ID存储在 TL 和 RL 寄存器中。
    CAN 的时间戳由 TDT 寄存器极性控制,注意TDT寄存器中 FMI 存储的是筛选器的索引号而非筛选器组号。一个筛选器组可以配置为1-4个筛选器,而索引号就是第一个筛选器组开始往后排的筛选器的编号。
    CAN收发器 TJA1050的作用是将数据转换成总线通信的电平
    CAN电平的线与特性,ID号越小的报文,优先级越高

    具体功能简易详细阅读手册,虽然读完后几天不用就会忘记了,┭┮﹏┭┮

1.固件库

1. 接口对象

固件库提供的接口中使用了四个结构体,详情如下:

  • 初始化结构体 :CAN_InitTypeDef
    typedef struct {
    	uint32_t Prescaler; /*配置 CAN 外设的时钟分频,可设置为 1-1024*/
    	uint32_t Mode; 		/*配置 CAN 的工作模式,回环或正常模式*/
    	uint32_t SJW; 		/*配置 SJW 极限值 */
    	uint32_t BS1; 		/*配置 BS1 段长度*/
    	uint32_t BS2; 		/*配置 BS2 段长度 */
    	uint32_t TTCM; 		/*是否使能 TTCM 时间触发功能*/
    	uint32_t ABOM;  	/*是否使能 ABOM 自动离线管理功能*/
    	uint32_t AWUM; 		/*是否使能 AWUM 自动唤醒功能 */
    	uint32_t NART; 		/*是否使能 NART 自动重传功能*/
    	uint32_t RFLM; 		/*是否使能 RFLM 锁定 FIFO 功能*/
    	uint32_t TXFP; 		/*配置 TXFP 报文优先级的判定方法*/
     } CAN_InitTypeDef;
    
  • 筛选器结构体 :CAN_FilterInitTypeDef
    typedef struct {
    	uint32_t FilterIdHigh; 			/*CAN_FxR1 寄存器的高 16 位 */
    	uint32_t FilterIdLow; 			/*CAN_FxR1 寄存器的低 16 位*/
     	uint32_t FilterMaskIdHigh; 		/*CAN_FxR2 寄存器的高 16 位*/
    	uint32_t FilterMaskIdLow; 		/*CAN_FxR2 寄存器的低 16 位 */
    	uint32_t FilterFIFOAssignment; 	/*设置经过筛选后数据存储到哪个接收 FIFO */
    	uint32_t FilterNumber; 			/*筛选器编号,范围 0-27*/
    	uint32_t FilterMode; 			/*筛选器模式 */
    	uint32_t FilterScale; 			/*设置筛选器的尺度 */
    	uint32_t FilterActivation;		/*是否使能本筛选器*/
    	uint32_t BankNumber; 			/*扇区序号*/
    } CAN_FilterInitTypeDef;
    
  • 发送结构体 :CanTxMsgTypeDef
    typedef struct {
    	uint32_t StdId; 	/*存储报文的标准标识符 11 位,0-0x7FF. */
    	uint32_t ExtId; 	/*存储报文的扩展标识符 29 位,0-0x1FFFFFFF. */
    	uint8_t IDE; 		/*存储 IDE 扩展标志 */
    	uint8_t RTR; 		/*存储 RTR 远程帧标志*/
    	uint8_t DLC; 		/*存储报文数据段的长度,0-8 */
    	uint8_t Data[8]; 	/*存储报文数据段的内容 */
    } CanTxMsgTypeDef;
    
  • 接收结构体:CanRxMsgTypeDef
     typedef struct {
     	uint32_t StdId; 	/*存储了报文的标准标识符 11 位,0-0x7FF. */
    	uint32_t ExtId; 	/*存储了报文的扩展标识符 29 位,0-0x1FFFFFFF. */
    	uint8_t IDE; 		/*存储了 IDE 扩展标志 */
    	uint8_t RTR; 		/*存储了 RTR 远程帧标志*/
    	uint8_t DLC; 		/*存储了报文数据段的长度,0-8 */
    	uint8_t Data[8]; 	/*存储了报文数据段的内容 */
    	uint8_t FMI; 		/*存储了 本报文是由经过筛选器存储进 FIFO 的,0-0xFF */
    	uint8_t FIFONumber; /*配置接收 FIFO 编号,可以是 CAN_FIFO0 或者 CAN_FIFO1 */
    } CanRxMsgTypeDef;
    

2.接口分析

固件库提供的常用接口如下:

//复位CAN外设的寄存器,使用前需要开时钟
void CAN_DeInit(void);

// CAN外设初始化
u8 CAN_Init(CAN_InitTypeDef* CAN_InitStruct);

// CAN过滤器初始化
void CAN_FilterInit(CAN_FilterInitTypeDef* CAN_FilterInitStruct);

//发送CAN数据包
u8 CAN_Transmit(CanTxMsg* TxMessage);

//接收CAN 数据包
void CAN_Receive(u8 FIFONumber, CanRxMsg* RxMessage);

//使 CAN 进入低功耗模式,复位INRQ进入初始化模式,置位SLEEP进入睡眠模式,然后判断INAK和SLAK确认初始化和睡眠状态
//CAN_Init 初始化时的 CAN_AWUM 使能本质就是清除了 MCR 的 SLEEP 位
u8 CAN_Sleep(void);

//将 CAN 唤醒
u8 CAN_WakeUp(void);

// 使能 CAN 的中断
	/*
	CAN_IT_TME 发送邮箱空中断屏蔽
	CAN_IT_FMP0 FIFO0 消息挂号中断屏蔽
	CAN_IT_FF0 FIFO0 满中断屏蔽
	CAN_IT_FOV0 FIFO0 溢出中断屏蔽
	AN_IT_FMP1 FIFO1 消息挂号中断屏蔽
	CAN_IT_FF1 FIFO1 满中断屏蔽
	CAN_IT_FOV1 FIFO1 溢出中断屏蔽
	CAN_IT_EWG 错误警告中断屏蔽
	CAN_IT_EPV 错误被动中断屏蔽
	CAN_IT_BOF 离线中断屏蔽
	CAN_IT_LEC 上次错误号中断屏蔽
	CAN_IT_ERR 错误中断屏蔽
	CAN_IT_WKU 唤醒中断屏蔽
	CAN_IT_SLK 睡眠标志位中断屏蔽 
	*/
void CAN_ITConfig(u32 CAN_IT, FunctionalState NewState);

//尽管函数名有IT字样
//但我觉得这个API应该解释为查看 CAN 外设当前的状态
	/*
	CAN_IT_RQCP0 邮箱 1 请求完成
	CAN_IT_RQCP1 邮箱 2 请求完成
	CAN_IT_RQCP2 邮箱 3 请求完成
	CAN_IT_FMP0 FIFO0 消息挂号
	CAN_IT_FULL0 FIFO0 已存入 3 消息
	CAN_IT_FOVR0  FIFO0 溢出
	CAN_IT_FMP1 FIFO1 消息挂号
	CAN_IT_FULL1 FIFO1 已存入 3 消息
	CAN_IT_FOVR1 FIFO1 溢出
	CAN_IT_EWGF 上限到达警告
	CAN_IT_EPVF 错误被动上限到达
	CAN_IT_BOFF 进入离线状态
	CAN_IT_WKUI 睡眠模式下 SOF 侦测
	*/
ITStatus CAN_GetITStatus(u32 CAN_IT);

//清除 CAN 外设状态的标志位
void CAN_ClearITPendingBit(u32 CAN_IT);

//判断 CAN 的出错状态
FlagStatus CAN_GetFlagStatus(u32 CAN_FLAG);
//清除 CAN 出错状态标志位
void CAN_ClearFlag(u32 CAN_Flag);

//返回指定邮箱当前的发送状态。
//实际是判断 待发送标志位RQCP、上一次发送结果TXOK、发送邮箱为空TME、三个寄存器位来实现
//如果标志位为待发送且邮箱为空则返回失败,因为如果发送前待发送标志位1且邮箱不为0的话肯定是出错了
//如果标志位为上一次发送成功且待发送且邮箱为空则成功,因为发送后待发送标志位清零,所以等于只判断了邮箱为空且发送成功
u8 CAN_TransmitStatus(u8 TransmitMailbox);

//取消指定邮箱的传输请求,实现是置位ABRQ
//作用是终止邮箱的发送请求,邮箱未挂起等待发送时则无效,当TXRQ置1时,数据开始发送,待发送标志位RQCP会清零,所以猜测,一旦开始发送了,通过这种方法就无法挽回了
void CAN_CancelTransmit(u8 Mailbox);

//释放一个 FIFO,本质置位RFOM寄存器位,移动FIFO指针,访问下一条数据
void CAN_FIFORelease(u8 FIFONumber);

//返回邮件的数量,本质是返回FMP的值
u8 CAN_MessagePending(u8 FIFONumber);

3. 实例代码

2. HAL库

1. 接口对象

2. 接口分析

3.实例代码

3.自制BB库

1.接口实现

  1. CAN外设初始化函数
//使用
BB_Can_Init(CANx, BB_Can_Init_Conf_Callback1);
	
//接口回调,内联回调函数里实现CAN外设的配置
static __inline void BB_Can_Init_Conf_Callback1(CAN_TypeDef* CANx)
{
	BB_Can_Set_MCR(CANx, 						//开启功能	
					CAN_MCR_NART
					);																															
	BB_Can_Reset_MCR(CANx, 						//关闭功能
					CAN_MCR_TTCM
					| CAN_MCR_ABOM
					| CAN_MCR_AWUM
					| CAN_MCR_RFLM
					| CAN_MCR_TXFP
					);		
	BB_Can_Set_BTR(CANx, 6, CAN_BS1_4tq, CAN_BS2_2tq, CAN_SJW_1tq, CAN_Mode_LoopBack);		//位同步段时间与模式设置
}

//接口实现,通过内联的方式直接操作寄存器,将时间开销降到最低
static __inline uint32_t BB_Can_Init(CAN_TypeDef* CANx, void (*conf_fun)(CAN_TypeDef* CANx))
{
	int32_t time_out = 0xffff;						//超时默认延时
	BB_Can_Reset_MCR(CANx, CAN_MCR_SLEEP);			//退出睡眠
	BB_Can_Set_MCR(CANx, CAN_MCR_RESET);			//进入复位状态
	
	while(!BB_Can_Read_MSR(CANx, CAN_MSR_INAK) && time_out--);					//等待初始化
	if (!BB_Can_Read_MSR(CANx, CAN_MSR_INAK)) return CAN_InitStatus_Failed;		//初始化出错

	conf_fun(CANx);									//通过指针函数的来实现对不同部分的调用
	
	time_out = 0xffff;								//超时默认延时															
	BB_Can_Reset_MCR(CANx, CAN_MCR_RESET);			//退出复位状态
	
	while(BB_Can_Read_MSR(CANx, CAN_MSR_INAK) && time_out--);					//退出初始化
	if (BB_Can_Read_MSR(CANx, CAN_MSR_INAK)) return CAN_InitStatus_Failed;		//退出初始化化出错
	
	return CAN_InitStatus_Success;					//返回成功
}
  1. CAN过滤器初始化函数
//使用
BB_Can_Filter_Init(0, BB_Can_Filter_Init_Conf_Callback0);

//接口回调
static __inline void BB_Can_Filter_Init_Conf_Callback0(uint8_t Filter_num)
{
	BB_Can_FilterScale_32bit(Filter_num);					//配置为32位模式
	BB_Can_FilterData_32bit(Filter_num, 0x1314, 0x1314);	//配置数据寄存器
	BB_Can_FilterMode_IdMask(Filter_num);					//配置为掩码模式
	BB_Can_Filter_FIFO1(Filter_num);						//关联到FIFO1
	BB_Can_Filter_ENABLE(Filter_num);						//打开总使能
}

//接口实现
static __inline void BB_Can_Filter_Init(uint8_t Filter_num, void (*conf_fun)(uint8_t Filter_num))
{
	BB_Can_Set_FMR(CAN_FMR_FINIT);		//进入初始化状态
	conf_fun(Filter_num);				//进入配置函数
	BB_Can_Reset_FMR(CAN_FMR_FINIT);	//退出初始化状态
}
  1. CAN中断配置函数
//使用
BB_CAN_ITConfig(CANx);

//接口实现
static __inline void BB_CAN_ITConfig(CAN_TypeDef* CANx)
{
	BB_Can_Set_IER(CANx,				//中断开启
					CAN_IT_FMP0
					);
	BB_Can_Reset_IER(CANx,				//中断关闭 
					CAN_IT_FF0
					);
}
  1. CAN 读状态接口
  1. CAN 读错误接口
  1. CAN 读函数
  1. CAN写函数

2.实例代码

你可能感兴趣的:(#,STM32)