【STM32F103 外设】CAN 数据收发标准库实现

CAN 初始化

注意:虽然 CAN 总线属于串行异步半双工通信,通过 CAN_H 和 CAN_L 形成差分信号,但在进行单片机 GPIO 初始化时,Tx 必须设置为输出模式,Rx 必须设置为输入模式,这里的 Tx 和 Rx 是单片机和 CAN 收发器之间的通信,而非直接与总线设备的通信。
CAN 收发器负责显性电平 0 / 隐性电平 1 与差分信号之间的转化。

#include "stm32f10x.h"

#define CAN_Rx 					GPIO_Pin_11
#define CAN_Tx 					GPIO_Pin_12

/** @brief CAN 初始化
  * @params None
  * @return None
  */
void MyCAN_Init(void)
{
	/* 开启时钟 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	/* GPIO 配置 */
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = CAN_Rx;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;						//复用推挽输出,将 IO 控制权交由 CAN 外设
	GPIO_InitStructure.GPIO_Pin = CAN_Tx;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	/* CAN 配置 */
	CAN_InitTypeDef CAN_InitStructure;
	CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack;						//回环模式,单个设备自收发
	CAN_InitStructure.CAN_ABOM = ENABLE;								//使能离线自动管理
	CAN_InitStructure.CAN_AWUM = ENABLE;								//使能自动唤醒
	CAN_InitStructure.CAN_NART = DISABLE;								//开启自动重传(一直传输,直至成功)
	CAN_InitStructure.CAN_RFLM = DISABLE;								//接收 FIFO 不锁定(溢出后覆盖旧数据)
	CAN_InitStructure.CAN_TTCM = DISABLE;								//不启用时间戳功能
	CAN_InitStructure.CAN_TXFP = DISABLE;								//按照报文 ID 发送(使能时,先请求先发送)
	/* 设置波特率 5e5 bit per second = 1 /(tq * (1 + BS1 + BS2))*/
	CAN_InitStructure.CAN_SJW = CAN_SJW_4tq;	
	CAN_InitStructure.CAN_BS1 = CAN_BS1_5tq;
	CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;							//(1+5+3)tq
	CAN_InitStructure.CAN_Prescaler = 8;								// tq = 8 / 36M =(2 / 9M)s
																		//发送一位数据时间为(2e-6s)
	CAN_Init(CAN1, &CAN_InitStructure);
}

位时序图
波特率 = (1 / 正常的位时间)
= 1 / (tq + tBS1 + tBS2)
= 1 / (tq + 5tq + 3tq)
= 1 / 9tq
= 1 / 36tPCLK(对于STM32F103C8T6芯片,tPCLK2 = (1 / 36 000 000)s)
= 500 000 bps

使用位时序的目的是为了位同步,配置接收方采样点位于数据位中心附近
CAN的数据位同步方式
1、硬同步:发送方开始发送数据时,接收方在接收到数据起始下降沿时将位时序拨到SS段,与发送方保持一致
2、再同步:在再同步补偿宽度加长PBS1或缩短PBS2,实现同步

CAN 总线仲裁

CAN 总线仅有一对差分信号,当多个设备同时开始发送数据时,需要非破坏性仲裁机制确保一个时间只有一个设备操作总线。即根据 ID 号仲裁,ID 号小的先发送,ID 号大的进入接收状态,等待下次仲裁再请求发送。依靠线与特性和回读机制实现。

CAN 标识符过滤器配置(重难点)

  • STM32F103C8T6的CAN控制器为应用程序提供了14个位宽可变的、可配置的过滤器组(13~0)。
  • 每个过滤器组的位宽都可以独立配置,以满足应用程序的不同需求。根据位宽的不同,每个过滤器组可提供:
    • 1个32位过滤器,包括:STDID[10:0]、EXTID[17:0]、IDE和RTR位、保留位
    • 2个16位过滤器,包括:STDID[10:0]、IDE、RTR和EXTID[17:15]位(实际在16位标准模式下不起作用)
    • 过滤器可配置为,屏蔽位模式(只要求处在与屏蔽寄存器中为 1 的位置匹配)和标识符列表模式(只通过列表内 ID 设备)

过滤器组位宽设置

#define CAN_STD_ID  			0x123
#define CAN_EXT_ID 				(uint32_t)0x1800f001
/** @brief CAN 标识符过滤器配置
  * @params None
  * @return None
  */
void CAN_Filter_Init(void)
{
	/* CAN 过滤器配置 */
	CAN_FilterInitTypeDef CAN_FilterInitStructure;
	CAN_FilterInitStructure.CAN_FilterActivation = ENABLE; 				//启动过滤器
	CAN_FilterInitStructure.CAN_FilterNumber = 0;						//指定过滤器组编号(STM32F103 有 0~13 共 14 个过滤器组)
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;//存放在 FIFO0 中
	/* 配置 CAN 过滤器为 32 位列表模式 */
	/* 16 位列表模式 */
	/* 16 位掩码模式 */
	/* 32 位列表模式 */
	/* 32 位掩码模式 */
	CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;						//过滤器位宽为 32 位
	CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdList;							//ID 列表模式
																							//收到报文的标识符必须与过滤器的值完全相等才能通过
																							//屏蔽位模式,可以指定标识符的某些位为指定值时能通过
	/* 过滤标准数据帧 ID:0x123 */
	CAN_FilterInitStructure.CAN_FilterIdHigh = (CAN_STD_ID << 21); 							//设置 STDID 
	CAN_FilterInitStructure.CAN_FilterIdLow &= ~(0x01 << 2);						//设置 IDE 位为 0 ,不使用扩展 ID
	/* 在标识符列表模式下,屏蔽寄存器也被当作标识符寄存器用 */
	/* 过滤标准数据帧 ID:1800 f001*/
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh = ((CAN_EXT_ID<<3)&0xffff)>>16;			
	CAN_FilterInitStructure.CAN_FilterMaskIdLow = ((CAN_EXT_ID<<3)&0xffff) | (0x01 << 2);	//设置 IDE 位为 1 ,使用扩展 ID
	CAN_FilterInit(&CAN_FilterInitStructure);
}

标识符配置说明:本例中采用32位标识符列表模式。
0x123 = 0b0000 0000 0000 0000 0000 0[001 0010 0011] 为标准标识符(未超过11位,所以需要放在过滤器的31~21位),方括号中表示STDID
0x1800 f001 = 0b000[1 1000 0000 00][00 1111 0000 0000 0001] 为扩展标识符(超过11位,需要放在过滤器的31~3位),第一个方括号中的11位为 STDID,第二个方括号中的18位为 EXTID
从标识符过滤器配置结构体中可得,需要将整个32位的过滤器拆分为两个16位数据传入,而两个标识符均为32位,所以直接传入会默认强转为uint16_t,丢弃高16位。
标识符过滤器配置结构体

对于0x123,只要左移21位即可将STDID存入高16位寄存器中,低16位寄存器中只需要将IDE置0(默认为0),表示非扩展标识符即可。
对于0x1800 f001,若直接强转会导致高16位丢失,所以先左移3位取出扩展标识符的有效部分1 1000 0000 0000 1111 0000 0000 0001,随后右移16位使有效标识符的最高位对应强转后的最高位(因为强制转换会丢弃高16位),而后存入高16位寄存器中,有效标识符的剩余部分直接存入低16位寄存器并将IDE置1,表示扩展标识符即可。

CAN 发送数据

/** @brief CAN 发送数据(实现一)
  * @params 
  * 	@arg ID 数据标识 ID
  * 	@arg Length 数据长度(取值范围为1~8)
  * 	@arg TxData 存放数据的数组名称
  * @return None
  */
void CAN_Send_Data(uint32_t ID, uint8_t Length, uint8_t *TxData)
{
	CanTxMsg TxMessage;
	TxMessage.StdId = ID;													//标准标识符
	TxMessage.ExtId = ID;													//扩展标识符
	TxMessage.IDE = CAN_Id_Standard;										//标准 ID 有效
	TxMessage.RTR = CAN_RTR_Data;											//数据帧标志位
	TxMessage.DLC = Length;													//数据字节数目
	for (uint8_t i = 0; i < Length; i++) {TxMessage.Data[i] = TxData[i];}	//获取发送内容
	CAN_Transmit(CAN1, &TxMessage);
}

/** @brief CAN 发送数据(实现二)
  * @params TxMessage CAN 发送数据结构体指针
  * @return None
  */
void CAN_Send_Data(CanTxMsg *TxMessage)
{
	CAN_Transmit(CAN1, TxMessage);
}

CAN 接收数据

/** @brief CAN 接收数据(实现一)
  * @params 
  * 	@arg ID 数据标识指针
  * 	@arg Length 数据长度指针
  * 	@arg TxData 存放接收数据的数组
  * @return None
  * @note C 语言中不支持函数多返回值,采用指针传参获取函数返回值
  */
void CAN_Receive_Data(uint32_t *ID, uint8_t *Length, uint8_t *RxData)
{
	CanRxMsg RxMessage;
	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++) {RxData[i] = RxMessage.Data[i];}	//获取收到的内容
	}
	else
	{
		/* ... ... */
	}
}
/** @brief CAN 接收数据(实现二)
  * @params RxMessage CAN 接收数据结构体指针
  * @return None
  */
void CAN_Receive_Data(CanRxMsg *RxMessage)
{
	CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
}

CAN 总线环回测试(自发自收)示例

#include "stm32f10x.h"

#define CAN_Rx 					GPIO_Pin_11
#define CAN_Tx 					GPIO_Pin_12
#define CAN_STD_ID  			0x555

void MyCAN_Init(void)
{
	/* 开启时钟 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	/* GPIO 配置 */
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = CAN_Rx;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;						//复用推挽输出,将 IO 控制权交由 CAN 外设
	GPIO_InitStructure.GPIO_Pin = CAN_Tx;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	/* CAN 配置 */
	CAN_InitTypeDef CAN_InitStructure;
	CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack;						//回环模式,单个设备自收发
	CAN_InitStructure.CAN_ABOM = ENABLE;								//使能离线自动管理
	CAN_InitStructure.CAN_AWUM = ENABLE;								//使能自动唤醒
	CAN_InitStructure.CAN_NART = DISABLE;								//开启自动重传(一直传输,直至成功)
	CAN_InitStructure.CAN_RFLM = DISABLE;								//接收 FIFO 不锁定(溢出后覆盖旧数据)
	CAN_InitStructure.CAN_TTCM = DISABLE;								//不启用时间戳功能
	CAN_InitStructure.CAN_TXFP = DISABLE;								//按照报文 ID 发送(使能时,先请求先发送)
	/* 设置波特率 5e5 bit per second = 1 /(tq * (1 + BS1 + BS2))*/
	CAN_InitStructure.CAN_SJW = CAN_SJW_4tq;	
	CAN_InitStructure.CAN_BS1 = CAN_BS1_5tq;
	CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;							//(1+5+3)tq
	CAN_InitStructure.CAN_Prescaler = 8;
	CAN_Init(CAN1, &CAN_InitStructure);
	
	/* CAN 过滤器配置 */
	CAN_FilterInitTypeDef CAN_FilterInitStructure;
	CAN_FilterInitStructure.CAN_FilterNumber = 0;						//指定过滤器组编号(STM32F103 有 0~13 共 14 个过滤器组)
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;//存放在 FIFO0 中
	CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;	//过滤器位宽为 32 位
	CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdList;		//ID 列表模式																		
	CAN_FilterInitStructure.CAN_FilterIdHigh = (CAN_STD_ID << 21); 		//设置 STDID 
	CAN_FilterInitStructure.CAN_FilterIdLow &= ~(0x01 << 2);			//设置 IDE 位为 0 ,不使用扩展 ID
	CAN_FilterInitStructure.CAN_FilterActivation = ENABLE; 				//启动过滤器

	CAN_FilterInit(&CAN_FilterInitStructure);
}

void MY_CAN_Transmit(uint32_t ID, uint8_t Length, uint8_t *TxData)
{
	CanTxMsg TxMessage;
	TxMessage.StdId = ID;													//标准标识符
	TxMessage.ExtId = ID;													//扩展标识符
	TxMessage.IDE = CAN_Id_Standard;										//标准 ID 有效
	TxMessage.RTR = CAN_RTR_Data;											//数据帧标志位
	TxMessage.DLC = Length;													//数据字节数目
	for (uint8_t i = 0; i < Length; i++) {TxMessage.Data[i] = TxData[i];}	//获取发送内容
	uint8_t TransmitMailbox = CAN_Transmit(CAN1, &TxMessage);
	
	uint32_t err_time = 0;
	while (CAN_Transmit(CAN1, &TransmitMailbox) != CAN_TxStatus_OK)			//等待数据发送完成
	{
		err_time += 1;
		if (err_time > 100000)
		{
			/* 可在此处添加错误提示代码 */
			break;
		}
	}		
}
/* 查询接收 */
void MY_CAN_Receive(uint32_t *ID, uint8_t *Length, uint8_t *RxData)
{
	/* 查询接收FIFO中是否有数据 */
	if (CAN_MessagePending(CAN1, CAN_FIFO0) > 0)
	{
		CanRxMsg *RxMessage
		CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
		/* ID判断 */
		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++) {RxData[i] = RxMessage.Data[i];}	//获取收到的内容
		}
	}
}

TODO:CAN 中断接收

欢迎批评指正!!!

文中图片来源:STM32F10xxx参考手册
参考教程:CAN总线入门教程-全面细致 面包板教学 多机通信

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值