一、数据收发模式

根据数据处理时机和CPU资源占用的不同,串口数据收发主要分为轮询中断两种基本模式。在此之上,利用空闲中断可以实现不定长数据的接收

二、 定长数据的收发

定长数据收发指在通信前,数据的长度(字节数)是已知且固定的。

1. 轮询模式

工作原理:CPU持续、主动地检测串口状态寄存器中的标志位(如TXE发送缓冲区空、RXNE接收缓冲区非空),根据状态来决定下一步操作。
优点:实现简单,代码流程直观,延迟稳定可预测。
缺点:在数据收发完成前,CPU被长期占用,无法执行其他任务,效率低下。

发送函数:

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);

huart: 串口实例指针(如 &huart1)
pData: 待发送数据缓冲区的首地址
Size: 待发送数据的字节数(定长)
Timeout: 超时时间(单位:毫秒)。若在指定时间内未完成发送,函数将返回超时错误。
返回值:函数执行状态(HAL_OK, HAL_ERROR, HAL_BUSY, HAL_TIMEOUT)

接收函数:

HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);

参数与发送函数类似。
pData:用于存储接收数据的缓冲区首地址。
Size:期望接收数据的字节数。函数会一直等待,直到收够指定数量的数据或超时。

2. 中断模式

工作原理:CPU启动数据传输后便立即返回,继续执行主程序。当数据传输完成(如发送完成、接收完成)时,串口硬件会触发一个中断信号,CPU随即暂停当前任务,转而执行对应的中断服务程序回调函数来处理后续事宜。
优点:不阻塞主程序,CPU利用率高,实时响应性好。
缺点:编程模型稍复杂,需要处理中断和回调。

发送函数:

HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

参数与轮询发送类似,但没有超时参数。函数启动发送后立即返回。

发送完成回调函数:

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);

当指定数量的数据全部发送完毕后,HAL库会在中断服务程序中自动调用此函数。
用户可以在此函数内编写发送完成后的操作,如点亮LED、启动下一次发送等。

接收函数:

HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

启动中断接收。函数会配置好接收缓冲区和长度,然后等待串口接收中断。

接收完成回调函数:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

当收够了指定数量(Size)的数据后,HAL库会自动调用此函数。
用户可以在此函数内处理刚刚接收到的完整一帧数据。

三、 不定长数据的接收

在实际应用中,数据长度往往是变化的。利用空闲中断是实现串口不定长数据接收的常用且高效的方法。

1. 空闲中断原理

空闲状态:在一帧数据传输结束后,通信线路(RX)会维持高电平,这个状态被称为“空闲状态”。
中断触发:当硬件检测到总线空闲状态持续时间大于一个字节的传输时间时,便会将空闲中断标志位(IDLE) 置1。
条件:如果此时空闲中断使能位(IDLEIE) 已被软件开启,就会触发串口空闲中断。

2. 实现流程

开启接收中断与空闲中断:在程序初始化时,不仅开启普通接收中断,还要使能空闲中断。
数据接收:每个字节到达时,都会触发接收中断,将数据存入用户定义的缓存数组中。
检测空闲:当一帧数据全部发送完毕,总线进入空闲状态,触发空闲中断。
处理数据:在空闲中断的服务程序或回调函数中,可以确定当前缓存数组中存储的数据就是一帧完整的不定长数据,随后将其取出处理(如打印、解析等)。
重启接收:处理完数据后,重新启动中断接收,等待下一帧数据。

四、 HAL库相关函数

通常通过 __HAL_UART_ENABLE_IT(huart, UART_IT_IDLE)使能空闲中断。
空闲中断触发后,会在中断服务程序中调用 HAL_UART_IRQHandler,用户可以在自己重写的 HAL_UARTEx_RxEventCallback回调函数中处理这帧不定长数据。

五、相关代码及分析

1. 输出重定向

使得在嵌入式系统中可以使用printf等标准输出函数,但实际输出会通过UART串口发送,而不是默认的控制台。

调用流程:printf("Hello") → 调用fputc逐字符输出 → HAL_UART_Transmit → UART1硬件发送

// 告诉编译器不要使用半主机模式(semihosting)
// 半主机模式是通过调试器进行输入输出的机制,在独立运行的嵌入式系统中需要禁用
#pragma import (__use_no_semihosting)

// 定义一个简化的FILE结构体
// 在标准库中FILE结构体很复杂,这里只需要满足链接器的最小要求
struct __FILE
{
	int handle;  // 文件句柄,在实际嵌入式应用中通常不会使用
};

// 声明标准输出文件流
// 这是C标准库中预定义的标准输出文件指针,链接器会使用这个变量
FILE __stdout;

// 重定义系统退出函数
// 在嵌入式系统中通常不需要程序退出,这个空函数防止链接器报错
void _sys_exit(int x)
{
	x=x;  // 空操作,只是为了避免"未使用参数"的编译器警告
}

// 重定向字符输出函数
// 这是最关键的函数,将标准输出的字符重定向到UART串口
int fputc(int ch, FILE *f)
{
  uint8_t temp[1] = {ch};  // 将要输出的字符存入临时数组
  HAL_UART_Transmit(&huart1, temp, 1, 2);   // 通过UART1发送字符
  return ch;  // 返回字符,符合fputc函数的原型要求
}

2. UART初始化函数

// UART1初始化函数
void MX_USART1_UART_Init(void)
{
  
  // 设置UART实例为USART1
  huart1.Instance = USART1;
  
  // 配置波特率为115200bps
  huart1.Init.BaudRate = 115200;
  
  // 设置数据字长为8位
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  
  // 设置停止位为1位
  huart1.Init.StopBits = UART_STOPBITS_1;
  
  // 设置无奇偶校验
  huart1.Init.Parity = UART_PARITY_NONE;
  
  // 设置工作模式为发送和接收模式
  huart1.Init.Mode = UART_MODE_TX_RX;
  
  // 设置硬件流控制为无(不使用RTS/CTS)
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  
  // 设置过采样率为16倍
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  
  // 调用HAL库的UART初始化函数,如果初始化失败则执行错误处理
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    // 初始化失败,调用错误处理函数
    Error_Handler();
  }
}

3. UART接收完成回调函数

UART接收完成回调函数属于STM32 HAL库的中断处理机制

UART接收数据 → 达到指定长度 → 产生中断 → HAL库处理 → 调用HAL_UART_RxCpltCallback → 执行用户代码

// UART接收完成中断回调函数
// 当UART接收到指定数量的数据后,会自动调用这个函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    // 检查触发回调的是否是USART1
    if(huart->Instance==USART1)
    {
        // 重新启动UART接收,准备接收6个字节的数据到rxBuffer
        HAL_UART_Receive(&huart1,(uint8_t *)&rxBuffer,6,1000);
        
        // 通过UART1发送tx_data中的数据,发送长度为字符串长度
        HAL_UART_Transmit(&huart1,(uint8_t *)&tx_data,strlen(tx_data),1000);
    }
}

4. UART接收中止完成回调函数​​​​​​

触发条件:当UART接收被主动中止时调用

常见场景:调用HAL_UART_AbortReceive()函数,接收超时,通信错误

功能:将接收到的数据回显发送

用途:常用于调试或实现echo功能

UART接收中止完成回调函数
void HAL_UART_AbortReceiveCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance==USART1)// 检查是否USART1的接收被中止
	{
		HAL_UART_Transmit(&huart1,(uint8_t *)&rxBuffer,num,1000);// 将接收到的所有数据通过UART1发送回去
		HAL_UART_Receive_IT(&huart1,(uint8_t *)&rxBuffer,1);// 重新启动UART1的中断接收
		num=0;// 重置数据计数器为0
	}
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐