HAL库基于中断与DMA的串口可控通信

HAL库基于中断与DMA的串口可控通信

一、实验目的

实验任务:了解串口协议和RS-232标准,以及RS232电平与TTL电平的区别;了解"USB/TTL转232"模块(以CH340芯片模块为例)的工作原理。 使用HAL库(或标准库)方式,设置USART1 波特率为115200,1位停止位,无校验位,分别采用中断方式、DMA方式完成下列任务:STM32系统给上位机(win10)连续发送“hello windows!”;当上位机给stm32发送字符“stop”后,stm32暂停发送“hello windows!”;发送一个字符“start”后,stm32继续发送;

二、中断与DMA简介

中断串口通信是一种基于中断机制的数据传输方式。在中断串口通信中,当有数据要传输时,计算机通过产生一个中断信号来通知 CPU,CPU暂停当前任务并执行中断服务程序来处理传输数据的操作。中断服务程序会读取或写入数据,并执行相应的操作。这种方式在传输数据时具有较高的针对性和可控性,可以实现高度灵活的数据处理。

在中断串口通信中,串口控制器提供了中断线,当有数据需要传输时,串口控制器产生一个中断请求,将数据送入接收或发送缓冲区。CPU在接收到中断请求后,暂停当前任务并跳转到中断服务程序进行数据的读取或写入处理。这种方式需要较多的 CPU 资源用于中断服务程序的运行,因此,在高速大容量数据传输时,可能对系统性能有一定影响。

DMA串口通信则是一种不需要CPU参与传输的数据传输方式。DMA(Direct Memory Access)是一种特殊的硬件设备,它能够直接访问系统内存而不需要通过 CPU。在DMA串口通信中,串口控制器和DMA控制器之间直接进行数据的传输,而不需要CPU的干预。通过配置DMA控制器,将相关传输的参数设置好,DMA控制器会独立地完成数据的传输并通知 CPU 传输完成,这样可以实现高速、低延迟的数据传输。

DMA串口通信适用于高速大容量的数据传输,因为它不需要CPU参与传输,所以可以充分利用 CPU 的计算能力进行其他任务处理,提高系统整体性能。同时,DMA串口通信也可以减少由于 CPU 资源占用导致的数据传输延迟,提高数据的实时性。

总结来说,中断串口通信和DMA串口通信都是常用的数据传输方式。中断串口通信适用于灵活的数据处理,而DMA串口通信适用于高速大容量的数据传输场景。具体选择哪种方式,需要根据实际应用的需求来进行取舍。

中断基本功能我们曾经了解过,DMA基本结构如下:
在这里插入图片描述

三、利用中断模式实现

3.1 利用STM32CubeMX建立一个中断工程

首先打开STM32CubeMX,在主界面点击新建工程

在这里插入图片描述

随后在新弹出的界面中搜索STM32F103C8,并点击对应芯片。

在这里插入图片描述
在随后的界面中,选择GPIO,打开GPIOA1和GPIOA2两个端口,作为我的指示灯进行使用。

在这里插入图片描述

随后,开启USART1串口,设置为异步通信。

在这里插入图片描述

然后点击“project manager",选择”project“,写入工程名字以及文件路径。编辑器选择MDK-ARM
在这里插入图片描述

然后点击”code generator“,选择以下选项

在这里插入图片描述

然后点击"GENERATE CODE",就可以生成工程了

在这里插入图片描述

3.2 中断工程内完善主函数代码

3.2.1 函数编写思路

不同于上次实验的是,本次实验要求发送一个字符串来控制发送。但在实际单片机的工程中,字符串都是通过将各个字符拆分放到数组里进行发送的,且keil中没有定义字符串的声明”string"。故我们采用以下思路:

编写一个字符串比较函数,将输入的数据字符串逐个与起始字符相比较,通过比较函数的返回值控制标志位,然后利用标志位的值不断改变而去控制字符串的发送。

由于收到的和发送的字符串大小应是统一的,故我们将指令修改为“stop!"和”start“。

3.2 2 函数主体编写过程

首先,我们在主函数里把GPIO口初始化设置成SET。

 HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET);
 HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET);

根据以上思路,我们在main.c中编写字符串比较函数,放在主函数上面。

int stringcompare(char str1[6],char str2[6])
	{
		uint8_t i=0;
	
		for(i = 0 ; i < 6 ; i++)
		{
		if (str1[i] != str2[i]) 
			
			return 0;//如果输入数据与原有数据不符,返回0
	}
	
	return 1;//如果位数全部相同,则返回1
}

然后,定义初始字符串以及标志位,并编写主函数如下:

char start[6] = "start";
uint8_t flag=3;
 
int main(void)
{
  	HAL_Init();
  	SystemClock_Config();
  	MX_GPIO_Init();
  	MX_USART1_UART_Init();
	  uint8_t hello[20]="Hello,Windows!\r\n";
    uint8_t stop[50]="Stop!please enter 'start' to continue!\r\n";
	  HAL_UART_Receive_IT(&huart1,(uint8_t*)start,5);
	
  	while (1)
  	{
		
		 if(flag==1)//标志位为1,发送hellowindows
		{					
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET);
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET);
		   
		    HAL_UART_Transmit_IT(&huart1,hello,20);
		    HAL_Delay(1000);
		}
		else if(flag==0)//标志位为0,停止发送0.
		{
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET);
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET);
			 HAL_UART_Transmit_IT(&huart1,stop,50);
			flag=3;
		}
	
  }
}

随后,在主函数后添加中断控制函数:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{

	if(stringcompare(start,"stop!"))//输入为stop时,标志位置0
	{
		flag=0;
		
	}
	

	else if(stringcompare(start,"start"))//输入为 start时,标志位置1
	{
		flag=1;
	}
	
	//重新设置中断
    HAL_UART_Receive_IT(&huart1,(uint8_t*)start,5);
}

HAL_UART_Receive_IT(&huart1,(uint8_t*)start,5)函数是HAL库自动配置的串口中断函数,在接收到电脑端发送的字符后自动触发串口中断,并执行中断程序的代码。

主函数完整代码如下

#include "main.h"
#include "usart.h"
#include "gpio.h"
 

 
void SystemClock_Config(void);

int stringcompare(char str1[6],char str2[6])
{
	for(uint8_t i = 0 ; i < 6 ; i++)
    {
		if (str1[i] != str2[i]) 
            return 0;
	}
	return 1;
}
 
char start[6] = "start";
uint8_t flag=3;
 
int main(void)
{
  	HAL_Init();
  	SystemClock_Config();
  	MX_GPIO_Init();
  	MX_USART1_UART_Init();
	  uint8_t hello[20]="Hello,Windows!\r\n";
    uint8_t stop[50]="Stop!please enter 'start' to continue!\r\n";
	  HAL_UART_Receive_IT(&huart1,(uint8_t*)start,5);
	
  	while (1)
  	{
		
		 if(flag==1)//标志位为1,发送hellowindows
		{					
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET);
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET);
		   
		    HAL_UART_Transmit_IT(&huart1,hello,20);
		    HAL_Delay(1000);
		}
		else if(flag==0)//标志位为0,停止发送0.
		{
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET);
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET);
			 HAL_UART_Transmit_IT(&huart1,stop,50);
			flag=3;
		}
	
  }
}
 
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{

	if(stringcompare(start,"stop!"))//输入为stop时,标志位置0
	{
		flag=0;
		
	}
	

	else if(stringcompare(start,"start"))//输入为 start时,标志位置1
	{
		flag=1;
	}
	
	//重新设置中断
    HAL_UART_Receive_IT(&huart1,(uint8_t*)start,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
32t24  * @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 */

	

3.3 烧录并测试结果

3.3.1 硬件设备连接

之前我们介绍过CH340串口模块如何与PC和单片机相连,请参考这篇博客

https://blog.csdn.net/Constellation_zZ/article/details/133146422

后来,我又买了一块ST-LINK烧录模块,相较于原有,它具有以下优点

  1. 不需要占用GPIO口引脚,直接在电源处四个引脚口进行连接。
  2. 烧录时不需要其他软件,只需要用keil的download就可以直接烧录,也不需要每次烧录时搬动跳键帽。

故我们使用ST-LINK进行烧录。我们先将硬件电路连接好。图中的显示屏模块是不需要的不管它。
在这里插入图片描述

实物图如下:

在这里插入图片描述

3.3.2 烧录结果

我们在安装好ST-LINK驱动后就可以正常使用ST-LINK了。ST-LINK安装驱动请参考下面

https://blog.csdn.net/qq_52158753/article/details/130161426

然后我们打开keil,点击download下载好程序

在这里插入图片描述

烧录完程序后,记得按一下板子上的复位键!

随后我们打开串口助手,设置参数如下:

在这里插入图片描述

然后点击打开串口,显示结果如下:

在这里插入图片描述

四、利用DMA模式实现

4.1 利用STM32CubeMX建立一个DMA工程

DMA工程创建与中断类似,只需要在原有的基础上加上了一些参数而已。

我们在原有中断配置的基础上,打开USART1的DMA Settings选项,在栏中进行添加

在这里插入图片描述

在这里插入图片描述

将USART1_RX和USART1_TX全部添加进来。添加结果如下:

在这里插入图片描述

我们点击GENERATE CODE生成代码,这样就配置完成了。

4.2 DMA工程内完善主函数代码

编程思路跟中断方法类似,只是我们所用的函数不一样。原有的中断触发相关函数全部改为DMA函数,如下所示


 HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size)//DMA方式下的发送函数
 
 HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)//DMA模式下触发中断函数

其他的函数主体,思路,参数设置那些都是一样的,那么不再多说,直接给出主函数代码

#include "main.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"
void SystemClock_Config(void);


int stringcompare(char str1[6],char str2[6])
	{
		uint8_t i=0;
	
		for(i = 0 ; i < 6 ; i++)
		{
		if (str1[i] != str2[i]) 
			
			return 0;//如果输入数据与原有数据不符,返回0
	}
	
	return 1;//如果位数全部相同,则返回1
}
 
char start[6] = "start";//初始字符串
uint8_t flag=3;//标志位
 
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_DMA_Init();
    MX_USART1_UART_Init();
	  HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET);
		HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET);
	//配置函数
	  uint8_t hello[20]="Hello,Windows!\r\n";
    uint8_t stop[50]="Stop!please enter 'start' to continue!\r\n";
  HAL_UART_Receive_DMA(&huart1,(uint8_t*)start,5);
	
  	while (1)
  	{
		
		 if(flag==1)//标志位为1,发送hellowindows
		{					
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET);
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET);
		   
		    HAL_UART_Transmit_DMA(&huart1,hello,20);
		    HAL_Delay(1000);
		}
		else if(flag==0)//标志位为0,停止发送0.
		{
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET);
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET);
			 HAL_UART_Transmit_DMA(&huart1,stop,50);
			flag=3;
		}
	
  }
}
 
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{

	if(stringcompare(start,"stop!"))//输入为stop时,标志位置0
	{
		flag=0;
		
	}
	

	else if(stringcompare(start,"start"))//输入为 start时,标志位置1
	{
		flag=1;
	}
	
	//重新设置中断
    HAL_UART_Receive_DMA(&huart1,(uint8_t*)start,5);
}
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 */

4.3 烧录并测试结果

烧录和串口参数同上,这里只给出输出结果

在这里插入图片描述

五、总结

本次实验要求在上次实验基础上做了提升,要求利用中断和DMA对发送程序进行可控。同时要求指令为一串字符串而不是一个字符。通过本次实验,我能使用数组来接收所发来的字符串,并利用设置标志位的方式来间接操控数据的发送和停止,在以后有关数据包发送的实验中用此种方式可以有效地解决问题

六、参考

基于STM32标准库的USART串口通信-CSDN博客

https://blog.csdn.net/Constellation_zZ/article/details/133936255

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值