串口DMA接收不定长数据

STM32F767—>串口通信接收不定长数据的处理方法_stm32串口超时中断-CSDN博客

STM32-HAL库串口DMA空闲中断的正确使用方式+解析SBUS信号_stm32 hal usart2 dma-CSDN博客

#define USART1_RxBuffSize 100
extern DMA_HandleTypeDef hdma_usart1_rx;	//此处声明的变量在其他地方定义
uint8_t USART1_RxBuffer[USART1_RxBuffSize];		//串口接收缓冲区
uint8_t USART1_RxLen = 0;	//接收到的数据长度
uint8_t data[USART1_RxBuffSize];
volatile uint8_t rxComplete = 0;  // 接收完成标志// 重定向printf start
//_write函数在syscalls.c中, 使用__weak定义以可以直接在其他文件中定义_write函数
int _write(int file, char *ptr, int len)
{if(HAL_UART_Transmit(&huart1,(uint8_t *)ptr,len,0xffff) != HAL_OK){Error_Handler();return -1;}return len;
}
// 重定向printf end
  • 串口控制数据收发!DMA仅仅用于传输数据,减轻CPU负担。

    __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx);
    
    • 将 DMA(Direct Memory Access,直接内存访问)与 UART(通用异步收发传输器)进行绑定的,在usart.cHAL_UART_MspInit硬件初始化中。
    • USART1 的接收操作能够通过 DMA 直接将数据传输到内存中,减少了 CPU 的参与,提高了数据处理的效率。

方法一:串口空闲中断+DMA

/* 开启串口DMA空闲中断接收,内部会使能串口空闲中断,并设置串口接收类型为空闲中断* 空闲的定义是总线上在一个字节的时间内没有再接收到数据,即空闲帧 */HAL_UARTEx_ReceiveToIdle_DMA(&huart1, USART1_RxBuffer, USART1_RxBuffSize);
/* 串口空闲中断回调函数 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{if(huart->Instance == USART1){/* 获取DMA中已经传输的数据个数* __HAL_DMA_GET_COUNTER访问DMA的NDTR寄存器(只读,用于指示要传输的剩余数据项数,每次DMA传输后,此寄存器将递减) */USART1_RxLen = USART1_RxBuffSize - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);/* 内存复制* 数据帧:帧头:0x65(A),帧长:4个字节 */if (USART1_RxBuffer[0] == 0x41 && USART1_RxLen == 4)	//接受完一帧数据{memcpy(data, USART1_RxBuffer, USART1_RxLen);}HAL_UART_Transmit_DMA(huart, USART1_RxBuffer, USART1_RxLen);// 再次开启串口DMA空闲中断,HAL_UARTEx_ReceiveToIdle_DMA → UART_Start_Receive_DMAHAL_UARTEx_ReceiveToIdle_DMA(huart, USART1_RxBuffer, USART1_RxBuffSize);}
}

方法二:串口接收超时中断+DMA

/* 接收超时中断* 波特率:串口通信的速率,即串口通信时每秒钟可以传输多少个二进制位* 时钟分频后,传输1bit所需的时钟周期数为1个时钟周期* 波特率 = 系统时钟频率 / (过采样倍数(8或16) * 时钟分频值)* 数据帧的格式:起始位 + 数据位 + 停止位* USART_RTOR的RTO[23:0]:此位域用于提供接收器的超时值(以波特时钟数为单位)* 	在标准模式下,如果在接收到最后一个字符后,在RTO值对应的时间内未检测到新的起始位,则RTOF标志置1* 	in terms of number of bits* 	写入超时多少个位数 */__HAL_UART_ENABLE_IT(&huart1, UART_IT_RTO);	//使能接收超时中断HAL_UART_ReceiverTimeout_Config(&huart1, 28800);	//设置接收超时时间,比如3*9600=28800,超时3s(波特率9600bit/s)HAL_UART_EnableReceiverTimeout(&huart1);	//使能接收超时功能HAL_UART_Receive_DMA(&huart1, USART1_RxBuffer, USART1_RxBuffSize);	//此函数检查功能CR2_RTOEN从而开启中断CR1_RTOIEwhile (1){ProcessReceivedData();}
/* 串口接收超时中断回调函数 */
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{if(huart->Instance == USART1){USART1_RxLen = USART1_RxBuffSize - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx); // 计算接收的数据长度rxComplete = 1; //设置接收完成标志memcpy(data, USART1_RxBuffer, USART1_RxLen); //复制接收缓冲区到数据memset(USART1_RxBuffer, 0, USART1_RxBuffSize); //清空接收缓冲区,避免残留数据干扰下一次接收HAL_UART_Receive_DMA(huart, USART1_RxBuffer, USART1_RxBuffSize); //重新开启DMA+串口接收}
}
// 在主循环或独立任务中进行协议处理
// 在中断处理程序中,应该尽量减少复杂的处理,避免阻塞系统
// 一般来说,回调函数只用于标记接收数据的完成状态,而实际的数据解析和协议处理可以放在主循环或独立的任务中来执行
//(回调函数里不要调用print打印信息!)
void ProcessReceivedData(void)
{if (rxComplete){rxComplete = 0; // 清除接收完成标志// 协议解析printf("ReceivedValidData: \r\n"); //printf必须带\r\n,否则不显示HAL_UART_Transmit_DMA(&huart1, data, USART1_RxLen); // 发送响应数据}
}
  • USART1_IRQHandlerHAL_UART_IRQHandler

    在代码中,UART 的接收超时(RTO)中断处理流程如下:

    1. 检测 UART 超时中断标志

      if (((isrflags & USART_ISR_RTOF) != 0U) && ((cr1its & USART_CR1_RTOIE) != 0U))
      

      这里 USART_ISR_RTOF 表示接收超时标志位,USART_CR1_RTOIE 表示接收超时中断使能位。如果 USART_ISR_RTOF 被置位且 USART_CR1_RTOIE 已启用,则会进入接收超时中断的处理流程。

    2. 清除 UART 超时标志

      __HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_RTOF);
      

      该行代码清除 UART 接收超时标志,以防止重复触发中断。

    3. 设置错误代码

      huart->ErrorCode |= HAL_UART_ERROR_RTO;
      

      HAL_UART_ERROR_RTO 添加到 huart->ErrorCode 中,记录接收超时错误。

    4. 进入错误处理
      代码随后检查 huart->ErrorCode 是否包含任何错误。如果 ErrorCode 不为 HAL_UART_ERROR_NONE,代码会根据错误类型采取相应的处理措施:

      • 如果错误被视为“阻塞性错误”,例如接收超时(RTO)、溢出错误(ORE)或 DMA 模式下的错误,则会中止接收传输,调用 UART_EndRxTransferHAL_DMA_Abort_IT 函数中止 DMA。
      • 如果错误是非阻塞性错误,会直接调用用户定义的 ErrorCallback
    5. 进入用户回调函数
      当代码检测到错误并将错误处理完成后,进入错误回调:

      #if (USE_HAL_UART_REGISTER_CALLBACKS == 1)huart->ErrorCallback(huart);
      #elseHAL_UART_ErrorCallback(huart);
      #endif
      

      这里调用了 HAL_UART_ErrorCallback 或用户注册的 ErrorCallback

  • HAL_UART_ErrorCallback

    __weak void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
    

    重写错误回调(当前为接收超时RTO)函数。

调试debug

  • 串口助手+STM32CubeIDE
  1. 点击调试debug,烧录程序并进入调试模式;

  2. 点击继续,运行程序;

  3. 串口助手选择串口,配置波特率等,以接收超时中断为例,发送字符AC,则大概3秒之后,串口助手窗口会返回AC;

  4. 若要实时查看变量的值,则在现场表达式进行调试;

  5. 若用表达式查看变量的值,需要点击暂挂,暂停程序运行才能查看:

    请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/60579.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

web实验3:虚拟主机基于不同端口、目录、IP、域名访问不同页面

创建配置文件: 创建那几个目录及文件,并且写内容: 为网卡ens160添加一个 IPv4 地址192.168.234.199/24: 再重新激活一下网卡ens160: 重启服务: 关闭防火墙、改宽松模式: 查看nginx端口监听情况:…

Jmeter中的监听器(二)

5--JSR223 Listener 用途 自定义数据处理:使用脚本语言处理测试结果,实现高度定制化的数据处理和分析。实时监控:实时处理和显示测试结果。集成外部系统:将测试结果发送到外部系统进行进一步处理和分析。日志记录:记…

DP动态规划基础题(Kadane算法)

动态规划(Dynamic Programming,简称DP)是一种在数学、管理科学、计算机科学、经济学和生物信息学等领域中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划算法通常用于优化问题,特别是那…

计算机组成原理——进位计数制

1.认识不同进制 通常的我们日常生活中用到的都是十进制,比如买东西或者期末成绩等等,当然肯定不止这一种进制方法,相关的还有二进制、八进制、十六进制,还有古罗马数字,通常古罗马数字近似可以看作是五进制的数&#x…

《.addClass()》

《.addClass()》 .addClass() 是一个在网页设计和开发中常用的 jQuery 函数,用于向 HTML 元素添加一个或多个类名。这个函数在美化网页、实现动态效果以及改善用户体验方面发挥着重要作用。本文将详细介绍 .addClass() 函数的用法、优势以及在实际开发中的应用案例。 1. .ad…

function and task

任务和函数 在Verilog语言中提供了任务和函数,可以将较大的行为级设计划分为较小的代码段,允许设计者将需要在多个地方重复使用的相同代码提取出来,编写成任务和函数,这样可以使代码更加简洁和易懂。 1.1任务 任务的定义 任务定义…

【C++】用红黑树封装set和map

在C标准库中,set容器和map容器的底层都是红黑树,它们的各种接口都是基于红黑树来实现的,我们在这篇文章中已经模拟实现了红黑树 ->【C】红黑树,接下来我们在此红黑树的基础上来看看如何封装set和map。 一、共用一颗红黑树 我…

4399 C++面试题及参考答案

C 和 C++ 的区别是什么? C 语言是一种过程式编程语言,而 C++ 是在 C 语言基础上发展而来的面向对象编程语言。 从语法层面来看,C 语言没有类和对象的概念。 而在 C++ 中可以定义类,把数据和操作数据的函数封装在一起,像这样: class Point { public:int x;int y;Point(int…

类与实例

1 问题如何理解类与实例? 2 方法 类与实例 类(class)的概述:用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。 类是一类事物,实例是具体的一个事物。 编程与生活是相通的&#xff0…

kafka常见面试题总结

Kafka 核心知识解析 一、Kafka 消息发送流程 Kafka 发送消息涉及两个线程:main 线程和 sender 线程。在 main 线程中,会创建一个双端队列 RecordAccumulator,main 线程负责将消息发送给 RecordAccumulator,而 sender 线程则从 R…

Rust 所有权机制

Rust 所有权机制 本文示例代码地址 所有权是Rust中最独特的特性,它让Rust无需GC就可以保证内存安全。 什么是所有权? 所有权(ownership)是 Rust 用于如何管理内存的一组规则。所有程序都必须管理其运行时使用计算机内存的方式…

2024/11/4 计网强化

10: 17: 22: 09: 11: 12: 13: 14: 15: 18: 19: 20: 21: 16:

hutool-雪花算法 id 生成器

配置 workId 和 datacenterId 设置工作机器ID(workerId)和数据中心ID(datacenterId),这两个参数用于确保在分布式环境中生成的ID是唯一的。使用配置 yaml 文件的方式获取,配置方式参考:https:/…

CentOS 9 配置网卡

在 CentOS 9 中配置网卡,通常涉及以下几个步骤: 1. 查看网络接口 首先,确认系统上存在的网络接口。可以使用 ip 命令或 ifconfig 命令查看网络接口的状态。 ip a 或者: ifconfig 这将列出所有可用的网络接口(例如…

力扣104 : 二叉树最大深度

补:二叉树的最大深度 描述: 给定一个二叉树 root ,返回其最大深度。二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 何解? 树一般常用递归:递到叶子节点开始倒着处理

机器情绪及抑郁症算法

🏡作者主页:点击! 🤖编程探索专栏:点击! ⏰️创作时间:2024年11月12日17点02分 点击开启你的论文编程之旅https://www.aspiringcode.com/content?id17230869054974 计算机来理解你的情绪&a…

M4 lotus 源码编译安装

查看系统版本 sw_vers ProductName: macOS ProductVersion: 15.1安装依赖 xcode-select -p /Library/Developer/CommandLineToolsbrew install go jq pkg-config hwloc coreutilscurl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh设置环境变量 export LIBRARY…

JAVA学习日记(十五) 数据结构

一、数据结构概述 数据结构是计算机底层存储、组织数据的方式。 数据结构是指数据相互之间以什么方式排列在一起的。 数据结构是为了更加方便的管理和使用数据,需要结合具体的业务场景来进行选择。 二、常见的数据结构 (一)栈 特点&…

i春秋-SQLi(无逗号sql注入,-- -注释)

练习平台地址 竞赛中心 题目描述 后台有获取flag的线索应该是让我们检查源码找到后台 题目内容 空白一片 F12检查源码 发现login.php 访问login.php?id1 F12没有提示尝试sql注入 常规sql注入 //联合注入得到表格列数 1 order by 3 # 1 union select 1,2,3 #&#xff08…

sql注入之二次注入(sqlilabs-less24)

二阶注入(Second-Order Injection)是一种特殊的 SQL 注入攻击,通常发生在用户输入的数据首先被存储在数据库中,然后在后续的操作中被使用时,触发了注入漏洞。与传统的 SQL 注入(直接注入)不同&a…