前言
在上个学期的单片机课设中,基于STC89C52实现了RS232转CAN通信,由于使用现成的CAN通信模块(包含CAN控制器和CAN收发器),省去了代码设计的过程,但对CAN通信没有系统的学习。最近由于在学习STM32的HAL库,心血来潮,想用STM32单片机实现。STM32F103C8T6内置一个CAN控制器,因此搭配TJA1050收发器,即可实现CAN通信。TJA1050收发器和RS232实现的是电平转换的功能,因此本次课程设计的关键在于使用STM32HAL库实现CAN数据的收发。
一、CAN简介
CAN 是 Controller Area Network 的缩写(以下称为 CAN),是 ISO国际标准化的串行通信
协议。在当前的汽车产业中,出于对安全性、舒适性、方便性、低公害、低成本的要求,各种各样的电子控制系统被开发了出来。由于这些系统之间通信所用的数据类型及对可靠性的要求不尽相同,由多条总线构成的情况很多,线束的数量也随之增加。为适应“减少线束的数量”、“通过多个 LAN,进行大量数据的高速通信”的需要 1986 年德国电气商博世公司开发出面向汽车的 CAN 通信协议。此后,CAN 通过 ISO11898 及 ISO11519 进行了标准化,现在在欧洲已是汽车网络的标准协议。
现在,CAN 的高性能和可靠性已被认同,并被广泛地应用于工业自动化、船舶、医疗设备、工业设备等方面。现场总线是当今自动化领域技术发展的热点之一,被誉为自动化领域的计算机局域网。它的出现为分布式控制系统实现各节点之间实时、可靠的数据通信提供了强有力的技术支持。
CAN 控制器根据两根线上的电位差来判断总线电平。总线电平分为显性电平和隐性电平,
二者必居其一。发送方通过使总线电平发生变化,将消息发送给接收方。
CAN协议具有以下特点:
1) 多主控制。 在总线空闲时,所有单元都可以发送消息(多主控制),而两个以上的单元同时开始发送消息时,根据标识符( Identifier 以下称为 ID)决定优先级。 ID 并不是表示发送的目的地址,而是表示访问总线的消息的优先级。两个以上的单元同时开始发送消息时,对各消息 ID 的每个位进行逐个仲裁比较。仲裁获胜(被判定为优先级最高)的单元可继续发送消息,仲裁失利的单元则立刻停止发送而进行接收工作。
2) 系统的 柔软 性。 与总线相连的单元没有类似于“地址”的信息。因此在总线上增加单元时,连接在总线上的其它单元的软硬件及应用层都不需要改 变。
3) 通信速度较快,通信距离远。 最高 1Mbps(距离小于 40M),最远可达 10KM(速率低
于 5Kbps)。
4) 具有错误检测、错误通知和错误恢复功能。 所有单元都可以检测错误(错误检测功能),检测出错误的单元会立即同时通知其他所有单元(错误通知功能), 正在发送消息的单元一旦检测出错误,会强制结束当前的发送。强制结束发送的单元会不断反复地重新发送此消息直到成功发送为止(错误恢复功能)。
5) 故障封闭功能。 CAN 可以判断出错误的类型是总线上暂时的数据错误(如外部噪声等)还是持续的数据错误(如单元内部故障、驱动器故障、断 线等)。由此功能,当总线上发生持续数据错误时,可将引起此故障的单元从总线上隔离出去。
6) 连接节点多。 CAN 总线是可同时连接多个单元的总线。可连接的单元总数理论上是没有限制的。但实际上可连接的单元数受总线上的时间延迟及电气负载的限制。降低通信速度,可连接的单元数增加;提高通信速度,则可连接的单元数减少。
正是因为CAN协议的这些特点,使得 CAN特别适合工业过程监控设备的互连,因此,越来越受到工业界的重视,并已公认为最有前途的现场总线之一。CAN协议经过 ISO标准化后有两个标准: ISO11898标准和 ISO11519 2标准。其中 ISO11898是针对通信速率为 125Kbps~1Mbps的高速 通信 标准 ,而 ISO11519 2是针对通信速率为 125Kbps以下的低速通信标准。
二、CAN通信协议
CAN总线包括CAN_H和CAN_L,总线上传输的电平分为显性电平和隐形电平。显性电平对应逻辑 0 ,CAN_H和 CAN_L之差为 2.5V左右。而隐性电平对应逻辑 1, CAN_H和 CAN_L之差为 0V。 在总线上显性电平具有优先权,只要有一个单元输出显性电平,总线上即为显性电平。而隐形电平则具有包容的意味,只有所有的单元都输出隐性电平,总线上才为隐性电平(显性电平比隐性电平更强)。另外, 在 CAN总线的起止端都有一个 120Ω的终端电阻,来做阻抗匹配,以减少回波反射。
CAN数据帧
CAN协议包括数据帧、遥控帧、错误帧、 过载帧、间隔帧。其中,数据帧和遥控帧有标准格式和扩展格式两种格式。标准格式有11 个位的标识符( ID),扩展格式有 29 个位的 ID。各种帧功能如下:
本文主要介绍数据帧,数据帧一般由 7个段构成,即:
(1) 帧起始。表示数据帧开始的段。
(2) 仲裁段。表示该帧优先级的段。
(3) 控制段。表示数据的字节数及保留位的段。
(4) 数据段。数据的内容,一帧可发送 0~8个字节的数据。
(5) CRC段。检查帧的传输错误的段。
(6) ACK段。表示确认正常接收的段。
(7)帧结束。表示数据帧结束的段。
数据帧格式如下:D表示显性电平, R表示隐形电平(下同)。
帧起始,标准帧和扩展帧都是由1个位的显性电平表示帧起始 。
仲裁段,表示数据优先级的段,标准帧和扩展帧格式在本段有所区别,如图:
标准格式的ID 有 11 个位,从 ID28 到 ID18 被依次发送,禁止高 7 位都为隐性(禁止设定: ID=1111111XXXX)。扩展格式的 ID 有 29 个位,基本 ID 从 ID28 到 ID18,扩展 ID 由ID17 到 ID0 表示。基本 ID 和标准格式的 ID 相同。禁止高 7 位都为隐性(禁止设定:基本ID=1111111XXXX)。
其中RTR位用于标识是否是远程帧( 0,数据帧 ;1,远程帧), IDE位为标识符选择位( 0使用标准标识符; 1,使用扩展标识符), SRR位为代替远程请求位,为隐性位,它代替了标准帧中的 RTR位。
控制段,由6个位构成,表示数据段的字节数。标准帧和扩展帧的控制段稍有不同。
数据段,该段可包含 0~8个字节的数据。从最高位( MSB)开始输出,标准帧和扩展帧在这个段的定义都是一样的。
CRC段,该段用于检查帧传输错误 。 由 15个位的 CRC顺序和 1个位的 CRC界定符(用于分隔的位)组成,标准帧和扩展帧在这个段的格式也是相同的。
ACK段,此段用来确认是否正常接收。由 ACK槽 (ACK Slot)和 ACK界定符 2个位组成。标准帧和扩展帧在这个段的格式也是相同的。
帧结束,这个段也比较简单,标准帧和扩展帧在这个段格式一样,由7个位的隐性位组成。
CAN位时序
由发送单元在非同步的情况下发送的每秒钟的位数称为位速率。一个位可分为
4 段。
⚫ 同步段SS
⚫ 传播时间段 PTS
⚫ 相位缓冲段 1( PBS1
⚫ 相位缓冲段 2( PBS2
这些段又由可称为Time Quantum(以下称为 Tq)的最小时间单位构成。1 位分为 4 个段,每个段又由若干个 Tq 构成,这称为位时序。1 位由多少个 Tq 构成、每个段又由多少个 Tq 构成等,可以任意设定位时序。通过设定位时序,多个单元可同时采样,也可任意设定采样点。
总线仲裁
在总线空闲态,最先开始发送消息的单元获得发送权。
当多个单元同时开始发送时,各发送单元从仲裁段的第一位开始进行仲裁。连续输出显性
电平最多的单元可继续发送。实现过程,如图:
上图中,单元 1和单元 2同时开始向总线发送数据,开始部分他们的数据格式 是一样的,故无法区分优先级,直到 T时刻,单元 1输出隐性电平,而单元 2输出显性电平,此时单元 1仲裁失利,立刻转入接收状态工作,不再与单元 2竞争,而单元 2则顺利获得总线使用权,继续发送自己的数据。这就实现了仲裁,让连续发送显性电平多的单元获得总线使用权。
三、bxCAN
STM32F103C8T6自带的CAN控制器为bxCAN。bxCAN是基本扩展CAN(Basic Extended CAN)的缩写,它支持CAN协议2.0A和2.0B。它的设计目标是,以最小的CPU负荷来高效处理大量收到的报文。它也支持报文发送的优先级要求(优先级特性可软件配置)。对于安全紧要的应用,bxCAN提供所有支持时间触发通信模式所需的硬件功能。
STM32F1的 bxCAN的主要特点有:
⚫ 支持 CAN协议 2.0A和 2.0B主动模式
⚫ 波特率最高达 1Mbps
⚫ 支持时间触发通信
⚫ 具有 3个发送邮箱
⚫ 具有 3级深度的 2个接收 FIFO
⚫ 可变的过滤器组( 最多 28个)
框图如下:
过滤器组的位宽和工作模式
STM32的标识符过滤是一个比较复杂的东东,它的存在减少了 CPU处理 CAN通信的开销。
STM32的过滤器组最多有 28个(互联型),但是 STM32F103ZET6只有 14个(增强型),每个
滤波器组 x由 2个 32为寄存器, CAN_FxR1和 CAN_FxR2组成。
STM32F1每个过滤器组的位宽都可以独立配置,以满足应用程序的不同需求。根据位宽的
不同,每个过滤器组可提供:
●1个 32位过滤器,包括: STDID[10:0]、 EXTID[17:0]、 IDE和 RTR位
●2个 16位过滤器,包括: STDID[10:0]、 IDE、 RTR和 EXTID[17:15]位
此外过滤器可配置为,屏蔽位模式和标识符列表模式。
为了过滤出一组标识符,应该设置过滤器组工作在屏蔽位模式。
为了过滤出一个标识符,应该设置过滤器组工作在标识符列表模式。
bxCAN工作模式
bxCAN有3个主要的工作模式:初始化、正常和睡眠模式。在硬件复位后,bxCAN工作在睡眠模式以节省电能,同时CANTX引脚的内部上拉电阻被激活。软件通过对CAN_MCR寄存器的INRQ或SLEEP位置’1’,可以请求bxCAN进入初始化或睡眠模式。一旦进入了初始化或睡眠模式,bxCAN就对CAN_MSR寄存器的INAK或SLAK位置’1’来进行确认,同时内部上拉电阻被禁用。当INAK和SLAK位都为’0’时,bxCAN就处于正常模式。在进入正常模式前,bxCAN必须跟CAN总线取得同步;为取得同步,bxCAN要等待CAN总线达到空闲状态,即在CANRX引脚上监测到11个连续的隐性位。
bxCAN测试模式
静默模式
在静默模式下,bxCAN可以正常地接收数据帧和远程帧,但只能发出隐性位,而不能真正发送报文。(发送端不能向总线发送有效数据,接收端可以接收总线传来的有效数据)
环回模式
在环回模式下,bxCAN把发送的报文当作接收的报文并保存(如果可以通过接收过滤)在接收邮箱里。(自发自收,避免外界干扰,可用于自测试)
静默环回模式
CAN发送流程
CAN发送流程为:程序选择 1个空置的邮箱( TME=1 设置标识符( ID),数据长度和
发送数据 设置 CAN_TIxR的 TXRQ位为 1,请求发送 邮箱挂号(等待成为最高优先级)
预定发送(等待总线空闲) 发送 邮箱空置。整个流程如图:
CAN接收流程
CAN接收流程为: FIFO空 收到有效报文 挂号 _1(存入 FIFO的一个邮箱,这个由硬件
控制,我们不需要理会) 收到有效报文 挂 号 _2 收到有效报文 挂号 _3 收到有效报文
溢出。
CAN通信的波特率计算如下:
波特率的计算公式,我们只需要知道 BS1和 BS2的设置,以及 APB1的时钟频率(一般为 36Mhz),就可以方便的计算出波特率。
以上为CAN的一些简要介绍。CAN内容比较复杂和难懂,具体的介绍可以参考STM32的开发手册和CAN协议手册。
四、STM32CubeMX配置与代码
基础配置见往期内容
本次CAN通信波特率为500Kbps,把时钟树拉满,APB1总线频率为36MHz,选择预分频系数Prescaler为4,TBS1为9Tq,TBS2为8Tq,CAN通信的波特率 =36000/[(9+8+1)*4]=500Kbps。
模式选择Normal,其余配置选择Disable(配置介绍见手册)
本次课程设计还需配置串口、OLED。
由于STM32CubeMX生成的代码没有过滤器的配置,因此在can.c文件中需要自己配置。写完之后在can.h文件中声明,在主函数调用。
void filter_Init(void)
{
/*过滤器接收所有报文,不过滤*/
CAN_FilterTypeDef can_filterconfig;
can_filterconfig.FilterMode = CAN_FILTERMODE_IDMASK; //配置屏蔽位模式
can_filterconfig.FilterScale = CAN_FILTERSCALE_32BIT; //配置位宽
can_filterconfig.FilterIdHigh = 0;
can_filterconfig.FilterIdLow = 0;
can_filterconfig.FilterMaskIdHigh = 0;
can_filterconfig.FilterMaskIdLow = 0;
can_filterconfig.FilterBank = 0; //配置过滤器组
can_filterconfig.FilterFIFOAssignment = CAN_FILTER_FIFO0; //配置对应FIFO
can_filterconfig.FilterActivation = CAN_FILTER_ENABLE; //滤波器激活
can_filterconfig.SlaveStartFilterBank = 14; //配置从过滤器组,默认14
HAL_CAN_ConfigFilter(&hcan,&can_filterconfig);
HAL_CAN_Start(&hcan); //开启CAN通信
HAL_CAN_ActivateNotification(&hcan,CAN_IT_RX_FIFO0_MSG_PENDING); //开启CAN接收中断
}
main.c
#include "main.h"
#include "can.h"
#include "usart.h"
#include "gpio.h"
#include "OLED.h"
#include "stdio.h"
void SystemClock_Config(void);
uint8_t can_rx_data[100];
uint8_t uart_buf[100];
uint8_t len;
CAN_TxHeaderTypeDef g_can1_txheader; /*CAN发送结构体*/
CAN_RxHeaderTypeDef g_can1_rxheader; /*CAN接收结构体*/
uint32_t tx_mail; //配置发送邮箱
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_CAN_Init();
MX_USART2_UART_Init();
filter_Init();
OLED_Init();
g_can1_txheader.StdId = 0x172; //标准ID
g_can1_txheader.ExtId = 0;
g_can1_txheader.DLC = 8; //数据长度
g_can1_txheader.IDE = CAN_ID_STD; //选择标准帧
g_can1_txheader.RTR = CAN_RTR_DATA; //选择数据帧
g_can1_txheader.TransmitGlobalTime = DISABLE;
printf("program is ready!");
printf("\r\n");
while (1)
{
HAL_UART_Receive_IT(&huart2,uart_buf,8); //串口中断接收
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) //USART接收中断函数
{
if(huart->Instance == USART2)
{
printf("USART Receive:%s",uart_buf);
printf("\r\n");
while(HAL_CAN_AddTxMessage(&hcan, &g_can1_txheader, uart_buf, &tx_mail) != HAL_OK);
OLED_ShowString(1,1,"UART_RX:");
OLED_ShowString(2,1,(char *)uart_buf);
}
}
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) //CAN接收中断函数
{
HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &g_can1_rxheader, can_rx_data);
OLED_ShowString(3, 1, "CAN received");
OLED_ShowString(4, 1, (char *)can_rx_data);
printf("CAN received %s\n", can_rx_data);
}