细说ARM MCU的串口接收数据的实现过程

目录

一、硬件及工程

1、硬件

2、软件目的

3、创建.ioc工程

二、 代码修改

1、串口初始化函数MX_USART2_UART_Init()

(1)MX_USART2_UART_Init()串口参数初始化函数

(2)HAL_UART_MspInit()串口功能模块初始化函数

2、串口中断的执行过程

3、启动串口接收中断

4、自动生成main函数

5、启动串口接收函数HAL_UART_Receive_IT()

6、修改main.c函数

7、从定义回调函数 

8、修改while(1)循环

三、下载运行

1、安装串口助手

2、验证串口通讯结果


一、硬件及工程

1、硬件

        本文旨在以实例说明STM32单片机通过串口接收数据的实现过程。本文中使用ST的开发板NUCLEO-G474RE,板上MCU型号为STM32G474RET6。配套的扩展板为作者根据说明书自己设计制造,链接:

2、软件目的

        实例运行后,通过串口通讯助手发送0x10时开发板上的LD2灯亮,其它数据灯灭。

3、创建.ioc工程

        配置GPIO→配置时钟源和DEBUG→配置外部中断→配置串口→配置串口中断→配置系统时钟→build生成。

       将PA4、PA5配置为输出GPIO_Output,PP,Pull up;PC13配置为中断模式GPIO_EXTI13,上升沿触发,用于检测按键B1的状态;PA2、PA3分别配置为串口USART2_TX和USART2_RX;外部时钟;Debug设置为Serial Wire;USART2的基本参数,波特率115200,数据长度8bit,无校验,停止位1;PA4别名BUZ,PA5别名LED,PC13别名KEY;串口中断抢占优先级1,外部中断抢占优先级4;

二、 代码修改

        硬件配置完毕后,启动代码生成,IDE自动将配置好的硬件信息转换成代码。

        自动生成的代码有些需要了解(比如初始化函数),而有些是需要修改的(比如while(1)循环里需要增加的代码,一些注释对里需要增加的代码),还有一些需要重写(比如,重写弱函数)。

1、串口初始化函数MX_USART2_UART_Init()

(1)MX_USART2_UART_Init()串口参数初始化函数

        MX_USART2_UART_Init()函数主要完成对USART2的模式和参数配置,如波特率、数据位、停止位等。因为串口模块要比GPIO复杂,所以配置参数也更多。

/*** @brief USART2 Initialization Function* @param None* @retval None*/
static void MX_USART2_UART_Init(void)
{/* USER CODE BEGIN USART2_Init 0 *//* USER CODE END USART2_Init 0 *//* USER CODE BEGIN USART2_Init 1 *//* USER CODE END USART2_Init 1 */huart2.Instance = USART2;huart2.Init.BaudRate = 115200;huart2.Init.WordLength = UART_WORDLENGTH_8B;huart2.Init.StopBits = UART_STOPBITS_1;huart2.Init.Parity = UART_PARITY_NONE;huart2.Init.Mode = UART_MODE_TX_RX;huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;huart2.Init.OverSampling = UART_OVERSAMPLING_16;huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;huart2.Init.ClockPrescaler = UART_PRESCALER_DIV1;huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;if (HAL_UART_Init(&huart2) != HAL_OK){Error_Handler();}if (HAL_UARTEx_SetTxFifoThreshold(&huart2, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK){Error_Handler();}if (HAL_UARTEx_SetRxFifoThreshold(&huart2, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK){Error_Handler();}if (HAL_UARTEx_DisableFifoMode(&huart2) != HAL_OK){Error_Handler();}

        MX_USART2_UART_Init()自动生成于main.c中;

        该函数内部出现的HAL_UART_Init(UART_HandleTypeDef *huart)在stm32g4xx_hal_uart.c中。

(2)HAL_UART_MspInit()串口功能模块初始化函数

        对于串口来说,针对引脚等参数的配置,是在文件stm32g4xx_hal_msp.c中。msp是MCU support package的缩写,指的是MCU相关的支持包。关于串口有3个支持包:

HAL_MspInit(void)
HAL_UART_MspInit(UART_HandleTypeDef *huart)
HAL_UART_MspDeInit(UART_HandleTypeDef *huart)

        由函数名可见,其中都带有MspInit字样。这类函数的作用是进行MCU功能模块(譬如串口、定时器、ADC等)的初始化。在固件库中,通常是采用这种方式将MCU的模块初始化代码集中起来,以方便代码在不同型号的MCU上移植。

        上述函数中,第一个是初始化全局Msp。后面两个函数的参数完全一样,函数名也很类似;区别是后一个函数名中多了两个字母“De”,是Default的缩写。

        HAL_UART_MspInit()函数可以对串口硬件初始化、配置引脚模式以及设置中断优先级并使能中断,与对GPIO进行初始化的MX_GPIO_Init()函数所完成的功类似。

        HAL_UART_MspDeInit()函数可以把串口复位成初始值,关闭串口并关闭串口中断。

        HAL_UART_MspInit()函数是由函数HAL_UART_Init()(在stm32g4xx_hal_uart.c文件中定义)调用的。而HAL_UART_Init()是由MX_USART2_UART_Init()函数调用的(在if语句的条件表达式中调用)。

2、串口中断的执行过程

        由于配置了串口的中断功能,所以当中断发生后就会调用相应的中断服务函数来完成一定的任务。

void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{... .../*if no error occurs */errorflags = (isrflags & (uint32_t)(USART_ISR_PE|USART_ISR_FE|USART_ISR_ORE|USART_ISR_NE));if(errorflags == 0U){/*------UART in mode Receiver-------*/if(((isrflags & USART_ISR_RXNE_RXFNE) != 0U) && (((crlits & USART_CR1_RXNEIE_RXFNEIE) != 0U)||((cr3its & USART_CR3_RXFTIE) != 0U))){if(huart->RxISR != NULL){huart->RxISR(huart);}return;}}
... ...
}

        当程序执行到huart →RxISR(huart)时,会调用UART_RxISR_8BIT()函数(如果配置数据字长为7位或8位,则调用此函数;如果数据字长为9位,则会调用另一函数UART_RxISR_16BIT),并且在该函数中会调用回调函数HAL_UART_RxCpltCallback()。这个回调函数是在stm32g4xx_hal_ uart.c中定义的弱函数。用户需要重写该函数,可以写在main.c中。

3、启动串口接收中断

        在使用中断之前,还要用到函数HAL_UART_Receive_IT()。该函数的格式如下:

HAL_UART_Receive_IT(UART_HandleTypeDef *huart,uint8_t *pData,uint16_t Size)

        该函数是给将要接收的数据定义一个缓冲区pData,并指定接收数据的长度为Size(也就是要接收的字节数)。这个Size决定了调用回调函数的频率。如果Size大于1,则不会每次中断都调用回调函数,而是到Size次之后,才会调用一次回调函数。此外,这个函数还有开启接收中断的功能,所以需要在main函数的初始化代码中调用一次HAL_UART_Receive_IT()函数。这样就可以确保开启接收中断。在执行一次回调函数时,接收中断会关闭,所以还需要再次开启接收中断。这个再次开启中断的动作,也可以在回调函数中通过调用HAL_UART_Receive_IT()函数来实现。

4、自动生成main函数

        完成上面的硬件配置,并自动生成代码后,然后在main.c中的初始化部分调用HAL_UART_ Receive _IT()函数设置参数并开启接收中断,然后写回调函HAL_UART_RxCpltCallback(),以便对接收的数据进行处理。删除了一些注释对。

#include "main.h"
UART_HandleTypeDef huart2;
/* USER CODE BEGIN PV */
/* USER CODE END PV */
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);
int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART2_UART_Init();/* USER CODE BEGIN 2 *//* USERCODE END 2 */while(1){}
}

        main函数中出现了一个串口初始化函数MX_USART2_UART_Init()。此外,在main.c中,首先定义了一个全局变量huart2,类型为UART_HandleTypeDef。huart2是一个结构体变量,通常也称为串口句柄。这个结构体是关于UART的,它的成员有很多,有的成员本身也是结构体类型。这个结构体有些复杂。在串口初始化函数MX_USART2_UART_Init中,使用了huart2变量。

static void MX_USART2_UART_Init(void)
{huart2.Instance = USART2;huart2.Init.BaudRate = 115200;huart2.Init.WordLength = UART_WORDLENGTH_8B;huart2.Init.StopBits = UART_STOPBITS_1;huart2.Init.Parity = UART_PARITY_NONE;huart2.Init.Mode = UART_MODE_TX_RX;huart2.Init.HwFlowCtl =UART_HWCONTROL_NONE;... ...
}

        由此可见,在MX_USART2_UART_Init函数中,第一句huart2.Instance =USART2,就将前面配置的USART2与结构体变量huart2关联了起来。

5、启动串口接收函数HAL_UART_Receive_IT()

        要实现串口接收中断,需要在主程序的初始化代码中调用HAL_UART_Receive_IT()函数。该函数的结构如下:

HAL_UART_Receive_IT(UART_HandleTypeDef *huart,uint8_t *pData,uint16_t Size)

        该函数有三个参数,第一个参数的类型就是UART_HandleTypeDef,所以要将该参数与USART2关联起来。因此,HAL_UART_Receive_IT()函数的调用要可将该函数放到MX_USART2 _UART_Init()函数之后的注释对中。

        HAL_UART_Receive_IT()函数的第二个参数是设置接收数据的缓冲区,可以定义一个长度为RXBUFFERSIZE的数组RxBuffer [RXBUFFERSIZE],当然这个数组以及RXBUFFERSIZE都需要另外定义(后面会将它们定义为全局变量)。

        HAL_UART_Receive_IT()函数的第三个参数用于指定接收数据的长度,这个数据长度可以与接收缓冲区的长度相同,即RXBUFFERSIZE。

        将RxBuffer[RXBUFFERSIZE]定义为全局变量(需要放到注释对中),并将对HAL_UART_ Receive_IT()函数的调用放置到MX_USART2_UART_Init()语句之后的注释对/* USER CODE BEGIN 2 */与/* USER CODE END 2 */中。

6、修改main.c函数

#include "main.h"
UART_HandleTypeDef huart2;
/* USER CODE BEGIN PV */
/* 直接使用了变量RXBUFFERSIZE */
uint8_t RxBuffer[RXBUFFERSIZE];
/* USER CODE END PV */
/*Private function prototypes */
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);
int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART2_UART_Init();/* USER CODE BEGIN2 */HAL_UART_Receive_IT(&huart2,(uint8_t*)RxBuffer,RXBUFFERSIZE);/* USER CODE END2 */while(1){}
}

        上面直接使用了变量RXBUFFERSIZE。对该变量的定义可以放到main.h头文件中,可以用define宏(也需放置到注释对中):

/* USER CODE BEGIN Private defines */
#define RXBUFFERSIZE 1 //接收缓冲区的长度
/* USER CODE END Private defines */

        将RXBUFFERSIZE定义为1,也就是1字节。

7、从定义回调函数 

        重定义串口中断接收的回调函数HAL_UART_RxCpltCallback()。这个函数已经在stm32g4xx_ hal_uart.c中有定义,只不过被定义为弱函数,实际就是一个空函数。需要重写它。与写EXTI的回调函数类似,也将该函数写在main.c中。

        串口有数据送来,会执行中断服务函数USART2_IRQHandler(),然后该函数又会调用函数HAL_UART_IRQHandler()。调用一定次数的HAL_UART_IRQHandler()函数后,就会自动执行回调函数HAL_UART_RxCpltCallback()。这里的“一定次数”是由HAL_UART_Receive_IT()函数的第三个参数决定的,也就是前面在主程序中用到的常量RXBUFFERSIZE。由于把RXBUFFERSIZE定义为1,所以串口收到1字节的数据后,会调用一次回调函数HAL_UART_RxCpltCallback()。当调用回调函数之时,1字节的数据已经放到了接收缓冲区中,也就是放到前面定义的数组RxBuffer中。 

        调用HAL_UART_Receive_IT()函数,不但实现了定义缓冲区并设置接收数据长度的功能,而且还有开启串口中断接收的功能。因此,在接收完指定长度的数据之后,需要重新开启接收中断的功能,否则后面就不会再进入中断了。可以在回调函数HAL_UART_RxCpltCallback()中调用一下HAL_UART_Receive_IT()函数,重新开启接收中断。对该函数的调用,可以连同EXTI的回调函数HAL GPIO_EXTI_Callback()一起写在main.c后面的注释对中:

/*USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{HAL_UART_Receive_IT(&huart2,(uint8_t *)RxBuffer,RXBUFFERSIZE);
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{HAL_GPIO_WritePin(BUZ_GPIO_Port,BUZ_Pin,GPIO_PIN_RESET);HAL_Delay(100);	//延时HAL_GPIO_WritePin(BUZ_GPIO_Port,BUZ_Pin,GPIO_PIN_SET);
}
/*USER CODE END 4 */

        在EXTI的回调函数中使用了中断的方式实现:当按键按下时,让蜂鸣器响一声。

8、修改while(1)循环

        根据串口送来的数据,控制发光二极管的亮灭。当接收到的数据为0x10(十六进制)时,点亮LD2;当接收到的数据不是0x10时,熄灭LD2。

/*USER CODE BEGIN WHILE */
while(1)
{/*USER CODE BEGIN 3 */if(RxBuffer[0] == 0x10)HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_SET);elseHAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_RESET);
}
/*USER CODE END 3 */

三、下载运行

1、安装串口助手

        好用的 Win10 串口调试助手 + 网口调试 - 知乎  https://zhuanlan.zhihu.com/p/109941792?eqid=a328954a0002e745000000066477efb6&utm_id=0

2、验证串口通讯结果

        开启串口通讯,发送0x10,开大坂上的LD2亮,发送其它内容,比如0x20,LD2灭。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/347421.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

批量申请SSL证书如何做到既方便成本又最低

假如您手头拥有1千个域名,并且打算为每一个域名搭建网站,那么在当前的网络环境下,您必须确保这些网站通过https的方式提供服务。这意味着,您将为每一个域名申请SSL证书,以确保网站数据传输的安全性和可信度。那么&…

面试-NLP八股文

机器学习 交叉熵损失: L − ( y l o g ( y ^ ) ( 1 − y ) l o g ( 1 − ( y ^ ) ) L-(ylog(\hat{y}) (1-y)log(1-(\hat{y})) L−(ylog(y^​)(1−y)log(1−(y^​))均方误差: L 1 n ∑ i 1 n ( y i − y ^ i ) 2 L \frac{1}{n}\sum\limits_{i1}^{n}…

【ai】openai-quickstart 配置pycharm工程

之前都是本地执行脚本【AI】指定python3.10安装Jupyter Lab环境为:C:\Users\zhangbin\AppData\Local\Programs\Python\Python310 参考之前创建的python工程 使用的是局部的私有的虚拟环境 pycharm给出的解释器 直接使用现有的,不new了 可以选择3.10 :可以选虚拟的:

Rust-02-变量与可变性

在Rust中,变量和可变性是两个重要的概念。 变量:变量是用于存储数据的标识符。在Rust中,变量需要声明其类型,例如: let x: i32 5; // 声明一个名为x的变量,类型为i32(整数)&#…

SpringCloud 前端-网关-微服务-微服务间实现信息共享传递

目录 1 网关获取用户校验信息并保存至请求头(前端-网关) 2 微服务获取网关中的用户校验信息(网关-微服务) 2.1 一般的做法是在公共的module中添加,此处示例为common 公共配置module中添加 2.2 定义拦截器 2.3 定义…

简单通用的系统安装、备份、还原方法,支持 ARM 系统【Ventory+FirePE+DiskGenius】

文章目录 0. 简介1. 制作 Ventory 启动盘1.1. 下载 Ventory1.2. 制作 Ventory 启动盘 2. 添加 FirePE 等系统镜像到启动盘2.1. 下载 FirePE2.2. 导出 .iso 系统镜像文件2.3. .iso 系统镜像文件添加至启动盘 3. 启动 FirePE 等系统镜像3.1. 在 bios 中选择启动盘启动3.2. 启动系…

# RocketMQ 实战:模拟电商网站场景综合案例(八)

RocketMQ 实战:模拟电商网站场景综合案例(八) 一、RocketMQ 实战:模拟电商网站场景综合案例–下单异常问题演示 1.png 1、如果订单在扣减库存、扣减优惠券、扣减余额后,在 未 确认订单 前,出现了异常&am…

vue+element el-select动态加减框数量及验证下拉框选项动态置灰(选中行的下拉框换个值后,原值没办法监控这个问题也解决了)

1效果: 2部分主要(HTML): 1:这个位置主要就是看看方法什么的吧,还有大概的结构 2:change"sort_Change(item,tablelists.orderbyList)这两个参数(都有大用): (1)item:代表每次你操作的这个数据 (2)tablelists.orderbyList:代表你这一共有几行数据(上边这个例子就会得到一个…

LCD电子广告牌课程设计

概述 1.1课程设计简介 亮丽实用的广告牌可以给我们的生活添加光彩、可以给店铺招揽生意。传统的广告牌都是固定的汉字,并且时间长了会掉色,使汉字模糊难认,这就给我的生活带来很多的不便。尤其到了晚上传统广告牌就会失去其该有的作用。所以在…

2024年安全现状报告

2024 年安全现状报告有些矛盾。尽管安全专业人员的道路困难重重,比如说严格的合规要求、不断升级的地缘政治紧张局势和更复杂的威胁环境,但整个行业还是在取得进展。 许多组织表示,与前几年相比,网络安全变得更容易管理。组织之间…

AI赋能银行国际结算审单:合合信息抽取技术的实践与应用

官.网地址:合合TextIn - 合合信息旗下OCR云服务产品 时下,银行国际业务是金融体系的重要组成部分,涵盖了外汇交易、国际结算、贸易融资、跨境投资等领域,这些业务对于国际贸易和全球经济发展具有重要作用。国际业务部门单据、凭证…

OpenGL系列(五)纹理贴图

概述 OpenGL纹理是一种在三维图形中应用纹理映射的技术。纹理是一张图像,可以应用到三维模型的表面上,从而使得模型看起来更加真实和具有细节。通过纹理映射,可以将图像的像素值与三维模型的顶点进行匹配,从而为模型的表面增加细节…

验证码识别接口、多种样式验证码识别接口、中英文验证码识别接口

验证码识别接口、多种样式验证码识别接口、中英文验证码识别接口 本文提供一个基于OCR和机器学习的验证码识别接口,能够识别较复杂的中文、英文验证码,在OCR的基础上针对验证码进行算法优化。本接口是收费的(最低0.5分1次调用,试…

23种设计模式之代理模式

代理模式 1、概念 代理模式:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问 代理模式是常用的结构型设计模式之一,在Java RMI、Web Service、Spring AOP等技术和框架中都使用了代理模式 2、代理模式结构 Subject&a…

解析 Spring 框架中的三种 BeanName 生成策略

在 Spring 框架中,定义 Bean 时不一定需要指定名称,Spring 会智能生成默认名称。本文将介绍 Spring 的三种 BeanName 生成器,包括在 XML 配置、Java 注解和组件扫描中使用的情况,并解释它们如何自动创建和管理 Bean 名称。 1. Be…

Nodejs 第七十七章(MQ高级)

MQ介绍和基本使用在75章介绍过了,不再重复 MQ高级用法-延时消息 什么是延时消息? Producer 将消息发送到 MQ 服务端,但并不期望这条消息立马投递,而是延迟一定时间后才投递到 Consumer 进行消费,该消息即延时消息 插件安装 R…

业务安全蓝军测评标准解读—业务安全体系化

目录 1.前言 2.业务蓝军测评标准 2.1 业务安全脆弱性评分(ISVS) 2.2 ISVS评分的参考意义 2.3 纵向对比 2.4 横向对比 3.业务蓝军测评案例 3.1 APP虚假安装案例 3.1.1 定义测评对象和攻击目标 3.1.2 制定攻击方案 3.1.3 执行攻击并评估 3.2 人脸识别绕过案例 3.2.…

STM32硬件接口I2C应用(基于MP6050)

目录 概述 1 STM32Cube控制配置I2C 1.1 I2C参数配置 1.2 使用STM32Cube产生工程 2 HAL库函数介绍 2.1 初始化函数 2.2 写数据函数 2.3 读数据函数 3 认识MP6050 3.1 MP6050功能介绍 3.2 加速计测量寄存器 ​编辑3.3 温度计量寄存器 3.4 陀螺仪测量寄存器 4 MP60…

微信小程序组件传值

虽然微信小程序是比较轻量的,但是还是拥有组件的 这是文件的基本目录 我们的代码基本都在pages和components文件夹中 在component中创建组件 在component中 ,创建一个目录 我创建了一个 head目录 用于配置头部信息 我在这里创建了 一个头部组件&…

linux-计划任务

作用:定时自动完成特定的工作 计划任务的分类 一次性的计划任务:例如下周三对文档的重要文件备份一次 周期性的计划任务:每天12:00创建一个文件 命令 一次性的任务计划 at batch 周期性计划任务 crontab anacron 一次性计划任务 …