HAL库中断与DMA的串口通信大师
文章目录
一、DMA介绍
直接存储器存取(DMA)是一种数据传输技术,它可以提供外设与存储器或存储器与存储器之间的高速数据传输,无需CPU干预,从而节省了CPU的资源。在STM32F103C8T6微控制器中,DMA资源分为DMA1和DMA2,其中DMA1拥有7个通道可供配置。
DMA1通过这些通道可以实现并行的数据传输,每个通道都支持软件触发和特定的硬件触发。这意味着可以通过编程的方式或通过外部硬件信号来触发DMA传输。这些通道的独立性使得多个外设或存储器之间可以同时进行数据传输,提高了系统性能和效率。
使用DMA技术可以有效地减少CPU的负载,因为CPU无需实时参与数据传输过程,可以将更多的时间用于执行其他任务。此外,DMA还可以实现高速数据传输,大大提升了系统的响应速度和数据处理能力。
DMA框图如下:
二、中断模式实现串口通信
在上周的实验中我们已经初步了解过了串口通信,接下来就需要我们在原有代码上进行提高和修改了。
1. 创建工程
首先打开STM32CubeMX,在主界面点击新建工程
随后在新弹出的界面中搜索STM32F103C8,并点击对应芯片。
开启USART1串口,设置为异步通信,并设置USART1中断
然后点击“project manager",选择”project“,写入工程名字以及文件路径。编辑器选择MDK-ARM
然后点击”code generator“,选择以下选项
然后点击"GENERATE CODE",就可以生成工程了
2. 代码编写
不同于上次实验的是,本次实验要求发送一个字符串来控制发送。我们需要定义一个接受数组,利用数组接收从电脑端发来的消息,并对其进行处理与判断。
根据以上思路,我们在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=2;//设置标志位
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
uint8_t hello[20]="Hello,Windows!\r\n";
HAL_UART_Receive_IT(&huart1,(uint8_t*)start,5);
while (1)
{
if(flag==1)//标志位为1,发送hellowindows
{
HAL_UART_Transmit_IT(&huart1,hello,20);
HAL_Delay(1000);
}
else if(flag==0)//标志位为0,停止发送.
{
flag=3;
}
}
}
中断控制函数(HAL_UART_Receive_IT(&huart1,(uint8_t*)start,5)函数是HAL库自动配置的串口中断函数,在接收到电脑端发送的字符后自动触发串口中断,并执行中断程序的代码。):
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);
}
完整代码如下
#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;//初始标志位
uint8_t hello[20]="Hello,Windows!";//需要发送的字符串
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
HAL_UART_Receive_IT(&huart1,(uint8_t*)start,5);
while (1)
{
if(flag==1)//开始发送
{
HAL_UART_Transmit_IT(&huart1,hello,20);
HAL_Delay(1000);
}
else if(flag==0)
{
flag=3;//停止发送
}
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//中断回调函数
{
if(stringcompare(start,"stop!"))//如果发送的是stop!,则停止发送字符串
{
flag=0;
}
else if(stringcompare(start,"start"))//如果发送的是start,则继续发送
{
flag=1;
}
HAL_UART_Receive_IT(&huart1,(uint8_t*)start,5);//再次设置中断,等待下一个字符串
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
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();
}
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();
}
}
void Error_Handler(void)
{
__disable_irq();
while (1)
{
}
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif
3.3 烧录并测试结果
我们打开野火串口助手,设置参数如下:
然后点击打开串口,显示结果如下:
三、DMA模式实现串口通信
1. 工程的建立
DMA工程只需要在原有的基础上加上了一些参数而已。在原有中断配置的基础上,打开USART1的DMA Settings选项,在栏中进行添加
将USART1_RX和USART1_TX全部添加进来就可以了。
2. 主函数编写
编程思路跟中断方法类似,只是我们所用的函数不一样。原有的中断触发相关函数全部改为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 "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;//初始标志位
uint8_t hello[20]="Hello,Windows!";//需要发送的字符串
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
HAL_UART_Receive_DMA(&huart1,(uint8_t*)start,5);//原来这里是IT,现在换成了DMA
while (1)
{
if(flag==1)//开始发送
{
HAL_UART_Transmit_DMA(&huart1,hello,20);
HAL_Delay(1000);
}
else if(flag==0)
{
flag=3;//停止发送
}
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//中断回调函数
{
if(stringcompare(start,"stop!"))//如果发送的是stop!,则停止发送字符串
{
flag=0;
}
else if(stringcompare(start,"start"))//如果发送的是start,则继续发送
{
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};
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();
}
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();
}
}
void Error_Handler(void)
{
__disable_irq();
while (1)
{
}
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif
3. 烧录并测试结果
四、总结
实验中,我们首先配置了USART1的相关参数,并初始化USART1。然后,我们使用中断方式和DMA方式分别实现了串口数据的发送和接收。在中断方式中,我们通过配置USART1的中断使能,并在中断处理函数中判断接收到的字符是否为"stop"或"start",从而控制是否发送数据。在DMA方式中,我们配置了USART1的DMA传输,并设置DMA的数据长度和传输方向。通过启动DMA传输,在接收到"stop"或"start"字符之后,我们通过修改字符来控制是否发送数据。
总体而言,通过这个实验,我再次感受到HAL库的简单便捷。无论是配置还是后期编程,HAL库总是能够系统性地帮我们先配置好模块功能,让我们更容易去上手。
五、参考
https://blog.csdn.net/weixin_66084944/article/details/133913040