基于HAL库串口的不定长字节连续传输

1.不定长字节传输的必要性及本文要解决的问题

由于在上一篇文章简单介绍过串口的传输原理并且使用过STM32cubemx对串口的初始化,因此本小章节不再继续赘述相关配置过程,如有需要请跳转https://blog.csdn.net/2401_88485312/article/details/146468025?spm=1001.2014.3001.5501

想必大家在使用串口的时候都有遇到一个问题就是在使用hal库提供的api函数发送或接收数据时都需要填入数据的字节长度,往往在实际应用中我们无法确定每次传输的字节大小具体是多少,因而需要使用一些特殊的编程方式来传输不定长数据,因为我个人在网络上查找相关方法时发现即便方法有许多但是很少有人会做相关讲解,因此我写此篇文章旨在分享个人经验帮助各位和我一样刚接触hal库串口函数的小白能够更好更灵活的使用hal库串口函数。

2.实现不定长字节传输的基本思路

在使用串口函数之前我先向大家讲解数据发送的基本原理(编码),在我们日常生活中因为地域文化原因各个国家使用的语言是各不相同的,为了方便交流大多数时候使用统一语言(英语)进行交流,计算机也是如此,因此数据的交换需要一个统一的编码格式,常见的编码格式有ASCII,UTF-8,GKB等等,然而这些不同的编码格式都有一个统一的特点,就是不管哪种编码格式它最终都会逐个字符的转化成一个个的十六进制组合也就是字节的组合,因此在收发数据时,实际接收的就是一个个字节,然后根据不同的编码格式进行组合翻译成原本的文本,当然还有一种常见的格式就是HEX格式,HEX格式一般用于数值的传输,在文本传输好像比较少(也可能是我浏览的文章不够多)。

由上所述传输连续不定长字节的主要问题就是计算出发送内容的字节大小,本次使用串口中断函数HAL_UART_Receive_IT()接收数据,我们的主要思路是先定义一个变量uint8_t rxbuff来接收PC端上位机发送的数据,然后定义一个缓存数组uint8_t TXarr[256](注意这里必须是uint8_t类型因为每次接收一个字节所以必须是8位的如果是16或32就没法收发了,这一个问题我找了几个小时)

代码如下

//uint8_t rxbuff[2];//使用两个字节传输的时候解开注释并把下面的注释掉
uint8_t rxbuff;//用于存储接收单个字节的变量
uint8_t ncount = 0;//用于指示数组位置的变量
uint8_t TXarr[256];//用于缓存多个字节的变量,当数据接收完毕一次性发送,但字节数不超过256

每次接收一个字节,也就是每收到一个字节进一次中断,然后在中断回调函数HAL_UART_RxCpltCallback()执行溢出判断(因为定义了缓存数组TXarr[256]只能存256个字节,如果超出返回错误标志)并且将收到的数据存入uint8_t TXarr[256],需要注意的是接收到的数据首先是给变量rxbuff,然后再在回调函数中将rxbuff的值传递给数组TXarr[256],当识别出结束标志位后将缓存数组的数据一次性发送到上位机,这里使用网络上大多数使用的回车作为标志位(也就是在输入完数据后加一个回车再发送,在c里面回车对应的ASCII码是0x0D

附上一张ASCII表(来源于b站江协科技PPT)

来源于江协科技PPT

,我使用的软件有一个回车加换行,所以最后一个应该是换行。

当程序判断是换行后就将数据发送出去,主要流程图如下

在main函数中while之前开启中断接收函数1.HAL_UART_Receive_IT()当收到数据进入中断2.USART1_IRQHandler()此函数内部调用3.HAL_UART_IRQHandler()---->4.UART_Receive_IT()---->5.HAL_UART_RxCpltCallback()其中只需要在开头调用HAL_UART_Receive_IT()就会自动执行2,3,4步,然后需要自行在5.HAL_UART_RxCpltCallback()中添加接收完成后需要执行的操作。

3.主要代码示例

//uint8_t rxbuff[2];//使用两个字节传输的时候解开注释并把下面的注释掉
uint8_t rxbuff;//用于存储接收单个字节的变量
uint8_t ncount = 0;//用于指示数组位置的变量
uint8_t TXarr[256];//用于缓存多个字节的变量,当数据接收完毕一次性发送,但字节数不超过256
#include <string.h>
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){

  if(huart == &huart1 )
  {
    if(ncount >= 255)
    {
      ncount = 0;
      memset((void *)TXarr,0x00,sizeof(TXarr));

    }
    else 
    {
      //TXarr[ncount] = rxbuff[0];
      //TXarr[ncount+1] = rxbuff[1];
      //ncount += 2;//使用两个字节传输的时候解开注释并把下面一行注释掉
      TXarr[ncount] = rxbuff;
      ncount++;
      if(TXarr[ncount-1]=='\n') //因为按下回车会自动换行所以以换行符结尾   
      {
        HAL_UART_Transmit(&huart1,(uint8_t *)&TXarr,sizeof(TXarr),0xffff);
        while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);//检测UART发送结束
			  ncount = 0;
			  memset(TXarr,0x00,sizeof(TXarr)); //清空数组
      }
      
    }
  }
    HAL_UART_Receive_IT(&huart1,(uint8_t *)&rxbuff,1);
    //HAL_UART_Receive_IT(&huart1,(uint8_t *)rxbuff,2);//使用两个字节传输的时候解开注释并把上面的注释掉

}

依旧需要注意的是此类方法是使用中断一个字节一个字节的接收,每次接收完一个字节后会清除中断使能,所以在最后依然要重新调用HAL_UART_Receive_IT(&huart1,(uint8_t *)&rxbuff,1),注释部分是用来验证每接收两个字节进一次中断的,但是不太建议大家这么操作,因为如果每两个字节存储一次需要由数组接收值然后又由接收数组传递数组的值到发送缓冲数组,发送缓冲数组的位置每一次都是两个字节两个字节的变动,比较容易出问题,而且只有当发送数据是偶数个的时候才能正确发送,如果是奇数个,前面两个字节两个两个接收完剩下最后一个字节一直等两个字节就接收不到,接收不到也就没有最后的停止字符发送缓冲数组就没法往外发送数据。

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "OLED.h"
#include "stm32f1xx_hal.h"
#include <stdio.h>
#include <string.h>
extern UART_HandleTypeDef huart1;   //声明串口
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
//uint8_t rxbuff[2];//使用两个字节传输的时候解开注释并把下面的注释掉
uint8_t rxbuff;//用于存储接收单个字节的变量
uint8_t ncount = 0;//用于指示数组位置的变量
uint8_t TXarr[256];//用于缓存多个字节的变量,当数据接收完毕一次性发送,但字节数不超过256

#include <string.h>

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/**
  * 函数功能: 重定向c库函数printf到DEBUG_USARTx
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:无
  */
int fputc(int ch, FILE *f)
{
  HAL_UART_Transmit_IT(&huart1, (uint8_t *)&ch, 1);
  return ch;
}
 
/**
  * 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:无
  */
int fgetc(FILE *f)
{
  uint8_t ch = 0;
  HAL_UART_Receive_IT(&huart1, &ch, 1);
  return ch;
}

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();


/* USER CODE BEGIN 2 */
  OLED_Init();
	HAL_UART_Receive_IT(&huart1, (uint8_t *)&TXarr, 1);
  memset(TXarr,0x00,sizeof(TXarr)); //清空数组
  //HAL_UART_Receive_IT(&huart1, (uint8_t *)TXarr, 1);//使用两个字节传输的时候解开注释并把上面的注释掉
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {


    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){

  if(huart == &huart1 )
  {
    if(ncount >= 255)
    {
      ncount = 0;
      memset((void *)TXarr,0x00,sizeof(TXarr));

    }
    else 
    {
      //TXarr[ncount] = rxbuff[0];
      //TXarr[ncount+1] = rxbuff[1];
      //ncount += 2;//使用两个字节传输的时候解开注释并把下面一行注释掉
      TXarr[ncount-1] = rxbuff;
      ncount++;
      if(TXarr[ncount]=='\n') //因为按下回车会自动换行所以以换行符结尾   
      {
        HAL_UART_Transmit(&huart1,(uint8_t *)&TXarr,sizeof(TXarr),0xffff);
        while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);//检测UART发送结束
			  ncount = 0;
			  memset(TXarr,0x00,sizeof(TXarr)); //清空数组
      }
      
    }
  }
    HAL_UART_Receive_IT(&huart1,(uint8_t *)&rxbuff,1);
    //HAL_UART_Receive_IT(&huart1,(uint8_t *)rxbuff,2);//使用两个字节传输的时候解开注释并把上面的注释掉

}


/* USER CODE BEGIN 4 */
 


/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

全部代码展示,大家可以自行解开注释测试两字节发送的代码,用的时候记得注释掉单字节发送的代码

错误代码示例主要是我自己用的时候遇到的一些问题

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){

  if(huart == &huart1 )
  {
    if(ncount >= 255)
    {
      ncount = 0;
      memset((void *)TXarr,0x00,sizeof(TXarr));//这个代码是为了防止一次性发送数据的字节大小超过缓冲数组大小

    }
    else 
    {
      //TXarr[ncount] = rxbuff[0];
      //TXarr[ncount+1] = rxbuff[1];
      //ncount += 2;//使用两个字节传输的时候解开注释并把下面一行注释掉
      TXarr[ncount] = rxbuff;
      
      if(TXarr[ncount]=='\n') //因为按下回车会自动换行所以以换行符结尾   (因为把ncount++放后面去了,所以就不需要减一了)
      {
        HAL_UART_Transmit(&huart1,(uint8_t *)&TXarr,sizeof(TXarr),0xffff);
        while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);//检测UART发送结束
			  ncount = 0;
			  memset(TXarr,0x00,sizeof(TXarr)); //清空数组
      }
    }
    ncount++;//这里只把这个换了个位置,但是数据会从数组的第二位开始发送
  }
    HAL_UART_Receive_IT(&huart1,(uint8_t *)&rxbuff,1);
    //HAL_UART_Receive_IT(&huart1,(uint8_t *)rxbuff,2);//使用两个字节传输的时候解开注释并把上面的注释掉

}

这是我一开始使用的代码我把ncount++放if后面去了就会导致如下结果

连续字节发送错误示例

出现这种结果的原因是当第一次成功发送后ncount被赋值0,然后又自增导致下一次进中断的时候ncount的值不再是0而是1,所以从数组的第二位开始赋值,至于没有被赋值的部分默认是0。

4.视频结果验证

连续字节发送正确示例,但发送的第一次是乱码我也没找到原因

5.小结

本章节旨在分享单片机编程经验,这次实验看似成功但仍然留有一个大问题,就是发送的第一个数据是错误的但后面发送的数据全部正常,我自己没找着原因,希望有懂得大佬指出错误让我能够改正。

最后提供一些我自己做测试的小经验,当使用非单字节连续传输时要充分考虑到接收数组大小能否容纳,其次就是接收数组的传递过程对应的值要一一对应不然数据一样会出错误,还是建议不要尝试多个字节的连续传输模式,单个字节既省时又省力,如果要发送大量的数据可以尝试dma转运,目前我还在学,等学会之后会发布一篇经验分享文章,关于数据发送建议大家可以找找资料因为数据编码传输的方式并不是串口专有的,其他通信协议一样适用,如果能完全理解,学习其他的通信协议能够事半功倍,以上是本篇文章全部内容,如果有更好的编程建议可在评论区分享供大家学习使用。

<think>嗯,用户想知道如何在嵌入式系统中用HAL接收不定长串口数据,特别是针对蓝桥杯的情况。我之前整理过相关的引用资料,里面提到了空闲中断和DMA的结合使用。首先,我需要回忆一下HAL串口接收的几种模式。 用户提到的蓝桥杯嵌入式开发,可能使用的是STM32系列单片机。HAL提供了不同的函数,比如HAL_UART_Receive_IT是中断接收,但需要指定数据长度。对于不定长数据,传统的做法可能需要检测空闲中断,也就是当串口一段时间没有数据传输时触发中断,这样可以确定一帧数据结束。 然后,引用[3]里提到了HAL_UARTEx_ReceiveToIdle_DMA这个函数,这是HAL的一个扩展功能,结合了DMA和空闲中断。DMA可以自动搬运数据,减少CPU负担,而空闲中断用来检测数据接收完成。这样就能实现不定长数据的接收了。 接下来,我需要把步骤理清楚。首先,初始化串口和DMA,配置相关的中断。然后,在代码中启用DMA接收,使用ReceiveToIdle函数。之后,处理空闲中断回调函数,这里可能需要关闭半传输中断,避免干扰。最后,在回调函数中处理接收到的数据,比如解析或者发送回去。 还要注意,用户可能对代码示例比较关注,所以需要提供一个简单的例子,展示如何初始化和处理中断。同时,要提醒用户注意缓冲区大小,防止溢出,以及及时重启接收以准备下一次数据。 另外,用户的问题里引用了三个资料,需要正确标注引用来源。比如,使用空闲中断和DMA的方法是引用[1]和[3]提到的,而发送函数可能引用[2]中的HAL_UART_Transmit。 可能还需要考虑错误处理,比如在接收过程中出现错误该怎么办,但用户的问题可能更关注基础实现,所以这部分可以暂时简略。最后,生成的相关问题要围绕实现中的关键点,比如空闲中断的作用、DMA的优势,以及如何结合两者来优化接收。</think>### 蓝桥杯嵌入式HAL串口接收不定长数据实现方法 #### 1. 核心原理 通过**空闲中断(IDLE)**检测数据接收完成,配合**DMA传输**实现高效数据搬运。空闲中断会在串口总线持续保持空闲状态(1个字节时间)时触发,标志一帧数据接收完成[^1][^3]。 #### 2. 实现步骤 ##### 2.1 硬件配置 1. 在CubeMX中启用USART的DMA接收通道(模式选择Circular) 2. 开启串口全局中断(NVIC Settings) ##### 2.2 代码实现 ```c // 定义接收缓冲区 #define MAX_RECV_LEN 100 uint8_t Receive_Buffer[MAX_RECV_LEN]; // 初始化函数中启动接收 HAL_UARTEx_ReceiveToIdle_DMA(&huart1, Receive_Buffer, MAX_RECV_LEN); __HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT); // 关闭半传输中断[^3] ``` ##### 2.3 中断回调函数 ```c void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) { if(huart == &huart1) { // 1. 处理接收数据(Size为实际接收长度) printf("Received %d bytes:", Size); HAL_UART_Transmit(&huart1, Receive_Buffer, Size, 100); // 回传数据[^2] // 2. 重启接收 HAL_UARTEx_ReceiveToIdle_DMA(&huart1, Receive_Buffer, MAX_RECV_LEN); __HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT); } } ``` #### 3. 关键API说明 | 函数 | 作用 | |------|------| | `HAL_UARTEx_ReceiveToIdle_DMA()` | 启动DMA接收,检测到空闲中断自动停止 | | `HAL_UART_Transmit()` | 使用超时机制发送数据[^2] | | `__HAL_DMA_DISABLE_IT()` | 禁用DMA半传输中断,避免数据分段 | #### 4. 注意事项 1. 接收缓冲区应大于最大预期数据长度 2. 每次回调后必须重新启动接收 3. DMA模式建议选择Circular实现循环缓冲
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值