STM32F103系列基于HAL库的串口通信(串口,串口中断,串口DMA)

前言

本文章讲述的是STM32F103系列基于HAL库开发的串口通信,包含普通串口串口中断以及串口DMA,感兴趣的可以好好看看。

本次实验用到的模块有:STM32C8T6系统板,0.96寸OLED屏幕以及HC-08蓝牙模块

普通串口接收(HAL_UART_Receive)

简单介绍:`HAL_UART_Receive`是 STM32 HAL 库中用于 UART 数据接收的阻塞函数。它接收指定数量的字节,并将数据存储到指定的缓冲区中。该函数会一直等待,直到数据接收完成或超时,因此会阻塞程序的执行。

首先打开STM32CubeMx,选择对应的芯片型号,首先进入system Core,选择SYS设置调试方式(DuBug)Serial Wire。然后返回上一级,进入Connectivity,选择对应的串口USARTx(x表示你所选择的串口)。如果没有特殊情况,默认选择Asynchronous异步通信模式

基础设置(Basic Parameters)

波特率(Baud Rate):115200 Bits/s(按个人需求来)

字长( Word Length):8 Bits/s(通常为一个字节)

奇偶校验位 (Parity) :None(通常不需要校验位)

停止位(Stop Bit):1 (用于标识数据帧的结束)

高级设置(advanced paramenters)

数据方向(Data Direction):Receive and Transmit(发送和接收)

过采样(Oversampling):16 samples

最后选择GENERATE CODE生成代码即可

核心代码

#include "main.h"
#include "OLED.h"
#include "GPIO_PIN_13.h"

UART_HandleTypeDef huart2;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);

int main(void)
{

  HAL_Init();// 初始化 HAL 库

  SystemClock_Config();// 配置系统时钟

  MX_GPIO_Init();// 初始化 GPIO
  MX_USART2_UART_Init();// 初始化 USART2 UART
	OLED_Init();// 初始化 OLED
	GPIOC_PIN_13_Init();// 初始化 GPIOC 的第 13 引脚

	char DATARCVD[10]; // 定义一个足够大的缓冲区
	
	uint8_t received_byte;// 定义一个变量,用于存储接收到的单个字节
	
  while (1)
  {
		HAL_UART_Receive(&huart2,&received_byte,1,10); // 通过 UART2 接收一个字节的数据,存储到 received_byte 中
		// 参数说明:
    // &huart2:指定 UART 句柄
    // &received_byte:接收数据存储的变量地址
    // 1:接收字节数
    // 10:接收超时时间(单位为毫秒)
		DATARCVD[0]=received_byte;// 将接收到的字节赋值给 DATARCVD 数组的第一个元素
		DATARCVD[1]='\0';// 在 DATARCVD 数组的第二个位置添加字符串结束符,确保 DATARCVD 是一个有效的字符串
		if(DATARCVD[0]=='H')// 判断接收到的字符是否为 'H'
		{
			HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);// 将 GPIOC 的第 13 引脚设置为高电平(GPIO_PIN_13)
			OLED_ShowString(1,1,"LED_OFF"); // 在 OLED 显示屏的第 1 行第 1 列显示 "LED_OFF"
			OLED_ShowString(2,1,DATARCVD);// 在 OLED 显示屏的第 2 行第 1 列显示接收到的数据(DATARCVD)
		}
		else if(DATARCVD[0]=='J') // 判断接收到的字符是否为 'J'
		{
		  HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);// 将 GPIOC 的第 13 引脚设置为低电平(GPIO_PIN_13)
			OLED_ShowString(1,1,"LED__ON");// 在 OLED 显示屏的第 1 行第 1 列显示 "LED__ON"
			OLED_ShowString(2,1,DATARCVD); // 在 OLED 显示屏的第 2 行第 1 列显示接收到的数据(DATARCVD)
		}
		
		OLED_ShowString(2,1,DATARCVD); 
		// 无论是否匹配 'H' 或 'J',都会在 OLED 的第 2 行第 1 列显示接收到的数据
    // 这一行代码可能会导致重复显示,因为上面的分支中已经显示过一次 DATARCVD
  }
}

 功能:

1. 数据接收:通过 UART2 接收一个字节的数据,并存储到   received_byte   中。

2. 数据处理:如果接收到的字符是   'H'  ,则点亮 GPIOC 的第 13 引脚(LED 熄灭),并在OLED 上显示   "LED_OFF"  。如果接收到的字符是   'J'  ,则熄灭 GPIOC 的第 13 引脚(LED 点亮),并在 OLED 上显示   "LED__ON"  。

3. OLED 显示:在 OLED 的第 2 行显示接收到的1个字节的数据。

优点

1. 逻辑简单:使用阻塞式接收函数,代码逻辑简单直观,易于理解和实现。不需要处理复杂的中断机制,适合初学者或对实时性要求不高的应用。

2. 易于调试:阻塞式接收函数的行为容易预测,调试时可以方便地跟踪数据接收和处理过程。不涉及中断优先级和同步问题,减少了调试的复杂性。

3. 资源占用低:不需要额外的缓冲区管理,减少了内存使用。不需要配置中断优先级,简化了系统资源管理。

缺点

1. 阻塞性: HAL_UART_Receive   是阻塞函数,会阻塞程序的执行,直到接收到数据或超时。如果数据接收间隔较长,可能会导致程序响应变慢,影响其他任务的执行。

2. 效率较低:由于是阻塞式接收,CPU 在等待数据接收时无法执行其他任务,资源利用率低。 每次只接收一个字节,效率较低,不适合高数据量的传输场景。

3. 代码冗余:在 OLED 上重复显示接收到的数据(  DATARCVD  ),可能会导致显示内容重复。• 代码中存在一些冗余操作,例如在分支中已经显示过   DATARCVD  ,最后又重复显示一次。

总结

HAL_UART_Receive是一个阻塞式的 UART 数据接收函数。它会在指定的时间内等待接收指定数量的字节,直到接收完成或超时。这种方式简单易用,适合数据量小且对实时性要求不高的场景,但会阻塞程序执行,降低 CPU 资源利用率。

串口接收中断(HAL_UART_Receive_IT)

简单介绍:`HAL_UART_Receive_IT`是 STM32 HAL 库中用于 UART 数据接收的中断驱动函数。它启动一个非阻塞的接收操作,允许程序在接收数据时继续执行其他任务。当接收完成时,会触发中断并调用回调函数`HAL_UART_RxCpltCallback`,通知用户处理接收到的数据。这种方式提高了程序的效率和实时性,特别适合处理大量数据或对实时性要求较高的场景。

在前面普通串口的基础上,选择NVIC Settings(设置中断),由于这个只是串口的中断调试,所以对中断优先级不做调整,默认即可。

核心代码 

#include "main.h"
#include "OLED.h"
#include "GPIO_PIN_13.h"

#define BUFFER_SIZE 10 // 定义接收缓冲区的大小,用于存储接收到的UART数据

UART_HandleTypeDef huart2; // UART2句柄,用于操作串口通信

void SystemClock_Config(void); // 系统时钟配置函数声明
static void MX_GPIO_Init(void); // GPIO初始化函数声明
static void MX_USART2_UART_Init(void); // UART2初始化函数声明

uint8_t DataRcvd[BUFFER_SIZE]; // 定义接收数据缓冲区,用于存储UART接收到的数据

int main(void)
{
  HAL_Init(); // 初始化HAL库,设置全局变量、中断优先级分组等
  SystemClock_Config(); // 配置系统时钟,设置CPU时钟频率等
  MX_GPIO_Init(); // 初始化GPIO,配置GPIO的模式、速度等
  MX_USART2_UART_Init(); // 初始化UART2,配置波特率、数据位、停止位等
  GPIOC_PIN_13_Init(); // 初始化GPIOC_PIN_13,用于控制LED
  OLED_Init(); // 初始化OLED显示屏
  HAL_UART_Receive_IT(&huart2, DataRcvd, 2); // 以中断方式接收2个字节数据,存储到DataRcvd缓冲区

  while (1) // 主循环
  {
    // 主循环为空,所有操作在中断回调函数中完成
  }
}

// UART接收中断完成回调函数,此函数在stm32f1xx_hal_uart.c文件中的第2622行
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart == &huart2) // 判断是否为huart2的中断
  {	
    char receivedChar[BUFFER_SIZE + 1] = {0}; // 用于存储字符串,+1用于存储字符串结束符'\0'
    for (int i = 0; i < BUFFER_SIZE; i++) // 将接收到的数据复制到字符串数组
    {	
      receivedChar[i] = DataRcvd[i];
    }
    // 显示接收到的字符串
    for (int i = 0; i < 200; i++) // 延时,防止显示过快
    {
      // 空循环,用于延时
    }
    OLED_ShowString(2, 1, receivedChar); // 在OLED上显示接收到的字符串,位置为第2行第1列

    // 根据接收到的第一个字符控制LED和显示信息
    if (DataRcvd[0] == 'H') // 如果接收到的字符为'H'
    {
      HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); // 点亮LED
      OLED_ShowString(1, 1, "LED_OFF"); // 在OLED上显示LED状态,位置为第1行第1列
    }
    else if (DataRcvd[0] == 'J') // 如果接收到的字符为'J'
    {
      HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); // 熄灭LED
      OLED_ShowString(1, 1, "LED__ON"); // 在OLED上显示LED状态,位置为第1行第1列
    }  
  }
  HAL_UART_Receive_IT(&huart2, DataRcvd, 2); // 继续以中断方式接收2个字节数据,保持接收功能
}

 串口中断的优点

1. 提高效率:

非阻塞:串口中断是非阻塞的,程序可以在等待串口数据接收或发送时继续执行其他任务,而不是在串口操作上浪费时间。

实时性:能够及时响应串口事件,确保数据的及时处理,适合对实时性要求较高的应用。

2. 资源利用合理:

CPU占用低:在没有串口事件发生时,CPU可以执行其他任务,不会被串口操作占用。

缓冲区管理:通过缓冲区管理,可以减少数据丢失的风险,提高数据传输的可靠性。

3. 灵活性高:

动态处理:可以根据接收到的数据动态调整程序的行为,例如根据接收到的命令执行不同的操作。

易于扩展:可以方便地扩展串口功能,例如增加协议解析、数据校验等功能。

串口中断的缺点

1. 复杂性增加:

代码复杂:需要编写中断处理函数,并且需要处理中断的优先级和同步问题,代码复杂度较高。

调试困难:中断相关的问题(如中断冲突、数据丢失等)较难调试,需要使用专业的调试工具和方法。

2. 资源限制:

 中断优先级:在多中断系统中,需要合理配置中断优先级,否则可能会导致高优先级中断抢占低优先级中断,影响系统稳定性。

 缓冲区大小:缓冲区大小有限,如果数据接收速度过快,可能会导致缓冲区溢出,丢失数据。

3. 对硬件要求高:

硬件支持:需要硬件支持中断功能,且硬件的中断处理能力需要足够强大。

稳定性要求:硬件的稳定性对串口中断的可靠性影响较大,硬件故障可能导致中断处理失败。

总结

HAL_UART_Receive_IT是一个用于启动 UART 接收中断的 HAL 库函数。它允许 UART 在接收到指定数量的数据后触发中断,通过回调函数`HAL_UART_RxCpltCallback`通知用户程序处理数据。这种方式是非阻塞的,可提高程序效率,适合对实时性要求较高的场景,但需要合理管理中断优先级和缓冲区,以避免数据丢失和冲突。

串口DMA(HAL_UARTEx_ReceiveToIdle_DMA)

简单介绍:`HAL_UARTEx_ReceiveToIdle_DMA`是 STM32 HAL 库提供的一个扩展功能,用于在 DMA 模式下接收串口数据,直到检测到空闲字符(即串口空闲一段时间)。它结合了 DMA 的高效数据传输能力和空闲检测机制,能够在接收不定长数据时自动停止接收,非常适合处理如协议帧、用户输入等不定长数据流的场景。

该函数是非阻塞的,启动后程序可以继续执行其他任务,直到接收完成或检测到空闲字符时触发回调函数`HAL_UARTEx_RxEventCallback`,通知用户处理接收到的数据。这种方式不仅提高了数据接收的效率,还减轻了 CPU 的负担,特别适合对实时性和数据处理效率要求较高的应用。

在前面串口中断的基础上选择DMA Settings(DMA设置),然后点击add添加USART_TX和USART_RX,其它设置选择默认即可。

核心代码

#include "main.h"
#include "GPIO_PIN_13.h"
#include "OLED.h"

UART_HandleTypeDef huart2; // UART2句柄,用于串口通信
DMA_HandleTypeDef hdma_usart2_rx; // USART2接收DMA句柄
DMA_HandleTypeDef hdma_usart2_tx; // USART2发送DMA句柄

void SystemClock_Config(void); // 系统时钟配置函数声明
static void MX_GPIO_Init(void); // GPIO初始化函数声明
static void MX_DMA_Init(void); // DMA初始化函数声明
static void MX_USART2_UART_Init(void); // USART2初始化函数声明

uint8_t RxData[256] = {0}; // 定义一个接收缓冲区,大小为256字节,用于存储接收到的数据

int main(void)
{
  HAL_Init(); // 初始化HAL库,设置全局变量、中断优先级分组等
  SystemClock_Config(); // 配置系统时钟,设置CPU时钟频率等

  MX_GPIO_Init(); // 初始化GPIO
  MX_DMA_Init(); // 初始化DMA
  MX_USART2_UART_Init(); // 初始化USART2 UART
  GPIOC_PIN_13_Init(); // 初始化GPIOC的第13引脚,用于控制LED
  OLED_Init(); // 初始化OLED显示屏

  // 使用扩展函数HAL_UARTEx_ReceiveToIdle_DMA,接收不定长的数据
  // 当接收到空闲字符时,触发接收完成事件
  HAL_UARTEx_ReceiveToIdle_DMA(&huart2, RxData, sizeof(RxData));

  // 关闭传输过半中断(这是HAL库默认开启的,需要我们手动关闭)
  // 避免不必要的中断处理
  __HAL_DMA_DISABLE_IT(huart2.hdmarx, DMA_IT_HT);

  while (1) // 主循环
  {
    // 主循环为空,所有操作在中断回调函数中完成
  }
}

// UART接收事件回调函数,此函数在stm32f1xx_hal_uart.h头文件的784行
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
  // 判断是否为USART2的接收事件
  if (huart->Instance == USART2)
  {
    // 将接收到的数据通过DMA发送回去,实现回传功能
    HAL_UART_Transmit_DMA(&huart2, RxData, Size);

    // 根据接收到的第一个字符控制LED和显示信息
    if (RxData[0] == 'H') // 如果接收到的字符为'H'
    {
      HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); // 点亮LED
      OLED_ShowString(1, 1, "LED_OFF"); // 在OLED显示屏的第1行第1列显示"LED_OFF"
    }
    else if (RxData[0] == 'J') // 如果接收到的字符为'J'
    {
      HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); // 熄灭LED
      OLED_ShowString(1, 1, "LED__ON"); // 在OLED显示屏的第1行第1列显示"LED__ON"
    }   

    // 在OLED显示屏的第2行第1列显示接收到的数据
    // 注意:将RxData转换为char指针,因为OLED_ShowString函数可能需要char类型参数
    OLED_ShowString(2, 1, (char*)RxData);

    // 重新启动接收不定长数据的功能
    HAL_UARTEx_ReceiveToIdle_DMA(&huart2, RxData, sizeof(RxData));

    // 再次关闭传输过半中断,确保不会被意外触发
    __HAL_DMA_DISABLE_IT(huart2.hdmarx, DMA_IT_HT);
  }
}

串口DMA的优点

1. 高效的数据传输:DMA可以直接在硬件层面完成数据的传输,无需CPU干预,大大提高了数据传输效率。特别适合处理大量数据,如文件传输或连续数据流。

2. 减轻CPU负担:CPU不需要频繁地处理串口数据的读取和写入,可以专注于执行其他任务,提高系统的整体性能。在多任务系统中,可以显著提高系统的响应速度和稳定性。

3. 实时性增强:DMA可以快速响应串口事件,确保数据的及时处理,适合对实时性要求较高的应用。例如,在工业自动化或传感器数据采集场景中,可以快速处理串口数据。

4. 灵活性高:可以根据需要配置DMA的传输大小、方向等参数,适应不同的应用场景。可以与中断或轮询方式结合使用,进一步优化系统性能。

串口DMA的缺点

1. 复杂性增加:需要配置DMA通道,包括源地址、目标地址、传输方向等,代码复杂度较高。需要处理DMA中断,编写中断处理函数,增加了开发难度。

2. 调试困难: DMA相关的错误(如地址错误、传输错误等)较难调试,需要使用专业的调试工具和方法。需要确保DMA和串口的配置正确,否则可能导致数据丢失或系统不稳定。

3. 资源占用:DMA需要占用硬件资源,如DMA通道和内存。在资源有限的系统中,可能需要合理分配DMA通道,避免资源冲突。

4. 对硬件要求高:需要硬件支持DMA功能,且硬件的DMA控制器需要足够强大。需要确保硬件的稳定性,硬件故障可能导致DMA传输失败。

总结

串口DMA是一种高效的串口通信方式,特别适合处理大量数据或对实时性要求较高的场景。它能够显著减轻CPU的负担,提高系统的整体性能。然而,使用DMA也增加了代码的复杂性和调试难度,需要合理配置和调试。在实际应用中,可以根据需求选择是否使用DMA,或者结合中断和轮询方式优化系统性能。

总结

`HAL_UART_Receive``HAL_UART_Receive_IT`和`HAL_UARTEx_ReceiveToIdle_DMA`是 STM32 HAL 库中用于 UART 数据接收的三种不同方式,各有特点和适用场景。


`HAL_UART_Receive`:阻塞式接收函数,程序会停留在接收操作上,直到数据接收完成或超时。适合数据量小、对实时性要求不高的场景,优点是实现简单,缺点是会阻塞程序执行,降低 CPU 资源利用率。

 `HAL_UART_Receive_IT`:中断驱动的非阻塞接收函数,启动后程序可以继续执行其他任务,接收完成后通过回调函数通知用户。适合对实时性要求较高的场景,能够提高程序效率,但需要合理管理中断优先级。

`HAL_UARTEx_ReceiveToIdle_DMA`:结合 DMA 和空闲检测的接收方式,适合接收不定长数据流。它利用 DMA 的高效传输能力,减轻 CPU 负担,同时通过空闲检测自动停止接收。这种方式适合处理协议帧等不定长数据,效率高且实时性强,但实现相对复杂,需要配置 DMA 和处理回调函数。

三种方式各有优缺点,开发者可以根据实际需求选择合适的接收方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值