只发CAN相关2 随时更新~~ 

一、CAN总线错误分析与解决

从实际工作中碰到的具体问题来分析一些常见的CAN总线错误和解决办法。

CAN节点数据收发过程

嵌入式~CAN-专辑2_嵌入式硬件

我们知道,CAN总线上的每个节点往总线上发送数据的同时,会读取总线上的数据,并与自己发送的数据作对比。

CAN信息发送成功后,在这个间隙内,接收节点可以准备要回复的信息,也就是把应答场填充为显性0,在发送时其为隐性1。

应答过程可能如下:当信息传输到ACK前的Del时,可以认为信息已经传输完毕,接收节点也接收到了足够的信息来检测接收的信息是否正确。

这时接收节点就会检测信号是否正确,如果正确,就将ACK置位为显性0,注意这时,发送节点因为还在发送而接收节点又将ACK信息置位为1,所以它就会在回读时检测到ACK为0,判断接收成功。

注意:这其中有个接收节点用显性覆盖隐性---覆盖ACK位的过程,覆盖+回读。

ACK前后各加一个Del,就是为了考虑到时间误差,让接收节点有足够的时间对ACK确认。这个过程说明,CAN发送是个双向互动的过程,发送节点一边发送,一边对节点进行回收确认数据正确,而接收节点也时刻接收,并在正确的时间将ACK设置为1。

CAN总线错误

CAN总线错误分别有发送和接收错误计数,计数达到一定的累计以后就会产生CAN BUS OFF, 这说明CAN总线上出现了严重的错误。

如下图CAN总线产生错误后的状态转换机制:

嵌入式~CAN-专辑2_嵌入式硬件_02

如果出现了BUS OFF,总线上的节点需要做一些动作,例如重启CAN控制器或是重新上电,但是这些都只是一些补救措施,最根本的还是需要找到引起BUS OFF的根源。

CAN总线分析的一些工具和文档:

  • CAN分析仪或者逻辑分析仪
  • 数字示波器
  • 相关的软件debug工具
  • CAN控制器芯片数据手册
  • 硬件电路图
  • CAN协议文档

CAN节点发送错误不成功

挂载在CAN总线上的一个节点向总线上发送数据不成功,用逻辑分析仪也看不到任何波形。

下面具体来看看怎么不成功。于是调试中断查看 CAN_STATUS 即 CAN状态寄存器显示 0xE5, 查看CPU数据手册:

嵌入式~CAN-专辑2_嵌入式硬件_03

CAN总线状态直接进入了BUS OFF状态,这意味着错误计数已经超限,查看CPU收发寄存器的收发错误计数显示发送错误计数TEC达到248, 接收错误计数为0。这很明显,数据压根没有发送到总线上。再进一步查看寄存器值LEC即LAST ERROR CODE 最后一个错误代码, 显示是BIT0 ERROR。 

嵌入式~CAN-专辑2_嵌入式硬件_04

查看上面的错误代码表可知,BIT0 错误也就是在发送数据期间,虽然CAN节点设备想要发送一个显性位,也就是逻辑0,但是CAN总线同时监听到总线上的数据位为隐性位,即逻辑1。

这意味着CAN core往总线上发送的数据第一位就已经出错了,压根没有将数据经过CAN收发器传送到CAN总线上。

由于是新的CPU的开发所以在怀疑硬件的问题的同时也在排查软件问题,但是经过一阵排查,没有发现软件上的问题。回头再分析硬件,又经过一阵排查溯源,发现 CPU 的 CAN 收发线与 CAN 收发器的收发线接反了。

嵌入式~CAN-专辑2_嵌入式硬件_05

总结

CAN节点发送数据不成功,首先分析是不是CAN控制器本身的问题,查看CPU中的CAN core的状态寄存器,分析是否有BUS OFF, 如果存在BUS OFF, 则进一步查看具体的错误信息。

是主动的错误还是被动的错,发送错误计数有没有超限,最后一次发生的错误状态是什么,查看是位填充错误还是格式错误等其他错误,然后具体问题具体分析。

二、使用时出现的一次错误

最近负责的一个项目用的主控芯片是STM32F407IGT6,需要和几个电机控制器进行通讯,有很多参数需要进行监控。
有一个问题一直无法解决。在开启CAN的接收中断,接收不到数据,问题卡了很久,下面简单分享一下解决的过程和思路。

CAN总线

CAN总线是一种串行通信协议,用于在微控制器和其他设备之间传输数据。CAN总线通常用于汽车、工业自动化和机器人等领域。

CAN总线的硬件通常由以下几个部分组成:

  • 控制器区域:包括CAN控制器和CAN收发器;
  • 总线电缆:用于连接CAN总线上的所有设备;
  • 终端电阻:用于终止总线,以减少反射和信号干扰;
  • 外部电源:用于为CAN总线提供电源;

CAN总线的控制器区域通常包括CAN控制器CAN收发器

  • CAN控制器负责处理CAN总线上的数据传输,包括数据发送和接收、错误检测和纠正等;
  • CAN收发器则负责将CAN控制器的信号转换为总线上的电信号,并将总线上的电信号转换为CAN控制器可以理解的信号。

CAN控制器

主板上的芯片STM32F407IGT6中带有两路的CAN控制器,分别为CAN1 和 CAN2,具体如下图所示;

嵌入式~CAN-专辑2_嵌入式硬件_06

CAN收发器

主板上使用的是芯片SN65HVD230,这是TI公司的一款性能强大且具体低功耗功能的CAN收发器,具体的典型应用电路如下所示;

嵌入式~CAN-专辑2_嵌入式硬件_07

调试过程

硬件排查

设备的调试过程中,首先要确保硬件链路上是否正常。最常见的方法就是直接用示波器进行检查。具体如下所示;

嵌入式~CAN-专辑2_嵌入式硬件_08

  1. 检查CAN控制器和CAN收发器之间是否正常;
  2. 检查CAN收发器的差分信号是否正常,这里可能要了解一下CAN总线电平的显性电平和隐性电平的特点,以及CAN底层协议的细节,会比较复杂;

个人比较推荐使用上述步骤检查硬件链路是否存在问题,那如何对数据进行分析呢?当然可以对着示波器的波形一点一点进行分析,但是这样是很低效的,这里我建议使用CAN分析仪进行数据抓包,下面我们继续进行介绍。

CAN分析仪

至于数据传输是否正确,可以使用CAN盒进行数据监听,下面是我使用的一款CAN分析仪,如图;

嵌入式~CAN-专辑2_嵌入式硬件_09

将CAN分析仪的CAN_HCAN_L分别并联到CAN收发器的CAN_HCAN_L上,然后打开CAN分析仪厂家提供的PC软件,就可以对CAN总线的数据进行监听;

嵌入式~CAN-专辑2_嵌入式硬件_10

  1. 将CAN分析仪接入到CAN总线;
  2. 将CAN分析仪连接到电脑(这里是USB接口),需要配置相同的波特率;
  3. 打开CAN分析仪配套的PC软件,进行数据的收发;

嵌入式~CAN-专辑2_嵌入式硬件_11

  1. 进行到这里,我在项目中遇到的问题是,发送正常,但是STM32F407无法接收到连续的数据,可以接收到一次数据,后面便无法再进入中断。这时候,只能再芯片端进行Debug了。

芯片CAN控制器调试

这里的代码用的HAL库,库版本相对来说比较老,是V1.7.10版本的,如下图所示;

 

嵌入式~CAN-专辑2_嵌入式硬件_12

当时我把项目升级到最新的HAL库,发现CAN部分的驱动改动比较大,另外,下文都是基于V1.7.10版本的HAL库。

CAN控制器的初始化代码如下所示;

void MX_CAN_Init(void)
{
 CAN_FilterConfTypeDef  sFilterConfig;

 /*CAN单元初始化*/
 hCAN.Instance = CANx;             /* CAN外设 */
 hCAN.pTxMsg = &TxMessage;
 hCAN.pRxMsg = &RxMessage;

 hCAN.Init.Prescaler = 6;          /* BTR-BRP 波特率分频器  定义了时间单元的时间长度 42/(1+6+7)/6 = 500Kbps */
 hCAN.Init.Mode = CAN_MODE_NORMAL; /* 正常工作模式 */
 hCAN.Init.SJW = CAN_SJW_1TQ;      /* BTR-SJW 重新同步跳跃宽度 1个时间单元 */
 hCAN.Init.BS1 = CAN_BS1_6TQ;      /* BTR-TS1 时间段1 占用了6个时间单元 */
 hCAN.Init.BS2 = CAN_BS2_7TQ;      /* BTR-TS1 时间段2 占用了7个时间单元 */
 hCAN.Init.TTCM = DISABLE;         /* MCR-TTCM  关闭时间触发通信模式使能 */
 hCAN.Init.ABOM = ENABLE;          /* MCR-ABOM  自动离线管理 */
 hCAN.Init.AWUM = ENABLE;          /* MCR-AWUM  使用自动唤醒模式 */
 hCAN.Init.NART = DISABLE;         /* MCR-NART  禁止报文自动重传   DISABLE-自动重传 */
 hCAN.Init.RFLM = DISABLE;         /* MCR-RFLM  接收FIFO 锁定模式  DISABLE-溢出时新报文会覆盖原有报文 */
 hCAN.Init.TXFP = DISABLE;         /* MCR-TXFP  发送FIFO优先级 DISABLE-优先级取决于报文标示符 */
 HAL_CAN_Init(&hCAN);

 /*CAN过滤器初始化*/
 sFilterConfig.FilterNumber = 0;                    /* 过滤器组0 */
 sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;  /* 工作在标识符屏蔽位模式 */
 sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; /* 过滤器位宽为单个32位。*/
 /* 使能报文标示符过滤器按照标示符的内容进行比对过滤,扩展ID不是如下的就抛弃掉,是的话,会存入FIFO0。 */
 sFilterConfig.FilterIdHigh         = 0x0000; //(((uint32_t)0x1314<<3)&0xFFFF0000)>>16;    /* 要过滤的ID高位 */
 sFilterConfig.FilterIdLow          = 0x0000; //(((uint32_t)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF; /* 要过滤的ID低位 */
 sFilterConfig.FilterMaskIdHigh     = 0x0000;   /* 过滤器高16位每位必须匹配 */
 sFilterConfig.FilterMaskIdLow      = 0x0000;   /* 过滤器低16位每位必须匹配 */
 sFilterConfig.FilterFIFOAssignment = 0;           /* 过滤器被关联到FIFO 0 */
 sFilterConfig.FilterActivation = ENABLE;          /* 使能过滤器 */ 
 sFilterConfig.BankNumber = 14;
 HAL_CAN_ConfigFilter(&hCAN, &sFilterConfig);
  
}

根据注释,可以大概看懂,另外再简单分析一下关键的几点;

  • 波特率设置为 500Kbps;
  • 对报文不进行过滤,可以接收任何扩展ID的数据;

虽然不进行任何过滤,但是还是无法接收到CAN回传的数据,无法进入的接收中断;

从STM32F407的编程手册里了解到;

嵌入式~CAN-专辑2_嵌入式硬件_13

不难发现,CAN1FIFO0产生接收中断需要满足三个条件中的任意一个;

  • FMPIE01 且 FMP01FIFO不为空会产生中断
  • FFIE01 且 FULL1FIFO满,会产生中断
  • FOVIE01 且 FOVR01FIFO溢出,会产生中断

手册里是这样描述的,如下图所示;

嵌入式~CAN-专辑2_嵌入式硬件_14

使用仿真器对芯片进行调试,设置断点,发现FMPIE0被清空了,具体如下图所示; 

嵌入式~CAN-专辑2_嵌入式硬件_15

FMPIE0这一位是FIFO0中有挂起的消息会产生中断的中断使能标志位;

嵌入式~CAN-专辑2_嵌入式硬件_16

所以到这里,问题有点明朗了,为什么无法进入中断?是中断使能位被清空了。

那么下面就是检查代码,看看是哪里把中断给disable了。

继续调试,发现在ESR寄存器中,TEC的值一直增加,然后EWGF被值1了;具体如下所示;

嵌入式~CAN-专辑2_嵌入式硬件_17

TECREC分别是发送错误计数器和接收错误计数器;

如 CAN 协议所述,错误管理完全由硬件通过发送错误计数器( CAN_ESR 寄存器中的 TEC 值)和接收错误计数器( CAN_ESR 寄存器中的 REC 值)来处理,这两个计数器根据错误 状况进行递增或递减。有关 TEC 和 REC 管理的详细信息,请参见 CAN 标准。两者均可由软件读取,用以确定网络的稳定性。此外, CAN 硬件还将在 CAN_ESR 寄存器中 提供当前错误状态的详细信息。通过 CAN_IER 寄存器( ERRIE 位等),软件可以非常灵活 地配置在检测到错误时生成的中断。

TEC大于96的时候,硬件会将EWGF1(错误警告标志位);在代码中找到了相应的宏定义;这下问题越来越清晰了。

嵌入式~CAN-专辑2_嵌入式硬件_18

全文搜索这个宏定义,在HAL_CAN_IRQHandler中找到了__HAL_CAN_DISABLE_IT(CAN_IT_FMP0),关闭了FIFO0的消息挂起中断, 整体代码如下;

/**
  * @brief  Handles CAN interrupt request  
  * @param  hcan: pointer to a CAN_HandleTypeDef structure that contains
  *         the configuration information for the specified CAN.
  * @retval None
  */
void HAL_CAN_IRQHandler(CAN_HandleTypeDef* hcan)
{
  uint32_t tmp1 = 0U, tmp2 = 0U, tmp3 = 0U;
  uint32_t errorcode = HAL_CAN_ERROR_NONE;

  /* Check Overrun flag for FIFO0 */
  tmp1 = __HAL_CAN_GET_FLAG(hcan, CAN_FLAG_FOV0);
  tmp2 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_FOV0);
  if(tmp1 && tmp2)
  {
    /* Set CAN error code to FOV0 error */
    errorcode |= HAL_CAN_ERROR_FOV0;

    /* Clear FIFO0 Overrun Flag */
    __HAL_CAN_CLEAR_FLAG(hcan, CAN_FLAG_FOV0);
  }
  /* Check Overrun flag for FIFO1 */
  tmp1 = __HAL_CAN_GET_FLAG(hcan, CAN_FLAG_FOV1);
  tmp2 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_FOV1);

  if(tmp1 && tmp2)
  {
    /* Set CAN error code to FOV1 error */
    errorcode |= HAL_CAN_ERROR_FOV1;

    /* Clear FIFO1 Overrun Flag */
    __HAL_CAN_CLEAR_FLAG(hcan, CAN_FLAG_FOV1);
  }

  /* Check End of transmission flag */
  if(__HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_TME))
  {
    tmp1 = __HAL_CAN_TRANSMIT_STATUS(hcan, CAN_TXMAILBOX_0);
    tmp2 = __HAL_CAN_TRANSMIT_STATUS(hcan, CAN_TXMAILBOX_1);
    tmp3 = __HAL_CAN_TRANSMIT_STATUS(hcan, CAN_TXMAILBOX_2);
    if(tmp1 || tmp2 || tmp3)  
    {
      tmp1 = __HAL_CAN_GET_FLAG(hcan, CAN_FLAG_TXOK0);
      tmp2 = __HAL_CAN_GET_FLAG(hcan, CAN_FLAG_TXOK1);
      tmp3 = __HAL_CAN_GET_FLAG(hcan, CAN_FLAG_TXOK2);
      /* Check Transmit success */
      if(tmp1 || tmp2 || tmp3)
      {
        /* Call transmit function */
        CAN_Transmit_IT(hcan);
      }
      else /* Transmit failure */
      {
        /* Set CAN error code to TXFAIL error */
        errorcode |= HAL_CAN_ERROR_TXFAIL;
      }

      /* Clear transmission status flags (RQCPx and TXOKx) */
      SET_BIT(hcan->Instance->TSR, CAN_TSR_RQCP0  | CAN_TSR_RQCP1  | CAN_TSR_RQCP2 | \
                                   CAN_FLAG_TXOK0 | CAN_FLAG_TXOK1 | CAN_FLAG_TXOK2);
    }
  }

  tmp1 = __HAL_CAN_MSG_PENDING(hcan, CAN_FIFO0);
  tmp2 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_FMP0);
  /* Check End of reception flag for FIFO0 */
  if((tmp1 != 0U) && tmp2)
  {
    /* Call receive function */
    CAN_Receive_IT(hcan, CAN_FIFO0);
  }

  tmp1 = __HAL_CAN_MSG_PENDING(hcan, CAN_FIFO1);
  tmp2 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_FMP1);
  /* Check End of reception flag for FIFO1 */
  if((tmp1 != 0U) && tmp2)
  {
    /* Call receive function */
    CAN_Receive_IT(hcan, CAN_FIFO1);
  }

  /* Set error code in handle */
  hcan->ErrorCode |= errorcode;

  tmp1 = __HAL_CAN_GET_FLAG(hcan, CAN_FLAG_EWG);
  tmp2 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_EWG);
  tmp3 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_ERR);
  /* Check Error Warning Flag */
  if(tmp1 && tmp2 && tmp3)
  {
    /* Set CAN error code to EWG error */
    hcan->ErrorCode |= HAL_CAN_ERROR_EWG;
  }
  
  tmp1 = __HAL_CAN_GET_FLAG(hcan, CAN_FLAG_EPV);
  tmp2 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_EPV);
  tmp3 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_ERR); 
  /* Check Error Passive Flag */
  if(tmp1 && tmp2 && tmp3)
  {
    /* Set CAN error code to EPV error */
    hcan->ErrorCode |= HAL_CAN_ERROR_EPV;
  }
  
  tmp1 = __HAL_CAN_GET_FLAG(hcan, CAN_FLAG_BOF);
  tmp2 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_BOF);
  tmp3 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_ERR);  
  /* Check Bus-Off Flag */
  if(tmp1 && tmp2 && tmp3)
  {
    /* Set CAN error code to BOF error */
    hcan->ErrorCode |= HAL_CAN_ERROR_BOF;
  }
  
  tmp1 = HAL_IS_BIT_CLR(hcan->Instance->ESR, CAN_ESR_LEC);
  tmp2 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_LEC);
  tmp3 = __HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_ERR);
  /* Check Last error code Flag */
  if((!tmp1) && tmp2 && tmp3)
  {
    tmp1 = (hcan->Instance->ESR) & CAN_ESR_LEC;
    switch(tmp1)
    {
      case(CAN_ESR_LEC_0):
          /* Set CAN error code to STF error */
          hcan->ErrorCode |= HAL_CAN_ERROR_STF;
          break;
      case(CAN_ESR_LEC_1):
          /* Set CAN error code to FOR error */
          hcan->ErrorCode |= HAL_CAN_ERROR_FOR;
          break;
      case(CAN_ESR_LEC_1 | CAN_ESR_LEC_0):
          /* Set CAN error code to ACK error */
          hcan->ErrorCode |= HAL_CAN_ERROR_ACK;
          break;
      case(CAN_ESR_LEC_2):
          /* Set CAN error code to BR error */
          hcan->ErrorCode |= HAL_CAN_ERROR_BR;
          break;
      case(CAN_ESR_LEC_2 | CAN_ESR_LEC_0):
          /* Set CAN error code to BD error */
          hcan->ErrorCode |= HAL_CAN_ERROR_BD;
          break;
      case(CAN_ESR_LEC_2 | CAN_ESR_LEC_1):
          /* Set CAN error code to CRC error */
          hcan->ErrorCode |= HAL_CAN_ERROR_CRC;
          break;
      default:
          break;
    }

    /* Clear Last error code Flag */ 
    hcan->Instance->ESR &= ~(CAN_ESR_LEC);
  }
  
  /* Call the Error call Back in case of Errors */
  if(hcan->ErrorCode != HAL_CAN_ERROR_NONE)
  {
    /* Clear ERRI Flag */ 
    hcan->Instance->MSR = CAN_MSR_ERRI; 
    /* Set the CAN state ready to be able to start again the process */
    hcan->State = HAL_CAN_STATE_READY;

    /* Disable interrupts: */
    /*  - Disable Error warning Interrupt */
    /*  - Disable Error passive Interrupt */
    /*  - Disable Bus-off Interrupt */
    /*  - Disable Last error code Interrupt */
    /*  - Disable Error Interrupt */
    /*  - Disable FIFO 0 message pending Interrupt */
    /*  - Disable FIFO 0 Overrun Interrupt */
    /*  - Disable FIFO 1 message pending Interrupt */
    /*  - Disable FIFO 1 Overrun Interrupt */
    /*  - Disable Transmit mailbox empty Interrupt */
    __HAL_CAN_DISABLE_IT(hcan, CAN_IT_EWG |
                               CAN_IT_EPV |
                               CAN_IT_BOF |
                               CAN_IT_LEC |
                               CAN_IT_ERR |
                               CAN_IT_FMP0|
                               CAN_IT_FOV0|
                               CAN_IT_FMP1|
                               CAN_IT_FOV1|
                               CAN_IT_TME);

    /* Call Error callback function */
    HAL_CAN_ErrorCallback(hcan);
  }  
}

最后,找到无法进入接收中断的原因,是CAN总线出现发送错误的情况,从而触发了错误警告标志位EWGF,进而将关闭了消息挂起中断。

总结

这里简单介绍了在STM32F407上的CAN总线调试过程,项目中难免会遇到各种问题,解决之后,大家要及时做好总结和复盘,技术在于积累和沉淀,相互学习,共同进步。

三、用示波器排查CAN的各种错误帧

嵌入式~CAN-专辑2_嵌入式硬件_19

摘要:CAN的BusOff源于错误帧的积累,而错误帧这个东西,是一个接收节点 认为数据有误 故意打断通信,好让发送节点感知到 并重发报文的设计。注意这里边有个“我觉得你有病”的认知陷阱,让CAN的诊断变得近似玄学。本文分享一种用CAN波形的幅度和脉宽信息来精确定位错误帧来源的方法,来自知乎的大灯。

我们先从基础的讲起。CAN节点的电路一般如下图所示,MCU内置了CAN控制器用来将MCU的数据封装为CAN帧格式,同时它也负责CAN帧的校验和错误帧的处理。控制器封装好的逻辑报文经TX RX送到CAN收发器,将逻辑信号转变为真正的总线差分波形。

嵌入式~CAN-专辑2_嵌入式硬件_20

1、CAN物理层

也就是CAN收发器干了啥?

嵌入式~CAN-专辑2_嵌入式硬件_21

一个典型的双节点CAN网络的物理层等效电路如上图,两颗120Ω终端电阻并联呈现总线电阻60Ω。黑框里是A、B两个节点的CAN收发器(Transceiver),它只负责电平转换。当总线静默时,收发器内部的2.5V电源经15KΩ电阻把CAN-H和CAN-L都拉到2.5V,总线这个状态称之为隐性。当节点A想要驱动总线的时候(TX=0),它同时把内部的上下两个MOS管导通,整个网络的电流流向:节点A的5V电源经二极管、24Ω、两颗终端电阻并联、24Ω、二极管回到节点A的地,总线这个状态称之为显性。CAN总线上的电压实际上就是终端电阻的分压。从节点B来看,CAN-H就变成3.5V,CAN-L变成1.5V,拉出了总线 H - L = 2V 的差分电压,大于0.7V的判断阈值,节点B就认为收到了一个显性(RX=0)。大家可以算一下分压值以增强记忆,后边会用到。

反直觉知识点①:总线无人驱动时,也就是各个节点都隐性时,CAN标准定义这时的TX/RX逻辑电平为1;总线有节点驱动显性,也就是主动拉开差分电压的时候,对应TX/RX端逻辑电平0,这个1/0的反逻辑类似I2C等OC门的驱动逻辑,努力适应一下。这么做我猜有两个原因:一是对地逻辑的抗扰能力强一些,NPN载流能力强&回流路径短;二是为了数学上的严谨性:1x1x1x1...1x0 = 0,任意节点驱动显性0,那总线就是显性0;所有节点隐性1,总线才是隐性1。但这样的反逻辑带来一个问题是,电路设计时需要尤其注意上下电时序,上电/休眠/唤醒过程中千万不要出现MCU已下电(TX拉低)但CAN收发器还供5V电的情况。如果实在难以避免,可以试试单3.3V的CAN收发器MAX3051,它不需要5V电源,逻辑电和驱动电共用同一路3.3V,肯定不会出现电源时序问题。

反直觉知识点②:理论上CAN_L短地,或 CAN_H短路12V,因60Ω终端电阻的存在,隐性时CAN-H与CAN-L之间基本还是重合的,显性时也能正常拉开压差,能维持正常通信,只不过丢包率可能会大一点。大家可以算算总线电压,示波器很容易诊断这个问题。另外,如果你看到CAN的通信电压不是以2.5V为中心对称的,也有可能是多个CAN线交叉错接,比如CAN1_L错接到了CAN2_L上

反直觉知识点③:一个CAN网络里,120Ω终端电阻1~4颗都能工作,少了的话 离终端电阻远的节点 抗扰度会差,多了的话 显性差分电压可能无法触发阈值。

反直觉知识点④:除了线路的最远端,任何稍长的CAN分支都可以加1K~4.7K的支线电阻,跑点电流来改善抗扰度。只要分支别太长,大致1Mbps以内的任何总线其终端电阻都是跑电流增强抗扰的,不涉及真正的阻抗或者反射抑制,双绞的要求也不是特别严格。

2、CAN链路层

也就是CAN控制器干了啥?

回顾完物理层,咱来看链路层,CAN帧的标准格式。当发送节的MCU将TX由1变0的时候,CAN收发器将CAN-H拉高&CAN-L拉低,接收节点收到了H-L>0.7V的压差后,接收节点的CAN收发器RX输出由1变0。下图是一个节点接收到CAN波形后解码出的RX逻辑。

嵌入式~CAN-专辑2_嵌入式硬件_22

一帧报文里边有比较关键的几段:仲裁段、控制段 、 数据段、CRC段、ACK段

仲裁段中的大部分是CAN报文的ID,起名为“仲裁”其实是因为这一段有优先级仲裁的功能:假设A、B两个节点在同一时刻抢发报文,节点A要发二进制ID为001的报文,B要发010。当A、B节点都在发第一位的显性0的时候,总线会同时被两个节点驱动显性,A、B节点回读总线也都是显性,相互之间还意识不到对方的存在。当节点A发到第二位的0,节点B发到第二位的1的时候,总线只有节点A驱动显性0,节点B不驱动 却发现总线被别人驱动了,此时节点B会认为CAN线上有比自己这帧010优先级更高的数据,节点B就会主动停发,让节点A独占总线发完。之后节点B怀揣着这帧数据再次参与总线优先级的仲裁。

反直觉知识点①:CAN作为一个对等网络,没有主从关系,报文全部广播,节点本身也没有优先级概念,只有报文ID的优先级。可以这么理解:CAN节点是“由事件驱动的”,比如刹车制动器,它能发高优先级的“刹车被踩下”的报文,也会发低优先级的“刹车油位正常”的报文,这些报文根据ID的大小在总线上自由竞争优先级,而不是刹车制动器这个节点的话语权一定高。这个特性就要求设计人员提前规划好所有报文优先级和周期(即“通信矩阵”)才能保证整个CAN网络如期运转。如果你的CAN网络有大量雷同节点,节点又只有一帧报文,那么ID数大(优先级低)的节点一定会在总线繁忙或干扰重发的时候"插不上话",可以试试把时间戳融合到ID里边,确保各节点的新数据优先级最高,旧数据自然会被仲裁掉。

反直觉知识点②:在A、B节点同时驱动第一个显性0的时候,总线被两个节点同时驱动,电压会显著高于2V。示波器上会看到在仲裁段的头部有明显的电平凸台,后续节点A抢占总线之后电压会回归正常的2V。

反直觉知识点③:各个节点的时钟同步是把每个bit做16~20份的数字切片来实现的,这个切的份数不建议太多或太少。详细机制请参阅 ZLG致远电子的这篇:CAN同步机制,你真的了解吗?

控制段中有几个控制位,这里拿几个常用的举例。IDE位为扩展ID的指示。如果IDE位为隐性1,就会在后边再续上18位的ID,共11+18=29位长度。比如0x9E就是个11位长度的ID,0x0151就是个29位的ID。R0位是CAN里边的预留位,在CAN-FD里被用作FD帧格式的标志位FDF,这一位为隐性1就会按FD的帧格式解码后续报文。DLC指示了后边的数据段的长度,例如1000表示后续会有8个Byte长度的数据。CAN-FD协议只在数据段会切换成高速率,比如2Mbps/8Mbps,前后其他段的速率保持500kbps不变。

反直觉知识点①:CAN与CAN-FD除了数据段波特率的不同,帧格式也有区别,CAN-FD多了一些控制位。比如FDF(也叫EDL)位用来指示是否按FD帧格式解码,BRS位用来指示是否需要切换高波特率,也就是说,一个FD帧可以全程500kbps不切速率的。

反直觉知识点②:CAN控制器的标准ISO11898-1里要求接收方不解读R0位的显隐性,所以CAN的控制器无法过滤FD帧。标准CAN网络里边一旦出现FD帧会因为多了BRS、ESI等控制位被认为是格式错误。同样的,因为CAN 2.0时代R0/FDF帧无意义,也有一些设备把发送出去的CAN帧的R0位错误地置了隐性1,这样的设备在CAN网络里一切正常,但若进入CAN-FD网络就会被解读成FD帧,进而因为缺少BRS、ESI等控制位被认为是格式错误。所以,CAN-FD并不是真的向下兼容CAN,因为旧时代的CAN设备并没有判别R0/FDF位的能力,一旦它进入FD网络就会疯狂地打断通信。

反直觉知识点③:DLC的长度,在CAN标准里DLC可以是0000~1000之间的二进制值,可以用8421的算法直接计算出数据长度。而在CAN-FD中,1001~1111之间的值则被解读为离散的12,16,20,24,32,48,64byte。

CRC段对于从帧头到Data结束之间的数据,CAN协议使用了CRC15这个比较特别的多项式计算校验,有兴趣的可以手算CRC试试。CAN-FD根据数据长度的不同使用了CRC17和CRC21,这里暂不做展开。

ACK段是由收到该帧的CAN节点回复的确认(Acknowledge)。注意 发送节点在ACK位一定发的是隐性1,由接收节点回应显性0,双方无缝衔接才在总线上呈现出一个完整的CAN报文。

反直觉知识点①:总线上任何节点 只要认为这个帧的结构正确,都会在ACK位回显性0,不管需不需要这一帧的ID和数据。为什么不需要的节点也会回ACK?因为等MCU算完会造成这一位的延迟,搅乱总线时序,不如只保障链路层本身的格式正确,纯芯片数字逻辑实现无延迟。嗯,90年代的总线要求不要太高。

反直觉知识点②:发送节点若发现自己这一帧没有ACK回应,它也会认为总线出错,重发16次后进入Passive error状态,有兴趣的自行研究一下,这里不做展开。

3、真实CAN波形

来看一个两节点案例:若节点A发送0x9E报文到总线,从节点B收到的总线波形和逻辑侧波形如下:

嵌入式~CAN-专辑2_嵌入式硬件_23

黄线为CAN-H,绿线为CAN-L,蓝线为节点B的逻辑侧RX,紫线为节点B的逻辑侧TX。可以看到,作为接收方的节点B,总线拉差分电压拉出显性的时候,收发器将RX拉0给到MCU。在节点B想要回应ACK的时候,MCU将TX拉0,CAN收发器在总线上拉出了一个歪斜的显性(歪斜是因为测量点的寄生电感影响)。RX在ACK位置的0,是收发器TX=0驱动总线显性之后 回读到的0。

再看一个比较真实的车上波形,CAN网络上大于4个节点:

嵌入式~CAN-专辑2_嵌入式硬件_24

黄色是CAN_H,高电平表示显性0,绿色是我们挂示波器这个节点的逻辑侧TX,低电平表示显性0。箭头A~D是一帧完整的CAN报文,箭头A ~ B这个过程中,我们挂示波器的这个节点和另一个节点正在进行优先级仲裁,根据我们之前讲到的物理层的分压原理,两个节点同时驱动电压会高一截。在箭头C这个bit 该节点想发隐性1但发现总线是显性0,那就说明有另外的节点在发送更高优先级的报文,我们这个节点会主动退出发送,成为接收节点,并在箭头D点校验成功后回应ACK,等待报文结束后这个节点再次参与总线仲裁,成功抢占总线如E点所示。

注意波形高度,在箭头A~B之间,差分电压略高于2V,这是正常现象,说明有两个节点同时驱动总线显性,但从逻辑看,因为H-L>0.7V所以都为显性0,纯数字逻辑的CAN控制器在箭头A~B之间还感知不到对方的存在,箭头C点之后才感知得到;而在箭头D点,因为除了发送节点之外的所有节点都在同时驱动ACK,所以总线电压比箭头A~B之间的双节点驱动 电压更高。

4、错误帧

终于到了错误帧,注意,错误帧不是由哪个节点发出的,而是由某个接收节点认为总线错误,才故意驱动总线打断发送方,在总线上呈现为一个错误帧。也就是说错误帧 一定是由 一个发送节点和至少一个 认为发送方有错的节点 共同形成的。

5、位填充

位填充规则是CAN协议的灵魂,简单来讲就几个字:逢五补一。当发送节点想要发连续5个bit的显性0的数据,会故意插入一个无意义的隐性1;当出现连续5个bit的隐性1,会故意插入一个无意义的显性0,如下图的紫色bit。如果发送节点漏填了这个0/1,或者这个0/1被干扰成了1/0,接收节点就会判定为“填充错误”,向总线上输出“主动错误标志”——连续六个显性0,故意破坏这一帧报文,发送节点感知到总线错误之后停止发送这一报文的后续部分。你说巧妙不巧妙?连续6个显性0本身就是破坏“逢五补一”规则的,被拿来当错误标志回给发送节点。

嵌入式~CAN-专辑2_嵌入式硬件_25

假如原始数据是0x00,二进制0000 0000,发送节点发到0000 0的时候发送节点会先插一个1,再发后续的000,成为0000 01000,共9bit长度,接收节点也会在第5bit的0之后预期一个无效的1,解码时抠掉。

假如原始数据是0000 0100,第六位自带1,发送节点发到00000的时候也会先插一个1,再发后续的100,成为0000 01100,共9bit长度。

6、回读确认

发送节点发送了0或1的时候,会回读确认总线是否和自己的发送相符,比如在仲裁段抢优先级失败就会等下一帧再发;如果发到了数据段,按理说此时总线应该只有自己,发着发着突然发现回读的0/1与自己发的不同,比如受到了干扰,发送节点就会输出“主动错误标志”——连续6bit显性0,来主动抛弃后续报文,同时让接收节点知道我这一帧有误。

在这时,接收节点收到第6bit显性0的时候,因违背逢五补一的位填充规则,也会往总线上输出“主动错误标志”,所以会在总线上看到连续12bit的显性0,前6个来自发送节点,后6个来自接收节点。

正常情况来说,总线上的显性不应该>5bits=10us。那么用示波器设置>11us的脉宽触发模式就很容易定位错误帧的位置,不一定要用解码示波器。

7、升维打击

CAN网络的幅度和电流可以为我们提供更多维度的信息,此所谓升维打击。

我们先来看一个正常帧,我们叫它节点A吧,它内部有终端电阻,蓝线为H-L的差分电压,紫线是我们节点A的CAN-H引脚电流,输出为正,输入为负。

嵌入式~CAN-专辑2_嵌入式硬件_26

先看蓝色的总线电压波形,从0x83到END之间是一帧正常波形,注意看帧头有多级台阶,帧尾ACK位置也特别高,这是正常的,可以理解 当多个节点同时驱动总线就会导致60Ω终端电阻上的分压高于2V。从这些台阶来看,可以判断出网络上至少有5个节点。为啥?先看报文中部的幅度,这肯定是只有一个节点抢占总线之后的波形,往前有两级台阶,可以认为A、B、C三个节点同时抢占总线出现了第一个高台,然后节点C优先级仲裁失败退出总线,A、B节点继续抢占出现了第二个台阶,之后节点A成功抢占到了总线优先级,发送中间的数据。最后的ACK位比3节点驱动的第一个bit更高,说明至少有4个节点在驱动ACK,再加上节点A,网络上至少有5个节点。

再看紫色的电流波形,已知节点A自己有终端电阻,外边有另一颗终端电阻。波形中部的数据区肯定是节点A在驱动总线,差分电压流经外边的终端电阻形成回路,所以我们在节点A的引脚上观察到了输出的正向电流;往前一个电压台阶的位置,电流为0,是A、B两个带终端电阻的节点在驱动总线,所以总线电压拉开了但电流仍是无进无出的;再往前一个台阶,A、B、C三个节点驱动,节点C的电流流入A、B的终端电阻,所以在节点A的引脚上测到了输入的负向电流;然后帧尾的ACK位置,至少有4个节点同时驱动,流入终端电阻A和B的负向电流更大了。

8、错误帧实战

这是一个两节点网络,一个节点发,另一个节点收,两方都有终端电阻,发送节点用的是TJA1042,接收节点用的是单3.3V收发器MAX3051。在帧头就发生了错误,这种错误帧一般源于时钟偏差或采样点过小。

我们将示波器的差分探头和电流探头挂在接收端,下图黄色为H-L的差分电压,蓝色为接收节点的输出电流,RX为收发器将H-L差分电压转换出的逻辑波形,MCU内部的CAN控制器会根据RX的0/1来解读总线。TX为接收节点的发送逻辑,MCU将TX拉低的时候收发器会往总线上驱动显性。

嵌入式~CAN-专辑2_嵌入式硬件_27

我们已知500kbps的每个bit宽2us,注意上图紫线TX在2 ~ 4箭头之间出现了连续2us * 6=12us的显性0,说明我们挂示波器的这个接收节点在此刻往外输出了一个“主动错误标志”,那一定是接收节点在此之前认为总线出现了错误。我们来往前看,箭头1~2之间总线差分电压和RX逻辑侧都只有10us/2us=5bit的显性0,帧前边都是长隐性1,这能有什么错?一个可能是我们碰到了传说中的过载帧,这个东西本应该很少见了;另一个可能是接收节点把对方来的正确报文认成了错的,这10us被接收节点认成了6bit,错误的采样点+硬同步(帧头对齐)做得稀烂的国产MCU更容易出现这样的帧头报错。

不管哪种,我们推演一下看看是否符合我们的理论,在箭头2~3之间发送节点应该是想发送一个隐性,但这时接收节点已经觉得不对开始发“主动错误标志”,将总线拉成了显性。然后发送节点读到这一bit自己想发送隐性但总线是显性,所以。。。仲裁区抢优先级失败退出总线,,,怎么可能,之前有6个连续显性呢,所以发送节点因违反“逢五补一”在箭头3~5也输出“主动错误标志”。所以就成了黄色总线波形的7个bit的“凸”型,中间的凸台的位置总线被两个节点驱动,电压高起一个台阶。

再注意一个细节,凸台的左肩膀和右肩膀高度不一样,左肩膀是接收节点MAX3051驱动的电平,它比 右肩膀TJA1042的驱动能力弱一些,总线电平低一点。这个特性可以用来区分总线上的不同设备。

蓝色线,是接收节点的输出电流。箭头1~2之间的负向电流为发送节点驱动总线,差分电压流经接收节点内部的终端电阻带来的负电流;箭头2~3之间的正电流是接收节点驱动的主动错误的第一个bit;后边3~4的凸台两个节点都在驱动显性 但对应的电流也是负的,这是因为发送节点的驱动能力强过接收节点,整个网络电流还是由发送节点灌入接收节点;再往后4~5的负电流是发送端驱动接收节点的终端电阻的电流。

下图我标出了两个节点的输出bit流,红框是“主动错误标志”。

嵌入式~CAN-专辑2_嵌入式硬件_28

这一帧的DLC=0x01,也就是只有1byte数据,数据区之后就是CRC区,我们的“主动错误标志”就发生在这个区,观察又没有填充错误,那就是我们挂示波器这个接收节点认为发送节点出现了CRC错误。但我们看到黄线在“主动错误标志”中间出现了凹坑,意味着发送节点还是想继续发隐性,并不认为自己有错,直到发现这一位被“主动错误标志”覆盖为显性才感知到位错误后抛弃后续报文。

原因最后定位到:过小的采样点+过大的再同步补偿宽度SJW让时钟误差逐步积累,这颗国产MCU的重同步又做得稀烂,把正常报文错读了一位导致算CRC错误。最后通过调整采样点和SJW宽度减少了这种错误的出现频次,得到正常波形如下:

嵌入式~CAN-专辑2_嵌入式硬件_29

错误发生在CRC区,我们放大一下,看看各节点都发生了啥:

嵌入式~CAN-专辑2_嵌入式硬件_30

从每一个台阶往前画12us的方框,得到每个节点输出的“主动错误标志”,分析可知:这是一帧节点B发送的报文,节点A认为它的CRC算错了,节点C凑了个热闹,三者一起形成了这个12bits长的“主动错误标志”。那,节点A为什么会认为CRC有错呢?大概率是因为之前的数据读错了一位。这么好的波形也能读错?是的,我们无法判断节点A所在的位置波形有多差,可能分支上没有终端电阻振铃很大呢?我们只能相信节点A不会乱搞。另外,采样点偏差会导致节点对噪声额外地敏感。

9、CAN-FD错误排查

来看一个A B C三节点CAN-FD错误帧的案例,节点C发,节点A、B收:黄色是H-L的差分电压,绿色是节点B的逻辑TX。0x0677和0x0176是两个错误帧。FD区波特率设置为2Mbps。

嵌入式~CAN-专辑2_嵌入式硬件_31

放大0x0176帧的细节:

嵌入式~CAN-专辑2_嵌入式硬件_32

  • 标尺A B之间时间长度约0.8us,由一个2Mbps FD bit的0.5us + 一个CAN-FD的TDC(300ns)组成。
  • 之后出现了6个FD bit(0.5us*6=3000ns)的连续显性位,电平高度与之前相同,之后有连续2usx6=12us的显性。
  • 在标尺B线后12us位置出现了一个电压跌落的小小的下降台阶,见下下图。

综上三条,认为节点C所在位置干扰过大/分支线路过长,节点C自己回读↓下图↓框出的bit位失败,自己往总线上输出“主动错误标志”(连续6bit=2usx6=12us的显性),其他设备在接收到第6个CAN-FD的bit=0.5usx6=3us的时候就读到了错误(违反FD速率的“逢五补一”规则),也往总线上叠加2us*6=12us的主动错误标志。然后,12us时节点C的主动错误标志先结束,其他节点的主动错误在2usx6+0.5usx6=15us后结束。至此,错误帧形态完成。

“逢五补一”这条规则是跟随波特率变化的,6个连续的高波特率0或1都会触发填充错误。但填充错误之后输出的“主动错误标志”是500kbps波特率的6bit,固定长度12us。

嵌入式~CAN-专辑2_嵌入式硬件_33

再来一个案例:CAN-FD采样点设置出错导致节点B把节点A发送的CAN-FD报文当CAN来解析出错。

黄色CAN_H,绿色CAN_L,蓝色L-H反向差分电压,紫线为节点B逻辑RX,青线为节点B逻辑TX。

嵌入式~CAN-专辑2_嵌入式硬件_34

注意看0x00前后的数据段,这一段是CAN-FD的2Mbps速率,节点B因为采样点设置错误读错了BRS这一波特率转换标志,仍按照标准的500kbps去解析节点A的2Mbps速率的数据,对RX信号2us一个采样我用黄色箭头标出来了,可以看到这恰好是6个连续显性0,违反“逢五补一”的规则,故而接收节点B在箭头2~4之间发“主动错误标志”,打断总线通信,告知发送节点你发错了。箭头2~3之间,发送节点A恰好也要发显性,所以节点A此时还没感觉到不对。箭头3之后,节点A想要拉隐性,电压出现一个坑,却发现总线还是显性,此时节点A判断出现了“位错误”,开始输出“主动错误标志”,想告知接收方放弃我这一帧报文。箭头4的位置节点B释放“主动错误标志”,箭头5的位置节点A释放“主动错误标志”。

如果数据比较巧,恰好能满足逢五补一的规则,那这种错误形态会在发送很多数据之后才会出现,但最晚也会被CRC拦截:

嵌入式~CAN-专辑2_嵌入式硬件_35

补充知识:CAN-FD网络各个节点的采样点必须完全相同,高速率导致对时序敏感很多,这一点与CAN网络容许一个范围显著不同。上边这一帧的BRS位怎么读错的呢?再一次违反直觉:CAN-FD的采样点影响发送节点的驱动波形!用示波器可以轻松量出FD的采样点位置。

看下图,CAN-FD报文的控制段中的BRS位(Bit Rate Switch)明显是短于前边的FDF、R0位的,采样点不匹配的话很容易读错。因为-FD的速率翻转是在这一bit的采样点位置发生的。比如采样点80%的2Mbps CAN-FD网络,BRS这一位的宽度为2us80%+0.5us20%=1.7us,而不是2us。接收节点的采样点如果设置大于85%就会错过整个BRS位(2us*85%=1.7us),从而导致如上的BRS位读错的问题。

嵌入式~CAN-专辑2_嵌入式硬件_36

以上,就是示波器升维破解CAN错误帧/BusOff的经验分享,总结一下:

  • 结合已知ID是哪个节点发的先验信息,逐个拔掉非终端节点,示波器观察“主动错误标志”,就能模糊定位错误源头;
  • 如果能引出敏感设备的TX,哪个节点认为哪个节点出了什么错就会非常清晰明了;
  • 其次,测量CAN的输出电流也能清楚地定位谁在驱动“错误标志”,进而找到故障点;
  • 如果上述难以实现,以12us间隔拆分“错误标志”的电压台阶,也能定位大部分错误原因;
  • 额外关注单bit宽度的电压台阶,能排除部分节点;

四、CAN比UART更难吗?

#1、CAN总线波特率

CAN总线属于异步通信,因此就有通信波特率,而这个波特率发生器就位于CAN控制器内部。我们不需要了解它是如何产生的,但需要了解它的含义。这章节针对初学者讲述以下两点内容。

1、异步通信

在串行通信中,主要分异步通信和同步通信

同步通信:通信设备之间通过同步信号(CLK时钟)来实现数据传输的通信叫同步通信。如I2C、SPI这类通信中都具有一个时钟信号,其实在STM32中USART也具有同步功能,只是我们大多数人都只用了它的异步功能。

异步通信:简单来说,就是通信设备之间通过约定一样的时间来收发数据。而这个时间就会决定本节说的波特率。

2、波特率

很多工程师一直都没彻底搞明白什么是波特率,我这里还是结合UART波特率来简述一下其含义。

在电子通信领域,波特(Baud)即调制速率,指的是有效数据信号调制载波的速率,即单位时间内载波调制状态变化的次数。它是对符号传输速率的一种度量,1波特即指每秒传输1个符号

UART每秒钟传送240个字符,而每个字符格式包含10位(1个起始位,1个停止位,8个数据位),这时的波特率为240Bd,比特率为10位*240个/秒=2400bps

从上面的描述可以总结:

  • 比特率:即单位时间内传送的二进制位数
  • 波特率:即单位时间内传输的符号个数

只有在每个符号只代表一个比特信息的情况下,波特率与比特率才在数值上相等,但是它们的意义并不相同。

#2、位时序

上面讲述了波特率,而决定波特率大小的就是本节说的位时序。在CAN标准中一个位可分为4段:

  • 同步段(SS)
  • 传播时间段(PTS)
  • 相位缓冲段1(PBS1)
  • 相位缓冲段2(PBS2)

这些段又由可称为 Time Quantum(简称Tq)的最小时间单位构成。

1位分为4个段,每个段又由若干个Tq构成,这称为位时序。

而在STM32参考手册中,将位时序分为三段,但它将它传播段和位段1合并在一起了,如下图所示:

嵌入式~CAN-专辑2_嵌入式硬件_37

1位由多少个Tq构成、每个段又由多少个Tq构成等,可任意设定位时序。通过设定位时序,决定传输的波特率:

嵌入式~CAN-专辑2_嵌入式硬件_38

这几个参数会在以后编程中进行配置,从而决定通信的波特率

关于同步,还有硬件同步、再同步等操作。但初学者可以不必过多理解,掌握上面基础内容就行了。更多关于位时序的内容可以参看 ISO 11898 标准。

#3、帧类型及格式说明

CAN总线是通过以下5种类型的帧进行通信:

  • 数据帧:用于发送单元向接收单元传送数据的帧
  • 遥控帧:用于接收单元向具有相同 ID 的发送单元请求数据的帧
  • 错误帧:用于当检测出错误时向其它单元通知错误的帧
  • 过载帧:用于接收单元通知其尚未做好接收准备的帧
  • 帧间隔:用于将数据帧及遥控帧与前面的帧分离开来的帧

数据帧和遥控帧有标准格式和扩展格式两种格式,标准格式有11个位的标识符ID,扩展格式有29个位的ID。

1、 数据帧

嵌入式~CAN-专辑2_嵌入式硬件_39

如上图所示,数据帧由7个段构成:

  • 帧起始:表示数据帧开始的段
  • 仲裁段:表示该帧优先级的段
  • 控制段:表示数据的字节数及保留位的段
  • 数据段:数据的内容,可发送 0~8 个字节的数据
  • CRC段:检查帧的传输错误的段
  • ACK段:表示确认正常接收的段
  • 帧结束:表示数据帧结束的段

理解数据帧的含义,请从认真理解它的定义:用于发送单元向接收单元传送数据的帧。

一般的CAN总线通信,总线上通信绝大部分时候都是数据帧。像在CANOpen协议中,用的最多的PDO过程数据对象就是通过数据帧进行的通信。

初学者可以先理解数据帧,然后其他就容易理解了。下面,我们再来讲述一下数据帧7段的详情

帧起始:标准和扩展格式相同。表示帧开始的段,1个位的显性位,如下图所示:

嵌入式~CAN-专辑2_嵌入式硬件_40

总线上的电平有显性电平和隐性电平两种。总线上执行逻辑上的线“与”时,显性电平的逻辑值为“0”,隐性电平为“1”。

“显性”具有“优先”的意味,只要有一个单元输出显性电平,总线上即为显性电平。并且,“隐性”具有“包容”的意味,只有所有的单元都输出隐性电平,总线上才为隐性电平。(显性电平比隐性电平更强)

仲裁段:标准格式和扩展格式在此的构成有所不同。仲裁段表示该帧优先级的段,扩展格式多了18位ID,如下图所示:

嵌入式~CAN-专辑2_嵌入式硬件_41

RTR = 0代表数据帧,RTR = 1代表远程帧。

为什么叫仲裁段,就是通过ID来判断总线上哪一个节点具有优先发送的权利。ID越小(0代表显性),优先级越高。

控制段:标准和扩展格式的构成有所不同。控制段由 6 个位构成,如下图所示:

嵌入式~CAN-专辑2_嵌入式硬件_42

它们除了都有4位表示数据段长度代码(DLC)外,标准帧有IDE(数值为0)位和r0保留位,扩展帧有r0和r1保留位。

保留位必须全部以显性电平发送。但接收方可以接收显性、隐性及其任意组合的电平。

数据段:标准和扩展格式相同。数据段表示传输数据的内容,从 MSB(最高位)开始输出,可发送 0~8 个字节的数据,长度由前面控制段决定。

CRC段:标准和扩展格式相同。CRC段是检查帧传输错误的帧,由 15 个位的 CRC 顺序和 1 个位的 CRC 界定符(用于分隔的位)构成。

嵌入式~CAN-专辑2_嵌入式硬件_43

相比485这类通信,CAN控制器就已经把CRC校验做了,不需要你的程序再次去计算,从而节约了处理器资源。

ACK段:标准和扩展格式相同。ACK段用来确认是否正常接收。由 ACK 槽(ACK Slot)和 ACK 界定符 2 个位构成。

嵌入式~CAN-专辑2_嵌入式硬件_44

A. 发送单元在 ACK 段发送 2 个位的隐性位。

B. 接收到正确消息的单元在 ACK 槽(ACK Slot)发送显性位, 通知发送单元正常接收结束。这称作“发送 ACK”或者“返回 ACK”。

帧结束:标准和扩展格式相同。帧结束是表示该该帧的结束的段。由 7 个位的隐性位构成。

嵌入式~CAN-专辑2_嵌入式硬件_45

2、遥控帧

嵌入式~CAN-专辑2_嵌入式硬件_46

和数据帧相比,遥控帧是接收单元向发送单元请求发送数据所用的帧。所以,遥控帧没有数据段。因此,遥控帧由如下 6 个段组成:

  • 帧起始(SOF):表示帧开始的段
  • 仲裁段:表示该帧优先级的段。可请求具有相同ID的数据帧
  • 控制段:表示数据的字节数及保留位的段
  • CRC段:检查帧的传输错误的段
  • ACK段:表示确认正常接收的段
  • 帧结束:表示遥控帧结束的段

这6个段和上面数据帧的内容基本一样,这里就不一一讲述了。下面,讲一下遥控帧和数据帧的区别:

一是,遥控帧的 RTR 位为隐性位,没有数据段;二是,没有数据段的数据帧和遥控帧可通过 RTR 位区别开来。

问题一:遥控帧没有数据段,数据长度码该如何表示?

遥控帧的数据长度码以所请求数据帧的数据长度码表示。

问题二:没有数据段的数据帧有何用途?

例如,可用于各单元的定期连接确认/应答、或仲裁段本身带有实质性信息的情况下。

3、错误帧

嵌入式~CAN-专辑2_嵌入式硬件_47

用于在接收和发送消息时检测出错误通知错误的帧。错误帧由错误标志和错误界定符构成。

(1) 错误标志

错误标志包括主动错误标志和被动错误标志两种:

  • 主动错误标志:6 个位的显性位
  • 被动错误标志:6 个位的隐性位

(2) 错误界定符

错误界定符由 8 个位的隐性位构成。

4、过载帧

嵌入式~CAN-专辑2_嵌入式硬件_48

过载帧是用于接收单元通知其尚未完成接收准备的帧。过载帧由过载标志和过载界定符构成:

过载标志:6 个位的显性位,过载标志的构成与主动错误标志的构成相同。

过载界定符:8 个位的隐性位,过载界定符的构成与错误界定符的构成相同。

5、帧间隔

嵌入式~CAN-专辑2_嵌入式硬件_49

帧间隔是用于分隔数据帧和遥控帧的帧。数据帧和遥控帧可通过插入帧间隔将本帧与前面的任何帧(数据帧、遥控帧、错误帧、过载帧)分开。

过载帧和错误帧前不能插入帧间隔。

(1) 间隔

3 个位的隐性位。

(2) 总线空闲

隐性电平,无长度限制(0 亦可);本状态下,可视为总线空闲,要发送的单元可开始访问总线。

(3) 延迟传送(发送暂时停止)

8 个位的隐性位,只在处于被动错误状态的单元刚发送一个消息后的帧间隔中包含的段。

五、CAN总线详解

1、简介

CAN是控制器局域网络(Controller Area Network, CAN)的简称,是一种能够实现分布式实时控制的串行通信网络。

优点:

传输速度最高到1Mbps,通信距离最远到10km,无损位仲裁机制,多主结构。近些年来,CAN控制器价格越来越低。

Ø 低成本:ECUs通过单个CAN接口进行通信,布线成本低。

Ø 高集成:CAN总线系统允许在所有ECUs上进行集中错误诊断和配置。

Ø 可靠性:该系统对子系统的故障和电磁干扰具有很强的鲁棒性,是汽车控制系统的理想选择。

Ø 高效率:可以通过id对消息进行优先级排序,以便最高优先级的id不被中断。

Ø 灵活性:每个ECU包含一个用于CAN总线收发芯片,随意添加CAN总线节点。

2、CAN总线网络

嵌入式~CAN-专辑2_嵌入式硬件_50

CAN总线网络主要挂在CAN_H和CAN_L,各个节点通过这两条线实现信号的串行差分传输,为了避免信号的反射和干扰,还需要在CAN_H和CAN_L之间接上120欧姆的终端电阻。为什么是120Ω,因为电缆的特性阻抗为120Ω,为了模拟无限远的传输线。

3、CAN收发器

CAN收发器的作用是负责逻辑电平和信号电平之间的转换。

嵌入式~CAN-专辑2_嵌入式硬件_51

即从CAN控制芯片输出逻辑电平到CAN收发器,然后经过CAN收发器内部转换将逻辑电平转换为差分信号输出到CAN总线上,CAN总线上的节点都可以决定自己是否需要总线上的数据。具体的引脚定义如下:

嵌入式~CAN-专辑2_嵌入式硬件_52

4、CAN信号表示

CAN总线采用不归零码位填充技术,也就是说CAN总线上的信号有两种不同的信号状态,分别是显性的(Dominant)逻辑0和隐形的(recessive)逻辑1,信号每一次传输完后不需要返回到逻辑0(显性)的电平。

嵌入式~CAN-专辑2_嵌入式硬件_53

显性与隐性电平的解释:

CAN的数据总线有两条,一条是黄色的CAN_High,一条是绿色的CAN_Low。当没有数据发送时,两条线的电平一样都为2.5V,称为静电平,也就是隐性电平。当有信号发送时,CAN_High的电平升高1V,即3.5V,CAN_Low的电平降低1V,即1.5V。

按照定义的:

  • CAN_H-CAN_L < 0.5V 时候为隐性的,逻辑信号表现为"逻辑1"- 高电平。
  • CAN_H-CAN_L > 0.9V 时候为显性的,逻辑信号表现为"逻辑0"- 低电平。

5、CAN信号传输

发送过程: CAN控制器将CPU传来的信号转换为逻辑电平(即逻辑0-显性电平或者逻辑1-隐性电平)。CAN发射器接收逻辑电平之后,再将其转换为差分电平输出到CAN总线上。

嵌入式~CAN-专辑2_嵌入式硬件_54

接收过程: CAN接收器将CAN_H 和 CAN_L 线上传来的差分电平转换为逻辑电平输出到CAN控制器,CAN控制器再把该逻辑电平转化为相应的信号发送到CPU上。

嵌入式~CAN-专辑2_嵌入式硬件_55

5、CAN数据传输

CAN总线传输的是CAN帧,CAN的通信帧分成五种,分别为数据帧、远程帧、错误帧、过载帧和帧间隔。

数据帧根据仲裁段长度不同分为标准帧(2.0A)和扩展帧(2.0B)

嵌入式~CAN-专辑2_嵌入式硬件_56

帧起始

由一个显性位(低电平)组成,发送节点发送帧起始,其他节点同步于帧起始;

帧结束

由7个隐形位(高电平)组成。

嵌入式~CAN-专辑2_嵌入式硬件_57

仲裁段

只要总线空闲,总线上任何节点都可以发送报文,如果有两个或两个以上的节点开始传送报文,那么就会存在总线访问冲突的可能。但是CAN使用了标识符的逐位仲裁方法可以解决这个问题。

CAN总线控制器在发送数据的同时监控总线电平,如果电平不同,则停止发送并做其他处理。如果该位位于仲裁段,则退出总线竞争;如果位于其他段,则产生错误事件。

嵌入式~CAN-专辑2_嵌入式硬件_58

帧ID越小,优先级越高。由于数据帧的RTR位为显性电平,远程帧为隐性电平,所以帧格式和帧ID相同的情况下,数据帧优先于远程帧;由于标准帧的IDE位为显性电平,扩展帧的IDE位为隐形电平,对于前11位ID相同的标准帧和扩展帧,标准帧优先级比扩展帧高。

嵌入式~CAN-专辑2_嵌入式硬件_59

数据段

一个数据帧传输的数据量为0~8个字节,这种短帧结构使得CAN-bus实时性很高,非常适合汽车和工控应用场合如图27所示。

嵌入式~CAN-专辑2_嵌入式硬件_60

数据量小,发送和接收时间短,实时性高,被干扰的概率小,抗干扰能力强。