STM32F407之CAN控制器的使用
CAN控制器局域网络 (Controller Area Network),是目前广泛应用的可靠串行总线通信之一,其国际标准号为ISO 11898。CAN总线协议已经成为汽车计算机控制系统和嵌入式工业控制局域网的标准总线。
CAN总线上的节点既可以发送数据也可以接收数据,没有主从之分,但属于半双工通信,同一时刻只能由一个节点发送数据,其他节点只能接收。发送数据具有自动竞争机制,优先级高的竞争成功,优先级通过数据帧ID逐位判断,显性电平胜出。通信过程不需要时钟信号,而采用约定波特率的方式实现同步。
一、总线结构
分为闭环和开环两种结构形式,两种形式适用于不同的通信速率和通信距离:
- 闭环:高速、短距离,通信速率一般为125kbit/s到1Mbit/s,1Mbit/s时可达40m
- 开环:低速、短距离,通信速率一般低于125kbit/s,40kbit/s时可达1km,5kbit/s时可达10km
总线典型电压却不同,但都采用2根差分信号线(CAN_High和CAN_Low)进行信号传输。所以,在设计方案时,要根据实际需要选择对应的结构形式,并选择对应的CAN收发器芯片。
CAN总线电压有两种:
- 隐形电平,即逻辑1
——闭环结构中为2.5V,开环结构中为1.75V - 显性电平,即逻辑0
——闭环结构中为3.5V,开环结构中为4.0V
注意
CAN控制器和CAN收发器之间通过CAN_RX和CAN_TX进行通信,类似于TTL。默认下,CAN收发器上电后会自动将CAN_RX和CAN_TX拉高,以表示准备就绪。当我们在调试时,如果板子没有接收发器,就需要人为将CAN_RX和CAN_TX信号线上拉,不然CAN控制器会因检测不到收发器而超时报错,无法启动。
二、传输协议
2.1帧
CAN一共有5种类型的帧:
-
数据帧:数据帧携带ID和数据从发送器至接收器。
-
远程帧/遥控帧:节点向总线网络发出只带有ID的帧,用以进行数据请求,具有该ID的节点接收到远程帧后发送相应ID的数据帧。
-
错误帧:任何节点检测到总线错误时就发送错误帧。
-
过载帧:接收节点未做好接收准备时发出的帧,发送节点收到过载帧时可暂缓发送数据帧。
-
帧间空间:用来将数据帧、远程帧与前后的帧分割开提供附加的延时。
2.2标准格式数据帧和远程帧
- SOF(Start Of Frame)帧起始位,显性电平0;
- RTR(Remote Transmit Request)远程帧标志位,显性电平0表示数据帧,优先级高于远程帧;
- IDE(Identifier Extension)ID扩展格式标志位,隐形电平1表示该帧为扩展格式;
- R0 保留位,默认显性电平0;
- DLC(Data Length Code)4位数据长度码,有效值为0-8,表示数据段字节数;远程帧始终为0;
- CRC 即15位CRC校验值;
- DEL0(Delimiter)界定位,默认为隐性电平1,在该位时间内,接收端完成对接收数据的校验检查,以确保在ACK时刻给出结果;
- ACK(Acknowledge)响应位,发送端在该时刻置隐性电平并读取总线电平,接收端在此时可将数据检查结果反馈在总线上,若数据正常置显性电平0,若数据异常置隐性电平1,这样发送端就可以知道数据传输的结果;
- DEL1(Delimiter)界定位,默认为隐性电平1;
- EOF(End Of Frame)帧结束,共7位隐性电平1,表示帧结束。
2.3扩展格式数据帧和远程帧
相较标准格式,除了增加了18位扩展标识符外,还增加了SRR(Substitute Remote Request替代远程请求位)和R1(保留位1),这两个位均相当于占位符,默认隐性电平1。
2.4优先级
- 最先开始发送消息的节点获得发送权;
- 多节点同时开始发送时,逐位仲裁,当发生电平互异时输出显性电平的节点获得优先发送权继续发送,隐性电平的节点退出本次发送;
- 数据帧优先级>远程帧优先级
- 标准格式帧优先级>扩展格式帧优先级
2.5位时序
标准CAN协议中,每一个位细分为四个阶段,每个阶段又由不同个数的时间片tq组成,每个时间片长度由CAN控制器的时钟频率决定,四个阶段分别为:
- SYNC_SEG:同步段,1 个时间片。它用于同步各种总线节点;
- PTS_SEG:传播段,1~8 时间片。它用于补偿网络上的信号延迟。
- PBS_SEG_1:相位缓冲段1,1~8 时间片。它用于补偿边缘相位误差,在重新同步期间可能会延长。
- PBS_SEG_2:相位缓冲段2,2~8 时间片。它用于补偿边缘相位误差。
那么 波特率 = 1/(1+PROP+PHASE1+PHASE2)
----------------------------------------------分割线-------------------------------------------------------------------------
但是,STM32自带的CAN控制器的位时序和上面的标准位时序有一些区别,只有三个阶段:同步段(SYNC_SEG)、 时间段 1(BS1)、 时间段 2(BS2)。其中,BS1段是 CAN 标准协议中的 PTS 段和 PBS1 段的结合, 而 BS2 段就相当于 PBS2 段。
2.6控制器配置
2.7收发功能
STM32F407自带2个CAN控制器,CAN1为带有512字节SRAM的主CAN控制器,CAN2是从控制器,和CAN1共享512字节SRAM,但CAN2要访问SRAM必须保证CAN1正常工作以提供SRAM读写时钟,也就是说CAN2不能独立工作。同时筛选器的配置也需要在CAN1中完成,CAN2无法配置。
- 每个CAN控制器有1个发送FIFO,其包含3个发送邮箱,每个发送邮箱可存放1条帧信息
- 每个CAN控制器有2个接收FIFO,每个接收FIFO有3个接收邮箱,每个接收邮箱可存放1条帧信息
- 具有16位定时器,可实现定时触发通信,并可在最后两个数据字节发送时间戳
- 两个CAN控制器共用28个筛选器,筛选器用于配置可接收ID列表或掩码,只有通过筛选的帧才会进入接收邮箱;在CAN1筛选器配置中通过SlaveStartFilterBank完成CAN2筛选器的配置。
- 一个筛选器只能配置给一个接收FIFO,但一个接收FIFO可以同时关联多个筛选器。
- 通过筛选器进入接收FIFO的帧数据会自动分配到空闲的邮箱中。
CAN有4种收发模式,除正常模式外的3种都叫做测试模式:
- 正常模式Normal:可发送到总线,也可从总线接收数据;
- 静默模式Silent:可向自身接收端发送显性电平,只能向总线发送隐性电平,即无法向外发送数据;可以接收总线数据和自身发送的数据;
- 回环模式Loopback:可向总线和自身接收端发送数据,但不能正常接收总线数据,只能接收自身发送的数据;
- 回环静默模式:只能自发自收,不能向总线正常发送数据或从总线接收数据。
2.8工作流程
- 模块初始化
void MX_CAN1_Init(void) >>> HAL_StatusTypeDef HAL_CAN_Init(CAN_HandleTypeDef *hcan)
- 配置筛选器
HAL_StatusTypeDef CAN_SetFilters()
{
CAN_FilterTypeDef canfilter;
canfilter.FilterBank = 0;//筛选器编号
canfilter.FilterMode = CAN_FILTERMODE_IDMASK;//筛选器模式:掩码模式mask or 表单模式list
canfilter.FilterScale = CAN_FILTERSCALE_32BIT;//32BIT表示为1个32位过滤值,16BIT表示为2个16位过滤值
canfilter.FilterIdHigh = 0x0020;//和FilterMaskIdHigh配合使用,接收到的ID & 掩码ID == 过滤器ID & 掩码ID
canfilter.FilterIdLow = 0x0000;
canfilter.FilterMaskIdHigh = 0x0020;//对于标准码,前11位为ID
canfilter.FilterMaskIdLow = 0x0000;
canfilter.FilterFIFOAssignment = CAN_RX_FIFO0;//过滤器应用于FIFO0
canfilter.FilterActivation = ENABLE;//使能过滤器
canfilter.SlaveStartFilterBank = 14;//过滤器0-13为CAN1使用;过滤器14-28号为CAN2使用
HAL_StatusTypeDef result = HAL_CAN_ConfigFilter(&hcan1, &canfilter);
return result;
}
- 启动控制器
HAL_StatusTypeDef HAL_CAN_Start(CAN_HandleTypeDef *hcan)
- 收发读取
//例程:发送帧
void CAN_SendMsg(uint8_t msgID,uint8_t frameType)
{
uint8_t TxData[8];
TxData[0]=0x56;
TxData[1]=0x57;
CAN_TxHeaderTypeDef TxHeader;
TxHeader.StdId= msgID;
TxHeader.RTR=frameType;//数据帧or遥控帧设定
TxHeader.IDE=CAN_ID_STD;
TxHeader.DLC=2;//数据字节数
TxHeader.TransmitGlobalTime=DISABLE;//禁用时间戳
while(HAL_CAN_GetTxMailboxesFreeLevel(&hcan1)<1) {}//等待有可用的发送邮箱
uint32_t TxMailbox;
if(HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox)!=HAL_OK)//向发送邮箱加载帧
{
uint8_t sendbytes[] = "CAN sendmsg err.\r\n";
HAL_Delay(10);
HAL_UART_Transmit(&huart1, sendbytes, sizeof sendbytes, 150);
return;
}
while(HAL_CAN_GetTxMailboxesFreeLevel(&hcan1)!=3){}//等待3个发送邮箱均发送完毕
}
//例程:循环查询方式接收
void CAN_GetPoll()
{
CAN_RxHeaderTypeDef RxHeader;
uint8_t RxData[8];
HAL_Delay(1);
uint8_t fifolevel = HAL_CAN_GetRxFifoFillLevel(&hcan1, CAN_RX_FIFO0);
if(fifolevel == 0)//检测是否有数据到达接受邮箱
{
uint8_t sendbytes[] = "CAN not getmsg.\r\n";
HAL_Delay(10);
HAL_UART_Transmit(&huart1, sendbytes, sizeof sendbytes, 150);
return;
}
if(HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &RxHeader, RxData)==HAL_OK)//若有数据到达则从FIFO中非空邮箱里按序提取帧
{
uint8_t sendbytes[] = "\r\nCAN getmsg:stid=";
HAL_Delay(10);
HAL_UART_Transmit(&huart1, sendbytes, sizeof(sendbytes)-1, 150);
uint8_t stid[4];
stid[0] = RxHeader.StdId>>24;
stid[1] = RxHeader.StdId>>16;
stid[2] = RxHeader.StdId>>8;
stid[3] = RxHeader.StdId;
HAL_Delay(10);
HAL_UART_Transmit(&huart1, stid, sizeof stid, 150);
if(RxHeader.RTR==CAN_RTR_DATA)//如果是数据帧则读取数据
{
uint8_t sendbytes[] = "\r\nCAN getmsg:data=";
HAL_Delay(10);
HAL_UART_Transmit(&huart1, sendbytes, sizeof(sendbytes)-1, 150);
HAL_Delay(10);
HAL_UART_Transmit(&huart1, RxData, sizeof RxData, 150);
}
}
}
如果开启了中断接收,可以在函数 void CAN1_RX0_IRQHandler(void) 中调用接收处理函数,
或重写回调函数void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
- 停止控制器
HAL_StatusTypeDef HAL_CAN_Stop(CAN_HandleTypeDef *hcan)