C/C++教程

STM32CubeMX教程21 CAN - 双机通信

本文主要是介绍STM32CubeMX教程21 CAN - 双机通信,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

1、准备材料

开发板(正点原子stm32f407探索者开发板V2.4)

STM32CubeMX软件(Version 6.10.0)

野火DAP仿真器

keil µVision5 IDE(MDK-Arm)

ST-LINK/V2驱动

CH340G Windows系统驱动程序(CH341SER.EXE)

XCOM V2.6串口助手

2、实验目标

使用STM32CubeMX软件配置两台STM32F407开发板的CAN1模块实现双机通信

3、实验流程

3.0、前提知识

3.0.1、CAN总体概述

STM32F407内部有两个CAN控制器,其中CAN1做为主CAN拥有所有的权限,而CAN2做为从CAN不能单独设置验证筛选器,每个CAN都有3个发送邮箱和两个接收FIFO,每个接收FIFO可以存储三条完整消息,具体的CAN框图如下图所示 (注释1)

CAN的总线网络结构有开环和闭环两种形式

闭环网络结构下,两根信号线H/L组成一个环路,在网络环路的两端连接120欧姆的电阻,这种网络是一种高速、短距离的CAN网络,通信速率最高1Mbit/s;

开环网络结构下,两根信号线H/L各自独立,在两根信号线上各自串联一个2.2千欧的电阻,这种网络是一种低速、远距离的CAN网络,通信速率最高125kbit/s;

如下图所示为开环/闭环的CAN总线网络结构 (注释2)

不管是开环还是闭环CAN网络结构都可以挂载多个节点,CAN网络上的每个节点由CAN控制器和CAN收发器组成,STM32F407内部集成的是CAN控制器,因此硬件设计时需要外部搭载CAN收发器才可以组成一个完整的CAN节点,如下图所示为笔者使用的开发板上搭载的CAN收发器芯片硬件原理图

这里CAN收发器芯片的TX/RX引脚并没有直接与单片机CAN1控制器的TX/RX引脚相连接,而是通过了一个跳线帽来进行调节,读者在做该实验的时候应该注意USB/CAN排针座应该利用跳线帽将CAN_TX/RX引脚与USB_D+/D-进行短接,具体硬件原理图如下图所示

3.0.2、CAN位时序和波特率

CAN通信是一种异步通信,异步通信的收发双方无时钟同步,因此需要确保收发双方发送/接收一帧数据的帧格式和波特率保持一致,这样才能保证收发双方正确的进行通信

CAN1/2挂载在APB1最高42MHz的时钟总线上,其一个时间片的长度由PCLK1频率和CAN分频参数决定,假设PCLK1频率为25MHz,CAN分频参数位5,则一个时间片的长度为5/25Mhz=0.0000002s=0.0002ms=0.2us=200ns

CAN网络上一个节点采集一个位数据的时序叫做位时序,位时序由同步段(SYNC_SEG)、位段 1(BS1)和位段2(BS2)三段组成,其中同步段固定为一个时间片,在该段总线上应该发生一次位信号的跳变;位段1定义了采样点的位置,其可以是1-16个时间片;位段2定义了发送点的位置,其可以是1-8个时间片;

除了上面几个可调节的参数外,CAN还有一个再同步跳转宽度(SJW)参数可以调节,其取值可以是1-4个时间片,通过调节该参数长短决定了CAN再同步时自动调节位段1和位段2长度缩短/加长的上限,此处笔者未深究该参数

通过调节时钟分频、位段1、位段2和再同步跳转宽度SJW四个参数,就可以确定CAN通信的波特率,如下图所示位位序数的结构图 (注释1)

3.0.3、CAN帧格式

CAN通信过程*有数据帧、遥控帧、错误帧、过载帧和帧间空间五种不同用途的帧,其中数据帧和遥控帧又有标准格式的帧和扩展格式的帧两种,数据帧可以理解为CAN网络上的节点发送消息ID+要发送的数据,遥控帧可以理解为CAN网络上某个节点需要另外一个节点的数据,收到遥控帧的节点就发送对应的数据给请求数据的节点,这里不详细介绍每个帧的格式,想要知道具体帧格式的读者请阅读其他文章,如下图所示为CAN数据/遥控帧一帧的结构图 (注释1)

在HAL库中有一个CAN发送消息头结构体CAN_TxHeaderTypeDef,以下为结构体内主要定义参数

  1. IDE(帧格式):可选参数CAN_ID_STD(标准格式帧)和CAN_ID_EXT(扩展格式帧)
  2. StdId(标准格式帧ID):可选值范围0-0x7FF(11位)
  3. ExtId(扩展格式帧ID):可选值范围0-0x1FFF FFFF(29位,其中标准11位+扩展18位)
  4. RTR(帧类型):可选参数CAN_RTR_DATA(数据帧)和CAN_RTR_REMOTE(遥控帧)
  5. DLC(发送数据的长度):可选值范围0-8
  6. TransmitGlobalTime(传输时间戳使能):ENABLE/DISABLE

3.0.4、CAN验收筛选器

CAN网络上的所有节点没有地址的概念,因此当某个节点发送了特定ID的一条数据帧的时候,所有节点都会收到该帧消息,但是该帧应该只被需要接收该帧的节点接收,而其他不需要接收该数据帧的节点应该自动筛除掉该消息,减少资源浪费,那一个节点如何判断是否应该接收该帧呢?

配置CAN控制器的验收筛选器,STM32F407的CAN提供了28个可调整/可配置的标识符筛选器组,注意CAN1/2共用这个标识符筛选器组,而且CAN2不能够单独直接配置,需要使用CAN1来配置,这里的筛选功能为硬件筛选功能,无需软件筛选,可以节省软件筛选所需的CPU资源

筛选器可配置为掩码模式或标识符列表模式,在掩码模式下,标识符寄存器与掩码寄存器关联,用以指示标识符的哪些位“必须匹配”,哪些位“无关”

在HAL库中有一个CAN过滤器配置结构体CAN_FilterTypeDef用于配置筛选器,以下为结构体内主要定义参数

  1. FilterMode(筛选器模式):可选参数CAN_FILTERMODE_IDMASK(掩码模式)和CAN_FILTERMODE_IDLIST(标识符列表模式)
  2. FilterBank(筛选器组):指定将初始化的筛选器组,单CAN时参数范围0-13,双CAN时参数范围0-27
  3. FilterFIFOAssignment(分配给筛选器的FIFO):指定分配给过筛选器的FIFO0/1,可选参数CAN_FILTER_FIFO0(FIFO0)和CAN_FILTER_FIFO1(FIFO1)
  4. FilterScale(筛选器宽度):CAN_FILTERSCALE_16BIT(两个16位)和CAN_FILTERSCALE_32BIT(一个32位)
  5. FilterActivation(筛选器使能):CAN_FILTER_DISABLE(不使能)和CAN_FILTER_ENABLE(使能)
  6. FilterIdHigh(CAN_FxR1 的高16位):在32位的屏蔽位模式下,用于指定这些位的标准值
  7. FilterIdLow(CAN_FxR1 的低16位):在32位的屏蔽位模式下,用于指定这些位的标准值
  8. FilterMaskIdHigh(CAN_FxR2的高16位):在32位的屏蔽位模式下,用于指定需要关心哪些位
  9. FilterMaskIdLow(CAN_FxR2的低16位):在32位的屏蔽位模式下,用于指定需要关心哪些位

这一部分很重要,不配置筛选器则CAN不能正常接收数据,具体配置请阅读本实验3.2.3小节程序,这里笔者自认为讲的不是很到位,读者可以阅读“STM32 CAN 过滤器、滤波屏蔽器配置总结”文章

3.0.5、CAN工作模式

CAN1/2有工作模式和测试模式两种模式,工作模式中又包括初始化模式、正常模式和睡眠模式三种,测试模式中包括静默模式、环回模式和环回与静默组合模式三种,测试模式主要用于测试单个CAN是否工作正常,本实验主要实现双机通信,因此CAN工作在正常模式即可,由于内容太多这里不再详细介绍,具体内容可参考STM32F4xx 参考手册 RM009,如下图所示为测试模式下结构示意图 (注释1)

3.0.6、CAN发送和接收流程

CAN1/2均有3个邮箱可以发送数据,当用户发送数据时只需要利用CAN_TxHeaderTypeDef结构体生成要发送的帧(具体可阅读本实验3.0.3小节),然后使用HAL_CAN_AddTxMessage()函数将生成的帧添加到邮箱即可,如果此时有空闲邮箱那么邮箱就会被挂起,当该邮箱具有最高优先级的时候就会安排发送出去,发送出去的过程完全由硬件实现,用户只需按要求生成帧,然后放入空闲邮箱即可,如下图所示为发送邮箱状态流程图 (注释1)

CAN1/2均有两个接收FIFO,每个接收FIFO都有三级深度,通俗理解就是有三个邮箱,可以接收三条信息,当发送的信息通过某个CAN网络上的节点验收筛选器成为一条有效信息时,那么该消息就会被该节点的CAN接收FIFO接收,同时该FIFO的接收0会被挂起,如果持续收到消息,该FIFO的接收1/2也会被挂起,直到三个邮箱全部用完,如果开启了CAN RX接收中断,那么当FIFO接收0/1/2/被挂起时会进入对应的中断服务回调函数中,当使用HAL_CAN_GetRxMessage()函数读取掉接收FIFO0/1的某级深度的消息时,该级别邮箱将会被释放,方便接收下一条消息,如下图所示为接收FIFO状态流程图 (注释1)

值得提醒的是,在本实验中由于笔者使用了两个一摸一样的开发板,因此下面配置的一套程序可以直接烧录到两个开发板上就可以通信,但是如果读者使用了不一样的开发板,每个板子的配置流程和下述流程一模一样,但请注意调节时钟树和CAN的参数配置时将两个开发板的CAN通信波特率调节为一致,两个开发板的连接应该如下图所示连接 (注释4)

另外就是尽量使用CAN1作为通信CAN,因为在STM32F407的两个CAN中CAN1为主CAN,本实验使用CAN2可能在接收信息时存在问题(大概率笔者没有彻底了解到应该对CAN2如何正确配置),读者可以自行尝试

3.1、CubeMX相关配置

3.1.0、工程基本配置

打开STM32CubeMX软件,单击ACCESS TO MCU SELECTOR选择开发板MCU(选择你使用开发板的主控MCU型号),选中MCU型号后单击页面右上角Start Project开始工程,具体如下图所示

开始工程之后在配置主页面System Core/RCC中配置HSE/LSE晶振,在System Core/SYS中配置Debug模式,具体如下图所示

详细工程建立内容读者可以阅读“STM32CubeMX教程1 工程建立”

3.1.1、时钟树配置

本实验时钟树建议按照下图所示将MCU时钟频率配置为100MHz,APB1时钟频率配置为25MHz,这样设置是为了能够得到一个整数的时间片,当然也可以和之前的实验类似,将所有总线频率均设置为最高频率

3.1.2、外设参数配置

本实验需要初始化开发板上KEY2用户按键做普通输入,具体配置步骤请阅读“STM32CubeMX教程3 GPIO输入 - 按键响应”

本实验需要需要初始化USART2作为输出信息渠道,具体配置步骤请阅读“STM32CubeMX教程9 USART/UART 异步通信”

单击Pinout & Configuration页面左边Connectivity/CAN1,在Mode中勾选Activated激活CAN1,在其下方的参数配置栏目中按照图示参数配置即可,位时序参数详解可以阅读本实验“3.0.2、CAN位时序和波特率”小节

3.1.3、外设中断配置

在Pinout & Configuration页面左边System Core/NVIC中勾选CAN1 TX interrupts和CAN1 RX0 interrupts发送接收两个中断,然后选择合适的中断优先级即可,具体如配置下图所示

3.2、生成代码

3.2.0、配置Project Manager页面

单击进入Project Manager页面,在左边Project分栏中修改工程名称、工程目录和工具链,然后在Code Generator中勾选“Gnerate peripheral initialization as a pair of 'c/h' files per peripheral”,最后单击页面右上角GENERATE CODE生成工程,具体如下图所示

详细Project Manager配置内容读者可以阅读“STM32CubeMX教程1 工程建立”实验3.4.3小节

3.2.1、外设初始化调用流程

在生成的工程代码中新增加了MX_CAN1_Init()函数,该函数对CAN1的参数进行了配置,并调用了CAN初始化函数HAL_CAN_Init()

在该CAN初始化函数HAL_CAN_Init()中调用了HAL_CAN_MspInit()函数对外设CAN1所需要的时钟使能,引脚复用和中断进行了配置

CAN1具体初始化调用流程如下图所示

3.2.2、外设中断调用流程

在STM32CubeMX中勾选CAN1的TX中断和RX0中断后,会在生成的工程代码stm32f4xx_it.c中新增CAN1_TX_IRQHandler()和CAN1_RX0_IRQHandler()中断服务函数

这两个中断服务函数均调用了HAL库的CAN中断统一处理函数HAL_CAN_IRQHandler(),在该函数中当CAN1邮箱0发送完成消息后会调用HAL_CAN_TxMailbox0CompleteCallback()函数,当CAN1FIFO0消息挂起时会调用HAL_CAN_RxFifo0MsgPendingCallback()函数,这两个函数均为虚函数,需要用户重新实现

CAN1接收/发送中断具体调用流程如下图所示

3.2.3、添加其他必要代码

在can.c中添加FIFO0的消息筛选器函数CAN_SetFilters(),然后添加CAN发送数据测试函数CAN1_Send_Test(),具体源代码如下所示 (注释3)

//设置筛选器,要在完成CAN初始化之后调用此函数
HAL_StatusTypeDef CAN_SetFilters(void)
{
    CAN_FilterTypeDef	canFilter;                      //筛选器结构体变量
     // Configure the CAN Filter
    canFilter.FilterBank = 0;		                    //筛选器组编号
    canFilter.FilterMode = CAN_FILTERMODE_IDMASK;	    //ID掩码模式
    canFilter.FilterScale = CAN_FILTERSCALE_32BIT;	    //32位长度
    //设置1:接收所有帧
    //  canFilter.FilterIdHigh = 0x0000;		            //CAN_FxR1 的高16位
    //	canFilter.FilterIdLow = 0x0000;			            //CAN_FxR1 的低16位
    //	canFilter.FilterMaskIdHigh = 0x0000;	            //CAN_FxR2的高16位。所有位任意
    //	canFilter.FilterMaskIdLow = 0x0000;		            //CAN_FxR2的低16位,所有位任意
    //设置2:只接收stdID为奇数的帧
    canFilter.FilterIdHigh = 0x0020;		            //CAN_FxR1 的高16位
    canFilter.FilterIdLow = 0x0000;			            //CAN_FxR1 的低16位
    canFilter.FilterMaskIdHigh = 0x0020;	            //CAN_FxR2的高16位
    canFilter.FilterMaskIdLow = 0x0000;		            //CAN_FxR2的低16位
    
    canFilter.FilterFIFOAssignment = CAN_RX_FIFO0;		//应用于FIFO0
    canFilter.FilterActivation = ENABLE;		        //使用筛选器
    canFilter.SlaveStartFilterBank = 14;		        //从CAN控制器筛选器起始的Bank
    HAL_StatusTypeDef result=HAL_CAN_ConfigFilter(&hcan1, &canFilter);
    return result;
}
 
/*CAN发送数据测试函数*/
void CAN1_Send_Test(uint32_t msgid, uint8_t *data)
{
    TxMessage.IDE = CAN_ID_STD;                         //设置ID类型
    TxMessage.StdId = msgid;                            //设置ID号
    TxMessage.RTR = CAN_RTR_DATA;                       //设置传送数据帧
    TxMessage.DLC = 4;                                  //设置数据长度
    if(HAL_CAN_AddTxMessage(&hcan1, &TxMessage, data, &TxMailbox) != HAL_OK) 
    {
        printf("CAN send test data fail!\r\n");
        Error_Handler();
    }
}

在can.c中重新实现CAN接收/发送中断处理函数,具体源代码如下所示

/*CAN接收FIFO0挂起中断处理函数*/
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
    uint8_t  data[8];
    HAL_StatusTypeDef status;
    if(hcan == &hcan1) 
    {
        status = HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxMessage, data);
        if (HAL_OK == status)
        {                             
            printf("--->Data Receieve!\r\n");
            printf("RxMessage.StdId is %#x\r\n", RxMessage.StdId);
            printf("data[0] is 0x%02x\r\n", data[0]);
            printf("data[1] is 0x%02x\r\n", data[1]);
            printf("data[2] is 0x%02x\r\n", data[2]);
            printf("data[3] is 0x%02x\r\n", data[3]);
            printf("<---\r\n");
        }
    }
}
 
/*CAN发送完成中断处理函数*/
void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef *hcan)
{
    printf("--->Into TxMailbox0CompleteCallback Function!\r\n");
    printf("--->CAN send test data success!\r\n\r\n");
}

在main.c主函数中设置CAN接收筛选器,启动CAN,使能中断,然后再主循环中实现按键控制,每当按键KEY2按下时就调用CAN1_Send_Test()函数发送数据

具体源代码如下所示

/*主循环外程序*/
printf("----- CAN Test Board #1 -----\r\n");
//设置筛选器
if (CAN_SetFilters() == HAL_OK)   
    printf("ID Filter: Only Odd IDs\r\n");
//启动CAN1模块
if (HAL_CAN_Start(&hcan1) == HAL_OK)  
    printf("CAN is started\r\n");
//启用CAN发送/接收中断
if(HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING | CAN_IT_TX_MAILBOX_EMPTY) != HAL_OK) 
{
    printf("CAN_IT_RX_FIFO0_MSG_PENDING Enable Fail\r\n");
        Error_Handler();
}
uint32_t msg_id=0;
uint8_t data[4] = {0x01, 0x02, 0x03, 0x04};
 
 
/*主循环内程序*/
/*按键KEY2按下*/
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET)
{
    HAL_Delay(50);
    if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET)
    {
        for(uint16_t i =0;i<4;i++)
            data[i]++;
 
        CAN1_Send_Test(msg_id++,data);
        while(!HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin));
    }
}

最后将在can.c中定义的消息筛选器函数、发送数据测试函数在can.h中声明即可,具体源代码如下所示

/*can.h中函数声明*/
void CAN1_Send_Test(uint32_t msgid, uint8_t *data);
HAL_StatusTypeDef CAN_SetFilters(void);

4、常用函数

/*CAN开始通信*/
HAL_StatusTypeDef HAL_CAN_Start(CAN_HandleTypeDef *hcan)
 
/*CAN停止通信*/
HAL_StatusTypeDef HAL_CAN_Stop(CAN_HandleTypeDef *hcan)
 
/*获取当前空闲邮箱数量*/
uint32_t HAL_CAN_GetTxMailboxesFreeLevel(const CAN_HandleTypeDef *hcan)
 
/*请求发送相应的邮箱内容*/
HAL_StatusTypeDef HAL_CAN_AddTxMessage(CAN_HandleTypeDef *hcan, const CAN_TxHeaderTypeDef *pHeader,const uint8_t aData[], uint32_t *pTxMailbox)
 
/*获取FIFO中挂起的消息数*/
uint32_t HAL_CAN_GetRxFifoFillLevel(const CAN_HandleTypeDef *hcan, uint32_t RxFifo)
 
/*读取FIFI中挂起的消息信息并释放邮箱*/
HAL_StatusTypeDef HAL_CAN_GetRxMessage(CAN_HandleTypeDef *hcan, uint32_t RxFifo,CAN_RxHeaderTypeDef *pHeader, uint8_t aData[])
 
/*CAN发送完成中断处理函数*/
void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef *hcan)
 
/*CAN接收中断处理函数*/
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)

5、烧录验证

烧录程序,开发板1/2上电后均显示CAN初始化成功可以开始通信

当第一次按下开发板1的KEY2按键,此时串口输出进入CAN发送完成中断处理函数中并成功发送信息的提示,但是开发板2并没有接收消息(因为开发板1/2均设置了只接收stdID为奇数的帧)

当第二次按下开发板1的KEY2按键时,可以发现开发板2收到了消息,并将接收到的消息打印了出来,具体实验现象如下图所示(左边为开发板1,右边为开发板2)

6、注释详解

注释1:图片来源 STM32F4xx 中文参考手册 RM009

注释2:图片来源 通信——CAN总线基础介绍

注释3:在CAN发送测试函数末尾不要使用printf输出,如果非要使用请在使用前进行1ms延时,否则可能进不去发送完成函数HAL_CAN_TxMailbox0CompleteCallback中

注释4:图片来源 STM32CubeMX | 36 - 使用CAN总线进行双板通信(TJA1050)

参考资料

STM32Cube高效开发教程(基础篇)

更多内容请浏览 STM32CubeMX+STM32F4系列教程文章汇总贴

这篇关于STM32CubeMX教程21 CAN - 双机通信的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!