【STM32+HAL库+CubeMX】UART轮询收发、中断收发、DMA收发方法及空闲中断详解

这篇具有很好参考价值的文章主要介绍了【STM32+HAL库+CubeMX】UART轮询收发、中断收发、DMA收发方法及空闲中断详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Author: DrinkCat(szt@drinkcat.com)
Copyright © 2023 DrinkCat
Original link: DrinkCat’s Blog

1. 什么是UART?

UART是一种异步串行通信接口,常用于通过串口与外部设备进行通信。它通过发送和接收数据帧来实现数据传输,使用起来相对简单。UART通常包含发送器(Transmitter)和接收器(Receiver),通过两根信号线(传输线)进行双向通信。

2. UART协议内容简介

stm32cubemx uart,STM32,stm32,嵌入式硬件,单片机

UART协议将一长串数据切成很多固定长度的小段,分别发送。每小段数据前后会加上一些附加数据以保证通信的实时性和准确性,最后形成的每个小段叫做一个数据包——即1帧数据。

  • 起始位:发出1位低电平信号,表示开始传输字符。
  • 数据位:真正发送的数据,一般为8位(1个字节),常采用ASCII编码,从最低位开始发送。
  • 校验位:用于检验接收到的数据是否正确,分为奇校验和偶校验。
  • 停止位:一组数据的结束传输的标志。可以是1位、1.5位、2位的高电平。
  • 空闲位:空闲时数据线为高电平状态,代表无数据传输。
  • 波特率:衡量传输速率的指标。UART通信中波特率等于比特率。

UART通信的两个设备间,以上因素必须完全一致才能实现数据通信。

3. UART轮询收发

UART轮询收发时,CPU会不断检测串口的状态位来判断数据收发的情况。

3.1 UART轮询收发的优缺点

UART轮询收发是一种简单直接的UART通信方式,它具有以下优点和缺点:

优点

  • 简单易实现:相比于中断或DMA方式进行数据收发,UART轮询收发的实现相对简单,不需要额外配置中断或DMA控制器,减少了开发的复杂性。
  • 低延迟:由于没有中断处理程序的介入和数据传输的等待时间,UART轮询收发可以实现较低的延迟,对实时性要求较高的应用场景较为适用。占用CPU资源,效能低:UART轮询收发需要通过不断的轮询来检查发送和接收缓冲区的状态,这会占用CPU的资源,导致CPU无法充分利用来执行其他任务。

缺点

  • 占用CPU资源,效能低:UART轮询收发需要通过不断的轮询来检查发送和接收缓冲区的状态,这会占用CPU的资源,导致CPU无法充分利用来执行其他任务。

UART轮询收发适用于简单的、对实时性要求不高的低速通信场景。但在对实时性、效率和灵活性要求较高的应用中,中断或DMA方式可能更加适合。在选择UART通信方式时,需要根据具体应用需求进行权衡和选择。

3.2 UART轮询收发相关的函数

初始化UART参数:首先,需要对UART进行初始化,包括波特率(Baud rate)、数据位数、校验位、停止位等参数的设置。这些参数决定了数据的传输格式。

初始化函数 HAL_UART_Init(UART_HandleTypeDef *huart);
功能 根据串口句柄指定的参数进行串口初始化。
入口参数 huart:串口句柄的地址指针。
返回值 HAL状态值。
说明 使用CubeMX配置工程时,初始化代码会自动生成,我们不需要再对串口进行初始化。

发送数据:要发送数据,首先将待发送的数据写入UART发送缓冲区,然后调用轮询发送函数。在轮询方式下,单片机会一直检查是否完成发送,直到超过设定时间或数据发送完成。

轮询发送函数 HAL_UART_Transmit(UART_Handle TypeDef *huart, uint 8_t *pData, uint 16_t Size, uint 32_t Timeout);
功能 在轮询方式下发送一定数量的数据。
入口参数 huart:串口句柄的地址。
pData:待发送数据的首地址。
Size:发送的字节数。
Timeout:超时等待时间, 以毫秒为单位。
返回值 HAL状态值。

接收数据:要接收数据,需要从UART接收缓冲区读取数据。同样,在轮询方式下,单片机会一直检查是否完成接收,直到超过设定时间或接收到所有数据。

轮询接收函数 HAL_UART_Receive(UART_Handle TypeDef *huart, uint 8_t *pData, uint 16_t Size, uint 32_t Timeout);
功能 在轮询方式下接收一定数量的数据。
入口参数 huart:串口句柄的地址。
pData:存放数据的首地址。
Size:接收数据的字节数。
Timeout:超时等待时间, 以毫秒为单位。
返回值 HAL状态值。

3.3 【实践】使用蓝牙模块发送数据

使用UART协议,向手机循环发送“Hello World”语句。

3.3.1 配置UART

stm32cubemx uart,STM32,stm32,嵌入式硬件,单片机

  1. 左侧connectivity中点击USART1。
  2. 右侧Mode,选择为Asynchronous(异步通信)
  3. 配置参数:
  • Baud Rate(波特率):9600Bits/s
  • Word Length(字长):8 Bits
  • Parity(奇偶校验位):无校验位
  • Stop Bits(停止位):1位

USART包括UART与USRT。我们使用USART的UART模式,即Asynchronous。

3.3.2 连接单片机与蓝牙模块

单片机 蓝牙模块
RX TX
TX RX
5V VCC
GND GND

3.3.3 代码实现

在main.c中添加如下语句:

/* 添加至引用区 */
#include "string.h" // strlen函数依赖该头文件
/* 添加至变量定义区*/
char txBuffer[] = "Hello World"; // 需要发送的数据
/* 写在main()的while(1)循环内 */
while(1)
{
    HAL_UART_Transmit(&huart1, (uint8_t *)txBuffer, strlen(txBuffer), 1000);
    HAL_Delay(1000);
}

4. UART中断收发

4.1 UART中断收发的优缺点

优点

  • 提高系统效率:相比于UART轮询收发方式,UART中断收发可以提高系统的效率。当每完成发送或接收一帧数据时,中断会通知CPU进行相应的处理,而不需要CPU不断地轮询发送或接收缓冲区,释放了CPU资源,使CPU可以同时执行其他任务;
  • 系统响应更快:通过使用中断机制,UART中断收发可以提供更快的系统响应时间。一旦有数据可发送或可接收时,中断立即触发,通知CPU进行相应操作,减少了数据传输的延迟。
  • 灵活性:UART中断收发具有较高的灵活性。中断处理程序可以对数据进行灵活的处理和控制,可以根据实际需求进行相应的操作,如解析数据、执行特定任务等。复杂性增加:相对于UART轮询收发方式,UART中断收发的实现相对复杂一些。需要正确配置中断触发条件和中断优先级,同时编写中断处理程序进行数据的发送和接收操作。

缺点

  • 中断开销:中断处理程序的执行会占用一定的CPU时间和系统资源。频繁的中断触发可能会增加系统的开销。
  • 实时性限制:尽管UART中断收发可以提高系统的响应时间,但它仍然受限于中断处理程序的执行时间和优先级。在高实时性要求的应用中,中断处理程序的执行时间必须保持足够短,以确保数据的及时处理和传输。
  • 占用CPU资源:在传输数据量较大时,如果采用中断方式,每收发一帧的数据,CPU都会被打断,造成CPU无法处理其他事务。因此在批量数据传输,通信波特率较高时,建议采用DMA方式。

UART中断收发相较于UART轮询收发,提高了系统的效率,但是遇到大量、高速的数据传输时仍然会对CPU的性能产生影响。

4.2 UART中断收发时触发中断的流程

stm32cubemx uart,STM32,stm32,嵌入式硬件,单片机

使能中断后,每收发1帧数据后,UART会触发UART全局中断。此时程序会进入到中断处理函数 USART1_IRQHandler()。在这个函数中,又调用了HAL库的中断处理函数HAL_UART_IRQHandler(&huart1),该函数会通过判断中断类型来决定调用哪个函数。

如果收发完成 -> 进入发送/接收回调函数;
如果产生错误 -> 进入错误回调函数;
如果未收发完 -> 继续发送/接收。

简而言之,每完成一帧数据的收发,都会调用一次中断处理函数,但只有当收发完成时才会调用发送/接收回调函数。在实际操作中,我们一般不需要对中断处理函数HAL_UART_IRQHandler()进行修改。我们将收发完成时的操作逻辑写在回调函数中即可。

4.3 UART中断收发相关的函数

4.3.1 发送相关函数

中断发送函数 HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
功能 在中断方式下发送一定数量的数据。
入口参数 huart:串口句柄的地址。
pData:发送数据的首地址。
Size:发送数据的字节数。
返回值 HAL状态值。
发送回调函数 HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
功能 本函数会在中断发送完成时被调用。
入口参数 huart:串口句柄的地址。
返回值

4.3.2 接收相关函数

中断接收函数 HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
功能 在中断方式下接收一定数量的数据。
入口参数 huart:串口句柄的地址。
pData:存放数据的首地址。
Size:接收数据的字节数。
返回值 HAL状态值。
接收回调函数 HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
功能 本函数会在中断接收完成时被调用。
入口参数 huart:串口句柄的地址。
返回值

4.4 回调函数的使用方式

stm32f1xx_hal_uart.c文件中,我们可以找到回调函数的声明方式(下以接收回调函数为例)。

stm32cubemx uart,STM32,stm32,嵌入式硬件,单片机

关注到函数声明前带有__weak修饰,我们一般将这种函数称之为“弱函数”。含有__weak标识的函数,我们可以自行声明一个同名函数,最终编译器在编译的时候会选择我们自己定义的函数。如果我们没有声明该函数,则会调用有__weak修饰的函数。因此,我们只需要在合适的位置自己声明一个回调函数即可,不必在该文件中进行修改。

4.5 【实践】使用中断方式收发数据

通过蓝牙向单片机发送三个英文字母,单片机将大小写翻转后发回。

4.5.1 配置UART

在上一节配置的基础上,我们打开UART1的全局中断。

stm32cubemx uart,STM32,stm32,嵌入式硬件,单片机

4.5.2 代码实现

我们在main.c中添加如下代码

/* 添加至宏定义区 */
#define rxDataLen 3 //接收三个字节的数据
/* 添加至变量定义区*/
char rxBuffer[rxDataLen]; // 存储接收到的数据的数组

int main()
{
    /* 添加至while(1)循环前*/
    HAL_UART_Receive_IT(&huart1, (uint8_t *)rxBuffer, rxDataLen); // 中断方式接收三个字节数据
    
    while(1) // 注意不要将中断接收写在while循环内
    {
        HAL_Delay(1);
    }
}

在完成一次接收后,我们需要将接收到的字母大小写翻转并输出。我们需要在接收回调函数中实现这一功能,定义接收回调函数如下。

// 接收回调函数(由于需要覆盖原先的弱函数,本函数名称不能更改)
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) 
{
    if(huart == &huart1) // 如果是串口1
    {
        for(int i = 0; i < rxDataLen; i++)
        {
            if(rxBuffer[i] >= 'a' && rxBuffer[i] <= 'z')
            {
                rxBuffer[i] += 'A' - 'a';
            }
            else if(rxBuffer[i] >= 'A' && rxBuffer[i] <= 'Z')
            {
                rxBuffer[i] -= 'A' - 'a';
            }
        }
        HAL_UART_Transmit_IT(&huart1, (uint8_t *)rxBuffer, rxDataLen);
    }

    // 使用HAL_UART_Receive_IT()时,只会进入一次中断。因此需要在回调函数内再次调用该函数
    HAL_UART_Receive_IT(&huart1, (uint8_t *)rxBuffer, rxDataLen);
}

5. UART使用DMA进行收发 & 空闲中断

5.1 啥是DMA?

DMA(Direct Memory Access,直接内存访问)是一种计算机系统中的数据传输技术,它允许外设直接访问寄存器,而无需通过CPU的干预。DMA技术可以提高数据传输的效率和系统性能,减轻CPU的负担。

简单点来说,DMA收发与轮询、中断都有所不同。在收发数据时,CPU只需告诉DMA数据的来源以及目的地址等信息即可,而无需参与中间的所有传输过程(例如中断收发时每收发一帧数据都会进入中断处理函数,而DMA只在接收完成等少数时刻触发中断,大大降低了CPU的压力)。

5.2 什么是空闲中断?

空闲中断(Idle Interrupt)是UART通信中的一种中断类型。当UART处于空闲状态(没有接收到数据)且持续时间超过一个帧的传输时间时,空闲中断会触发,调用上节提到的函数USART1_IRQHandler。空闲中断可以用于检测数据帧的结束或接收数据的完成。

5.3 相关函数

使能UART的特定中断(宏) __HAL_UART_ENABLE_IT(__HANDLE__, __INTERRUPT__);
参数 __HANDLE__:指向UART句柄的指针;
__INTERRUPT__:要使能的中断标志位。
说明 本节会使用到空闲中断,即UART_IT_IDLE
DMA发送函数 HAL_UART_Transmit_DMA(UART_Handle TypeDef *huart, uint 8_t *pData, uint 16_t Size);
入口参数 huart:串口句柄的地址。
pData:发送数据的首地址。
Size:发送数据的字节数。
DMA接收函数 HAL_UART_Receive_DMA(UART_Handle TypeDef *huart, uint 8_t *pData, uint 16_t Size);
入口参数 huart:串口句柄的地址。
pData:存放数据的首地址。
Size:接收数据的字节数。
禁用DMA传输(宏) __HAL_DMA_DISABLE(_HANDLE_);
参数 __HANDLE__:指向UART句柄的指针
使能DMA传输(宏) __HAL_DMA_ENABLE(_HANDLE_);
参数 __HANDLE__:指向UART句柄的指针
设置DMA传输计数器值(宏) __HAL_DMA_SET_COUNTER(__HANDLE__, __COUNTER__);
参数 __HANDLE__:指向UART句柄的指针
__COUNTER__:要设置的计数器值。
获取DMA传输的计数器值(宏) __HAL_DMA_GET_COUNTER(__HANDLE__);
参数 __HANDLE__:指向UART句柄的指针
宏定义 #define __HAL_DMA_GET_COUNTER(__HANDLE__) ((__HANDLE__)->Instance->CNDTR)
说明 这个宏需要我们自行定义。
我们可以利用这个宏定义来获取接收到的数据的长度。
接收到的数据长度 = 设置的长度 - 当前DMA计数器的值,即
rxLen = rxBufferLen - __HAL_DMA_GET_COUNTER(__HANDLE__);

5.4 【实践】使用DMA & 空闲中断实现不定长数据的接收

结合DMA以及空闲中断,实现接收不定长度的一段数据,发送这一数据的长度。

5.4.1 配置UART

参考前两节,配置好UART以及全局中断。我们还需要开启UART的DMA接收。

5.4.2 代码实现

在本节中,我们将代码按用途划分为不同函数,逐一实现。先定义如下全局变量以及宏变量。

#define rxBufferLen 40 // 接收数据的最大长度
#define __HAL_DMA_SET_COUNTER(__HANDLE__, __COUNTER__) ((__HANDLE__)->Instance->CNDTR = (uint16_t)(__COUNTER__))
char rxBuffer[rxBufferLen]; // 用于存放接收到的数据

程序运行之初,我们需要先对DMA接收进行初始化,该初始化函数应当在main()函数内调用一次。

void UART_InitDMAReceive() // 初始化函数
{
    __HAL_UART_CLEAR_IDLEFLAG(&huart1); // 清除空闲中断标志位
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // 开启UART的空闲中断
    HAL_UART_Receive_DMA(&huart1, (uint8_t*)rxBuffer, rxBufferLen ); // 启动DMA接收
}

每当发生空闲中断或接收到的数据超过缓存区大小时,会产生一次中断。我们首先需要自行编写中断回调函数。

void UART_DMAIdleCallback(UART_HandleTypeDef *huart)
{
    if(huart == &huart1)
    {
        // 停止DMA接收
        __HAL_DMA_DISABLE(huart->hdmarx);
        // 计算接收到的数据长度
        uint8_t dataLen = rxBufferLen - __HAL_DMA_GET_COUNTER(huart->hdmarx) + '0'; 
        // 发送数据长度,使用轮询方法
        HAL_UART_Transmit(&huart1, &dataLen, 1, 10);

        // 重启DMA接收
        __HAL_DMA_SET_COUNTER(huart->hdmarx, rxBufferLen); // 重设DMA计数器
        __HAL_DMA_ENABLE(huart->hdmarx); // 使能DMA接收
    }
}

我们在中断处理函数内添加如下内容(该函数位于文件stm32g4xx_it.c内)。文章来源地址https://www.toymoban.com/news/detail-689663.html

void USART1_IRQHandler(void)
{
    /* USER CODE BEGIN USART1_IRQn 0 */

    /* USER CODE END USART1_IRQn 0 */
    HAL_UART_IRQHandler(&huart1);
    /* USER CODE BEGIN USART1_IRQn 1 */
    if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET) // 判断中断是否为空闲中断
    {
        __HAL_UART_CLEAR_IDLEFLAG(&huart1); // 清除空闲中断标志位
        UART_DMAIdleCallback(&huart1); // 空闲回调函数
    }
    /* USER CODE END USART1_IRQn 1 */
}

到了这里,关于【STM32+HAL库+CubeMX】UART轮询收发、中断收发、DMA收发方法及空闲中断详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • STM32-UART-DMA HAL库缓冲收发

    1.1、注意事项: HAL库的DMA底层基本都会默认开启中断使能,如果在STM32CubeMx禁用了中断相关的功能,程序可能会进入空中断回调出不来。 切记使用STM32-HAL库的DMA发送时需要开启USART中断和DMA中断。 在一般时间要求不是很高很高的场合,使用HAL库自带的函数就可以,并不会很频

    2024年01月18日
    浏览(37)
  • HAL库STM32CUBEMX学习记录(一)——USART(串口中断收发数据)

    一、首先使用STM32CUBEMX新建一个工程 二、打开工程文件 1.在usart.c中添加以下代码  2.然后在最后面加入中断回调函数 3.在usart.h文件中加入  4.新建一个cmd.c文件,创建命令check函数 5.在mian函数中的while(1)循环中调用USART1_Check(USART_RX_BUF)函数 6.最后串口初始化函数后打开串口中

    2024年02月16日
    浏览(34)
  • stm32 hal库uart使用 DMA中断只能发送一次的问题

    1.stm32 uart使用DMA,无论发送还是接收都各自有一个数据流中断。这个数据流中断是框架代码不必过多关心。 2.使用了DMA后,uart的global中断是否要使用?标准做法是在cubemx上要勾选的,不然,就会产生只能发送一次的问题。 问题的原因? 我相信这是hal库的问题,理论上讲有一

    2024年02月11日
    浏览(34)
  • (stm32之HAL库)UART工作在DMA模式要打开串口中断吗?

    最近学习了stm32(F4xx)的串口在DMA模式下的使用,期间以ST官方提供的例程进行参考学习,发现其初始化过程中是打开了UART的中断的,而且HAL库中stm32f4xx_hal_uart.c文件中的DMA模式使用说明里也有这么一句话: 即在非循环模式下(也就是发完一次数据就停止的常用模式)需要配置

    2024年02月12日
    浏览(29)
  • STM32CubeMX-HAL库-UART串口接收中断回调函数代码分析

            CubeMx中HAL库函数的调用不同于库函数调用,在学习CubeMx串口通信时,不理解HAL库中的回调函数是怎么被调用的,于是查看每个的定义,参考其他人写的博客,总算弄明白了HAL库中断调用与库函数不同之处。写下这篇博客一是加深自己的理解,二是希望对不理解HA

    2024年02月02日
    浏览(44)
  • STM32+UART串口+DMA收发

    目录 1、cubemax端配置 1.1 初始化配置 1.2 GPIO配置  1.3 UART配置 1.3.1 串口基础配置 1.3.2 DMA配置 2、keil端代码设计 2.1 初始化配置 2.2 DMA接收初始化配置 2.3 DMA发送配置  2.4 接收回调函数设置 2.5 回调函数内容代码编写 2.5.1 接收回调函数 2.5.2 发送回调函数 2.6 回调函数内容代码优化

    2024年02月07日
    浏览(34)
  • STM32使用DMA传输UART空闲中断中接收的数据遇到的问题以及解决方法

    STM32使用DMA传输UART空闲中断中接收的数据遇到的问题以及解决方法 CubeMX配置 串口配置:使用默认配置(传输数据长度为8 Bit,奇偶检验无,停止位为1 Bit, 接收和发送都使能),因为我的是LIN项目所以使用的时串口的LIN模式,一般就是异步通信 打开DMA传输 打开串口接收中断

    2024年02月05日
    浏览(31)
  • 【STM32】HAL库 STM32CubeMX——DMA (串口DMA发送接收)

    软件: STM32CubeMX KEIL5 mcuisp 串口通信助手 硬件: STM32F103C8Tx 杜邦线,面包板,USB转TTL DMA,全称Direct Memory Access,即直接存储器访问。 DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。 我们知道系统的运

    2024年02月12日
    浏览(56)
  • STM32:串口轮询模式、中断模式、DMA模式和接收不定长数据

           在STM32每个串口的内部都有两个寄存器:发送数据寄存器(TDR)/发送移位寄存器,当我们调用HAL_UART_Transmit 把数据发送出去时,CPU会将数据依次将数据发送到数据寄存器中,移位寄存器中的数据会根据我们设置的比特率传化成高低电平从TX引脚输出。待发送移位寄存器中发

    2024年02月07日
    浏览(42)
  • STM32 HAL DMA中断配置

    使用HAL库方式DMA中断时,在网上找了好多资料都没有怎么介绍。所以就自己研究了一下,并做个记录。我的芯片型号是STM32G030。下面我以I2C传数据为例介绍下HAL库是如何使用DMA中断的。 我使用的是I2C2,简单配置下参数,加上DMA通道。 DMA貌似默认开启了中断,蓝色的勾勾是我

    2024年04月23日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包