jjzjj

第十四篇,STM32的CAN总线通信

肖爱Kun 2025-02-18 原文

1.CAN总线的概念

    CAN指的是控制器局域网网络(Controller Area Network),由德国博世汽车电子厂商开发出来。

    CAN使用差分信号,具有较强的抗干扰能力和传输稳定性

    CAN属于多主通信,网络中所有的节点都可以作为主设备进行通信

    CAN的网络扩展极其方便,CAN网络中扩展了新的通信单元,网络中旧的单元和硬件无需任何改变。

    CAN具有较强的纠错能力,可以发现传输中出现的错误,并对错误节点进行隔离;所有的单元都可以检测错误;检测出错误的单元会立即同时通知其他所有单元;正在发送消息的单元一旦检测出错误,会强制结束当前的发送。被强制结束发送的单元会不断反复地重新发送此消息直到成功发送为止。

2.CAN的差分信号

模分信号使用电平的绝对值来表示逻辑的差别;

差分信号使用两个电平的差值来表示逻辑值。CAN传输使用两根数据线 ------------- CANH和CANL

CAN总线高速ISO11898标准:

如果CANH(3.5V)和CANL(1.5V)的电压差 = 2V,此时表示逻辑0,叫做显性电平 ;

如果CANH(2.5V)和CANL(2.5V)的电压差 = 0V,此时表示逻辑1,叫做隐性电平

//如果多个节点同时控制总线电平,由总线仲裁最终显示的是显性电平(0)

3.CAN通信协议

 CAN使用5种通信帧,下面以数据帧为例来介绍帧结构

数据帧的分析:

    数据帧分为标准数据帧扩展数据帧l两种格式。

    D --- 表示显性电平

    R --- 表示隐性电平

 数据帧由7部分组成:

数据帧七个部分的实现

标准格式中标识符(ID)有11bit,从ID28到ID18被依次发送,禁止高7位都为隐性(禁止设定:ID = 1111 111X XXX);

扩展格式的ID有29bit,基本ID从ID28到ID18,扩展ID由ID17到ID0表示,禁止高7位都为隐性(禁止设定:ID = 1111 111X XXX)。
RTR位用于标识是否是远程帧(0:数据帧;1:遥控帧);

IDE位用于标识符选择位(0:使用标准标识符;1:使用扩展标识符);

SRR位代替遥控帧请求位,为隐性位,代替了标准帧中的RTR位。

(1)帧起始

显示1位显性电平

(2)仲裁段

    用来实现帧的优先级帧的过滤

1.标准帧     

标准数据帧的仲裁段由11位ID1位RTR位组成,RTR用来区分数据帧(显性电平)和遥控帧     

2.扩展帧     

扩展数据帧由 29位ID,1位RTR,1位SRR1位IDE组成;

RTR用来区分数据帧(显性电平)遥控帧     

SRR用来代替标准帧中的RTR位,由于SRR是隐性电平,相同ID的标准帧优先级高于扩展帧   

IDE用来区分标准帧(显性电平)和扩展帧,显性电平表示标准帧,隐性电平表示扩展帧     

报文的优先级由总线通过ID仲裁来判断,当总线上同时出现显性电平和隐形电平时,最终显示为显性电平;当多个节点同时竞争总线占有权时,谁先出现隐形电平,将失去总线占有权,转为接收状态   

(3)控制段

表数据帧里数据段的字节数,r0,r1为保留位,默认是显性电平 ;

4位DLC表示数据段的长度(0~8)

(4)数据段

用户需要发送的数据内容,可一次性发送0–8个字节的数据(每个数据占用一个字节)

长度0~8字节,高位先出

(5)CRC段

CRC错误校验,由15位CRC校验码和1位CRC界定符组成, 校验出了错误信息,可利用错误帧请求重发,重发次数可设定;用于检查帧传输错误(检查范围:起始端,仲裁段,控制段,数据段)。

(6)ACK段

由1位ACK槽和1位ACK界定符组成,发送方的ACK槽是隐性电平 ,接收方确认收到正确的数据后以显性电平应答

(7)帧结束

 7位隐形电平,由7个隐形位(逻辑1)组成,因此ID仲裁断禁止出现1111111****形式的格式

4.CAN的位时序

CAN传输1位由4段组成:同步段,传播段,相位缓冲段1,相位缓冲段2

1位结束后会使用再同步补偿宽度(SJW)进行再补偿。这样设计的原因是让发送方和接收方的时序保持一致(消除误差)

(1)同步段(SS)

同步段用于实现实训的调整,完成显性/隐形电平的转换,也就是准备该位要发送的电平

(2)传播时间段(PTS)

用于吸收网络上的物理延迟,物理延迟包括发送的延迟,接收的延迟和信号传播的延迟

(3)相位缓冲段(PBS)

对于电平转换未被包含的部分进行补偿,同时和SJW一起补偿各单位的时钟误差,电平的读取在该段完成。

(4)再同步补偿宽度(SJW)

补偿前面同步的误差

    接收方的采样一定在PBS1和PBS2之间,由于SS和PTS误差的变动,PBS的补偿也会随着误差的变化而变化,实现传输1位时间的固定,SJW对PBS的补偿进行二次补偿。

1位时间 = 8 ~ 25Tq, SS = 1Tq ,PTS = 1 ~ 8Tq ,PBS = 2 ~ 8Tq ,SKW = 1 ~ 4Tq

5.stm32f407的CAN控制器

    stm32芯片带有bxCAN控制器,支持CAN协议的2.0A和2.0B,最快速度1Mbps,能够自动发送CAN报文,支持标准和扩展数据帧,控制器带有三个发送邮箱存储发送的报文,还有两个FIFO,支持ID过滤,可配置自动重发等功能。

(1)工作模式

    初始化模式:初始化CAN控制器

    正常模式:正常收发数据

    睡眠模式:低功耗

(2)测试模式

    静默模式:停止对外发送报文

    环回模式:报文不发往总线,直接发给自己

    环回和静默组合模式

    测试模式

(3)传输速率

波特率 = 42MHz/分频系数/(tbs1+tbs2+sjw)

(4)接收中断

FMPx表示收到了报文

6.原理图

 查看原理图可知

CAN总结的接口最终连接到了CPU的PD0和PD1,具有CAN的复用功能。

7.CAN总线通信的库函数实现

 添加CAN通信的库函数源码

(1)开启时钟

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD,ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1,ENABLE);

(2)将GPIO配置为CAN复用功能

GPIO_Init(...); GPIO_PinAFConfig(...);

(3)初始化CAN

uint8_t CAN_Init(CAN_TypeDef* CANx, CAN_InitTypeDef* CAN_InitStruct);
参数:     
CANx - 哪个CAN     
CAN_InitStruct - CAN初始化结构     

typedef struct
{   
  uint16_t CAN_Prescaler;   /*!< 预分频系数 1 to 1024. */      
  uint8_t CAN_Mode;         /*!< 工作模式 @ref CAN_operating_mode */   uint8_t CAN_SJW;          /*!< 再同步补偿宽度极限值 @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 or DISABLE. */      FunctionalState CAN_ABOM;  /*!< 自动离线使能 ENABLE or DISABLE. */   FunctionalState CAN_AWUM;  /*!< 自动唤醒使能 ENABLE or DISABLE. */   FunctionalState CAN_NART;  /*!< 自动重发使能 ENABLE or DISABLE. */   FunctionalState CAN_RFLM;  /*!< 接收FIFO锁定使能 ENABLE or DISABLE. */   FunctionalState CAN_TXFP;  /*!< 发送FIFO优先级使能 ENABLE or DISABLE. */ } CAN_InitTypeDef;    

(4)初始化过滤器

void CAN_FilterInit(CAN_FilterInitTypeDef* CAN_FilterInitStruct);
//参数就是过滤器初始化结构

typedef struct
{ 
  uint16_t CAN_FilterIdHigh;         /*!< 过滤器ID寄存器高16位 0x0000 and 0xFFFF */   uint16_t CAN_FilterIdLow;          /*!< 过滤器ID寄存器低16位 0x0000 and 0xFFFF */   uint16_t CAN_FilterMaskIdHigh;     /*!< 过滤器ID掩码寄存器高16位 0x0000 and 0xFFFF */   uint16_t CAN_FilterMaskIdLow;      /*!< 过滤器ID掩码寄存器低16位 0x0000 and 0xFFFF */   uint16_t CAN_FilterFIFOAssignment; /*!< 接收FIFO的选择 @ref CAN_filter_FIFO */      uint8_t CAN_FilterNumber;          /*!< 过滤器编号 0 to 13. */   
  uint8_t CAN_FilterMode;            /*!< 过滤模式 @ref CAN_filter_mode */   uint8_t CAN_FilterScale;           /*!< 过滤器长度 @ref CAN_filter_scale */   FunctionalState CAN_FilterActivation; /*!< 使能/禁止 ENABLE or DISABLE. */ } CAN_FilterInitTypeDef;

(5)配置发送/接收的结构体

//发送数据结构体

typedef struct

{   uint32_t StdId;  /*!< 标准帧ID 0 to 0x7FF. */   
    uint32_t ExtId;  /*!< 扩展帧ID 0 to 0x1FFFFFFF. */   
    uint8_t IDE;     /*!< IDE标志 标准帧/扩展帧 @ref CAN_identifier_type */   
    uint8_t RTR;     /*!< RTR标志 数据帧/遥控帧 @ref CAN_remote_transmission_request */          uint8_t DLC;     /*!< 数据段长度 0 to 8 */   
    uint8_t Data[8]; /*!< 发送的数据 0 to 0xFF. */ 
} CanTxMsg;

//接收数据结构体
 typedef struct 
{ 
  uint32_t StdId;  /*!< 标准帧ID 0 to 0x7FF. */   
  uint32_t ExtId;  /*!< 扩展帧ID 0 to 0x1FFFFFFF. */   
  uint8_t IDE;     /*!< IDE标志 标准帧/扩展帧 @ref CAN_identifier_type */   
  uint8_t RTR;     /*!< RTR标志 数据帧/遥控帧 @ref CAN_remote_transmission_request */   uint8_t DLC;     /*!< 数据段长度 0 to 8 */   uint8_t Data[8]; /*!< 接收的数据 0 to 0xFF. */   uint8_t FMI;     /*!< 过滤器放入的内容 0 to 0xFF */ 
} CanRxMsg;

(6)初始化CAN的接收中断

NVIC_Init(...); CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);

(7)发送和接收数据

接收:     void CAN_Receive(CAN_TypeDef* CANx, uint8_t FIFONumber, CanRxMsg* RxMessage);

发送:     

    uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage);     uint8_t CAN_TransmitStatus(CAN_TypeDef* CANx, uint8_t TransmitMailbox);

8.上位机的使用

    打开软件

(1)串口设置

打开后会读到当前模块的配置

(2)将CAN模块设置为环回模式,波特率500Kbps

 (4)测试模块的发送和接收

9.连接模块和开发板

下一步需要编写程序实现CAN总线的通信 ,如果没有can模块进行测试,可以在初始化can 的时候把模式设置为环回模式,把数据发送给自己进行数据收发的测试。

#include <stm32f4xx.h>
#include <can.h>
#include <stdio.h>

void can1_init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	CAN_InitTypeDef CAN_InitStruct;
	CAN_FilterInitTypeDef CAN_FilterInitStruct;
	NVIC_InitTypeDef NVIC_InitStruct;
	
	//1.开启GPIOD和CAN1时钟
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1,ENABLE);
	
	//2.初始化GPIO为CAN功能
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;//复用模式
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;//推挽输出
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//高速
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;//无上下拉
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
	GPIO_Init(GPIOD,&GPIO_InitStruct);
	
	GPIO_PinAFConfig(GPIOD,GPIO_PinSource0,GPIO_AF_CAN1);
	GPIO_PinAFConfig(GPIOD,GPIO_PinSource1,GPIO_AF_CAN1);
	
	//3.初始化CAN 42M / 4 /(1+12+8) = 500Kbps
	CAN_InitStruct.CAN_Prescaler = 4;//4分频
	CAN_InitStruct.CAN_SJW = CAN_SJW_1tq;//SJW宽度
	CAN_InitStruct.CAN_BS1 = CAN_BS1_12tq;//PBS1宽度
	CAN_InitStruct.CAN_BS2 = CAN_BS2_8tq;//PBS2宽度
	CAN_InitStruct.CAN_Mode = CAN_Mode_LoopBack;//CAN_Mode_Normal;//正常模式
	CAN_InitStruct.CAN_ABOM = DISABLE;//自动离线
	CAN_InitStruct.CAN_AWUM = DISABLE;//自动唤醒
	CAN_InitStruct.CAN_NART = DISABLE;//自动重发
	CAN_InitStruct.CAN_RFLM = DISABLE;//锁定接收
	CAN_InitStruct.CAN_TTCM = DISABLE;//时间触发
	CAN_InitStruct.CAN_TXFP = DISABLE;//发送报文优先级
	CAN_Init(CAN1,&CAN_InitStruct);
	
	//4.初始化过滤器
	CAN_FilterInitStruct.CAN_FilterActivation = ENABLE;//使能过滤器
	CAN_FilterInitStruct.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;//安装过滤器到FIFO0
	CAN_FilterInitStruct.CAN_FilterMode = CAN_FilterMode_IdMask;//过滤器模式 --- 掩码
	CAN_FilterInitStruct.CAN_FilterNumber = 0;//过滤器0
	CAN_FilterInitStruct.CAN_FilterScale = CAN_FilterScale_16bit;//过滤器长度
	CAN_FilterInitStruct.CAN_FilterIdHigh = 0x0000;
	CAN_FilterInitStruct.CAN_FilterIdLow = 0x0000;
	CAN_FilterInitStruct.CAN_FilterMaskIdHigh = 0x0000;
	CAN_FilterInitStruct.CAN_FilterMaskIdLow = 0x0000;
	CAN_FilterInit(&CAN_FilterInitStruct);
	
	//5.初始化CAN接收中断
	NVIC_InitStruct.NVIC_IRQChannel = CAN1_RX0_IRQn;//CAN1 FIFO0接收中断通道
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x2;//抢占优先级
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x2;//响应优先级
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;//使能
	NVIC_Init(&NVIC_InitStruct);
	
	CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);
}

CanTxMsg CAN1_TX_MSG;
CanRxMsg CAN1_RX_MSG;

//can1发送函数 0 - 成功  非0 - 失败
u8 can1_send_message(u8 *data,u8 len,u32 message_id)
{
	u8 i,mailbox;
	u32 cnt = 0;
	
	if(len>8)
		return 1;
	
	//根据id选择标准帧/扩展帧
	if(message_id>0x7ff){//扩展帧
		CAN1_TX_MSG.IDE = CAN_Id_Extended;
	}
	else{
		CAN1_TX_MSG.IDE = CAN_Id_Standard;
	}
	
	CAN1_TX_MSG.RTR = CAN_RTR_Data;//数据帧
	CAN1_TX_MSG.DLC = len;//数据长度
	CAN1_TX_MSG.ExtId = message_id;//扩展帧ID
	CAN1_TX_MSG.StdId = message_id;//标准帧ID
	//发送的数据
	for(i=0;i<len;i++){
		CAN1_TX_MSG.Data[i] = data[i];
	}
	
	//发送数据等待发送成功
	mailbox = CAN_Transmit(CAN1,&CAN1_TX_MSG);
	do{
		cnt++;
	}while(CAN_TransmitStatus(CAN1,mailbox)!=CAN_TxStatus_Ok&&cnt<1000);
	
	if(cnt<1000)
		return 0;
	else
		return 1;
}

//CAN FIFO1中断处理函数
void CAN1_RX0_IRQHandler(void)
{
	if(CAN_GetITStatus(CAN1,CAN_IT_FMP0)==SET){//收到数据
		CAN_Receive(CAN1,CAN_FIFO0,&CAN1_RX_MSG);
		/*
		//原路发回
		if(CAN1_RX_MSG.IDE==CAN_Id_Standard){//标准帧
			can1_send_message(CAN1_RX_MSG.Data,CAN1_RX_MSG.DLC,CAN1_RX_MSG.StdId);
		}
		else if(CAN1_RX_MSG.IDE==CAN_Id_Extended){
			can1_send_message(CAN1_RX_MSG.Data,CAN1_RX_MSG.DLC,CAN1_RX_MSG.ExtId);
		}
		*/
		printf("%s\r\n",CAN1_RX_MSG.Data);
		
		//清除标志
		CAN_ClearITPendingBit(CAN1,CAN_IT_FMP0);
	}
}

有关第十四篇,STM32的CAN总线通信的更多相关文章

  1. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  2. ruby-on-rails - 新 Rails 项目 : 'bundle install' can't install rails in gemfile - 2

    我已经像这样安装了一个新的Rails项目:$railsnewsite它执行并到达:bundleinstall但是当它似乎尝试安装依赖项时我得到了这个错误Gem::Ext::BuildError:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcheckingforlibkern/OSAtomic.h...yescreatingMakefilemake"DESTDIR="cleanmake"DESTDIR="

  3. ruby CSV : How can I read a tab-delimited file? - 2

    CSV.open(name,"r").eachdo|row|putsrowend我得到以下错误:CSV::MalformedCSVErrorUnquotedfieldsdonotallow\ror\n文件名是一个.txt制表符分隔文件。我是专门做的。我有一个.csv文件,我转到excel,并将文件保存为.txt制表符分隔的文件。所以它是制表符分隔的。CSV.open不应该能够读取制表符分隔的文件吗? 最佳答案 尝试像这样指定字段分隔符:CSV.open("name","r",{:col_sep=>"\t"}).eachdo|row|

  4. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

  5. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  6. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  7. ruby - 混帐 & ruby : How can I unset the GIT_DIR variable from inside a ruby script? - 2

    我编写了一个非常简单的“部署”脚本,作为我的裸git存储库中的post-updateHook运行。变量如下livedomain=~/mydomain.comstagingdomain=~/stage.mydomain.comgitrepolocation=~/git.mydomain.com/thisrepo.git(bare)core=~/git.mydomain.com/thisrepo.gitcore==addedremoteintoeachlive&stagegitslive和stage都初始化了gitrepos(非裸),我已经将我的裸仓库作为远程添加到它们中的每一个(名为co

  8. ruby-on-rails - 在 Ruby 或 Rails 中,hash.merge({ :order => 'asc' }) can return a new hash with a new key. 什么可以返回带有已删除键的新散列? - 2

    在Ruby(或Rails)中,我们可以做到new_params=params.merge({:order=>'asc'})现在new_params是一个带有添加键:order的散列。但是是否有一行可以返回带有已删除key的散列?线路new_params=params.delete(:order)不会工作,因为delete方法返回值,仅此而已。我们必须分3步完成吗?tmp_params=paramstmp_params.delete(:order)returntmp_params有没有更好的方法?因为我想做一个new_params=(params[:order].blank?||para

  9. ruby - 类型错误 : can't convert String into Integer - 2

    我有代码:classScenedefinitialize(number)@number=numberendattr_reader:numberendscenes=[Scene.new("one"),Scene.new("one"),Scene.new("two"),Scene.new("one")]groups=scenes.inject({})do|new_hash,scene|new_hash[scene.number]=[]ifnew_hash[scene.number].nil?new_hash[scene.number]当我启动它时出现错误:freq.rb:11:in`[]'

  10. STM32的HAL和LL库区别和性能对比 - 2

    LL库和HAL库简介LL:Low-Layer,底层库HAL:HardwareAbstractionLayer,硬件抽象层库LL库和hal库对比,很精简,这实际上是一个精简的库。LL库的配置选择如下:在STM32CUBEMX中,点击菜单的“ProjectManager”–>“AdvancedSettings”,在下面的界面中选择“AdvancedSettings”,然后在每个模块后面选择使用的库总结:1、如果使用的MCU是小容量的,那么STM32CubeLL将是最佳选择;2、如果结合可移植性和优化,使用STM32CubeHAL并使用特定的优化实现替换一些调用,可保持最大的可移植性。另外HAL和L

随机推荐