TOGOUTECH

第十四篇,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. javascript - 如何防止 JavaScript 中的事件链 - 2

    我有4个图标,当鼠标悬停在上面时,使用jQueryanimate滚动一个div。问题是当我快速、来回地悬停在所有四个图标上时,悬停功能链接在一起,即使在鼠标移开时,滚动动画仍然运行,直到所有发生的鼠标悬停事件完成。我想在鼠标移开时取消所有等待执行的事件!在下面找到来源:调用的函数是:functionsidebar_slider(val){$('#sidebar-slider').animate({scrollLeft:val},500)}有人知道更好的方法吗?有关我的问题的示例,请导航至http://a3mediauk.co.uk看看侧边栏! 最佳答案

  2. javascript - 在不同系统之间比较 JavaScript 的 getTime() 是否安全? - 2

    JavaScript'sgetTime()返回“自1970年1月1日00:00:00UTC以来的毫秒数”。我可以相信这在不同的机器上是相似的吗?我不需要它精确到毫秒,只需精确到几秒。或者我需要使用外部时间服务API,asinthisquestion?JavaScript从哪里获取当前时间-它取决于机器的时钟吗? 最佳答案 CanIrelyonthisbeingsimilaracrossdifferentmachines?没有。WheredoesJavaScriptgetthecurrenttimefrom运行此javascript的

  3. javascript - 一次重绘/回流中的多个 DOM 更新? - 2

    我有一个表格,其中填充了已连接用户的列表。列表本身并不经常更改,但每一行中的其中一个是每秒更新的计时器(hh:mm:ss)。要更新计时器,我正在做这样的事情:varcurTime=newDate().getTime()/1000;$('.timerCell').each(function(){var$this=$(this);varinitialTime=$this.data('sessionStartTimestamp');//.datagetswhenthecellisdynamicallycreatedbasedondatareceivedinanajaxrequest.$thi

  4. javascript - 如何使用 javascript 从包含 html 代码的变量中获取选定的 div 部分? - 2

    我有一个变量var'html',它包含一些html代码。其中有一个ID为messages的div。我只想使用javascript将“html”变量中的那个div内容放入另一个变量中。请帮忙。. 最佳答案 在我看来,使用jQuery最简单的方法:varmessage=$('').append(html).find('#message').text();//.html(); 关于javascript-如何使用javascript从包含html代码的变量中获取选定的div部分?,我们在Stac

  5. javascript - 如何生成包含可动态添加和删除行的表格的 Div? - 添加了 JSfiddle - 2

    在JSFiddle中,我尝试使用javascript动态生成div。这些div将包含表格,其中最后两行可以使用添加按钮递增。我试过fiddle中的代码。ins_row()函数用于在表格中添加在div中生成的行。addEvent()函数用于生成div当单击“添加产品”按钮时,将生成一个包含一行表格的div。当点击添加按钮时,最后两行应该根据点击次数继续添加。如果直接点击div的删除按钮,则应删除整个表格和div。当直接点击生成行的删除按钮时,应该只删除该行而不是整个div。问题这里的问题是正在生成带表格的div,但我不知道如何在表格中添加行。Seeitinactionhere预期输出注意

  6. javascript - Flash 运行时在使用 PLupload 的 IE8 中不起作用 - 2

    我在$(function(){...});正文中有简单的javascript函数varuploader=newplupload.Uploader({runtimes:'html5,flash,silverlight',browse_button:'pickfiles',container:'uploader',max_file_size:'20mb',unique_names:true,multiple_queues:false,//drop_element:'dropzone',url:'/Home/Upload',flash_swf_url:'../../../Scripts/up

  7. 基于本地时钟的 Javascript 事件触发器 - 2

    我有一个场景,其中一台客户端PC将驱动多个LCD显示器,每个显示器显示一个浏览器窗口。这些浏览器窗口使用jquery显示动画循环中的不同数据。我需要确保两个浏览器可以同步旋转以完全同时旋转,否则它们将在不同时间显示动画。所以我的问题是-我可以触发jquery以根据本地PC时钟交替显示内容吗?例如每次时钟秒==0,显示版本1,每次时钟秒==30,显示版本2等等? 最佳答案 这是(根据我的经验)让计时器尽可能接近时钟时间触发的最精确方法://getcurrenttimeinmsecstonearest30secondsvarmsecs=

  8. javascript - 如何像 jsfiddle.net 那样调整多个相邻文本区域的大小? - 2

    如何像jsfiddle.net网站一样,通过在区域1、2、3上拖动鼠标来调整textarea的大小?我的代码是:HTML:ABCDJS:$(function(){window.onresize=resize;resize();});functionresize(){varh=(window.innerHeight||(window.document.documentElement.clientHeight||window.document.body.clientHeight));vardivHight=20+$("#div_left").height();//20=bodypaddin

  9. javascript - 真实世界 URL 的 URL 验证正则表达式 - 2

    我想验证给定的字符串是URL。匹配文本中的URL也很好,但不是必需的。我已经搜索并进行了实验,但到目前为止,我还没有找到可以满足这些要求的东西:不得接受在被视为链接时会带来安全风险的字符串。例如,clickme是一个有效的HTML元素,并且至少在某些浏览器中确实有效(引发警报等)。我担心如果我允许任意方案(见下文),它可能会损害安全性(如前所述,例如,此处:WhatisthebestregularexpressiontocheckifastringisavalidURL?)。必须在JavaScript中正常工作。如果它在Java中也能同样工作,那就太好了——我正在GWT中开发,所以这很

  10. javascript - 模块模式中的构造函数 - 2

    在javascript中使用模块模式时,应该如何定义构造函数(如果有的话)。我希望我的构造函数适合标准模块模式而不是全局的。为什么这样的东西不起作用,它完全是胡说八道吗?varHOUSE=function(){return{Person:function(){varself=this;self.name="john";functionname(){returnself.name;}}};}();varme=newHOUSE.Person();alert(me.name()); 最佳答案 您的代码几乎没问题。但是,函数name()不是

随机推荐