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烧录模块,相较于原有,它具有以下优点
- 不需要占用GPIO口引脚,直接在电源处四个引脚口进行连接。
- 烧录时不需要其他软件,只需要用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对发送程序进行可控。同时要求指令为一串字符串而不是一个字符。通过本次实验,我能使用数组来接收所发来的字符串,并利用设置标志位的方式来间接操控数据的发送和停止,在以后有关数据包发送的实验中用此种方式可以有效地解决问题
六、参考
https://blog.csdn.net/Constellation_zZ/article/details/133936255