第十四篇:STM32 CAN总线通信的探索

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);
	}
}

物联沃分享整理
物联沃-IOTWORD物联网 » 第十四篇:STM32 CAN总线通信的探索

发表评论