STM32 HAL库中断和DMA控制实现串口通信

目录

一、解决的问题

二、串口通讯协议和RS-232的介绍以及USB/TTL转232模块的工作原理 

 1、 串口协议和RS-232标准:

 (1)串口协议:

(2)RS-232 标准: 

 2、RS232电平与TTL电平的区别 

 3、USB/TTL转232“模块(CH340芯片为例) 

(1)基本原理: 

(2)CH340模块介绍:

​三、搭建STM32开发环境(HAL库环境)

 四、利用HAL库新建一个中断控制串口通信的工程 

 五、完善通过中断方式控制串口通信的keil5工程

 (1)本工程中几个函数简介:

(2)编写代码思路: 

 (3)完善keil5工程代码:

六、基于中断控制串口通信的电路连接与烧录运行

 1、电路连接:

 2、 USB转TTL环境配置:

 3、下载烧录软件与串口通信软件: 

 4、keil5工程里面对于USB转TTL的配置:

​5、编译生产hex文件,用于后面的烧录步骤:

​6、烧录:

7、配置XCOM,打开XCOM软件,按下图所示进行配置:

​8、运行结果演示:

七、基于中断控制串口通信的keil5仿真调试

 1、进入keil5仿真:

 2、开始仿真:

八、利用HAL库新建一个DMA控制串口通信的工程 

九、完善通过DMA方式控制串口通信的keil5工程

1、 本工程中的几个函数简介:

2、代码编写思路

3、完善keil5工程代码:

十、 基于DMA方式控制串口通信的电路连接与烧录运行

1、电路的连接、软件的下载以及环境的配置:

2、运行结果演示: 

十一、基于DMA控制串口通信的keil5仿真调试

 1、进入keil5仿真与开始仿真:

2、仿真结果演示:​

十二、总结

十三、参考资料


一、解决的问题

     1、使用HAL库(或标准库)方式,设置USART1 波特率为115200,1位停止位,无校验位,分别采用中断方式、DMA方式完成下列任务:

     STM32系统给上位机(win10)连续发送“hello windows!”;当上位机给stm32发送字符“stop”后,stm32暂停发送“hello windows!”;发送一个字符“start”后,stm32继续发送;

    2、 在没有示波器条件下,可以使用Keil的软件仿真逻辑分析仪功能观察串口输出波形,并分析时序状态正确与否,计算波特率实际为多少。

二、串口通讯协议和RS-232的介绍以及USB/TTL转232模块的工作原理 

 1、 串口协议和RS-232标准:

 (1)串口协议:

        串口通讯(Serial Communication)是一种设备间非常常用的串行通讯方式,因为它简单、便捷,因此大部分电子设备都支持该通讯方式,电子工程师在调试设备时也经常使用该通 讯方式输出调试信息。
       在计算机科学里,大部分复杂的问题都可以通过分层来简化。如芯片被分为内核层和片上外设;STM32标准库则是在寄存器与用户代码之间的软件层。对于通讯协议,我们也以分层的方式来理解,最基本的是把它分为物理层和协议层。

名称 组成作用
物理层 具有机械、电子功能部分的特性,确保原始数据在物理媒体的传输
协议层 规定通讯逻辑,统一收发双方的数据打包、解包标准。

 在串口通讯的物理层有很多标准及变种,下面主要讲解 RS-232 标准!

(2)RS-232 标准: 

 RS-232 标准主要规定了信号的用途通讯接口以及信号的电平标准。 

       在上面的通讯方式中,两个通讯设备的“DB9接口”之间通过串口信号线建立起连接,串口信号线中使用“RS-232标准”传输数据信号。由于RS-232电平标准的信号不能直接被控制器直接识别,所以这些信号会经过一个“电平转换芯片”转换成控制器能识别的“TTL校准”的电平信号,才能实现通讯。 

 2、RS232电平与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库点亮流水灯-CSDN博客

 四、利用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)

延时ms函数。 

(2)编写代码思路: 

main函数外用一个char类型的数组:rcData,接收发过来的连续字符串,默认为:start
main函数中进入死循环,调用HAL_UART_Receive_IT使能接收中断
如果电脑发送了字符串,接收变量flag的值会变
如果接收变量为:start,led阴极置低电平,led亮,向电脑发数据“hello windows!”
如果接收变量为:stop!,led阴极置高电平,led灭,不向电脑发送数据

 (3)完善keil5工程代码:

 首先,点击刚刚生成的keil5工程文件,双击main.c文件,然后再main.c中找到图示框住的函数, 接着右击此函数,进入其定义的地方处:

(2)将图中框住的部分改为SET即可:此步骤是将这个GPIO口设置为高电平,初始时不亮!

 (3)回到main.c文件中,详细编写主要代码:

(1:设置接收中断,函数原型简介:

 HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
  UART_HandleTypeDef *huart      UATR的别名    
 huart1  *pData      			接收到的数据存放地址
 Size                      		接收的字节数
 功能:串口中断接收,以中断方式接收指定长度数据。
 大致过程是,设置数据存放位置,接收数据长度,然后使能串口接收中断。
 接收到数据时,会触发串口中断。
 再然后,串口中断函数处理,直到接收到指定长度数据
 而后关闭中断,进入中断接收回调函数,不再触发接收中断。(只触发一次中断)

 具体化运用在本项目中代码为:

HAL_UART_Receive_IT(&huart1,(uint8_t*)rcData,5);

 (2:用字符串进行判断,因此接受变量用数组来储存,Size要改成数组的大小为:6。单片机收到串口助手发的信息后,与"stop2!"和"start"进行匹配。根据匹配结果执行不同的代码。“stop!”,"start"与收到的数据都用uint8_t数组保存。为执行匹配操作,我们需要写一个函数对每一位进行判断与匹配:

int strEqual(char rcData[15],char rcData2[15]){
	for(uint8_t i = 0 ; i < 15 ; i++){
		if (rcData[i] != rcData2[i]) return 0;
	}
	return 1;
}

 (3:在main函数前面添加上如下代码(接收信息储存数组,接收信息匹配处理函数,信息标志flag):

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;
}

char rcData[6] = "start";
uint8_t flag=1;

 (4:main里面的while(1)替换为如下信息接收与发送处理代码:

        if(flag==0)
		{
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_SET);
		}
		else if(flag==1)
		{					
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_RESET);
		    uint8_t hello[20]="hello windows!\n";
		    HAL_UART_Transmit_IT(&huart1,hello,20);
		    HAL_Delay(600);
		}

 (5:在main函数下面重写中断处理函数

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	//当输入的指令为“stop!"时,发送提示并改变flag=0
	if(strEqual(rcData,"stop!"))
	{
		flag=0;
	}
	
	//当输入的指令为"start"时,发送提示并改变flag=1
	else if(strEqual(rcData,"start"))
	{
		flag=1;
	}
	//重新设置中断
    HAL_UART_Receive_IT(&huart1,(uint8_t*)rcData,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 "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 */
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;
}

char rcData[6] = "start";
uint8_t flag=1;

int main(void)
{
  	HAL_Init();
  	SystemClock_Config();
  	MX_GPIO_Init();
  	MX_USART1_UART_Init();

	HAL_UART_Receive_IT(&huart1,(uint8_t*)rcData,5);
	
  	while (1)
  	{
		if(flag==0)
		{
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_SET);
		}
		else if(flag==1)
		{					
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_RESET);
		    uint8_t hello[20]="hello windows!\n";
		    HAL_UART_Transmit_IT(&huart1,hello,20);
		    HAL_Delay(600);
		}
  }
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	//当输入的指令为“stop!"时,发送提示并改变flag=0
	if(strEqual(rcData,"stop!"))
	{
		flag=0;
	}
	
	//当输入的指令为"start"时,发送提示并改变flag=1
	else if(strEqual(rcData,"start"))
	{
		flag=1;
	}
	//重新设置中断
    HAL_UART_Receive_IT(&huart1,(uint8_t*)rcData,5);
}


/**
  * @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 */

六、基于中断控制串口通信的电路连接与烧录运行

 1、电路连接:

USB转TTL与STM32的连接,参考下图: 

 

 PA0——黄灯;

连接好的实物图如下所示:

 2、 USB转TTL环境配置:

需要在电脑上安装CH340驱动(USB串口驱动)或者CH341驱动(USB串口驱动):

在网上下载好所需的CH340驱动(USB串口驱动)或者CH341驱动(USB串口驱动),如下图所示:

 点击CH341SER_2.EXE进行安装CH341驱动,会弹出一下弹窗,点击安装,等待安装成功即可:

 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最小系统板子的复位键即可开始运行:

注:输入“start”:让STM32单片机继续向电脑发送信息;

输入“stop!”:让STM32单片机停止向电脑发送消息 ;

七、基于中断控制串口通信的keil5仿真调试

 1、进入keil5仿真:

(1)点击第一步,Target界面中,选择跟正确的晶振大小,使用8MHz的外部晶振:

(2)接着进行Debug页的设置:  

(3)点击图示圈住的地方进入仿真调试界面: 

  (4)选择逻辑分析仪: 

(5)点击Setup设置添加要进行观察的引脚波形信息:

 波形信息配置:添加波形信息的时候,如果自己选择的串口配置的是USART1,就在添加信息里面输入:USART1_RS;同理可得,如果自己选择的串口配置的是USART2,就在添加信息里面输入:USART2_RS;我项目里配置的是USART1,所以我在添加信息时就输入:USART1_RS。

我这里选择的USART1对应的波形颜色是绿色!!!

 2、开始仿真:

(1)点击图示圈住的部分,进行仿真   

可以调节下图所示几个比较重要与常用的按钮来进行对图像的总体放大与缩小等等操作 :

  (2)仿真结果:

放大处理之后波形图像:

八、利用HAL库新建一个DMA控制串口通信的工程 

(1)打开STM32CubeMX,在主界面点击:ACCESS TO MCU SELECTOR:

(2)选择的单片机型号以及点击开始工程项目: 

 (3)按照下图所示进行配置RCC:

 (4)设置USART1:选择异步通信、参数选择默认和使能串口:

(5)添加两个通道:

 (6)进入Project Manager(工程管理),进行工程设置点击生成工程与代码:

注意:路径不能包含中文和空格,不然生成的工程文件无法在Keil中打开;

九、完善通过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中
  •  HAL_Delay(uint ms): 
  • HAL_Delay(uint ms)
    

    延时ms函数。  

    2、代码编写思路

    main函数外用一个uint8_t类型的数组:rcData,接收发过来的连续字符串,默认为:start
    main函数中进入死循环,调用HAL_UART_Receive_DMA()
    如果电脑发送了字符串,接收变量flag的值会变
    如果接收变量为:start,led阴极置低电平,向电脑发数据“hello windows!”
    如果接收变量为:stop!,led阴极置高电平,不向电脑发送数据

    3、完善keil5工程代码:

     (1)在main.c文件中,详细编写主要代码:

    (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);
    }

      (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、仿真结果演示:

     放大处理之后波形图像:

    十二、总结

          本人在这篇blog:STM32使用HAL库中断控制串口通信-CSDN博客,已经提前接触并且完成了有关中断控制串口通信:向单片机发送单个字符的实验;本次又在这个的实验的基础上完成了对于利用中断控制串口通信:向单片机发送连续字符串的实验。总的来说,是温习前面已经会了的中断控制串口通信;从局部来说,也是进步:学习并且尝试了新的内容:向单片机发送连续字符串的实验而不是单个字符!!!

         对于DMA控制串口通信方式,本人之前既没有听说过,也没有接触过。在本实验中,本人学习了有关DMA的知识,并且同时付出行动去实践实验,最终成功利用DMA方式也实现了串口通信:向单片机发送单个字符的实验!!!

        谢谢你的观看,希望你能有所收获!完!!!

    十三、参考资料

    1、stm32使用hal库中断控制串口通信_stm32 hal库串口中断接收_终极末影龙的博客-CSDN博客 

    2、HAL库中断方式进行串口通信_醉意丶千层梦的博客-CSDN博客 

    3、STM32使用HAL库中断控制串口通信-CSDN博客 

    4、基于HAL库实现DMA串口通信_hal_dma_start_it_醉意丶千层梦的博客-CSDN博客

    5、STM32F0x HAL库学习笔记(7)DMA数据的传输配置:串口数据的DMA发送与接收_hal_dma_start-CSDN博客

    6、【STM32】HAL库 STM32CubeMX教程十一—DMA (串口DMA发送接收)_hal库dma串口接收-CSDN博客

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 HAL库中断和DMA控制实现串口通信

    发表评论