CAN总线学习笔记—STM32篇
目录
CAN外设简介
CAN网拓扑结构(引脚
CAN框图
CAN基本结构
发送过程
接收流程
发送和接收配置位
标识符过滤器
过滤器配置示例
测试模式
工作模式
位时间特性
中断
时间触发通信
错误处理和离线恢复
CAN外设函数
代码
CAN总线单个设备环回测试/3个设备通信相互通信
CAN外设简介
STM32内置bxCAN外设(CAN控制器),支持CAN2.0A和2.0B,可以自动发送CAN报文和按照过滤器自动接收指定CAN报文,程序只需处理报文数据而无需关注总线的电平细节
波特率最高可达1兆位/秒
3个可配置优先级的发送邮箱(发送时有3个缓存区,可以存入3个待发报文)
2个3级深度的接收FIFO(接收缓存区,可以存2*3=6个报文)
14个过滤器组(过滤报文ID)(互联型28个)
时间触发通信、自动离线恢复、自动唤醒、禁止自动重传、接收FIFO溢出处理方式可配置、发送优先级可配置、双CAN模式
STM32F103C8T6 CAN资源:CAN1
CAN网拓扑结构(引脚
CAN框图
CAN基本结构
CAN_TX是输出,配置为复用推挽输出模式。
CAN_RX是输入,配置为上拉输入。
发送邮箱有3个,缓冲区,减少CPU等待时间,防止拥堵。
发送可以设置发送策略,先请求先发送或者按ID号优先级发送。
当CAN总线出现数据帧或者遥控帧的报文时,控制器就会把报文收下来,通过过滤器对报文进行过滤,当不是我们想要的,报文通过不了过滤器就直接丢弃。(可以对任一一个过滤器进行设置,把我们想要的报文ID规则,写入到过滤器中。)
可以指定某个过滤器报文进入FIFO0或者FIFO1。
接收FIFO,类似排队,先来后到。两个FIFO可以分流。可以配置重要的进FIFO0,不重要的进FIFO1等,自行规定。
配置了FIFO锁定,则FIFO满后,新的报文会直接丢弃,不锁定,FIFO满后,新的报文会把邮箱2的数据踢出去,自己占据邮箱2。
发送过程
基本流程:选择一个空置邮箱→写入报文 →请求发送
RQCP(请求完成)=X,任意值
TXOK(发送成功)=X,任意值
TME(发送邮箱空)=1,当前邮箱空置
CAN总线=IDLE,总线空闲。
NART(禁止自动重传)=0,使用重传,直到发送成功为止。
ABRQ(中止发送)
接收流程
基本流程:选择一个空置邮箱→写入报文 →请求发送
FMP(报文数目)
FOVR(FIFO溢出)
FULL=1,FIFO满后置1.
RFOM(释放邮箱)。
发送和接收配置位
NART(禁止自动重传):置1,关闭自动重传,CAN报文只被发送1次,不管发送的结果如何(成功、出错或仲裁丢失);置0,自动重传,CAN硬件在发送报文失败时会一直自动重传直到发送成功
TXFP(发送优先级):置1,优先级由发送请求的顺序来决定,先请求的先发送;置0,优先级由报文标识符来决定,标识符值小的先发送(标识符值相等时,邮箱号小的报文先发送)
RFLM(接收FIFO锁定模式):置1,接收FIFO锁定,FIFO溢出时,新收到的报文会被丢弃;置0,禁用FIFO锁定,FIFO溢出时,FIFO中最后收到的报文被新报文覆盖
标识符过滤器
每个过滤器的核心由两个32位寄存器组成:R1[31:0]和R2[31:0]
FSCx(配置过滤器位宽):位宽设置
置0,16位;置1,32位
FBMx(配置过滤器模式):模式设置
置0,屏蔽模式;置1,列表模式
FFAx(过滤器关联设置):关联设置
置0,FIFO 0;置1,FIFO 1
FACTx(过滤器激活设置):激活设置
置0,禁用;置1,启用
X=0~13,共14个过滤器
4种工作状态。
列表模式,ID要全部一样才能通过。
屏蔽模式,ID部分一样就能通过,可以通过一类ID。在R1写入ID,在R2里表明过滤中哪些位必须匹配,在R2的数据位写1表示在R1里的ID对应位必须匹配一致,写0表示R1里的ID0或1均可。R2的IDE必须给1,确保格式正确。
也可以1个寄存器拆开两个用,R1的低位写一个目标ID,R1的高位是对应的屏蔽位。R2同理。
过滤器配置示例
16位寄存器映像是左对齐的,但赋值语句是右对齐的,故要左移5位。
低5位默认给0,接收数据帧标准格式。如果想接收遥控帧,那么写入数据后或上0x10,把RTR位置1。
左对齐后右边有多少位就左移多少位。
测试模式
静默模式:用于分析CAN总线的活动,不会对总线造成影响
环回模式:用于自测试,同时发送的报文可以在CAN_TX引脚上检测到
环回静默模式:用于热自测试,自测的同时不会影响CAN总线
工作模式
初始化模式:用于配置CAN外设,禁止报文的接收和发送
正常模式:配置CAN外设后进入正常模式,以便正常接收和发送报文
睡眠模式:低功耗,CAN外设时钟停止,可使用软件唤醒或者硬件自动唤醒
AWUM:置1,自动唤醒,一旦检测到CAN总线活动,硬件就自动清零SLEEP,唤醒CAN外设;置0,手动唤醒,软件清零SLEEP,唤醒CAN外设
SLAK(睡眠确认状态位)=1,表示进入睡眠模式。
INAK(初始化确认位)=0,表示没有进入初始化模式。
SLEEP置1,表示请求睡眠
INRQ置1,表示请求进入初始化
SYNC信号,总线空闲信号
位时间特性
SS = 1Tq
BS1 = 1~16Tq (在STM32中PTS和PBS1合在一起)
BS2 = 1~8Tq
SJW=1~4Tq
波特率 = APB1时钟频率 / 分频系数 / 一位的Tq数量
= 36MHz / (BRP[9:0]+1) / (1 + (TS1[3:0]+1) + (TS2[2:0]+1))
中断
CAN外设占用4个专用的中断向量
发送中断:发送邮箱空时产生
FIFO 0中断:收到一个报文/FIFO 0满/FIFO 0溢出时产生
FIFO 1中断:收到一个报文/FIFO 1满/FIFO 1溢出时产生
状态改变错误中断:出错/唤醒/进入睡眠时产生
时间触发通信
TTCM:置1,开启时间触发通信功能;置0,关闭时间触发通信功能
CAN外设内置一个16位的计数器,用于记录时间戳
TTCM置1后,该计数器在每个CAN位的时间自增一次,溢出后归零
每个发送邮箱和接收FIFO都有一个TIME[15:0]寄存器,发送帧SOF时,硬件捕获计数器值到发送邮箱的TIME寄存器,接收帧SOF时,硬件捕获计数器值到接收FIFO的TIME寄存器
发送邮箱可配置TGT位,捕获计数器值的同时,也把此值写入到数据帧数据段的最后两个字节,为了使用此功能,DLC必须设置为8
错误处理和离线恢复
TEC和REC根据错误的情况增加或减少
ABOM:置1,开启离线自动恢复,进入离线状态后,就自动开启恢复过程;置0,关闭离线自动恢复,软件必须先请求进入然后再退出初始化模式,随后恢复过程才被开启。(从离线到错误主动的开关,ABOM置1自动恢复)
CAN外设函数
/* Function used to set the CAN configuration to the default reset state *****/
void CAN_DeInit(CAN_TypeDef* CANx); //给CAN结构体赋初值
/* Initialization and Configuration functions *********************************/
uint8_t CAN_Init(CAN_TypeDef* CANx, CAN_InitTypeDef* CAN_InitStruct);//初始化CAN外设
void CAN_FilterInit(CAN_FilterInitTypeDef* CAN_FilterInitStruct); //过滤器初始化
void CAN_StructInit(CAN_InitTypeDef* CAN_InitStruct); //配置结构体参数
void CAN_SlaveStartBank(uint8_t CAN_BankNumber); //用于配置CAN2的起始滤波器号,互联型才用
void CAN_DBGFreeze(CAN_TypeDef* CANx, FunctionalState NewState); //用于调试时的冻结模式
void CAN_TTComModeCmd(CAN_TypeDef* CANx, FunctionalState NewState); //使能TTCM模式中的TGT位
/* Transmit functions *********************************************************/
uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage); //发送CAN报文,参数写入要发送的报文,返回值表示报文返回了哪个邮箱
uint8_t CAN_TransmitStatus(CAN_TypeDef* CANx, uint8_t TransmitMailbox); //获取发送邮箱的状态,参数写入要检查的邮箱,返回值是此邮箱的状态
void CAN_CancelTransmit(CAN_TypeDef* CANx, uint8_t Mailbox);
/* Receive functions **********************************************************/
void CAN_Receive(CAN_TypeDef* CANx, uint8_t FIFONumber, CanRxMsg* RxMessage); //读取接收FIFO的数据,自带释放FIFO
void CAN_FIFORelease(CAN_TypeDef* CANx, uint8_t FIFONumber); //释放FIFO
uint8_t CAN_MessagePending(CAN_TypeDef* CANx, uint8_t FIFONumber); //获取指定FIFO队列里排队报文的数目,返回值返回FIFO中排队报文的数目
/* Operation modes functions **************************************************/
uint8_t CAN_OperatingModeRequest(CAN_TypeDef* CANx, uint8_t CAN_OperatingMode); //工作模式请求
uint8_t CAN_Sleep(CAN_TypeDef* CANx);
uint8_t CAN_WakeUp(CAN_TypeDef* CANx);
/* Error management functions *************************************************/
uint8_t CAN_GetLastErrorCode(CAN_TypeDef* CANx); //获取最近一次的错误码,返回值表示最近的错误是什么类型
uint8_t CAN_GetReceiveErrorCounter(CAN_TypeDef* CANx); //获取接收错误计数器,获取REC计数器的值
uint8_t CAN_GetLSBTransmitErrorCounter(CAN_TypeDef* CANx); //获取发送错误计数器的低8位,获取TEC的值
/* Interrupts and flags management functions **********************************/
void CAN_ITConfig(CAN_TypeDef* CANx, uint32_t CAN_IT, FunctionalState NewState); //中断输出使能
FlagStatus CAN_GetFlagStatus(CAN_TypeDef* CANx, uint32_t CAN_FLAG); //获取标志位状态
void CAN_ClearFlag(CAN_TypeDef* CANx, uint32_t CAN_FLAG); //清除标志位
ITStatus CAN_GetITStatus(CAN_TypeDef* CANx, uint32_t CAN_IT); //获取中断状态
void CAN_ClearITPendingBit(CAN_TypeDef* CANx, uint32_t CAN_IT); //清除中断挂起位
代码
CAN总线单个设备环回测试/3个设备通信相互通信
MyCAN.c
#include "stm32f10x.h" // Device header
void MyCAN_Init(void)
{
//开启外设//
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
//GPIO初始化//
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //TX要用复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
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);
//CAN外设初始化//
CAN_InitTypeDef CAN_InitStructure;
CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack; //设置CAN外设测试模式 3个设备通信时改成normal
CAN_InitStructure.CAN_Prescaler = 48; //分频系数 波特率 = 36M / 48 / (1 + 2 + 3) = 125K
CAN_InitStructure.CAN_BS1 = CAN_BS1_2tq; //PBS1的值
CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq; //PBS2的值
CAN_InitStructure.CAN_SJW = CAN_SJW_2tq; //执行再同步的最大Tq数
CAN_InitStructure.CAN_NART = DISABLE; //设置不自动重传模式
CAN_InitStructure.CAN_TXFP = DISABLE; //发送邮箱优先级,ENABLE先请求先发送,DISABLE ID小的先发
CAN_InitStructure.CAN_RFLM = DISABLE; //FIFO锁定,ENABLE溢出时新报文丢弃
CAN_InitStructure.CAN_AWUM = DISABLE; //自动唤醒
CAN_InitStructure.CAN_TTCM = DISABLE; //时间触发通信模式
CAN_InitStructure.CAN_ABOM = DISABLE; //离线自动恢复
CAN_Init(CAN1, &CAN_InitStructure);
//CAN过滤器初始化//
CAN_FilterInitTypeDef CAN_FilterInitStructure;
CAN_FilterInitStructure.CAN_FilterNumber = 0; //指定第几个过滤器
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000; //R1高位
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000; //R1低位
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000; //R2高位 屏蔽模式
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000; //R2低位
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit; //选择过滤器位宽
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask; //指定过滤器模式
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0; //配置过滤器关联
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE; //激活过滤器
CAN_FilterInit(&CAN_FilterInitStructure);
}
//封装发送报文函数//
void MyCAN_Transmit(uint32_t ID, uint8_t Length, uint8_t *Data)
{
CanTxMsg TxMessage; //结构体传递参数
TxMessage.StdId = ID; //标准ID
TxMessage.ExtId = ID; //拓展ID
TxMessage.IDE = CAN_Id_Standard; //选择格式
TxMessage.RTR = CAN_RTR_Data; //遥控标志位
TxMessage.DLC = Length; //数据段长度
for (uint8_t i = 0; i < Length; i ++)
{
TxMessage.Data[i] = Data[i]; //数据段内容 将数组传递过来
}
uint8_t TransmitMailbox = CAN_Transmit(CAN1, &TxMessage); //将邮箱号写入TransmitMailbox
uint32_t Timeout = 0;
while (CAN_TransmitStatus(CAN1, TransmitMailbox) != CAN_TxStatus_Ok) //检查邮箱状态
{
Timeout ++;
if (Timeout > 100000) //超时退出,防止程序卡死
{
break;
}
}
}
//判断邮箱是否有数据//
uint8_t MyCAN_ReceiveFlag(void)
{
if (CAN_MessagePending(CAN1, CAN_FIFO0) > 0) //检查FIFO0的队伍长度
{
return 1; //队伍0里有报文
}
return 0; //队伍0里没有报文
}
//接收报文封装函数//
void MyCAN_Receive(uint32_t *ID, uint8_t *Length, uint8_t *Data) //要定义输出参数,用指针实现,相当于函数多个返回值
{
CanRxMsg RxMessage; //当函数结束后RxMessage内存储的就是读出FIFO0队伍报文数据
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
if (RxMessage.IDE == CAN_Id_Standard) //判断报文格式
{
*ID = RxMessage.StdId;
}
else
{
*ID = RxMessage.ExtId;
}
if (RxMessage.RTR == CAN_RTR_Data) //判断是不是数据帧
{
*Length = RxMessage.DLC; //数据长度
for (uint8_t i = 0; i < *Length; i ++)
{
Data[i] = RxMessage.Data[i]; //将RxMessage中Data的值放到Data[i]
}
}
else
{
//...收到遥控帧
}
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
#include "MyCAN.h"
uint8_t KeyNum;
uint32_t TxID = 0x555; //发送报文数据 3个设备通信时,每个设备分别下载一次
uint8_t TxLength = 4;
uint8_t TxData[8] = {0x11, 0x22, 0x33, 0x44};
uint32_t RxID; //接收报文变量
uint8_t RxLength;
uint8_t RxData[8];
int main(void)
{
OLED_Init();
Key_Init();
MyCAN_Init(); //CAN初始化
OLED_ShowString(1, 1, "TxID:");
OLED_ShowHexNum(1, 6, TxID, 3);
OLED_ShowString(2, 1, "RxID:");
OLED_ShowString(3, 1, "Leng:");
OLED_ShowString(4, 1, "Data:");
while (1)
{
KeyNum = Key_GetNum();
if (KeyNum == 1)
{
TxData[0] ++;
TxData[1] ++;
TxData[2] ++;
TxData[3] ++;
MyCAN_Transmit(TxID, TxLength, TxData); //发送报文数据
}
if (MyCAN_ReceiveFlag()) //判断是否收到报文数据
{
MyCAN_Receive(&RxID, &RxLength, RxData); //接收报文数据,前面两个变量要使用地址进行传递
OLED_ShowHexNum(2, 6, RxID, 3);
OLED_ShowHexNum(3, 6, RxLength, 1);
OLED_ShowHexNum(4, 6, RxData[0], 2);
OLED_ShowHexNum(4, 9, RxData[1], 2);
OLED_ShowHexNum(4, 12, RxData[2], 2);
OLED_ShowHexNum(4, 15, RxData[3], 2);
}
}
}
作者:贪念空山