uart1接收不定长度数据和发送:STM32 HAL库串口+DMA+IDLE空闲中断

2023-05-16

在这里插入图片描述

DMA增加:
在这里插入图片描述
usart1 gpio 默认即可
在这里插入图片描述
usart1中断必须打开
在这里插入图片描述

在 STM32 中。USART 发送接收有三种基本方式,轮询、中断和 DMA。
1.轮询方式为堵塞模式,使用超时管理机制。它每次接收一个字节,在规定时间内接收固定长度的数据。在对于某些数据不固定长度接收的数据,轮询的方式有时候不够灵活。
2.可以使用中断的方式,如每一个字节都中断一次,当时比较消耗系统资源。特别是HAL库中,从中断到回调函数运行了不少的程序,频繁的中断很可能造成数据溢出。为了避免这个问题,我们使用指定接收一定长度的数据,再调用回调函数,这会让我们可以接收大数据,但是这种情况则造成了,要求每次的包是固定长度。
3.为了接收不定长度数据,网上最常用的办法是使用空闲中断,即在串口空闲的时候,触发一次中断,通知内核,本次运输完成了。数据传输过程为了尽量不占用CPU的处理数据时间,所以就使用DMA接收串口的数据。DMA(Direct memory access),即直接存储器访问。用于在外设与存储器之间以及存储器与存储器之间提供一种高速数据传输的方式。它在开始发送和接收完成数据时会给CPU相应的信号或者中断,在数据传输过程中无需CPU参与,通过硬件方式为RAM与I/O设备提供一条直接传送数据的通道。
main.h

#define USART_DMA_IDLE//利用usart空闲中断和DMA和IDLE来实现不定长数据的接收
#define LENGTH 100

main.c

  uint8_t Rx1Buffer[LENGTH];//定义接收数组,数据缓存区
  uint8_t Rx1Count = 0;//实际接收数据数量
  volatile uint8_t Rx1Flag = 0;//数据接收标记0未完成;1接收完成
  //extern DMA_HandleTypeDef hdma_usart1_rx;
  //在STM32CubeMX生成DMA_HandleTypeDef hdma_usart1_rx放在在uart.c中,此处用extern调用来  
int main(void)
{
MX_DMA_Init();
MX_USART1_UART_Init();
 /* USER CODE BEGIN 2 */
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);              //使能IDLE中断
HAL_UART_Receive_DMA(&huart1, RxBuffer, sizeof(RxBuffer));//使能DMA传输,将串口1的数据搬运到RxBuffer
 /* USER CODE END 2 */
 while (1)
  {
  }
}  

usart.h

#include "stdio.h"//meset
void user_UART_IRQHandler(UART_HandleTypeDef *huart);

usart.c

UART_HandleTypeDef huart1;
UART_HandleTypeDef huart2;
DMA_HandleTypeDef hdma_usart1_rx;
DMA_HandleTypeDef hdma_usart1_tx;

#ifdef USART_DMA_IDLE
  #include "string.h"
  extern uint8_t Rx1Buffer[LENGTH];
  extern uint8_t Rx1Count;
  extern volatile uint8_t Rx1Flag;//声明对main.c中外部变量RxFlag的应用
#endif//USART_DMA_IDLE

void user_UART_IDLECallback(UART_HandleTypeDef *huart)//自自定义中断处理函数
{
	if(huart->Instance == USART1)
	{
	  if (Rx1Flag)/判断接收是否完成。Rx1Flag=1
	  {
	    HAL_UART_DMAStop(&huart1);//停止本次DMA传输
	    //发送空闲中断时,已接收到数据的数量=数据总量-DMA数据流剩余未能接收的数据的数量
	    Rx1Count = LENGTH - (__HAL_DMA_GET_COUNTER(&hdma_usart1_rx));
	    printf("RxCount:%d\r\n", Rx1Count);
	    HAL_UART_Transmit(&huart1, Rx1Buffer, Rx1Count, 100);//采用轮询方式把数据原样发送给PC。16进制数据会补0.待研究。
	    Rx1Count = 0;//清零已接收到数据的数量
	    Rx1Flag = 0;//设置数据接收标志:0接收未完成
	    HAL_UART_Receive_DMA(&huart1, Rx1Buffer, sizeof(Rx1Buffer));//启动再次串口DMA接收
	    //程序第一次串口DMA接收是在main.c中void MX_USART1_UART_Init(void)
	  } 
	}
}
void user_UART_IRQHandler(UART_HandleTypeDef *huart)//a串口空闲中断函数
{
	if(huart->Instance == USART1)
	{
	  if ((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE))!=RESET)
	  //调用__HAL_UART_GET_FLAG函数判断是否产生IDLE中断。如果没有清除空闲中断(表示接收到了数据)
	  //务必__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE)用()把整体包起来,不然编译错误
	  {
	    __HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除空闲中断标志,否则会一直不断进入中断
	    Rx1Flag = 1;//设置数据接收标志:1接收完成
	    user_UART_IDLECallback(huart);//调用自定义b中断处理函数
	  }
	}
}

stm32f1xx_it.c

/* External variables --------------------------------------------------------*/
extern DMA_HandleTypeDef hdma_usart1_rx;//添加dma,显示这一行
extern DMA_HandleTypeDef hdma_usart1_tx;//添加dma,显示这一行
extern UART_HandleTypeDef huart1;//串口1全局中断打开,显示这一行
/* USER CODE BEGIN Includes */
#include "usart.h"
/* USER CODE END Includes */
/*
串口中断函数:void USART1_IRQHandler(void)
串口回调函数:void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle)
*/
void USART1_IRQHandler(void)
{
  HAL_UART_IRQHandler(&huart1);//使能huart1产生中断
  #ifdef USART_DMA_IDLE
    user_UART_IRQHandler(&huart1);//用户自定义函数,用来处理a串口空闲中断函数
  #endif//USART_DMA_IDLE
}

1.1 DMA发送函数
*HAL_UART_Transmit_DMA(UART_HandleTypeDef huart, uint8_t pData, uint16_t Size);
函数主要功能是以DAM模式发送pData指针指向的数据中固定长度的数据,并同时设置和使能DMA中断,具体怎么设置和使能中断的,打开此函数源码会发现下面这个函数。
HAL_DMA_Start_IT(huart->hdmatx, (uint32_t )tmp, (uint32_t)&huart->Instance->DR, Size);
此函数是启动DMA传输并启用中断。从函数源码可以看出此函数首先判断DMA传输状态是否是Ready:
如果是Ready则使能了DMA三个中断:DMA 半传输,DMA传输完成和DMA传输出错。如果发送数据正常,进入2次进入DMA中断(DMA 半传输和DMA传输完成);错误的话进入DMA传输出错中断;从这里也能看出HAL库比标准库严谨但效率低,DMA 半传输中断如果觉得效率低可以在程序中屏蔽掉,这样数据正常发送完成就只会进入一次DMA完成中断,三种DMA中断其实是同一个函数
HAL_DMA_IRQHandler(DMA_HandleTypeDef hdma)

此函数功能是处理DMA中断请求,主要工作是清除中断标志位,改写DMA的状态,只有把状态改成HAL_DMA_STATE_READY,下一次才能正常使用DMA功能,否则会进入 HAL_BUSY状态
如果不是Ready状态,则进入HAL_BUSY状态,这也是为什么连续使用HAL_UART_Transmit_DMA()函数发送数据,第二次会发不出来数据,而且第二次函数会进入HAL_BUSY状态,所以要想使用HAL_UART_Transmit_DMA()函数连续发送数据,相邻两次之间要有延时间隔或者检测DMA数据是否完成。
1.2 DMA接收函数
**HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef huart, uint8_t pData, uint16_t Size)
函数说明:此函数的功能在DMA模式下接收大量数据,同时设置DMA线和哪个串口外设连接,以及将DMA线接收到的数据搬 *pData对应地内存中,和上面DMA发送函数一样,此函数同时具有设置和使能DMA中断的功能。可以看出此函数不仅是一个接收函数同时也是一个初始化函数,在main()之前调用,初始化串口和DMA连接和DMA接收BUF,以及设置和使能中断。如果DMA模式设置成循环模式时,只需设置这一次,如果DMA模式设置成正常模式时,每次读取完数据后需要再从新设置一次(就是再调用一次此函数),分析函数源码会发现此函数内部同样会调用以下两个函数,使用方法和分析和发送类似,不再赘述。

HAL_DMA_Start_IT(huart->hdmatx, *(uint32_t *)tmp, (uint32_t)&huart->Instance->DR, Size);
HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma);

主要说说HAL_UART_Receive_DMA(),怎么配合IDLE串口空闲中断使用,main()函数之前一般调用此函数,一个主要目的是指明DMA传输串口数据存到指定的地方。一般情况我们会开辟一个全局变量的缓存extern uint8_t receive_buff[BUFFER_SIZE]
比如函数初始化为HAL_UART_Receive_DMA(&huart2, (uint8_t*)receive_buff, BUFFER_SIZE); 就是设置串口2接收到数据通过DMA线直接到receive_buff中了,配合串口空闲中断,当进入串口空闲中断,说明一帧数据已接收完成。我们读取receive_buff相应长度的数据就是此次接收一帧的数据,这里还需要再介绍一个函数;__HAL_DMA_GET_COUNTER(HANDLE)
此函数的功能:获取当前DMA通道传输中 receive_buff[BUFFER_SIZE]缓存还剩余多少个数据单元。这样就能算出这一帧数据到底接收了多少单元的数据(数据长度=缓存总长度-缓存剩余的长度),

length = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);

判断数据接收完成
那么问题来了,怎么知道数据是否接收完成呢?
其实,有很多方法:
对于定长的数据,只需要判断一下数据的接收个数,就知道是否接收完成,这个很简单,暂不讨论。
对于不定长的数据,其实也有好几种方法,麻烦不介绍,下面这种方法是最简单的,充分利用了stm32的串口资源,效率也是非常之高。
DMA+串口空闲中断这两个资源配合,简直就是天衣无缝啊,无论接收什么不定长的数据,管你数据有多少,来一个我就收一个
先看看stm32串口的状态寄存器:
在这里插入图片描述
当我们检测到触发了串口总线空闲中断的时候,我们就知道这一波数据传输完成了,然后我们就能得到这些数据,去进行处理即可。这种方法是最简单的,根本不需要我们做多的处理,只需要配置好,串口就等着数据的到来,dma也是处于工作状态的,来一个数据就自动搬运一个数据。
1.3空闲函数调用
为了减少误进入串口空闲中断,串口RX的IO管脚一定设置成Pull-up<上拉模式>,串口空闲中断只是接收的数据时触发,发送时不触发。
首先在main()之前初始化的时候调用__HAL_UART_ENABLE_IT(&huart2,UART_IT_RXNE);
函数的功能是打开了串口的接收中断。注意这个时候我还没有打开空闲中断。而是在接收到了一个byte以后打开空闲中断。
当发送一帧数据接收完成后,会进入串口中断函数,如下函数

HAL_UART_IRQHandler(UART_HandleTypeDef *huart)

HAL库提供的这个串口中断函数,并没有针对空闲中断的处理,所以得我们自己加相应的代码。

if(RESET != __HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE))   //判断是否是空闲中断
{
    
    __HAL_UART_CLEAR_IDLEFLAG(&huart2);                     //清楚空闲中断标志(否则会一直不断进入中断)
    USAR_UART_IDLECallback(huart);                          //调用中断回调函数
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

uart1接收不定长度数据和发送:STM32 HAL库串口+DMA+IDLE空闲中断 的相关文章

随机推荐

  • C++11:继承

    目录 继承的基本概念 继承方式 基类和派生类对象赋值转换 切片 继承中的作用域 派生类的四个成员函数 xff1a 构造函数 拷贝构造函数 赋值重载 析构函数 静态成员 继承与友元 多继承 菱形继承 多继承的指针偏移问题 组合 继承的基本概念
  • socket编程二十六:基于UDP的服务器端和客户端

    前面的文章中我们给出了几个 TCP 的例子 xff0c 对于 UDP 而言 xff0c 只要能理解前面的内容 xff0c 实现并非难事 UDP中的服务器端和客户端没有连接 UDP 不像 TCP xff0c 无需在连接状态下交换数据 xff0
  • 下载高清电影的必须收藏的网站

    下载高清电影的必须收藏的网站 Posted 2012 12 06 分类 生活范儿 电影 生活范儿 电影 CHD 虽然蓝光推出 xff0c 但是高清已经势不可挡 xff0c 动辄几G甚至几十G一部的电影冲击着我们的视觉 xff0c 也考验着我
  • 电赛总结|电赛注意事项

    电赛总结 赛前 1 准备模块非常重要 如果没有提前准备模块 xff0c 在赛中也是在想尽办法买模块 xff0c 只是花更多的钱和运费等 xff0c 也不会去自己搭 所以赛前一定要准备模块 常见模块 降压模块 xff0c 升压稳压模块 xff
  • Putty使用教程

    Putty作为免费且开源的老牌 SSH 客户端 xff0c PuTTY 经常用于 Windows 下连接管理远程服务器 为方便刚接触 VPS 的新手参考使用 xff0c 本文配合截图介绍 PuTTY 的基础用法及一些设置技巧 xff0c 希
  • #Python实现话题的发布与订阅

    Python实现话题的发布与订阅 首先我们的先了解ROS文件系统的基本框架 xff0c 如下图所示 xff1a 由上图可知 xff0c py文件放在工作包里面的scripts文件夹内 xff0c 所以 xff0c 整活 xff01 1 在工
  • #创建自定义topic

    创建自定义topic 前面我们学了用C 43 43 和Python创建发布者与订阅者 xff0c 这次我们创建自定义的话题 xff0c 其实同C 43 43 实现topic差不多 xff0c 都是编写 cpp文件 步骤有点多且繁琐 xff0
  • #使用TF实现海龟机器人跟随

    使用TF实现海龟机器人跟随 昨天粗略地讲解了一会儿TF变换 xff0c 用的是ROS系统中自带的功能包实现小海龟跟随的功能 xff08 具体见 初识TF变换 xff09 今天我们将用自己编写节点的方式实现小海龟跟随的功能 xff0c 并且
  • #创建虚拟机器人URDF模型

    创建虚拟机器人URDF模型 题外话 xff1a 作业发布已有一两天了 xff0c 之所以今天才编辑这篇博客 xff0c 是因为我也遇到问题了 xff0c 现在以及解决了 xff08 小细节 xff1a 创建功能包之前先编译工作空间确保里面已
  • # gazebo 仿真

    gazebo 仿真 1 给 base link 添加惯性 xff0c 碰撞以及 gazebo 属性 在路径xqrobot description urdf xacro 件夹下新建 件夹 gazebo xff0c 并在 gazebo 件下创建
  • #Gmapping

    Gmapping 开始之前先安装两个功能包 xff0c 命令如下 xff1a sudo apt span class token operator span get install ros span class token operator
  • #navigation

    navigation 1 安装相关依赖 sudo apt span class token operator span get install ros span class token operator span kinetic span
  • # Qt_day1

    Qt day1 1 项目框架 span class token macro property span class token directive hash span span class token directive keyword i
  • ros先订阅后发布 无法收到消息的解决办法

    现象 今天遇到的问题是 使用的是Ros1 在先订阅后发布时 会导致订阅者无法收到订阅的消息 除非在发布者发布后重新订阅 思考 以前使用的是Ros2似乎并不关心订阅和发布的先后顺序 nbsp 似乎都可以收到消息 nbsp nbsp 这个问题后
  • C/C++中关于struct和class类的区别

    struct和class的主要的区别在于两者默认的访问权限有所不同 在不设置类中的成员属性和成员方法的权限时 xff0c struct默认的访问权限是公共权限 xff0c class默认的访问权限是私有权限 补充 xff1a 成员属性和成员
  • C++中STL容器的主要使用及含义

    1 stack栈容器的使用 假如栈中存放的是字符串 xff0c 我们做如下定义 xff1a stack lt string gt ss 设该变量名为ss 其主要用法如下 xff1a ss push a 存入栈中元素a ss top 读取栈顶
  • 电赛备赛记录第一篇(控制部分)

    2022 5 25 九校联赛备赛阶段第一天 联赛小车系统沿用去年国赛使用的树莓派驱动底板与外设 整车情况良好 xff0c 摄像头通信 连接均正常 xff0c 现已拼装完整 复产复工的初步成果为 xff1a 小车可以实现开机自启动的程序运行
  • 数据结构——栈详解

    1 栈 Stack 是一种线性存储结构 xff0c 它具有如下特点 xff1a xff08 1 xff09 栈中的数据元素遵守 先进后出 34 First In Last Out 的原则 xff0c 简称FILO结构 xff08 后进先出的
  • 双目相机标定

    一 运行环境 opencv2 windows vs 二 图像获取 分割 保存 参考博客opencv打开双目摄像头 图像切割保存 scutqq的博客 CSDN博客 双目图像分割 include amp lt opencv2 core core
  • uart1接收不定长度数据和发送:STM32 HAL库串口+DMA+IDLE空闲中断

    DMA增加 xff1a usart1 gpio 默认即可 usart1中断必须打开 在 STM32 中 USART 发送接收有三种基本方式 xff0c 轮询 中断和 DMA 1 轮询方式为堵塞模式 xff0c 使用超时管理机制 它每次接收一