STM32-CAN

发布于:2025-07-19 ⋅ 阅读:(10) ⋅ 点赞:(0)

CAN通信:异步通信;半双工;差分信号

闭环总线网络:高速短距离;40m;1MPBS 

开环总线网络:匹配电阻区别;低速远距离;1KM;125KBPS 

 在理想情况下,节点不受限制;差分信号两根线的电压差来代表0,1

显性优先(0);共用总线时,同一时间只有一个节点发送其余节点接收 

 

 SS段:同步段,节点与总线时序同步1Tq

RTS段:传播时间段(1-8Tq)

PBS1段:1-8Tq

PBS2段:用于检测边沿阶位误差2-8Tq

重新同步PBS1+;PBS2-;的长度为重新同步补偿的宽度:SJW (软件可限制位数)

相位超前:PBS1+ 

相位滞后:PBS2- 

 

谁先出现1谁接收

 

 SOF:帧起始0

ID:

RTR:0数据帧;1远程请求帧

IDE:标准数据0;扩展数据1

r0:保留位0

DLC:数据长度4位(0~8)

数据段:原始数据;最高字节在前0-8个字节

CRC段:校验位15位;发送接收CRC码不同向发送方返回错误信息重新发送

CRC界定符:1;与ACK间隔

ACK槽:1

ACK段:0

ACK界定符:隔开

帧结束:7个1

 

  • STM32 CAN1
  • 发送邮箱:3个,有4个寄存器

  • 接受邮箱:缓存6个,锁定模式下;FIFO溢出丢弃新数据保留原数据;非锁定模式下,新数据覆盖原数据

  • 验收筛选器:两个寄存器,用于检查使用的ID

 ID和掩码

 

掩码为1时,筛选的掩码必须要跟ID码一致 

  • CAN2:要使能CAN1 

4种工作模式:

波特率计算:

 

 软件流程:

  • 使能CAN时钟,将对应的引脚复用映射为CAN功能APB1
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); //打开CAN1时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   //PA端口时钟打开
  • 设置CAN工作模式;波特率等
typedef struct
{
  uint16_t CAN_Prescaler;   /*!< 指定时间量子长度,取值范围为1到1024 */
  
  uint8_t CAN_Mode;         /*!< 指定CAN工作模式,此参数可以是@ref CAN_operating_mode中的值 */

  uint8_t CAN_SJW;          /*!< 指定CAN硬件在重新同步时允许延长或缩短位的最大时间量子数,
                               此参数可以是@ref CAN_synchronisation_jump_width中的值 */

  uint8_t CAN_BS1;          /*!< 指定位段1的时间量子数,此参数可以是@ref         CAN_time_quantum_in_bit_segment_1中的值 */

  uint8_t CAN_BS2;          /*!< 指定位段2的时间量子数,此参数可以是@ref CAN_time_quantum_in_bit_segment_2中的值 */
  
  FunctionalState CAN_TTCM; /*!< 启用或禁用时间触发通信模式,此参数可以设置为ENABLE或DISABLE */
  
  FunctionalState CAN_ABOM; /*!< 启用或禁用自动离线管理,此参数可以设置为ENABLE或DISABLE */

  FunctionalState CAN_AWUM; /*!< 启用或禁用自动唤醒模式,此参数可以设置为ENABLE或DISABLE */

  FunctionalState CAN_NART; /*!< 启用或禁用无自动重传模式,此参数可以设置为ENABLE或DISABLE */

  FunctionalState CAN_RFLM; /*!< 启用或禁用接收FIFO锁定模式,此参数可以设置为ENABLE或DISABLE */

  FunctionalState CAN_TXFP; /*!< 启用或禁用发送FIFO优先级,此参数可以设置为ENABLE或DISABLE */
} CAN_InitTypeDef;
  • 设置CAN筛选器
typedef struct
{
  uint16_t CAN_FilterIdHigh;         /*!< 指定过滤器识别号(32位配置时为高16位,16位配置时为第一个ID)
                                           此参数取值范围为0x0000至0xFFFF */

  uint16_t CAN_FilterIdLow;          /*!< 指定过滤器识别号(32位配置时为低16位,16位配置时为第二个ID)
                                           此参数取值范围为0x0000至0xFFFF */

  uint16_t CAN_FilterMaskIdHigh;     /*!< 根据工作模式指定过滤器掩码号或识别号(32位配置时为高16位,
                                           16位配置时为第一个掩码/ID)
                                           此参数取值范围为0x0000至0xFFFF */

  uint16_t CAN_FilterMaskIdLow;      /*!< 根据工作模式指定过滤器掩码号或识别号(32位配置时为低16位,
                                           16位配置时为第二个掩码/ID)
                                           此参数取值范围为0x0000至0xFFFF */

  uint16_t CAN_FilterFIFOAssignment; /*!< 指定将分配给该过滤器的FIFO(0或1)
                                           此参数可以是@ref CAN_filter_FIFO中的值 */
  
  uint8_t CAN_FilterNumber;          /*!< 指定要初始化的过滤器编号,范围从0到13 */

  uint8_t CAN_FilterMode;            /*!< 指定要初始化的过滤器模式
                                           此参数可以是@ref CAN_filter_mode中的值 */

  uint8_t CAN_FilterScale;           /*!< 指定过滤器尺度
                                           此参数可以是@ref CAN_filter_scale中的值 */

  FunctionalState CAN_FilterActivation; /*!< 启用或禁用过滤器
                                           此参数可以设置为ENABLE或DISABLE */
} CAN_FilterInitTypeDef;
  • 选择CAN中断类型,开启中断
/**
  * @brief  配置CAN中断使能
  * @param  CANx: 指向CAN外设结构体的指针(CAN1或CAN2)
  * @param  CAN_IT: 指定要配置的CAN中断源
  *         可使用以下参数的组合:
  *           - CAN_IT_TME: 发送邮箱空中断
  *           - CAN_IT_FMP0: FIFO0消息挂起中断
  *           - CAN_IT_FMP1: FIFO1消息挂起中断
  *           - CAN_IT_FF0: FIFO0溢出中断
  *           - CAN_IT_FF1: FIFO1溢出中断
  *           - CAN_IT_FOV0: FIFO0满中断
  *           - CAN_IT_FOV1: FIFO1满中断
  *           - CAN_IT_EWG: 错误警告中断
  *           - CAN_IT_EPV: 错误被动中断
  *           - CAN_IT_BOF: 总线关闭中断
  *           - CAN_IT_LEC: 错误码变化中断
  *           - CAN_IT_WKU: 唤醒中断
  *           - CAN_IT_SLK: 睡眠中断
  * @param  NewState: 新状态(ENABLE或DISABLE)
  * @retval 无
  */
void CAN_ITConfig(CAN_TypeDef* CANx, uint32_t CAN_IT, FunctionalState NewState)
  • CAN发送和接收消息

发送:

/**
  * @brief  向CAN总线发送消息
  * @param  CANx: 指向CAN外设结构体的指针(CAN1或CAN2)
  * @param  TxMessage: 指向包含待发送消息参数的CanTxMsg结构体指针
  *         结构体成员需预先配置:
  *           - StdId/ExtId: 标准/扩展标识符
  *           - IDE: 标识符类型(标准/扩展)
  *           - RTR: 帧类型(数据帧/远程帧)
  *           - DLC: 数据长度(0-8字节)
  *           - Data[]: 待发送数据(若为远程帧则忽略)
  * @retval uint8_t: 发送邮箱状态
  *           - 0x00: 无空闲邮箱,发送失败
  *           - 0x01: 使用邮箱0,发送请求已提交
  *           - 0x02: 使用邮箱1,发送请求已提交
  *           - 0x03: 使用邮箱2,发送请求已提交
  */
uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage)
typedef struct
{
  uint32_t StdId;  /*!< 指定标准标识符,取值范围为0到0x7FF */

  uint32_t ExtId;  /*!< 指定扩展标识符,取值范围为0到0x1FFFFFFF */

  uint8_t IDE;     /*!< 指定待发送消息的标识符类型,此参数可以是@ref CAN_identifier_type中的值
                       (注:IDE=0表示使用标准标识符StdId,IDE=1表示使用扩展标识符ExtId) */

  uint8_t RTR;     /*!< 指定待发送消息的帧类型,此参数可以是@ref CAN_remote_transmission_request中的值
                       (注:RTR=0表示数据帧,用于发送数据;RTR=1表示远程帧,用于请求数据) */

  uint8_t DLC;     /*!< 指定待发送帧的数据长度,取值范围为0到8(单位:字节) */

  uint8_t Data[8]; /*!< 包含待发送的数据,数组中每个元素的取值范围为0到0xFF */
} CanTxMsg;

接收:

/**
  * @brief  从指定CAN FIFO接收消息并存储到接收结构体中
  * @param  CANx: 指向CAN外设结构体的指针(CAN1或CAN2)
  * @param  FIFONumber: 指定要读取的FIFO编号
  *         此参数可以是以下值之一:
  *           - CAN_FIFO0: 读取FIFO 0中的消息
  *           - CAN_FIFO1: 读取FIFO 1中的消息
  * @param  RxMessage: 指向接收消息结构体的指针,用于存储接收到的CAN消息
  *         函数会填充以下成员:
  *           - StdId/ExtId: 接收到的标准/扩展标识符
  *           - IDE: 标识符类型(标准/扩展)
  *           - RTR: 帧类型(数据帧/远程帧)
  *           - DLC: 数据长度(0-8字节)
  *           - Data[]: 接收到的数据(若为远程帧则Data[]内容未定义)
  *           - Timestamp: 接收时间戳(需启用时间戳功能)
  *           - FMI: 过滤器匹配索引(标识哪个过滤器匹配了该消息)
  * @retval 无
  */
void CAN_Receive(CAN_TypeDef* CANx, uint8_t FIFONumber, CanRxMsg* RxMessage)
/**
  * @brief  获取指定CAN FIFO中待处理的消息数量
  * @param  CANx: 指向CAN外设结构体的指针(CAN1或CAN2)
  * @param  FIFONumber: 指定FIFO编号
  *         此参数可以是以下值之一:
  *           - CAN_FIFO0: FIFO 0
  *           - CAN_FIFO1: FIFO 1
  * @retval uint8_t: 待处理的消息数量
  *         返回值范围:0-3(FIFO深度为3级)
  */
uint8_t CAN_MessagePending(CAN_TypeDef* CANx, uint8_t FIFONumber)
  • CAN状态获取(检测标志位)

代码如下:

#include "can.h"
#include "usart.h"

//CAN初始化
//tsjw:重新同步跳跃时间单元.范围:CAN_SJW_1tq~ CAN_SJW_4tq
//tbs2:时间段2的时间单元.   范围:CAN_BS2_1tq~CAN_BS2_8tq;
//tbs1:时间段1的时间单元.   范围:CAN_BS1_1tq ~CAN_BS1_16tq
//brp :波特率分频器.范围:1~1024;  tq=(brp)*tpclk1
//波特率=Fpclk1/((tbs1+tbs2+1)*brp);
//mode:CAN_Mode_Normal,普通模式;CAN_Mode_LoopBack,回环模式;
//Fpclk1的时钟在初始化的时候设置为36M,如果设置CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_LoopBack);
//则波特率为:36M/((8+9+1)*4)=500Kbps
//返回值:0,初始化OK;
//    其他,初始化失败;
void CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	CAN_InitTypeDef        CAN_InitStructure;
	CAN_FilterInitTypeDef  CAN_FilterInitStructure;
	
#if CAN_RX0_INT_ENABLE 
	NVIC_InitTypeDef  		NVIC_InitStructure;
#endif
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); //打开CAN1时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   //PA端口时钟打开
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;		//PA11	   
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; 	 //上拉输入模式
	GPIO_Init(GPIOA, &GPIO_InitStructure);	

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;		//PA12	   
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 	 //复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	 //IO口速度为50MHz
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	//CAN单元设置
   	CAN_InitStructure.CAN_TTCM=DISABLE;	//非时间触发通信模式   
  	CAN_InitStructure.CAN_ABOM=DISABLE;	//软件自动离线管理	  
  	CAN_InitStructure.CAN_AWUM=DISABLE;//睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位)
  	CAN_InitStructure.CAN_NART=ENABLE;	//使用报文自动传送 
  	CAN_InitStructure.CAN_RFLM=DISABLE;	//报文不锁定,新的覆盖旧的  
  	CAN_InitStructure.CAN_TXFP=DISABLE;	//优先级由报文标识符决定 
  	CAN_InitStructure.CAN_Mode= mode;	 //模式设置 
  	CAN_InitStructure.CAN_SJW=tsjw;	//重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位 CAN_SJW_1tq~CAN_SJW_4tq
  	CAN_InitStructure.CAN_BS1=tbs1; //Tbs1范围CAN_BS1_1tq ~CAN_BS1_16tq
  	CAN_InitStructure.CAN_BS2=tbs2;//Tbs2范围CAN_BS2_1tq ~	CAN_BS2_8tq
  	CAN_InitStructure.CAN_Prescaler=brp;  //分频系数(Fdiv)为brp+1	
  	CAN_Init(CAN1, &CAN_InitStructure);   // 初始化CAN1
	
	//配置过滤器
 	CAN_FilterInitStructure.CAN_FilterNumber=0;	  //过滤器0
  	CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask; 
  	CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit; //32位 
  	CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000;////32位ID
  	CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;
  	CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;//32位MASK
  	CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
   	CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;//过滤器0关联到FIFO0
  	CAN_FilterInitStructure.CAN_FilterActivation=ENABLE; //激活过滤器0
  	CAN_FilterInit(&CAN_FilterInitStructure);//滤波器初始化
	
#if CAN_RX0_INT_ENABLE 
	CAN_ITConfig(CAN1,CAN_IT_FMP0,ENABLE);				//FIFO0消息挂号中断允许.		    

	NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;     // 主优先级为1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;            // 次优先级为0
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
#endif
}

#if CAN_RX0_INT_ENABLE	//使能RX0中断
//中断服务函数			    
void USB_LP_CAN1_RX0_IRQHandler(void)
{
  	CanRxMsg RxMessage;
	int i=0;
    CAN_Receive(CAN1, 0, &RxMessage);
	for(i=0;i<8;i++)
	printf("rxbuf[%d]:%d\r\n",i,RxMessage.Data[i]);
}
#endif

//can发送一组数据(固定格式:ID为0X12,标准帧,数据帧)	
//len:数据长度(最大为8)				     
//msg:数据指针,最大为8个字节.
//返回值:0,成功;
//		 其他,失败;
u8 CAN_Send_Msg(u8* msg,u8 len)
{	
	u8 mbox;
	u16 i=0;
	CanTxMsg TxMessage;
	TxMessage.StdId=0x12;	 // 标准标识符为0
	TxMessage.ExtId=0x12;	 // 设置扩展标示符(29位)
	TxMessage.IDE=0;		  // 使用扩展标识符
	TxMessage.RTR=0;		  // 消息类型为数据帧,一帧8位
	TxMessage.DLC=len;							 // 发送两帧信息
	for(i=0;i<len;i++)
		TxMessage.Data[i]=msg[i];				 // 第一帧信息          
	mbox= CAN_Transmit(CAN1, &TxMessage);   
	i=0;
	while((CAN_TransmitStatus(CAN1, mbox)==CAN_TxStatus_Failed)&&(i<0XFFF))i++;	//等待发送结束
	if(i>=0XFFF)return 1;
	return 0;		
}

//can口接收数据查询
//buf:数据缓存区;	 
//返回值:0,无数据被收到;
//		 其他,接收的数据长度;
u8 CAN_Receive_Msg(u8 *buf)
{		   		   
 	u32 i;
	CanRxMsg RxMessage;
    if( CAN_MessagePending(CAN1,CAN_FIFO0)==0)return 0;		//没有接收到数据,直接退出 
    CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);//读取数据	
    for(i=0;i<RxMessage.DLC;i++)
    buf[i]=RxMessage.Data[i];  
	return RxMessage.DLC;	
}


主函数代码(KEY_UP_PRESS按下切换模式正常模式和回环模式;KEY1_PRESS按下,发送数据和接收数据)

#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "key.h"
#include "can.h"


int main()
{
	u8 i=0,j=0;
	u8 key;
	u8 mode=0;
	u8 res;
	u8 tbuf[8];
	u8 rbuf[8];
	
	SysTick_Init(72);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  //中断优先级分组 分2组
	LED_Init();
	USART1_Init(115200);
	KEY_Init();
	CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_Normal);//500Kbps波特率
	
	while(1)
	{
		key=KEY_Scan(0);
		if(key==KEY_UP_PRESS)  //模式切换
		{
			mode=!mode;
			CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,mode);
			if(mode==0)
			{
				printf("Normal Mode\r\n");
			}
			else
			{
				printf("LoopBack Mode\r\n");
			}
		}
		if(key==KEY1_PRESS)  //发送数据
		{
			for(j=0;j<8;j++)
			{
				tbuf[j]=j;
			}
			res=CAN_Send_Msg(tbuf,8);
			if(res)
			{
				printf("Send Failed!\r\n");
			}
			else
			{
				printf("发送数据:");
				for(j=0;j<8;j++)
				{
					printf("%X  ",tbuf[j]);
				}
				printf("\r\n");
			}
		}
		res=CAN_Receive_Msg(rbuf);
		if(res)
		{	
			printf("接收数据:");
			for(j=0;j<8;j++)
			{
				printf("%X  ",rbuf[j]);
			}
			printf("\r\n");
		}
		
		i++;
		if(i%20==0)
		{
			LED1=!LED1;
		}
		delay_ms(10);
	}
}


网站公告

今日签到

点亮在社区的每一天
去签到