STM32—HAL库中断/DMA控制和完成串口通信
一、解决的问题
1、安装 stm32CubeMX,配合Keil,使用HAL库(或标准库)方式,设置USART1 波特率为115200,1位停止位,无校验位,完成下列任务:
1)STM32系统给上位机(win10)连续发送“hello windows!”。win10采用“串口助手”工具接收。
2)在完成以上任务基础,继续扩展功能:当上位机给stm32发送一个字符“#”后,stm32暂停发送“hello windows!”;发送一个字符“*”后,stm32继续发送;
2、在没有示波器条件下,可以使用Keil的软件仿真逻辑分析仪功能观察串口输出波形,并分析时序状态正确与否,计算波特率实际为多少。
3. 采用串口中断方式重做上面任务二(2)的串口通信实验。STM32采用串口DMA方式,用115200bps或更高速率向上位机连续发送数据。
二、串口通讯协议和RS-232的介绍以及USB/TTL转232模块的工作原理
1.串口协议和RS-232标准:
(1)串口协议:
串口通讯(Serial Communication)是一种设备间非常常用的串行通讯方式,因为它简单、便捷,因此大部分电子设备都支持该通讯方式,电子工程师在调试设备时也经常使用该通 讯方式输出调试信息。
在计算机科学里,大部分复杂的问题都可以通过分层来简化。如芯片被分为内核层和片上外设;STM32标准库则是在寄存器与用户代码之间的软件层。对于通讯协议,我们也以分层的方式来理解,最基本的是把它分为物理层和协议层。
名称 | 组成作用 |
---|---|
物理层 | 具有机械、电子功能部分的特性,确保原始数据在物理媒体的传输 |
协议层 | 规定通讯逻辑,统一收发双方的数据打包、解包标准。 |
(2)RS-232标准:
RS-232 标准主要规定了信号的用途,通讯接口以及信号的电平标准。
在上面的通讯方式中,两个通讯设备的“DB9接口”之间通过串口信号线建立起连接,串口信号线中使用“RS-232标准”传输数据信号。由于RS-232电平标准的信号不能直接被控制器直接识别,所以这些信号会经过一个“电平转换芯片”转换成控制器能识别的“TTL校准”的电平信号,才能实现通讯。
2、RS-232电平与TTL电平的区别
根据通讯使用的电平标准不同,串口通讯可分为 TTL标准和 RS-232标准:
标准名称 | 逻辑1 | 逻辑0 |
---|---|---|
TLL | 2.4V~5V | 0~0.5V |
RS-232 | -15V~3V | +3V~+15V |
从表格中不难看出,两种标准划分的逻辑电压不同。在电子电路中常使用 TTL 的电平标准,理想状态下,使用 5V 表示二进制逻辑 1,使用 0V 表示逻辑 0;而为了增加串口通讯的远距离传输及抗干扰能力,它使用-15V表示逻辑 1,+15V 表示逻辑 0。
下图为用RS232与TTL电平校准表示同一个信号时的对比:
3、USB/TTL转232“模块(CH340芯片为例)
(1)基本原理:
USB转串口即实现计算机USB接口到物理串口之间的转换。可以为没有串口的计算机或其他USB主机增加串口,使用USB转串口设备等于将传统的串口设备变成了即插即用的USB设备。
USB主机检测到USB转串口设备插入后,首先会对设备复位,然后开始USB枚举过程。USB枚举时过程会获取设备描述符、配置描述符、接口描述符等。描述符中会包含USB设备的厂商ID,设备ID和Class类别等信息。操作系统会根据该信息为设备匹配相应的USB设备驱动。
USB虚拟串口的实现在系统上依赖于USB转串口驱动,一般由厂家直接提供,也可以使用操作系统自带的CDC类串口驱动等。驱动主要分为2个功能,其一注册USB设备驱动,完成对USB设备的控制与数据通讯,其二注册串口驱动,为串口应用层提供相应的实现方法。
串口收发对应的驱动数据流向一览表:
发送or接收 | 数据流向 |
---|---|
串口发送 | 串口应用发送数据→USB串口驱动获取数据→驱动将数据经过USB通道发送给USB串口设备→USB串口设备接收到数据通过串口发送 |
串口接收 | USB串口设备接收串口数据→将串口数据经过USB打包后上传给USB主机→USB串口驱动获取到通过USB上传的串口数据→驱动将数据保存在串口缓冲区提供给串口应用读取 |
(2)CH340模块介绍:
CH340电路与实物图:
TXD:发送端,一般表示为自己的发送端,正常通信必须接另一个设备的RXD。
RXD:接收端,一般表示为自己的接收端,正常通信必须接另一个设备的TXD。
正常通信的时候本身的TXD永远接设备的RXD。
USB转TTL串口模块与单片机连接电路图如下所示:
三、搭建STM32开发环境(HAL库环境)
参考我的博客:STM32使用HAL库点亮流水灯,并使用proteus仿真
四、利用HAL库新建一个工程
(1)打开STM32CubeMX,在主界面点击:ACCESS TO MCU SELECTOR:
(2)选择的单片机型号以及点击开始工程项目:
(3)配置GPIO:PA0。如果仅仅是完成串口通信的话,这一步可以跳过。但是根据实验要求,为了区分串口通信的开启与关闭,要使用一个LED灯来显示。当串口通信开启(STM32向电脑发送信息)的时候,LED灯亮,当串口通信关闭(STM32停止向电脑发送消息)的时候,LED灯灭。
(4)配置USART1,我们使用USART1进行数据传输。在这个界面按下图进行配置。我们对USART1的配置要做的只有两件事:一是选择串口工作模式为异步,二是开启USART1全局中断
(5)进入Project Manager(工程管理),进行工程设置点击生成工程与代码:
注意:路径不能包含中文和空格,不然生成的工程文件无法在Keil中打开;
五、完善keil5工程
(1)本工程中几个函数简介:
HAL_UART_Receive_IT:
HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_int *data, uint16_t Size)
/*
huart:使用哪个串口进行通信
data: 一个地址,用于保存接受到的数据
Size: 接收的数据个数
*/
在调用此函数后,程序会将对应串口的接收中断开启,当我们向单片机发送数据时会触发这个中断。在触发这个中断后,程序会接收数据到你传入的地址中,会读取Size个数据。读取完成后,关闭接收中断使能。
由于程序在接收完数据后会关闭接收中断。因此这个函数我们要写在main的死循环中,保证接收中断可以一直开启。
HAL_UART_Transmit_IT:
HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_int *data, uint16_t Size)
/*
huart:使用哪个串口进行通信
data: 一个地址,里面是要发送的数据通常是数组
Size: 发送的数据个数
*/
使用这个函数开启发送中断,发送寄存器为空时触发中断,将要发送的数据送入发送寄存器并发送。发送完成后关闭中断。在此实验中,我们把它当做普通的发送函数即可。
HAL_GPIO_WritePin:
HAL_GPIO_WritePin(GPIOX,GPIO_PIN_X,GPIO_PIN_STATUS)
/*
GPIOX:目标GPIO的组号
GPIO_PIN_X: 目标GPIO的引脚编号
GPIO_PIN_STATUS: 引脚状态
*/
使用这个函数修改GPIO_ODR寄存器,将非复用输出的GPIO引脚输出电平设置成自己想要的。
HAL_Delay(uint ms):
HAL_Delay(uint ms)
(2)编写代码思路:
main函数中用一个uint8类型的变量,接收发过来的字符(*/#),默认为*
进入死循环,调用HAL_UART_Receive_IT使能接收中断
如果电脑发送了字符,接收变量的值会变
如果接收变量为*,led阴极置低电平,led亮,向电脑发数据“hello windows”
如果接收变量为#,led阴极置高电平,led灭,不向电脑发送数据
(3)完善keil5工程代码:
首先,点击刚刚生成的keil5工程文件,双击main.c文件,然后再main.c中找到图示框住的函数, 接着右击此函数,进入其定义的地方处:
(2)将图中框住的部分改为SET即可:此步骤是将这个GPIO口设置为高电平,初始时不亮
(3)回到main.c文件中,在main函数里把while(1)那一块替换成如下代码:
uint8_t rcData = '*';
while (1)
{
//接收中断使能
HAL_UART_Receive_IT(&huart1,&rcData,1);
if(rcData == '#')
{//如果接收#
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_SET);
}
else if(rcData == '*')
{//如果接收*
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_RESET);
uint8_t hello[20]="hello world\n";
HAL_UART_Transmit_IT(&huart1,hello,20);
HAL_Delay(500);
}
}
六、电路连接与烧录运行
1、电路连接:
USB转TTL与STM32的连接,参考下图:
PA0——白灯;
连接好的实物图如下所示:
2、 USB转TTL环境配置:
需要在电脑上安装CH340驱动(USB串口驱动)或者CH341驱动(USB串口驱动):
在网上下载好所需的CH340驱动(USB串口驱动)或者CH341驱动(USB串口驱动)
3、下载烧录软件与串口通信软件:
烧录软件推荐使用:FLYMCU,如下图所示图样:
串口通信软件推荐使用:XCOM,如下图所示图样:
4、keil5工程里面对于USB转TTL的配置:
5、编译生产hex文件,用于后面的烧录步骤:
6、烧录:
(1)将USB转TTL插上电脑的USB接口上去,打开刚刚下载好的FLYMCU软件,按照下图所示进行相关配置,其中的第二步就是选择刚刚上一步编译生成的hex文件:
(2)改变STM32最小系统板子的跳线帽连接方式:BOOTO的跳线帽连接方式由0——>1:
(3)点击FLYMCU的开始编程(P),接着马上点击STM32最小系统板子的复位键即可完成烧录:
第一步:
第二步:
烧录成功示意图:
烧录完成之后还需要下面的关键一步,把STM32最小系统板子的跳线帽连接方式还原:BOOTO的跳线帽连接方式由1——>0:
7、配置XCOM,打开XCOM软件,按下图所示进行配置:
8、运行结果演示:
打开串口,并且同时点击STM32最小系统板子的复位键即可开始运行:
注:输入“*”:让STM32单片机继续向电脑发送信息;
输入“#”:让STM32单片机停止向电脑发送消息;
七、仿真调试
1、进入keil5仿真:
(1)点击第一步,Target界面中,选择跟正确的晶振大小,使用8MHz的外部晶振:
(2)接着进行Debug页的设置:
(3)点击图示圈住的地方进入仿真调试界面:
(4)选择逻辑分析仪:
(5)点击Setup设置添加要进行观察的引脚:
添加引脚信息:添加引脚信息时候,PA脚输入:PORTA,PB脚输入PORTB,PC脚输入PORTC;接着输入".",接着在“.”后面输入对应引脚号,最后回车即可;例如:我这里要输入PA0这个引脚的信息:添加一个引脚,添加一个引脚信息:PORTA.0,最后回车完成添加
接着对引脚的配置信息进行修改,如下图所示:
我这里选择的引脚仿真波形对应的颜色为:PA0——红色;
2、开始仿真:
(1)点击图示圈住的部分,进行仿真
(2)仿真结果:
3、仿真结果分析:
下图中的红色波形线表示开始仿真之后PA0一直处于低电平状态,LED黄灯亮,也表示STM32一直在向电脑发送消息。直到电脑发送“#”才会使得PA0上升到高电平状态,LED黄灯灭,STM32停止向电脑发送信息:
因为PA0被我们设置为用来完成这个功能:当串口通信开启(STM32向电脑发送信息)的时候,LED灯亮,当串口通信关闭(STM32停止向电脑发送消息)的时候,LED灯灭。
所以由刚刚的分析与图示情况来看,串口PA0的输出波形正确,完全符合本人板子设置与仿真预期的效果,而且通过观察发现PA0时序状态也是正常的
八、利用HAL库新建一个DMA控制串口通信的工程
同四,直到添加信道:
九、完善通过DMA方式控制串口通信的keil5工程
1、 本工程中的几个函数简介:
HAL_UART_Transmit_DMA():串口DMA模式发送
HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
功能:串口通过DMA发送指定长度的数据。
参数:
UART_HandleTypeDef *huart UATR的别名 如 : UART_HandleTypeDef huart1; 别名就是huart1
*pData 需要发送的数据
Size 发送的字节数
本文运用举例:
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)message, sizeof(message));
HAL_UART_Receive_DMA():串口DMA模式接收
HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
功能:串口通过DMA接受指定长度的数据。
参数:
UART_HandleTypeDef *huart UATR的别名 如 : UART_HandleTypeDef huart1; 别名就是huart1
*pData 需要存放接收数据的数组
Size 接受的字节数
本文运用举例:
HAL_UART_Receive_DMA(&huart1,(uint8_t*)rx_buf,5);//设置DMA接收到的数据存放在rx_buf中
2、代码编写思路
main函数外用一个uint8_t类型的数组:rcData,接收发过来的连续字符串,默认为:start
main函数中进入死循环,调用HAL_UART_Receive_DMA()
如果电脑发送了字符串,接收变量flag的值会变
如果接收变量为:start,led阴极置低电平,向电脑发数据“hello windows!”
如果接收变量为:stop!,led阴极置高电平,不向电脑发送数据
3、完善keil5工程代码:
(1)在main.c文件中,详细编写主要代码:
重新定义串口接收完成回调函数:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
//当输入的指令为“stop!"时,发送提示并改变flag=0
if(strEqual(rx_buf,"stop!"))
{
flag=0;
}
//当输入的指令为"start"时,发送提示并改变flag=1
else if(strEqual(rx_buf,"start"))
{
flag=1;
}
HAL_UART_Receive_DMA(&huart1,(uint8_t*)rx_buf,5);
}
(2:用字符串进行判断,因此接受变量用数组来储存,Size要改成数组的大小为:6。单片机收到串口助手发的信息后,与"stop2!"和"start"进行匹配。根据匹配结果执行不同的代码。“stop!”,"start"与收到的数据都用uint8_t数组保存。为执行匹配操作,我们需要写一个函数对每一位进行判断与匹配:
int strEqual(char rcData[6],char rcData2[6])
{
for(uint8_t i = 0 ; i < 6 ; i++){
if (rcData[i] != rcData2[i]) return 0;
}
return 1;
}
(3:在main函数前面添加上如下代码(接收信息储存数组,接收信息匹配处理函数,信息标志flag):
uint8_t flag=1;
uint8_t rx_buf[6];//接收串口数据存放的数组
int strEqual(char rcData[6],char rcData2[6])
{
for(uint8_t i = 0 ; i < 6 ; i++){
if (rcData[i] != rcData2[i]) return 0;
}
return 1;
}
(4:main里面的while(1)替换为如下信息接收与发送处理代码:
if(flag==1)
{
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)message, sizeof(message));
HAL_Delay(600);
}
5:在main函数下面重写串口接收完成回调函数:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
//当输入的指令为“stop!"时,发送提示并改变flag=0
if(strEqual(rx_buf,"stop!"))
{
flag=0;
}
//当输入的指令为"start"时,发送提示并改变flag=1
else if(strEqual(rx_buf,"start"))
{
flag=1;
}
HAL_UART_Receive_DMA(&huart1,(uint8_t*)rx_buf,5);
}
6:完善之后的main.c的全部代码编写如下所示:
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2023 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ——————————————————————*/
#include "main.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ———————————————————-*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef ———————————————————–*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ————————————————————*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro ————————————————————-*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ———————————————————*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes ———————————————–*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ———————————————————*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
uint8_t flag=1;
uint8_t rx_buf[6];//接收串口数据存放的数组
int strEqual(char rcData[6],char rcData2[6])
{
for(uint8_t i = 0 ; i < 6 ; i++){
if (rcData[i] != rcData2[i]) return 0;
}
return 1;
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
//当输入的指令为“stop!"时,发送提示并改变flag=0
if(strEqual(rx_buf,"stop!"))
{
flag=0;
}
//当输入的指令为"start"时,发送提示并改变flag=1
else if(strEqual(rx_buf,"start"))
{
flag=1;
}
HAL_UART_Receive_DMA(&huart1,(uint8_t*)rx_buf,5);
}
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
HAL_Init();
uint8_t message[] = "hello windows!\n"; //定义数据发送数组
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
HAL_UART_Receive_DMA(&huart1,(uint8_t*)rx_buf,5);//设置DMA接收到的数据存放在rx_buf中
while (1)
{
if(flag==1)
{
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)message, sizeof(message));
HAL_Delay(600);
}
}
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
十、 基于DMA方式控制串口通信的电路连接与烧录运行
1、电路的连接、软件的下载以及环境的配置:与中断方式一致!
2、运行结果演示:
打开串口,并且同时点击STM32最小系统板子的复位键即可开始运行:
注:输入“start”:让STM32单片机继续向电脑发送信息;
输入“stop!”:让STM32单片机停止向电脑发送消息 ;
十一、基于DMA控制串口通信的keil5仿真调试
1、进入keil5仿真与开始仿真:
参考本文前面第七大点:基于中断控制串口通信的keil5仿真调试!里面有详细介绍了关于如何进入keil5仿真、环境设置和详细操作等等!!!
2、仿真结果演示:
十二、总结
实验心得
通过本次实验,我深入理解了串口通信的原理以及波特率对数据传输效率的影响。实验中,理论计算的传输时间与实际结果存在一定差异,主要原因在于协议开销和误码重传等实际因素的影响。这让我认识到,在实际工程中需要综合考虑传输效率和数据完整性,合理选择波特率和通信参数。
实验还验证了接地线(GND)的重要性。虽然在短距离通信中偶尔可以不接地线,但长期传输容易因信号漂移导致数据错误。这进一步强调了统一电位参考的重要性,地线的稳定性对通信可靠性至关重要,是实验设计和设备连接中必须重视的环节。
此外,本次实验让我掌握了串口工具的使用和测试方法,提高了对数据传输效率与误差的分析能力。结合理论与实践,通过数据对比和实验验证,积累了宝贵经验。
十三、参考资料
1.STM32—HAL库中断/DMA控制和完成串口通信
stm32使用hal库中断控制串口通信
作者:Elon Josh