CAN总线协议的理解以及移植stm32代码并使用

news/2024/6/3 17:04:32 标签: stm32, 嵌入式硬件, 单片机

什么是CAN总线协议

是一种异步半双工的通讯协议,只有CAN_High与CAN_Low两条信号线。
有两种连接形式:闭环总线(高速)和开环总线(远距离)

他使用的是一种差分信号来传输电信号
所谓差分信号就是两条信号线电平之差,来传输电信号1/0,

该方式优点是抗干扰能力强、可以远距离传输,因为当信号线过长的时候电阻累积过大,使得传输到的电信号不准确,而差分信号是使用两条信号线电平差,两条信号线只要长度相似信号误差都不会很大。

电位差为0时为逻辑1,电位差>=2.0时为逻辑0。

CAN协议的协议层原理

协议层内规定了一个数据位有四个段,分别为:SS段、PTS段、PBS1段、PBS2段
1Tq为1个时基单元
在这里插入图片描述
SS段:是同步段,若通讯节点检测到总线上信号的跳变沿被包含在 SS 段的范围之内,则表示节点与总线的时序是同步的,当节点与总线同步时,采样点采集到的总线电平即可被确定为该位的电平。SS 段的大小固定为 1Tq。

PTS段:是传播时间段,这个时间段是用于补偿网络的物理延时时间。是总线上输入比较器延时和输出驱动器延时总和的两倍。PTS 段的大小可以为 1~8Tq。

PBS1段:PBS1 译为相位缓冲段,补偿SS段的误差,它的时间长度在重新同步的时候可以加长,如下图相位超前,既ss段比预计的时间超期一个时间基准,可以通过PBS1段校准。

PBS2段:这是另一个相位缓冲段,它的时间长度在重新同步时可以缩短,补偿整个数据对齐。
在这里插入图片描述

CAN报文

当节点1与节点2同时发送报文时,总线电平会有优先级,按照ID优先级选择其1
当节点1或节点2发现自身电平和总线电平不同时会停止发送报文,总线显性电平为0,有1和0时总线电平取0

帧种类及用途:

在这里插入图片描述

数据帧的结构

在这里插入图片描述
仲裁段:0为最高优先级,用于总线判断优先级
控制段:

  • RTR位为0表示数据帧(发送数据的),为1表示遥控帧(请求数据的)
  • IDE位为0表示标准格式,为1表示为扩展格式
  • DLE表示本报文的数据有多少个字节,4个数据位,不过只能写入0~8
  • SRR 位 ,只存在于扩展格式,它用于替代标准格式中的 RTR位。由于扩展帧中的 SRR 位为隐性位,RTR 在数据帧为显性位,所以在两个 ID 相同的标准格式报文与扩展格式报文中,标准格式的优先级较高。

CRC 段:为了保证报文的正确传输,CAN 的报文包含了一段 15 位的 CRC 校验码,一旦接收节点算出的CRC 码跟接收到的 CRC 码不同,则它会向发送节点反馈出错信息,利用错误帧请求它重新发送。CRC 部分的计算一般由 CAN 控制器硬件完成,出错时的处理则由软件控制最大重发数。在 CRC 校验码之后,有一个 CRC 界定符,它为隐性位,主要作用是把 CRC 校验码与后面的 ACK段间隔起来。

ACK 段:ACK 段包括一个 ACK 槽位,和 ACK 界定符位。类似 I2C 总线,在 ACK 槽位中,发送节点发送的是隐性位,而接收节点则在这一位中发送显性位以示应答。在 ACK 槽和帧结束之间由 ACK 界定符间隔开。

STM32的CAN外设

stm32f103c8芯片中带有CAN的控制器,但不带CAN的收发器,所以如果想用最小系统板进行CAN外设实验,需要自己连接CAN的收发器。

在这里插入图片描述

CAN内部框图剖析

有一下五个主要部分

  • CAN控制内核
  • CAN发送邮箱
  • CAN接收fifo
  • 验收筛选器
  • 整体控制逻辑

stm32CAN_55">stm32的CAN外设寄存器

主要使用的功能:

  • DBF调试冻结功能:可设置CAN处于工作状态或者禁止收发状态,静止收发时仍可访问接收FIFO的数据
  • TTCM:生成时间戳,在CAN_RDTxR和CAN_TDTxR中保存
  • ABOM:自动离线管理,在节点检测到它发送错误或者接收错误超过一定值时会自动进入离线状态。
  • AWUM:自动唤醒,如果软件进入CAN外设的低功耗模式,如果使能了该功能,当CAN总线检测到活动时自动换醒。
  • NART:自动重传,报文发送失败会自动重传至成功为止
  • RFLM:锁定模式,使能该模式的话,只有6个接收FIFO如果6个满了还未处理则第7个来时不接入FIFO,否则会覆盖第一个FIFO
  • TXFP:报文发送优先级判断方法:ID优先级/时序优先级

寄存器

工作模式寄存器CAN_BTR

寄存器的31位30位分别控制两个模式在这里插入图片描述
31位:是否静默
30位:是否回环
23-20位:TS2,表示TSS2占用多少个时间单元,TSS2=Tq*(TS2+1)
19-15位:TS1,与上同理
25-24位:SJW,定义每次可活动增减TS1与TS2数据位的最大值(调整时序时最大增减的时间单元个数)
9-0位:BRP,波特率分频器Tq=(BRP+1)*tPCLK

其他寄存器

  • 标识符寄存器CAN_ITxR:存储发送报文的ID、扩展ID,IDE位和RTR位,其中的TMIDxR_TxRQ位置1即可以发送数据
  • 数据长度控制寄存器CAN_TDTxR:存储待发送报文的DLC段
  • 低位数据寄存器CAN_TDLxR:存储发送报文的Date0~Date3
  • 高位数据寄存器CAN_TDHxR:存储发送报文的Date4~Date7

CAN的接收邮箱

每个CAN有两个接收FIFO邮箱,每个邮箱有3级
两个FIFO接收邮箱公用28个筛选器组(f103是14个、f105是28个)
Alt

掩码模式

一个筛选器有两个寄存器,在上图的掩码映射中,写入1就代表ID寄存器中的这一位需要筛选,如果全写入0,即不筛选。

stm32CAN_87">stm32的CAN代码移植使用

在hal_can.h文件中

#define GPIO_CAN_RX_Pin                 GPIO_Pin_8
#define GPIO_CAN_TX_Pin                 GPIO_Pin_9

初始化CAN的GPIO重定义引脚

/****************************************************************************
*@*名称 : hal_CAN_GPIO_Config
*@*功能 : CAN的引脚口(PB8R/9T)
*@*形参 : 无
*@*返回值 : 无
****************************************************************************/
static void hal_CAN_GPIO_Config(void)
{
	GPIO_InitTypeDef  GPIO_InitStruct_CAN_TX;
	GPIO_InitTypeDef  GPIO_InitStruct_CAN_RX;
	

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap1_CAN1,ENABLE);
	
	GPIO_InitStruct_CAN_TX.GPIO_Pin = GPIO_CAN_TX_Pin ;
	GPIO_InitStruct_CAN_TX.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct_CAN_TX.GPIO_Speed = GPIO_Speed_50MHz;	
	
	GPIO_Init(GPIOB,&GPIO_InitStruct_CAN_TX);
	
	GPIO_InitStruct_CAN_RX.GPIO_Pin = GPIO_CAN_RX_Pin ;
	GPIO_InitStruct_CAN_RX.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStruct_CAN_RX.GPIO_Speed = GPIO_Speed_50MHz;	
	
	GPIO_Init(GPIOB,&GPIO_InitStruct_CAN_RX);
}

初始化CAN的模式

这里需要提一下,因为核心板没有CAN的接收器,如果要验证功能,要使用回环模式

/****************************************************************************
*@*名称 : hal_CAN_Mode_Config
*@*功能 : 定义CAN模块的模式
*@*形参 : 无
*@*返回值 : 无
****************************************************************************/
static void hal_CAN_Mode_Config(void)
{
	CAN_InitTypeDef	GAN_InitStruct_Mode;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);

	/*CAN寄存器初始化*/
	CAN_DeInit(CAN1);
	CAN_StructInit(&GAN_InitStruct_Mode);
	
	GAN_InitStruct_Mode.CAN_Mode = CAN_Mode_Normal; //普通工作模式,CAN_Mode_LoopBack为回环模式
	GAN_InitStruct_Mode.CAN_SJW = CAN_SJW_1tq; //可活动时间单元1tq
	GAN_InitStruct_Mode.CAN_BS1 = CAN_BS1_5tq;
	GAN_InitStruct_Mode.CAN_BS2 = CAN_BS2_3tq;
	/*CAN单元初始化*/
	GAN_InitStruct_Mode.CAN_TTCM=DISABLE;			   //MCR-TTCM  关闭时间触发通信模式使能
	GAN_InitStruct_Mode.CAN_ABOM=ENABLE;			   //MCR-ABOM  自动离线管理 
	GAN_InitStruct_Mode.CAN_AWUM=ENABLE;			   //MCR-AWUM  使用自动唤醒模式
	GAN_InitStruct_Mode.CAN_NART=DISABLE;			   //MCR-NART  禁止报文自动重传	  DISABLE-自动重传
	GAN_InitStruct_Mode.CAN_RFLM=DISABLE;			   //MCR-RFLM  接收FIFO 锁定模式  DISABLE-溢出时新报文会覆盖原有报文  
	GAN_InitStruct_Mode.CAN_TXFP=DISABLE;			   //MCR-TXFP  发送FIFO优先级 DISABLE-优先级取决于报文标示符 
	
	/* CAN Baudrate = 1 MBps (1MBps已为stm32的CAN最高速率) (CAN 时钟频率为 APB1 = 36 MHz) */
	GAN_InitStruct_Mode.CAN_Prescaler =4;		   BTR-BRP 波特率分频器  定义了时间单元的时间长度 36/(1+5+3)/4=1 Mbps
	
	CAN_Init(CAN1,&GAN_InitStruct_Mode)
}

初始化筛选器

/****************************************************************************
*@*名称 : hal_CAN_Filter_Config
*@*功能 : 初始化CAN模块的过滤器
*@*形参 : 无
*@*返回值 : 无
****************************************************************************/
static void hal_CAN_Filter_Config(void)
{
	CAN_FilterInitTypeDef GAN_InitStruct_Filter0;
	CAN_FilterInitTypeDef GAN_InitStruct_Filter1;
	
	GAN_InitStruct_Filter0.CAN_FilterActivation = ENABLE;			//使能筛选器
	GAN_InitStruct_Filter0.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0 ;				//筛选器被关联到FIFO0
	GAN_InitStruct_Filter0.CAN_FilterIdHigh = (((CAN_TargetID<<3)|CAN_Id_Extended|CAN_RTR_Data))>>16;
	GAN_InitStruct_Filter0.CAN_FilterIdLow = ((CAN_TargetID<<3)|CAN_Id_Extended|CAN_RTR_Data);
	GAN_InitStruct_Filter0.CAN_FilterMaskIdHigh = 0xFFFF;			//筛选器高16位每位必须匹配
	GAN_InitStruct_Filter0.CAN_FilterMaskIdLow= 0xFF00;			//筛选器低16位每位必须匹配
	GAN_InitStruct_Filter0.CAN_FilterMode = CAN_FilterMode_IdMask;		//工作在掩码模式
	GAN_InitStruct_Filter0.CAN_FilterNumber = 0;
	GAN_InitStruct_Filter0.CAN_FilterScale = CAN_FilterScale_32bit;	//筛选器位宽为单个32位。
	
	CAN_FilterInit(&GAN_InitStruct_Filter0);
	/*CAN通信中断使能*/
	
	
	GAN_InitStruct_Filter1.CAN_FilterActivation = ENABLE;			//使能筛选器
	GAN_InitStruct_Filter1.CAN_FilterFIFOAssignment = CAN_Filter_FIFO1 ;				//筛选器被关联到FIFO1
	GAN_InitStruct_Filter1.CAN_FilterIdHigh = ((CAN_FIFO1_ID<<3)|CAN_Id_Extended|CAN_RTR_Data)>>16;
	GAN_InitStruct_Filter1.CAN_FilterIdLow = ((CAN_FIFO1_ID<<3)|CAN_Id_Extended|CAN_RTR_Data);
	GAN_InitStruct_Filter1.CAN_FilterMaskIdHigh = 0xFFFF;			//筛选器高16位每位必须匹配
	GAN_InitStruct_Filter1.CAN_FilterMaskIdLow= 0xFF00;			//筛选器低16位每位必须匹配
	GAN_InitStruct_Filter1.CAN_FilterMode = CAN_FilterMode_IdMask;		//工作在掩码模式
	GAN_InitStruct_Filter1.CAN_FilterNumber = 2;
	GAN_InitStruct_Filter1.CAN_FilterScale = CAN_FilterScale_32bit;	//筛选器位宽为单个32位。
	
	CAN_FilterInit(&GAN_InitStruct_Filter1);
	/*CAN通信中断使能*/
	CAN_ITConfig(CAN1, CAN_IT_FMP1, ENABLE);
	CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);
}

这里我打开了两个接收FIFO
打开两个接收FIFO时需要注意:
该筛选器关联到的FIFO:GAN_InitStruct_Filter1.CAN_FilterFIFOAssignment
筛选器编号:GAN_InitStruct_Filter1.CAN_FilterNumber
这里不要配置好了一个,另一个就直接复制结构体导致错误了。

筛选器掩码配置要注意:因为低3位是配置RTR的标志位,高29位才是掩码ID配置,所以一开始将掩码ID左移3位,然后在或上IDE 位标志“宏 CAN_ID_EXT”以及RTR 位标
“宏 CAN_RTR_DATA”然后再赋值入筛选器的高八位和低八位

GAN_InitStruct_Filter0.CAN_FilterIdHigh = (((CAN_TargetID<<3)|CAN_Id_Extended|CAN_RTR_Data))>>16;
GAN_InitStruct_Filter0.CAN_FilterIdLow = ((CAN_TargetID<<3)|CAN_Id_Extended|CAN_RTR_Data);
GAN_InitStruct_Filter0.CAN_FilterMaskIdHigh = 0xFFFF; //筛选器高16位每位必须匹配
GAN_InitStruct_Filter0.CAN_FilterMaskIdLow= 0xFF00; //筛选器低16位每位必须匹配

初始化CAN的外设中断NVIC

/****************************************************************************
*@*名称 : hal_CAN_NVIC_Config
*@*功能 : 初始化CAN模块的中断
*@*形参 : 无
*@*返回值 : 无
****************************************************************************/
static void hal_CAN_NVIC_Config(void)
{
	  NVIC_InitTypeDef NVIC_InitStructure;
    /* Configure one bit for preemption priority */
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    /*中断设置*/
    NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;	   //CAN1 RX0中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;		   //抢占优先级0
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;			   //子优先级为0
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
	
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    /*中断设置*/
    NVIC_InitStructure.NVIC_IRQChannel = CAN1_RX1_IRQn;	   //CAN1 RX0中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;		   //抢占优先级0
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			   //子优先级为1
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

这里要注意,想要打开两个FIFO的中断是有区别的
FIFO0的中断通道名称是:USB_LP_CAN1_RX0_IRQn
FIFO1的中断通道名称是:CAN1_RX1_IRQn

FIFO0的中断函数名称是:void USB_LP_CAN1_RX0_IRQHandler(void)
FIFO1的中断函数名称是:void CAN1_RX1_IRQHandler(void)

CAN发送函数

/****************************************************************************
*@*名称 : CAN_SetMsg
*@*功能 : 配置CAN的发送信箱进行发送
*@*形参 : CanTxMsg型的结构体地址,发送的拓展ID,发送的数据存放地址
*@*返回值 : 无
****************************************************************************/
void CAN_SetMsg(CanTxMsg *TxMessage,uint32_t EXT_ID ,uint8_t* Data)
{	  
	uint8_t ubCounter = 0;

  //TxMessage.StdId=0x00;						 
  TxMessage->ExtId=EXT_ID;					 //使用的扩展ID
  TxMessage->IDE=CAN_ID_EXT;					 //扩展模式
  TxMessage->RTR=CAN_RTR_DATA;				 //发送的是数据
  TxMessage->DLC=8;							 //数据长度为8字节
	
	/*设置要发送的数据0-7*/
	for (ubCounter = 0; ubCounter < 8; ubCounter++)
  {
    TxMessage->Data[ubCounter] = Data[ubCounter];
  }
	
	
	CAN_Transmit(CAN1, TxMessage);//将TMIDxR_TxRQ标志位置1,发送CAN发送邮箱的报文发送出去
}

接收中断函数编写

/****************************************************************************
*@*名称 : USB_LP_CAN1_RX0_IRQHandler
*@*功能 : CAN模块的FIFO0接收中断函数
*@*形参 : 无
*@*返回值 : 无
****************************************************************************/
void USB_LP_CAN1_RX0_IRQHandler(void)
{
	/*从邮箱中读出报文*/
	CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);

	/* 比较ID是否为0x1111 */ 
	if((RxMessage.ExtId >= 0x1110) && (RxMessage.IDE==CAN_ID_EXT) && (RxMessage.DLC==8) )
	{
		CAN_flag = 1; 					       //接收成功  
	}
	else
	{
		CAN_flag = 0; 					   //接收失败
	}
}

总结

在以上函数中我打开了两个接收FIFO,希望能一次性接收6个CAN的报文,但在实际实验中,连续发送6个报文,只能接收3个成功。
查了好久的原因还是没有解决该问题,如果有大佬知道为什么,希望能帮我解惑。


http://www.niftyadmin.cn/n/5167018.html

相关文章

EMQX服务器的API接口使用介绍_完成上位机开发

一、前言 在前面的两篇文章里分别介绍了再Windows和Ubuntu下利用EMQX搭建自己的私有MQTT服务器,实现设备上云。 这篇文章介绍如何利用EMQX提供的API接口,开发用户使用的上位机(我这里分别采用python 和 Qt 进行开发测试),完成对应的功能开发。凭证获取,主题发布、消息获取…

线程池的工作原理

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一份大厂面试资料《史上最全大厂面试题》&#xff0c;Springboot、微服务、算法、数据结构、Zookeeper、Mybatis、Dubbo、linux、Kafka、Elasticsearch、数据库等等 …

【Unity ShaderGraph】| 如何快速制作一个炫酷的 全息投影效果

前言 【Unity ShaderGraph】| 如何快速制作一个炫酷的 全息投影效果一、效果展示二、 全息投影效果 前言 本文将使用ShaderGraph制作一个 炫酷的 全息投影效果 &#xff0c;可以直接拿到项目中使用。对ShaderGraph还不了解的小伙伴可以参考这篇文章&#xff1a;【Unity Shader…

社区团购商品数据抓取

爬虫程序的实现需要使用到C#编程语言以及相关爬虫框架&#xff0c;如Scrapy、WebScraper等。以下是一个简单的示例&#xff0c;展示了如何使用C#爬取网站上的商品数据&#xff1a; using System; using System.Net; using System.IO; using HtmlAgilityPack;class Program {st…

12 # 手写 findIndex 方法

findIndex 的使用 findIndex() 方法返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回 -1。 <script>var arr [1, 3, 5, 7, 8];var result arr.findIndex(function (ele, index, array) {console.log("ele----->", ele);conso…

<C++> list模拟实现

目录 前言 一、list的使用 1. list的构造函数 2. list iterator的使用 3. list capacity 4. list modifiers 5. list的算法 1. unique​ 2. sort 3. merge 4. remove 5. splice 二、list模拟实现 1. 设置节点类 && list类 2. push_back 3. 迭代器 重载 * 重载前置 …

【强化学习】结合Python实战深入分析原理

【文末送书】今天推荐一本强化学习领域优质Python算法书籍&#xff0c;揭密ChatGPT关键技术PPO和RLHF。 目录 前言时间旅行和平行宇宙强化学习策略梯度算法代码案例文末送书 前言 时间循环是一类热门的影视题材&#xff0c;其设定常常如下&#xff1a;主人公可以主动或被动的回…

java通过FTP跨服务器动态监听读取指定目录下文件数据

背景&#xff1a; 1、文件数据在A服务器&#xff08;windows&#xff09;&#xff08;不定期在指定目录下生成&#xff09;&#xff0c;项目应用部署在B服务器&#xff08;Linux&#xff09;&#xff1b; 2、项目应用在B服务器&#xff0c;监听A服务器指定目录&#xff0c;有新…