STM32CubeMX教程21 CAN - 双机通信

发布时间:2024年01月09日

目录

1、准备材料

2、实验目标

3、实验流程

3.0、前提知识

3.0.1、CAN总体概述

3.0.2、CAN位时序和波特率

3.0.3、CAN帧格式

3.0.4、CAN验收筛选器

3.0.5、CAN工作模式

3.0.6、CAN发送和接收流程

3.1、CubeMX相关配置

3.1.1、时钟树配置

3.1.2、外设参数配置

3.1.3、外设中断配置

3.2、生成代码

3.2.1、外设初始化调用流程

3.2.2、外设中断调用流程

3.2.3、添加其他必要代码

4、常用函数

5、烧录验证

5.1、实验具体流程

5.2、实验现象

6、注释详解

参考资料


1、准备材料

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

STM32CubeMX软件(Version 6.10.0

野火DAP仿真器

keil μVision5 IDE(MDK-Arm

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收发器芯片硬件原理图

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通信波特率调节为一致,两个开发板的连接应该如下图所示连接(注释)

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

3.1、CubeMX相关配置

请先阅读“STM32CubeMX教程1 工程建立”实验3.4.1小节配置RCC和SYS

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、生成代码

请先阅读“STM32CubeMX教程1 工程建立”实验3.4.3小节配置Project Manager

单击页面右上角GENERATE CODE生成工程

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、烧录验证

5.1、实验具体流程

“配置KEY2用户按键 -> 配置串口USART1 -> 激活CAN1并配置参数 -> 配置CAN1 TX/RX0中断 -> 生成的代码中实现筛选器配置 -> 实现CAN发送函数 -> 重新实现CAN发送 /接收中断函数 -> 主函数中启动CAN并激活中断 -> 主循环中实现按键控制CAN发送程序”

5.2、实验现象

烧录程序,开发板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高效开发教程(基础篇)

文章来源:https://blog.csdn.net/lc_guo/article/details/135443216
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。