串口DMA接收不定长数据

news/2024/11/14 12:21:54 标签: 单片机, 嵌入式

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_DMA
		HAL_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_RTOIE

while (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);
      #else
          HAL_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.niftyadmin.cn/n/5751891.html

相关文章

飞牛私有云访问外网

飞牛私有云 fnOS NAS 是一款有着卓越的性能以及强大的兼容性和智能化的管理界面,它之所以能在 NAS 市场中脱颖而出,是因为 fnOS 基于最新的 Linux 内核(Debian发行版)深度开发,不仅兼容主流 x86 硬件,还支持…

OpenSSH 安全漏洞——版本更新

1. 查看版本:ssh -V 2. 下载安装包(同时需要更新zlib、ssl) cd /usr/local/src wget https://www.zlib.net/zlib-1.3.1.tar.gz wget https://www.openssl.org/source/openssl-3.2.1.tar.gz wget https://cdn.openbsd.org/pub/OpenBSD/OpenS…

方法论-秘籍型思维和流程型思维

“秘籍型思维”和“流程型思维”是两种不同的思考模式,它们的核心差异在于问题解决的方式、策略的制定以及思维的灵活性。 一、秘籍型思维(Secretive Thinking) 定义: “秘籍型思维”强调通过掌握某些核心、独特的技巧、经验或…

QQ 小程序已发布,但无法被搜索的解决方案

前言 我的 QQ 小程序在 2024 年 8 月就已经审核通过,上架后却一直无法被搜索到。打开后,再在 QQ 上下拉查看 “最近使用”,发现他出现一下又马上消失。 上线是按正常流程走的,开发、备案、审核,没有任何违规&#xf…

vue计算属性 初步使用案例

<template><div><h1>购物车</h1><div v-for"item in filteredItems" :key"item.id"><p>{{ item.name }} - {{ item.price }} 元</p><input type"number" v-model.number"item.quantity"…

Rust 所有权机制

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

contos7.9 部署3节点 hadoop3.4 集群 非高可用

contos7.9 部署3节点 hadoop3.4 集群 非高可用 contos7.9 部署3节点 hadoop3.4 集群 非高可用环境信息服务器角色分配服务器配置服务器配置初始化 init_server.sh配置主机名映射所有节点配置 hosts文件 配置免密登录 hadoop 安装环境配置下载安装包下载 jdk1.8hadoop3.4 分发安…

巧妙注入的奥秘:在 Spring 中优雅地使用 List 和 Map

文章目录 一、注入 List&#xff1a;同类项&#xff0c;一次拿下二、注入 Map&#xff1a;当键值对遇上多态三、进阶&#xff1a;使用 Qualifier 灵活注入四、总结总结推荐阅读文章 在 Spring 框架里&#xff0c;我们经常需要将不同的组件组织到集合中&#xff0c;比如在注入多…