STM32 CAN笔记(一)

基本帧结构
在这里插入图片描述
在这里插入图片描述
CAN帧数据详细如上图所示。
1.起始位
在I2C,SPI,USART通信协议中都有起始位。通信线静默状态(无数据时),总线上的电平状态为静默状态。
表示开始就要打破静默状态,总线上的节点知道通信要开始了。启动相应的接收准备。表示帧开始传输

2.标识符 ID
在I2C协议中 发送起始位后,需要发送目标节点的地址,表示数据的归属。
在CAN中没有目标节点地址,ID代表着这条数据的归属。
例如 ,ID0x 01 表示温度。那么所有关心温度的节点都可以识别到这条消息。
根本上来说CAN的数据传输是一种广播,ID 区分了广播的数据和意义。总线上的节点根据ID来接收或者丢弃。
如果是I2C要进行广播,显然要把所有的节点的地址都发一次
同理消息ID有仲裁的消息,在I2C中一样。回读自己发送的ID,一旦不同则仲裁出局,根本上来说是线与。

3.远程传输请求
简单说就是,发起请求。有点像I2C中的read/Write。需要写数据的时候,是要携带数据的。read的时候不需要带数据,告诉对方是read。
发起请求标志。接收到这个请求的节点会回馈这个请求,发送相应的报文。

4.R1,R0
预留,以后CAN协议要扩展,先留下两个坑位,实现兼容。

5.数据长度
有效载荷 数据的长度,也就是一帧数据所携带的真正内容。

6.CRC校验
1-5步骤的数据进行CRC校验

7.ACK
ACK必定是一个非静默电平。表示有人应答。

9.EOF
结束

理解上述基本帧后,再去看扩展帧。这里不叙述扩展帧。
在这里插入图片描述
1.邮箱
为什么叫邮箱
CAN 帧的定义实际上是CAN协议的数据链路层的定义。定义了一帧数据的结构,有开头有结尾。所以收发数据的时候,按照这个定义来组包->发送,接收->解包。那么这包就是这里的帧。显然可以定义一种数据结构来表示帧数据,邮箱就是这个数据结构,而且它是硬件实现的寄存器组。多个寄存器按照CAN逻辑链路的定义组成一组。这一组就叫做一个邮箱,邮箱对应的是一帧数据。
把邮箱进行标号,就有邮箱1,邮箱2,邮箱3。邮箱之间的数据排列如果是队列形式的那么就可以叫做FIFO。用于存储接收帧的邮箱组就叫做接收FIFO。

2.筛选器
CAN传输数据是用广播的方式,所有连接在总线上的节点都可以接受到发送端的数据。
所以只要总线上有数据传输,那么就一直接收。这个时候需要判别出哪些数据时本节点需要的。
显然一般的方法是把CAN一帧的数据读进CPU,对比一下ID是不是我要。这很费事。
所以,在硬件上设计了一个对比机制,根本上解放了CPU。CPU只要处理需要的数据。
这个对比机制也就是筛选机制就是 筛选器,筛选的主体就是ID。

筛选器的根本就是队ID处理
a.完整的ID筛选
ID11位完全符合,才会接收入邮箱
b.掩码筛选
ID 与运算 掩码 =掩码 ,表示通过
当然分扩展和基础帧
在这里插入图片描述
筛选器的部分应该就没啥问题了。
在CAN模块的框图中,数据流动的箭头表示。通过筛选器的数据才会进入到接收FIFO

CAN的工作模式

有三种主要的工作模式: 初始化、 正常和睡眠。
CAN通信是广播发送,所以相对来说,每个节点都需要接收数据。
CAN不带时钟信号线,所以CAN的节点需要在每次显性电平的跳变沿进行同步和补偿。每个节点的接收模块是比较“忙的”
总线形式是所有节点都连接在一起,向一条马路一样如果有问题,会影响到整条通路的状况。
1.有节能的需求,应为CAN控制器会比较忙。
2.减少节点出错,发送错误的数据

CAN控制器
初始化状态:这个状态允许对控制寄存器进行配置
正常状态:寄存器不能进行配置
睡眠状态:节能

在这里插入图片描述
SLAK,INAK 组合用来表示当前状态
SLEEP 睡眠请求
INRQ 初始化请求

  1. ACK = 硬件通过将 CAN_MSR 寄存器的 INAK 或 SLAK 位置 1 来确认请求的等待状态
  2. SYNC = bxCAN 等待 CAN 总线变为空闲(即在 CANRX 上监测到连续 11 个隐性位)的状态
    切换的逻辑如上图所示。

发送处理

发送就是按照CAN帧的定义把相应的数据装填到 数据结构(邮箱)中。然后拍一下邮箱的屁股,说走吧
数据的装填 :标识符、数据长度代码 (DLC) 和数据本身
显然我们只能往一个空的邮箱里面写数据。
然后把 配置 TXRQ: 发送邮箱请求 (Transmit mailbox request) 就让它发送。
如果这个时候,发送正在排队,可以根据ID优先级和邮箱号的顺序进行。

在这里插入图片描述

接收处理

为了接收 CAN 消息,提供了构成 FIFO 的三个邮箱。为了节约 CPU 负载,简化软件并保证数据一致性, FIFO 完全由硬件进行管理。应用程序通过 FIFO 输出邮箱访问 FIFO 中所存储的消息。
当消息依据 CAN 协议正确接收(直到 EOF 字段的倒数第二位都没有发送错误) 并且成功通过标识符筛选后,该消息将视为有效

接收到的数据会放入接收FIFO,这个和所有带FIFO的通信模块一样,FIFO有很多状态
接收到一个消息,接收到两个,接收FIFO满了,FIFO上溢了。
都会有相应的状态寄存器表示,如果对应的中断使能打开还可以产生中断,这样CPU可以计时的介入处理任务。

中断处理

在这里插入图片描述

不得不说STM32系列为什么这么流行,他的手册写的非常好,容易理解。
上图是中断
发送中断可由以下事件产生:
— 发送邮箱 0 变为空, CAN_TSR 寄存器的 RQCP0 位置 1。
— 发送邮箱 1 变为空, CAN_TSR 寄存器的 RQCP1 位置 1。
— 发送邮箱 2 变为空, CAN_TSR 寄存器的 RQCP2 位置 1。
● FIFO 0 中断可由以下事件产生:
— 接收到新消息, CAN_RF0R 寄存器的 FMP0 位不是“00”。
— FIFO0 满, CAN_RF0R 寄存器的 FULL0 位置 1。
— FIFO0 上溢, CAN_RF0R 寄存器的 FOVR0 位置 1。
●FIFO 1 中断可由以下事件产生:
— 接收到新消息, CAN_RF1R 寄存器的 FMP1 位不是“00”。
— FIFO1 满, CAN_RF1R 寄存器的 FULL1 位置 1。
— FIFO1 上溢, CAN_RF1R 寄存器的 FOVR1 位置 1。
● 错误和状态改变中断可由以下事件产生:
— 错误状况,有关错误状况的更多详细信息,请参见 CAN 错误状态寄存器
(CAN_ESR)。
— 唤醒状况, CAN Rx 信号上监测到 SOF。
— 进入睡眠模式。

模块代码,先分析官方的CAN代码,初始化流程
以下代码用作分析

static void CAN_Config(void)
{
  GPIO_InitTypeDef  GPIO_InitStructure;
  
  /* CAN GPIOs configuration **************************************************/

  /* Enable GPIO clock */
  RCC_AHB1PeriphClockCmd(CAN_GPIO_CLK, ENABLE);

  /* Connect CAN pins to AF9 */
  GPIO_PinAFConfig(CAN_GPIO_PORT, CAN_RX_SOURCE, CAN_AF_PORT);
  GPIO_PinAFConfig(CAN_GPIO_PORT, CAN_TX_SOURCE, CAN_AF_PORT); 
  
  /* Configure CAN RX and TX pins */
  GPIO_InitStructure.GPIO_Pin = CAN_RX_PIN | CAN_TX_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;
  GPIO_Init(CAN_GPIO_PORT, &GPIO_InitStructure);

  /* CAN configuration ********************************************************/  
  /* Enable CAN clock */
  RCC_APB1PeriphClockCmd(CAN_CLK, ENABLE);
  
  /* CAN register init */
  CAN_DeInit(CANx);

  /* CAN cell init */
  CAN_InitStructure.CAN_TTCM = DISABLE;
  CAN_InitStructure.CAN_ABOM = DISABLE;
  CAN_InitStructure.CAN_AWUM = DISABLE;
  CAN_InitStructure.CAN_NART = DISABLE;
  CAN_InitStructure.CAN_RFLM = DISABLE;
  CAN_InitStructure.CAN_TXFP = DISABLE;
  CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;
  CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;
    
  /* CAN Baudrate = 1 MBps (CAN clocked at 30 MHz) */
  CAN_InitStructure.CAN_BS1 = CAN_BS1_6tq;
  CAN_InitStructure.CAN_BS2 = CAN_BS2_8tq;
  CAN_InitStructure.CAN_Prescaler = 2;
  CAN_Init(CANx, &CAN_InitStructure);

  /* CAN filter init */
  CAN_FilterInitStructure.CAN_FilterNumber = 0;
  CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
  CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
  CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;
  CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
  CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;
  CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
  CAN_FilterInitStructure.CAN_FilterFIFOAssignment = 0;
  CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
  CAN_FilterInit(&CAN_FilterInitStructure);
  
  /* Transmit Structure preparation */
  TxMessage.StdId = 0x321;
  TxMessage.ExtId = 0x01;
  TxMessage.RTR = CAN_RTR_DATA;
  TxMessage.IDE = CAN_ID_STD;
  TxMessage.DLC = 1;
  
  /* Enable FIFO 0 message pending Interrupt */
  CAN_ITConfig(CANx, CAN_IT_FMP0, ENABLE);
}

流程还是比较清晰的
1.配置CAN模块的GPIO 输出引脚
2.配置CAN时钟模块,初始化 CAN cell (CAN模块的控制部分,模块框图右边)
3.CAN filter 过滤器的配置
4 .Transmit Structure 传输数据的配置

CAN_InitStruct.CAN_ABOM =  DISABLE;

在这里插入图片描述

CAN_InitStruct.CAN_AWUM =  DISABLE;

在这里插入图片描述

	CAN_InitStruct.CAN_BS1  =  CAN_BS1_5tq;
	CAN_InitStruct.CAN_BS2  =  CAN_BS2_4tq;

BS1,BS2段所占用的Tq 长度。

	CAN_InitStruct.CAN_Mode =  CAN_Mode_Normal;

正常模式 (CAN_Mode_Normal)、回环模式 (CAN_Mode_LoopBack)、静默模式 (CAN_Mode_Silent) 以及回环静默模式(CAN_Mode_Silent_LoopBack)

CAN_InitStruct.CAN_NART =  DISABLE;

在这里插入图片描述

	CAN_InitStruct.CAN_RFLM =  DISABLE;

在这里插入图片描述

CAN_InitStruct.CAN_SJW =   CAN_SJW_4tq;

在这里插入图片描述

CAN_InitStruct.CAN_TTCM =  DISABLE;

在这里插入图片描述

	CAN_InitStruct.CAN_TXFP =  DISABLE;

在这里插入图片描述

CAN_InitStruct.CAN_Prescaler

在这里插入图片描述

F407 APB2 总线时钟频率最大为 42MHz , CAN_Prescaler在此基础上进行分频 得到时间份额 Tq

波特率 为: [ clk(ABP1) /CAN_Prescaler ] / [ 1(SS) + BS1+BS2 ]

实验

在没有CAN收发器的开发板上,缺少物理电平转换。那么可以用示波器或者逻辑分析仪来观察CAN_TX的变化

设置预分频为42,Tq = 42M/42 = 1M

CAN_InitStruct.CAN_BS1  =  CAN_BS1_5tq;
CAN_InitStruct.CAN_BS2  =  CAN_BS2_4tq;

波特率 为: [ clk(ABP1) /CAN_Prescaler ] / [ 1(SS) + BS1+BS2 ] =100Kbps
ACK 在没有其他设备的情况下是 1 ,隐性
代码如下

STM32F407
/*

PA11  CAN1_RX
PA12  CAN1_TX

*/


void app_can_init(void)
{
	GPIO_InitTypeDef  GPIO_InitStruct;

	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);

	GPIO_InitStruct.GPIO_Mode= GPIO_Mode_AF ;
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP ;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11;
	GPIO_InitStruct.GPIO_PuPd =GPIO_PuPd_UP  ;
	GPIO_InitStruct.GPIO_Speed =GPIO_Fast_Speed  ;

	GPIO_Init(GPIOA, &GPIO_InitStruct);
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12;
	GPIO_Init(GPIOA, &GPIO_InitStruct);


	
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource11,GPIO_AF_CAN1);
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource12,GPIO_AF_CAN1);

	app_can_config();
}

void app_can_config(void)
{
	CAN_InitTypeDef  CAN_InitStruct;
	CAN_FilterInitTypeDef CAN_FilterInitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1,ENABLE);



	CAN_InitStruct.CAN_ABOM =  DISABLE;
	CAN_InitStruct.CAN_AWUM =  DISABLE;
	CAN_InitStruct.CAN_BS1  =  CAN_BS1_5tq;
	CAN_InitStruct.CAN_BS2  =  CAN_BS2_4tq;
	CAN_InitStruct.CAN_Mode =  CAN_Mode_LoopBack;
	CAN_InitStruct.CAN_NART =  DISABLE;
	CAN_InitStruct.CAN_Prescaler = 42;
	CAN_InitStruct.CAN_RFLM =  DISABLE;
	CAN_InitStruct.CAN_SJW =   CAN_SJW_4tq;
	CAN_InitStruct.CAN_TTCM =  DISABLE;
	CAN_InitStruct.CAN_TXFP =  DISABLE;
	
	CAN_Init(CAN1, & CAN_InitStruct);


	CAN_FilterInitStructure.CAN_FilterNumber=14;						//筛选器组14
	CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;	//工作在掩码模式
	CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;	//筛选器位宽为单个32位。


	CAN_FilterInitStructure.CAN_FilterIdHigh= ((((u32)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF0000)>>16;		//要筛选的ID高位 
	CAN_FilterInitStructure.CAN_FilterIdLow= (((u32)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF; //要筛选的ID低位 
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh= 0xFFFF;			//筛选器高16位每位必须匹配
	CAN_FilterInitStructure.CAN_FilterMaskIdLow= 0xFFFF;			//筛选器低16位每位必须匹配
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0 ;				//筛选器被关联到FIFO0
	CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;			//使能筛选器
	CAN_FilterInit(&CAN_FilterInitStructure);

}

void app_can_send(void)
{	

	CanTxMsg TxMessage;
	TxMessage.Data[0]= 170; /* 0xAA*/
	TxMessage.DLC = 1;
	TxMessage.ExtId = 0x00;
	TxMessage.IDE  = CAN_ID_STD;
	TxMessage.RTR  = CAN_RTR_DATA;
	TxMessage.StdId = 0x1B5;

	
	CAN_Transmit(CAN1,&TxMessage);
}

波特率测量如下在这里插入图片描述
CAN包数据如下
在这里插入图片描述
我们可以看到事实上携带的有效 数据只有1 byte 也就是 8 bit 约为 80us,一包数据下图大概有530us
所以有效载荷 80/530 = 15%

最高8byte的时候 880/(530+807) = 58%
所以can基本帧有效载荷率大概在15~58% 应为再同步会插入一些同步位,所以是大概值。
在这里插入图片描述
分析一下 CAN的发送,数据的的发送是按照固定的格式 前面我们理解为邮箱
发送函数如下所示

uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage)
{
  uint8_t transmit_mailbox = 0;
  /* Select one empty transmit mailbox */
  if ((CANx->TSR&CAN_TSR_TME0) == CAN_TSR_TME0)
  {
    transmit_mailbox = 0;
  }
  else if ((CANx->TSR&CAN_TSR_TME1) == CAN_TSR_TME1)
  {
    transmit_mailbox = 1;
  }
  else if ((CANx->TSR&CAN_TSR_TME2) == CAN_TSR_TME2)
  {
    transmit_mailbox = 2;
  }
  else
  {
    transmit_mailbox = CAN_TxStatus_NoMailBox;
  }

  if (transmit_mailbox != CAN_TxStatus_NoMailBox)
  {
    /* Set up the Id */
    CANx->sTxMailBox[transmit_mailbox].TIR &= TMIDxR_TXRQ;
    if (TxMessage->IDE == CAN_Id_Standard)
    {
      assert_param(IS_CAN_STDID(TxMessage->StdId));  
      CANx->sTxMailBox[transmit_mailbox].TIR |= ((TxMessage->StdId << 21) | \
                                                  TxMessage->RTR);
    }
    else
    {
      assert_param(IS_CAN_EXTID(TxMessage->ExtId));
      CANx->sTxMailBox[transmit_mailbox].TIR |= ((TxMessage->ExtId << 3) | \
                                                  TxMessage->IDE | \
                                                  TxMessage->RTR);
    }
    
    /* Set up the DLC */
    TxMessage->DLC &= (uint8_t)0x0000000F;
    CANx->sTxMailBox[transmit_mailbox].TDTR &= (uint32_t)0xFFFFFFF0;
    CANx->sTxMailBox[transmit_mailbox].TDTR |= TxMessage->DLC;

    /* Set up the data field */
    CANx->sTxMailBox[transmit_mailbox].TDLR = (((uint32_t)TxMessage->Data[3] << 24) | 
                                             ((uint32_t)TxMessage->Data[2] << 16) |
                                             ((uint32_t)TxMessage->Data[1] << 8) | 
                                             ((uint32_t)TxMessage->Data[0]));
    CANx->sTxMailBox[transmit_mailbox].TDHR = (((uint32_t)TxMessage->Data[7] << 24) | 
                                             ((uint32_t)TxMessage->Data[6] << 16) |
                                             ((uint32_t)TxMessage->Data[5] << 8) |
                                             ((uint32_t)TxMessage->Data[4]));
    /* Request transmission */
    CANx->sTxMailBox[transmit_mailbox].TIR |= TMIDxR_TXRQ;
  }
  return transmit_mailbox;
}

主要是通过结构体 CanTxMsg* TxMessage,按照成员的定义把数据装入发送邮箱。
那么接收的过程也应该是一样的

void CAN_Receive(CAN_TypeDef* CANx, uint8_t FIFONumber, CanRxMsg* RxMessage)

其他系列的MCU原理也类似。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值