2023暑假电控组培训第四讲——CAN

  • 前言
    在RoboMaster比赛中,无论是电机驱动还是板间通信,CAN通信都发挥了极其重要的作用,但是相比于其它基本知识,CAN是一个相对而言比较复杂的通讯协议,有着更多的特性需要去记忆,也因此成为了很多RMer电控入门阶段的美好回忆,本篇文章将从理论出发,结合实践,讲解如何使用CAN通信。
  • 什么是CAN通信?
    CAN 是控制器域网 (Controller Area Network, CAN) 的简称,是由研发和生产汽车电子产 品著称的德国 BOACH 公司开发,并最终成为国际标准(ISO11898),CAN 是国际上应用 最广泛的现场总线之一。 在北美和西欧,CAN 总线协议已经成为汽车计算机控制系统和嵌 入式工业控制局域网的标准总线,并且拥有以 CAN 为底层协议专为大型货车和重工机械车 辆设计的 J1939 协议。近年来,它具有的高可靠性和良好的错误检测能力受到重视,被广泛应用于汽车计算机控制系统和环境温度恶劣、电磁辐射强及振动大的工业环境。
  • 理论部分
    • 硬件层面
      • 信息传递方式——差分信号​​

        CAN控制器通过组成总线的2根线(CAN-H和CAN-L)的电位差来确定总线的电平,信号是以两线之间的“差分”电压形式出现,总线电平分为显性电平和隐性电平。

        这样,通过采用两种互补的逻辑数值"显性"和"隐性"的方法,用"显性"数值表示逻辑"0",而"隐性"表示逻辑"1"。从而,CAN总线仅通过CAN_H与CAN_L两条线即可实现信息的传递。
        这时我们可以联想到之前学过的另一种通信方式UART

        CAN UART
        比赛中应用 板间通信,大多数电机驱动 与操作手通信
        总线结构 多主机、多从机的总线结构,其中多个节点可以同时发送和接收消息 点对点连接,只有一个发送器和一个接收器
        通信速率 支持高速传输,可以实现较高的数据速率,1 Mbps或更高 低速通信,通常在几十 kbps 到几 Mbps 的范围内
        数据帧格式 使用帧格式来发送和接收数据,其中包括标识符、数据和检验位 异步通信,使用起始位、数据位、校验位和停止位来传输数据
        可靠性和冲突处理 具有较高的可靠性和抗干扰能力,可以检测和处理冲突,确保数据的完整性和正确性 较为简单,没有内置的错误检测和冲突处理机制

        部分内容将会在下面详细讲解

      • 帧仲裁
        在编写控制程序时,我们往往需要处理来自多个传感器或上位机的数据,那么当我们用CAN传递信息时,如果多个模块同时向我们反馈信息,如果不对顺序进行安排,数据将会混在一起无法解算。这种情况下就需要进行仲裁,判断哪个设备可以占用总线,而其他设备要转变为接收或者等待

        CAN的仲裁机制巧妙地利用了差分信号的特性,即显性电平覆盖隐形电平的特性,如果出现多个设备同时发送的情况,则先输出隐形电平的设备会失去对总线的占有权。下图中D为显性电平,R为隐形电平,通过该图可以很容易地理解CAN的仲裁机制。

      • 波特率

        CAN有着很高的通讯速率,通过查阅手册可知,一般RM系列电调的通讯速率为1Mbps,只有波特率一致的情况下,主控才能成功与电调进行通讯,CAN的通讯速率的决定因素包括以下四点:

        • 同步段 (SYNC_SEG) :位变化应该在此时间段内发生。只有一个时间片的固定长度 (1 x tq )

        • 位段 1(BS1) :定义采样点的位置。其持续长度可以在 1 到 16 个时间片之间调整

        • 位段 2(BS2) :定义发送点的位置。其持续长度可以在 1 到 8 个时间片之间调整

        • 同步跳转宽度( SJW ):定义位段加长或缩短的上限。它可以在 1 到 4 个时间片之间调整

        CAN 总线波特率和 tq (Time Quantum),tBS1 (Time Quanta in Bit Segment 1)和 tBS2 (Time Quanta in Bit Segment 1) 的值直接相关,tq 通过总线分频后直接得到,tBS1和 tBS2 则通过 TS1 和 TS2 放大为 tq 的若干倍,需要注意的是下图中 tBS1 和 tBS2 的计 算是通过 tq(TS1+1)和 tq(TS2+1)得到的,而在 CubeMX 中,我们配置的 Time Quanta in Bit Segment 的值对应的就是 (TS1+1)和(TS2+1)。至于CAN究竟挂在哪个总线上就需要查阅数据手册了

    • 软件层面
      • 过滤器
        把CAN总线看作是一个兵种组,每天会为组员安排不同任务,但是队员们并不是每个人都要做全部工作的,不加区分地把数据传给每一个人只会导致工作的混乱。因此我们可以在任务前面加上标识,如电控任务,机械任务,电路任务,视觉任务让每个人只接收自己技术组的任务,而直接过滤掉与自己无关的工作

        事实上,bxCAN的过滤器就是采用上述方法,你只需要设置好你感兴趣的那些CAN报文ID,那么MCU就只能收到这些CAN报文,是从硬件上过滤掉,完全不需要软件参与进来,从而节省了大大节省了MCU的时间,可以更加专注于其他事务,这个就是bxCAN过滤器的意义所在。

        CAN的过滤器模式分为掩码模式列表模式

        • 列表模式

          简单来说就是制作一张ID表,如果来的数据的ID在这张表中则接收,否则不收。

        • 掩码模式

          假设某学校的学工号是 入学年份+学生序号,那么,我想要确定学生的年级的话只需要看学工号就可以了,但是为了省事,其实我们完全可以把学工号后面的序号忽略,只看前面的入学年份部分。这就是掩码模式。

      • 标准数据帧

        CAN的一个标准数据帧包括以下几个部分

        • 仲裁场

          仲裁场包括12位标识符,作用在前面帧仲裁处已进行讲解
        • 控制场

          仲裁场后跟随的是控制场,存放数据长度DLC,数据场中要填写CAN发送的数据
  • 实践部分
    • CUBE配置

      1.选择外部高速晶振

      2.Debug选Serial Wire

      3.配置时钟树

      接下来结合时钟树讲解一下波特率的计算配置

      经查阅,我们知道STM32F407IGHX的CAN挂在APB1总线上,因此我们在时钟树上找到APB1,外设频率为42MHz

      打开CAN

      此时我们设的预分频(Prescaler)为3,所以$$t_q=\frac{1}{f}=\frac{1}{\frac{42MHz}{3}}=\frac{1}{14}ns$$

      因而,由理论部分的公式求得:波特率(Baud Rate)Rb=1000000bit/s.

      选取工作模式为 Normal

      关于各种工作模式,介绍如下:

      工作模式 模式说明 详细说明
      Normal 正常模式 可发可收
      Loopback 回环模式 自发自收
      Silent 静默模式 只收不发
      Loopback combine with Silent 回环静默模式
      记得打开中断以接收。
    • 代码部分

      • 发送初始化

        • 变量含义

          StdId:CAN总线消息的标准ID

          IDE:指定CAN总线消息的标识符扩展位

          RTR:指定CAN总线消息的远程传输请求位

          DLC:指定CAN总线消息的数据长度码

            chassis_tx_message.StdId = 0x222; //标准ID
            chassis_tx_message.StdId = 0x222; //标准ID
            chassis_tx_message.RTR = CAN_RTR_DATA;//设置了远程传输请求位
            chassis_tx_message.IDE = CAN_ID_STD;//使用了标准的11位标识符
            chassis_tx_message.DLC = 0x08;    //8字节长
          
      • 接收初始化

            CAN_FilterTypeDef CAN_FilterType;
            CAN_FilterType.FilterBank=0;                        //使用0号过滤器                   
            CAN_FilterType.FilterIdHigh=0x0000;                 //设置验证码高低各4字节
            CAN_FilterType.FilterIdLow=0x0000;
            CAN_FilterType.FilterMaskIdHigh=0x0000;             //设置屏蔽码高低各4字节
            CAN_FilterType.FilterMaskIdLow=0x0000;
            CAN_FilterType.FilterFIFOAssignment=CAN_RX_FIFO0;   //通过CAN的信息放入0号FIFO
            CAN_FilterType.FilterMode=CAN_FILTERMODE_IDMASK;    //采用掩码模式
            CAN_FilterType.FilterScale=CAN_FILTERSCALE_32BIT;   //设置32位宽
            CAN_FilterType.FilterActivation=ENABLE;             //激活滤波器
            CAN_FilterType.SlaveStartFilterBank=14;
            HAL_CAN_ConfigFilter(hcan, &CAN_FilterType);        //配置过滤器
            HAL_CAN_Start(hcan);                                //开启CAN总线
            HAL_CAN_ActivateNotification(hcan,CAN_IT_RX_FIFO0_MSG_PENDING);//激活CAN接收
        
      • 发送函数

        HAL_StatusTypeDef HAL_CAN_AddTxMessage(CAN_HandleTypeDef *hcan, CAN_TxHeaderTypeDef *pHeader, uint8_t aData[], uint32_t *pTxMailbox)
        
        函数名 HAL_CAN_AddTXMessage
        函数功能 将一段数据通过 CAN 总线发送
        返回值 HAL_StatusTypeDef,HAL 库定义的几种状态,如果本次 CAN 发送成功, 则返回 HAL_OK
        参数1 CAN_HandleTypeDef *hcan,即 can 的句柄指针,如果是 can1 就输入&hcan1,can2 就输入&hcan2
        参数2 CAN_TxHeaderTypeDef *pHeader,待发送的 CAN 数据帧信息的结构体指 针,包含了 CAN 的 ID,格式等重要信息
        参数3 uint8_t aData[],装载了待发送的数据的数组名称
        参数4 uint32_t *pTxMailbox,用于存储 CAN 发送所使用的邮箱号

        实例:队内CAN发送函数

        /**
            * @brief      向CAN总线发送信息
            * @param      phcan: 指向CAN句柄的指针
            * @param      txdata: 要发送的信息
            * @retval     无
            */
            void Can_SendMessage(CAN_HandleTypeDef* phcan, CAN_TxHeaderTypeDef* pheader, uint8_t txdata[])
            {
                uint32_t mailbox;
                /* Start the Transmission process */
                uint32_t ret = HAL_CAN_AddTxMessage(phcan, pheader, txdata, &mailbox);
                if (ret != HAL_OK) {
                    /* Transmission request Error */
                
                        Can_Error_Source = 5;
                        Can_Error_Message_Header = *pheader;
                        Can_ErrorHandler(ret);
                }
            }
        
      • 接收函数

        HAL_StatusTypeDef HAL_CAN_GetRxMessage(CAN_HandleTypeDef *hcan, uint32_t RxFifo, CAN_RxHeaderTypeDef *pHeader, uint8_t aData[]) 
        
        函数名 HAL_CAN_GetRxMessage
        函数功能 接收 CAN 总线上发送来的数据
        返回值 HAL_StatusTypeDef,HAL 库定义的几种状态,如果本次 CAN 接收成功, 则返回 HAL_OK
        参数1 CAN_HandleTypeDef *hcan,即 can 的句柄指针,如果是 can1 就输入&hcan1,can2 就输入&hcan2
        参数2 uint32_t RxFifo,接收时使用的 CAN 接收 FIFO 号,一般为 CAN_RX_FIFO0
        参数3 CAN_RxHeaderTypeDef *pHeader,存储接收到的 CAN 数据帧信息的结构 体指针,包含了 CAN 的 ID,格式等重要信息
        参数4 uint8_t aData[],存储接收到的数据的数组名称

        实例:CAN接收电机反馈值

        void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) 
        {
            
        HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &chassis_rx_message, rxData);
        int i = (int)chassis_rx_message.StdId - 0x204 - 1;
        wheelrxdata.rxangle[i] = ((rxData[0]<<8) | rxData[1]);
            wheelrxdata.rxspeed[i] = ((rxData[2]<<8) | rxData[3]);
            wheelrxdata.rxI[i] = ((rxData[4]<<8) | rxData[5]);
            wheelrxdata.rxT[i] = rxData[6];
        return;
        }
        
      • 总结:

        使用CAN通信的基本思路:

        • 按需求初始化发送/接收(一般是两者兼有)
        • 开启发送函数
        • 书写中断回调函数
        • 得到数据,对数据进行处理以进行进一步操作
posted @ 2023-07-01 15:41  北京理工大学机器人队  阅读(182)  评论(0编辑  收藏  举报