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);
		}
	}
}

作者:贪念空山

物联沃分享整理
物联沃-IOTWORD物联网 » CAN总线学习笔记—STM32篇

发表回复