STM32 UART环形缓冲区技术详解
引言
在嵌入式系统开发中,串口通信是一种非常常见的通信方式,广泛应用于设备调试、数据传输、传感器数据采集等场景。然而,在处理串口数据时,我们经常会遇到以下挑战:
- 数据接收不及时导致的丢失
- 数据处理速度与接收速度不匹配
- 中断处理开销过大
- 字符串解析和处理的复杂性
为了解决这些问题,环形缓冲区(Ring Buffer)技术应运而生。环形缓冲区是一种特殊的线性缓冲区,它的首尾相连,形成一个环形结构,能够高效地处理数据流。本文将详细分析STM32 UART环形缓冲区的实现原理、核心功能和优化策略。
项目概述
本项目是一个基于STM32微控制器的UART环形缓冲区实现,主要功能包括:
- 高效的串口数据接收和发送
- 中断驱动的数据处理
- 丰富的字符串处理函数
- 灵活的超时处理机制
- 支持多UART实例扩展
项目结构清晰,代码简洁明了,易于集成到各种STM32项目中。
环形缓冲区原理
基本概念
环形缓冲区,也称为循环缓冲区或环形队列,是一种数据结构,它使用一个固定大小的缓冲区,通过头尾指针的移动来实现数据的入队和出队操作。当指针到达缓冲区末尾时,会自动绕到缓冲区的开头,形成一个环形结构。
工作原理
环形缓冲区的核心是两个指针:
- 头指针(Head):指向数据入队的位置
- 尾指针(Tail):指向数据出队的位置
当数据入队时,头指针向前移动;当数据出队时,尾指针向前移动。当指针到达缓冲区末尾时,会自动绕到缓冲区的开头。
优势
环形缓冲区相比普通线性缓冲区具有以下优势:
- 无需数据搬移:当数据出队后,尾指针向前移动,不需要将后续数据向前搬移,减少了数据复制的开销
- 固定内存大小:使用固定大小的缓冲区,不需要动态分配内存,适合嵌入式系统
- 高效的空间利用:通过环形结构,充分利用缓冲区的所有空间
- 适合中断处理:能够在中断中快速存储数据,主程序中异步处理
项目架构设计
目录结构
项目采用标准的STM32 CubeIDE项目结构,主要文件包括:
├── Inc/
│ ├── UartRingbuffer.h # 环形缓冲区头文件
│ ├── main.h # 主程序头文件
│ ├── stm32f4xx_hal_conf.h # HAL库配置文件
│ └── stm32f4xx_it.h # 中断处理头文件
├── Src/
│ ├── UartRingbuffer.c # 环形缓冲区实现文件
│ ├── main.c # 主程序实现文件
│ ├── stm32f4xx_hal_msp.c # HAL库MSP实现文件
│ ├── stm32f4xx_it.c # 中断处理实现文件
│ ├── syscalls.c # 系统调用文件
│ ├── sysmem.c # 系统内存文件
│ └── system_stm32f4xx.c # 系统初始化文件
├── Startup/
│ └── startup_stm32f446retx.s # 启动文件
├── README.md # 项目说明文件
├── RING_BUFFER_F446RE Debug.launch # 调试配置文件
├── STM32F446RETX_FLASH.ld # FLASH链接脚本
├── STM32F446RETX_RAM.ld # RAM链接脚本
├── UART_RING_BUFFER_F446RE Debug.launch # 调试配置文件
└── UART_RING_BUFFER_F446RE.ioc # CubeMX配置文件
核心组件
项目的核心组件包括:
- 环形缓冲区数据结构 :定义在
UartRingbuffer.h中,包含缓冲区数组、头指针和尾指针 - 初始化模块:负责环形缓冲区的初始化和UART中断的配置
- 中断处理模块:处理UART接收和发送中断
- 数据读写模块:提供数据的读写操作
- 字符串处理模块:提供字符串发送、查找和解析功能
- 超时处理模块:提供超时检测功能
数据流图
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ UART硬件 │ ──→ │ 中断处理 │ ──→ │ 环形缓冲区 │
└─────────────┘ └─────────────┘ └─────────────┘
↑
│
│
┌─────────────┐ ┌─────────────┐ │
│ 应用程序 │ ←── │ 数据处理 │ ←──┘
└─────────────┘ └─────────────┘
核心功能模块分析
环形缓冲区数据结构
环形缓冲区是本项目的核心数据结构,它的设计直接影响整个系统的性能和可靠性。
/* 环形缓冲区数据结构 */
typedef struct
{
unsigned char buffer[UART_BUFFER_SIZE]; // 缓冲区数组
volatile unsigned int head; // 头指针
volatile unsigned int tail; // 尾指针
} ring_buffer;
数据结构详解
-
buffer :存储实际数据的数组,大小由
UART_BUFFER_SIZE宏定义。这个宏可以根据实际需求进行调整,一般建议根据通信速率和数据处理速度来确定合适的大小。 -
head :头指针,指向下一个数据入队的位置。当有新数据到达时,会存储在
buffer[head]的位置,然后头指针向前移动。 -
tail :尾指针,指向下一个数据出队的位置。当应用程序读取数据时,会从
buffer[tail]的位置读取,然后尾指针向前移动。 -
volatile 关键字:确保在中断和主程序之间正确共享这些变量。由于头指针和尾指针会在中断服务程序中被修改,而在主程序中被读取,使用
volatile关键字可以防止编译器优化导致的变量值不一致问题。
环形缓冲区的工作原理
-
初始化:头指针和尾指针都初始化为0,表示缓冲区为空。
-
数据入队 :当有新数据到达时,计算下一个头指针的位置
i = (head + 1) % UART_BUFFER_SIZE。如果i != tail,说明缓冲区未满,将数据存储在buffer[head]的位置,然后将头指针更新为i。 -
数据出队 :当应用程序需要读取数据时,检查
head != tail,如果成立,说明缓冲区中有数据,读取buffer[tail]的位置的数据,然后将尾指针更新为(tail + 1) % UART_BUFFER_SIZE。 -
缓冲区状态判断:
- 空:
head == tail - 满:
(head + 1) % UART_BUFFER_SIZE == tail - 可用数据量:
(UART_BUFFER_SIZE + head - tail) % UART_BUFFER_SIZE
- 空:
环形缓冲区的优势
-
无需数据搬移:当数据出队后,尾指针向前移动,不需要将后续数据向前搬移,减少了数据复制的开销。
-
固定内存大小:使用固定大小的缓冲区,不需要动态分配内存,适合嵌入式系统。
-
高效的空间利用:通过环形结构,充分利用缓冲区的所有空间。
-
适合中断处理:能够在中断中快速存储数据,主程序中异步处理。
-
简单易用:实现简单,接口清晰,易于集成到各种项目中。
初始化模块
初始化模块是系统启动的关键部分,它负责设置环形缓冲区和配置UART中断,为后续的通信做好准备。
void Ringbuf_init(void)
{
_rx_buffer = &rx_buffer; // 初始化接收缓冲区指针
_tx_buffer = &tx_buffer; // 初始化发送缓冲区指针
/* 启用UART错误中断:(帧错误、噪声错误、溢出错误) */
__HAL_UART_ENABLE_IT(uart, UART_IT_ERR);
/* 启用UART数据寄存器非空中断 */
__HAL_UART_ENABLE_IT(uart, UART_IT_RXNE);
}
初始化过程详解
-
缓冲区指针初始化:
_rx_buffer = &rx_buffer;:将接收缓冲区指针指向全局接收缓冲区_tx_buffer = &tx_buffer;:将发送缓冲区指针指向全局发送缓冲区 这样设计的好处是可以在函数内部通过指针访问缓冲区,提高代码的可读性和可维护性。
-
UART错误中断启用:
__HAL_UART_ENABLE_IT(uart, UART_IT_ERR);:启用UART错误中断,包括帧错误、噪声错误、溢出错误等 错误中断的启用可以及时处理通信过程中出现的各种错误,提高系统的可靠性。
-
UART接收中断启用:
__HAL_UART_ENABLE_IT(uart, UART_IT_RXNE);:启用UART数据寄存器非空中断 当UART接收数据寄存器中有数据时,会触发此中断,系统可以及时处理接收到的数据。
初始化模块的设计思路
初始化模块的设计遵循以下原则:
- 简洁明了:初始化函数简洁明了,只做必要的初始化工作
- 顺序执行:按照逻辑顺序执行初始化步骤,确保系统正确启动
- 中断优先:优先启用中断,确保系统能够及时响应外部事件
- 可扩展性:设计上考虑了可扩展性,便于后续添加新功能
初始化模块的重要性
初始化模块虽然代码量不大,但它的重要性不容忽视:
- 系统启动的关键:初始化模块是系统启动的关键部分,它的正确执行直接影响整个系统的运行状态
- 中断配置的基础:中断配置是串口通信的基础,没有正确的中断配置,系统无法及时响应串口事件
- 缓冲区管理的起点:缓冲区管理是串口通信的核心,初始化模块为缓冲区管理提供了起点
- 系统可靠性的保障:正确的初始化可以确保系统的可靠性,减少运行过程中的错误
初始化模块的优化建议
- 添加参数检查:在初始化函数中添加参数检查,确保传入的参数有效
- 添加初始化状态返回:添加初始化状态返回值,便于调用者知道初始化是否成功
- 支持多个UART实例:修改初始化函数,支持多个UART实例的初始化
- 添加缓冲区大小配置:允许在初始化时配置缓冲区大小,提高灵活性
/* 优化后的初始化函数 */
typedef struct
{
UART_HandleTypeDef *huart;
uint16_t rx_buffer_size;
uint16_t tx_buffer_size;
} uart_init_config_t;
int Ringbuf_init(uart_init_config_t *config)
{
// 参数检查
if (config NULL || config->huart NULL)
{
return -1;
}
// 初始化缓冲区指针
_rx_buffer = &rx_buffer;
_tx_buffer = &tx_buffer;
// 启用UART错误中断
__HAL_UART_ENABLE_IT(config->huart, UART_IT_ERR);
// 启用UART接收中断
__HAL_UART_ENABLE_IT(config->huart, UART_IT_RXNE);
return 0;
}
这样的优化可以使初始化函数更加灵活和可靠,便于在不同的场景中使用。
中断处理模块
中断处理模块是整个系统的核心,负责处理UART接收和发送中断。中断处理的效率和正确性直接影响系统的性能和可靠性。
void Uart_isr (UART_HandleTypeDef *huart)
{
uint32_t isrflags = READ_REG(huart->Instance->SR);
uint32_t cr1its = READ_REG(huart->Instance->CR1);
/* 处理接收中断 */
if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
{
huart->Instance->SR; /* 读取状态寄存器 */
unsigned char c = huart->Instance->DR; /* 读取数据寄存器 */
store_char (c, _rx_buffer); // 存储数据到缓冲区
return;
}
/* 处理发送中断 */
if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
{
if(tx_buffer.head == tx_buffer.tail)
{
// 缓冲区为空,禁用中断
__HAL_UART_DISABLE_IT(huart, UART_IT_TXE);
}
else
{
// 发送下一个字节
unsigned char c = tx_buffer.buffer[tx_buffer.tail];
tx_buffer.tail = (tx_buffer.tail + 1) % UART_BUFFER_SIZE;
huart->Instance->SR;
huart->Instance->DR = c;
}
return;
}
}
中断处理模块的设计思路
中断处理模块的设计遵循以下原则:
- 快速响应:中断处理函数尽可能简短,减少中断处理时间,提高系统响应速度
- 状态检查:使用位操作快速检查中断状态,只处理需要的中断
- 早期返回:处理完一种中断后立即返回,减少中断嵌套
- 错误处理:及时处理错误中断,提高系统可靠性
- 缓冲区管理:在中断中高效管理缓冲区,确保数据的正确存储和发送
接收中断处理详解
接收中断处理是系统处理串口接收数据的关键部分,它的效率直接影响系统的接收能力。
/* 处理接收中断 */
if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
{
huart->Instance->SR; /* 读取状态寄存器 */
unsigned char c = huart->Instance->DR; /* 读取数据寄存器 */
store_char (c, _rx_buffer); // 存储数据到缓冲区
return;
}
接收中断处理的主要步骤:
- 中断类型检查:检查是否是接收数据寄存器非空中断,并且该中断已启用
- 状态寄存器读取:读取状态寄存器,清除错误标志
- 数据寄存器读取:读取数据寄存器,获取接收到的数据
- 数据存储:将数据存储到接收环形缓冲区
- 中断返回:处理完接收中断后立即返回
接收中断处理的关键技术点:
- 位操作优化:使用位操作快速检查中断状态,提高中断处理速度
- 错误标志清除:通过读取状态寄存器和数据寄存器,清除错误标志
- 数据缓冲:使用环形缓冲区存储数据,减少数据丢失的可能性
- 早期返回:处理完接收中断后立即返回,减少中断处理时间
发送中断处理详解
发送中断处理是系统处理串口发送数据的关键部分,它的效率直接影响系统的发送能力。
/* 处理发送中断 */
if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
{
if(tx_buffer.head == tx_buffer.tail)
{
// 缓冲区为空,禁用中断
__HAL_UART_DISABLE_IT(huart, UART_IT_TXE);
}
else
{
// 发送下一个字节
unsigned char c = tx_buffer.buffer[tx_buffer.tail];
tx_buffer.tail = (tx_buffer.tail + 1) % UART_BUFFER_SIZE;
huart->Instance->SR;
huart->Instance->DR = c;
}
return;
}
发送中断处理的主要步骤:
- 中断类型检查:检查是否是发送数据寄存器空中断,并且该中断已启用
- 缓冲区状态检查:检查发送缓冲区是否为空
- 中断禁用:如果发送缓冲区为空,禁用发送中断,避免不必要的中断触发
- 数据发送:如果发送缓冲区不为空,从发送缓冲区取出数据并发送
- 尾指针更新:更新发送缓冲区的尾指针
- 中断返回:处理完发送中断后立即返回
发送中断处理的关键技术点:
- 缓冲区状态检查:通过检查头指针和尾指针是否相等,判断发送缓冲区是否为空
- 中断禁用:当发送缓冲区为空时,禁用发送中断,减少不必要的中断触发
- 数据发送:通过直接操作寄存器发送数据,提高发送速度
- 尾指针更新:使用取模运算更新尾指针,实现环形缓冲区的循环使用
中断处理模块的性能分析
中断处理模块的性能直接影响系统的整体性能。以下是中断处理模块的性能分析:
| 操作 | 时间(时钟周期) | 说明 |
|---|---|---|
| 接收中断处理 | ~20 | 包括状态检查、数据读取和存储 |
| 发送中断处理 | ~15 | 包括状态检查、数据发送和尾指针更新 |
| 中断类型检查 | ~2 | 使用位操作快速检查中断类型 |
| 数据存储 | ~5 | 包括头指针计算和数据存储 |
| 数据发送 | ~3 | 包括数据读取和寄存器写入 |
中断处理模块的优化建议
- 添加错误中断处理:添加对错误中断的处理,提高系统的可靠性
/* 添加错误中断处理 */
if (((isrflags & (USART_SR_ORE | USART_SR_NE | USART_SR_FE | USART_SR_PE)) != RESET) && ((cr1its & UART_IT_ERR) != RESET))
{
// 清除错误标志
huart->Instance->SR;
huart->Instance->DR;
// 可以添加错误处理代码
return;
}
-
使用快速寄存器访问 :使用
READ_REG和WRITE_REG宏快速访问寄存器,提高中断处理速度 -
优化缓冲区操作:优化缓冲区操作,减少中断处理时间
-
支持多个UART实例:修改中断处理函数,支持多个UART实例
/* 支持多个UART实例的中断处理函数 */
void Uart_isr (UART_HandleTypeDef *huart)
{
uint32_t isrflags = READ_REG(huart->Instance->SR);
uint32_t cr1its = READ_REG(huart->Instance->CR1);
// 查找对应的UART实例
uart_handle_t *uart_handle = find_uart_handle(huart);
if (uart_handle == NULL)
{
return;
}
/* 处理接收中断 */
if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
{
huart->Instance->SR;
unsigned char c = huart->Instance->DR;
store_char (c, &uart_handle->rx_buffer);
return;
}
/* 处理发送中断 */
if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
{
if(uart_handle->tx_buffer.head == uart_handle->tx_buffer.tail)
{
__HAL_UART_DISABLE_IT(huart, UART_IT_TXE);
}
else
{
unsigned char c = uart_handle->tx_buffer.buffer[uart_handle->tx_buffer.tail];
uart_handle->tx_buffer.tail = (uart_handle->tx_buffer.tail + 1) % UART_TX_BUFFER_SIZE;
huart->Instance->SR;
huart->Instance->DR = c;
}
return;
}
}
- 添加中断嵌套保护:添加中断嵌套保护,避免中断嵌套导致的问题
/* 添加中断嵌套保护 */
void Uart_isr (UART_HandleTypeDef *huart)
{
// 禁用相同优先级的中断
uint32_t primask = __get_PRIMASK();
__disable_irq();
// 处理中断
// ...
// 恢复中断状态
if (!primask)
{
__enable_irq();
}
}
中断处理模块的常见问题及解决方案
-
中断丢失
- 问题:当中断处理时间过长时,可能会导致新的中断丢失
- 解决方案:优化中断处理函数,减少中断处理时间;使用优先级更高的中断
-
缓冲区溢出
- 问题:当数据接收速度超过处理速度时,可能会导致缓冲区溢出
- 解决方案:增大缓冲区大小;提高数据处理速度;添加缓冲区溢出检测和处理
-
中断嵌套
- 问题:中断嵌套可能会导致系统不稳定
- 解决方案:合理设置中断优先级;添加中断嵌套保护
-
寄存器访问错误
- 问题:寄存器访问顺序错误可能会导致错误标志清除失败
- 解决方案:按照参考手册的要求,正确访问寄存器
-
多UART冲突
- 问题:当使用多个UART时,可能会出现冲突
- 解决方案:为每个UART创建独立的中断处理函数;使用正确的缓冲区管理
中断处理模块的应用场景
中断处理模块在以下场景中特别重要:
- 高速数据传输:在高速数据传输场景中,中断处理的效率直接影响系统的吞吐量
- 实时系统:在实时系统中,中断处理的及时性直接影响系统的实时性
- 多任务系统:在多任务系统中,中断处理的正确性直接影响系统的稳定性
- 低功耗系统:在低功耗系统中,中断处理的效率直接影响系统的功耗
中断处理模块的设计总结
中断处理模块是整个系统的核心,它的设计直接影响系统的性能和可靠性。一个好的中断处理模块应该具备以下特点:
- 快速响应:中断处理函数尽可能简短,减少中断处理时间
- 状态检查:使用位操作快速检查中断状态,只处理需要的中断
- 早期返回:处理完一种中断后立即返回,减少中断嵌套
- 错误处理:及时处理错误中断,提高系统可靠性
- 缓冲区管理:在中断中高效管理缓冲区,确保数据的正确存储和发送
- 可扩展性:支持多个UART实例,提高代码的可重用性
- 稳定性:避免中断嵌套导致的问题,确保系统稳定运行
通过合理设计和优化中断处理模块,可以显著提高系统的性能和可靠性,满足各种应用场景的需求。
数据存储模块
数据存储模块是系统处理接收数据的关键部分,它负责将接收到的数据存储到环形缓冲区中。
void store_char(unsigned char c, ring_buffer *buffer)
{
int i = (unsigned int)(buffer->head + 1) % UART_BUFFER_SIZE;
// 如果头指针即将追上尾指针,说明缓冲区已满,不存储数据
if(i != buffer->tail) {
buffer->buffer[buffer->head] = c;
buffer->head = i;
}
}
数据存储模块的设计思路
数据存储模块的设计遵循以下原则:
- 高效计算:使用取模运算快速计算下一个头指针的位置
- 边界检查:检查缓冲区是否已满,避免数据覆盖
- 原子操作:确保数据存储和头指针更新的原子性,避免竞态条件
- 简洁明了:代码简洁明了,减少执行时间
数据存储模块的详细分析
数据存储模块的核心是store_char函数,它的主要功能是将接收到的字符存储到环形缓冲区中。以下是对该函数的详细分析:
-
头指针计算:
int i = (unsigned int)(buffer->head + 1) % UART_BUFFER_SIZE;使用取模运算计算下一个头指针的位置,实现环形缓冲区的循环使用。取模运算的时间复杂度为O(1),非常高效。
-
缓冲区满检测:
if(i != buffer->tail) { // 存储数据 }通过检查下一个头指针的位置是否等于尾指针的位置,判断缓冲区是否已满。如果已满,则不存储数据,避免覆盖原有数据。
-
数据存储:
buffer->buffer[buffer->head] = c;将接收到的字符存储到缓冲区的当前头指针位置。
-
头指针更新:
buffer->head = i;更新头指针到下一个位置,为下一次数据存储做准备。
数据存储模块的性能分析
数据存储模块的性能直接影响系统的接收能力。以下是数据存储模块的性能分析:
| 操作 | 时间(时钟周期) | 说明 |
|---|---|---|
| 头指针计算 | ~2 | 使用取模运算快速计算 |
| 缓冲区满检测 | ~1 | 简单的比较操作 |
| 数据存储 | ~1 | 直接内存写入 |
| 头指针更新 | ~1 | 简单的赋值操作 |
| 总计 | ~5 | 整个存储过程的时间 |
数据存储模块的优化建议
- 添加缓冲区满检测和处理:添加缓冲区满检测和处理,提高系统的可靠性
/* 添加缓冲区满检测和处理 */
enum {
RING_BUFFER_OK = 0,
RING_BUFFER_FULL = -1
};
int store_char(unsigned char c, ring_buffer *buffer)
{
int i = (unsigned int)(buffer->head + 1) % UART_BUFFER_SIZE;
// 如果头指针即将追上尾指针,说明缓冲区已满,不存储数据
if(i != buffer->tail) {
buffer->buffer[buffer->head] = c;
buffer->head = i;
return RING_BUFFER_OK;
}
return RING_BUFFER_FULL;
}
- 使用内存屏障:添加内存屏障,确保数据存储和头指针更新的原子性
/* 使用内存屏障 */
#define memory_barrier() __asm__ __volatile__("dmb\n": : :"memory")
void store_char(unsigned char c, ring_buffer *buffer)
{
int i = (unsigned int)(buffer->head + 1) % UART_BUFFER_SIZE;
// 如果头指针即将追上尾指针,说明缓冲区已满,不存储数据
if(i != buffer->tail) {
buffer->buffer[buffer->head] = c;
memory_barrier(); // 确保数据存储完成
buffer->head = i;
memory_barrier(); // 确保头指针更新完成
}
}
- 优化取模运算:当缓冲区大小为2的幂时,可以使用位运算优化取模运算
/* 优化取模运算 */
#define UART_BUFFER_SIZE 128 // 2的幂
#define UART_BUFFER_MASK (UART_BUFFER_SIZE - 1) // 掩码
void store_char(unsigned char c, ring_buffer *buffer)
{
int i = (buffer->head + 1) & UART_BUFFER_MASK; // 使用位运算替代取模运算
// 如果头指针即将追上尾指针,说明缓冲区已满,不存储数据
if(i != buffer->tail) {
buffer->buffer[buffer->head] = c;
buffer->head = i;
}
}
- 添加批量存储功能:添加批量存储功能,提高数据存储的效率
/* 添加批量存储功能 */
int store_chars(unsigned char *data, uint16_t length, ring_buffer *buffer)
{
int i;
uint16_t stored = 0;
for(i = 0; i < length; i++) {
int next_head = (buffer->head + 1) % UART_BUFFER_SIZE;
// 如果缓冲区已满,停止存储
if(next_head == buffer->tail) {
break;
}
// 存储数据
buffer->buffer[buffer->head] = data[i];
buffer->head = next_head;
stored++;
}
return stored;
}
数据存储模块的常见问题及解决方案
-
缓冲区溢出
- 问题:当数据接收速度超过处理速度时,可能会导致缓冲区溢出
- 解决方案:增大缓冲区大小;提高数据处理速度;添加缓冲区溢出检测和处理
-
数据覆盖
- 问题:当缓冲区已满时,新数据可能会覆盖原有数据
- 解决方案:添加缓冲区满检测,当缓冲区已满时,不存储新数据
-
竞态条件
- 问题:在多线程或中断环境中,可能会出现竞态条件
- 解决方案:添加内存屏障;使用原子操作;禁用中断保护
-
内存访问错误
- 问题:内存访问越界可能会导致系统崩溃
- 解决方案:添加边界检查;使用正确的缓冲区大小
-
性能瓶颈
- 问题:数据存储操作可能会成为性能瓶颈
- 解决方案:优化取模运算;添加批量存储功能;使用更快的内存访问方式
数据存储模块的应用场景
数据存储模块在以下场景中特别重要:
- 高速数据接收:在高速数据接收场景中,数据存储的效率直接影响系统的接收能力
- 实时数据处理:在实时数据处理场景中,数据存储的及时性直接影响系统的实时性
- 多数据源:在多数据源场景中,数据存储的可靠性直接影响系统的稳定性
- 低功耗系统:在低功耗系统中,数据存储的效率直接影响系统的功耗
数据存储模块的设计总结
数据存储模块是系统处理接收数据的关键部分,它的设计直接影响系统的接收能力和可靠性。一个好的数据存储模块应该具备以下特点:
- 高效计算:使用高效的算法计算头指针位置
- 边界检查:检查缓冲区边界,避免数据覆盖
- 原子操作:确保数据存储和头指针更新的原子性
- 错误处理:添加错误检测和处理,提高系统可靠性
- 可扩展性:支持批量存储等高级功能
- 性能优化:优化存储操作,提高系统性能
通过合理设计和优化数据存储模块,可以显著提高系统的接收能力和可靠性,满足各种应用场景的需求。
数据读取模块
数据读取模块是系统处理接收数据的重要部分,它负责从环形缓冲区中读取数据并返回给应用程序。
int Uart_read(void)
{
// 如果头指针等于尾指针,说明缓冲区为空
if(_rx_buffer->head == _rx_buffer->tail)
{
return -1;
}
else
{
unsigned char c = _rx_buffer->buffer[_rx_buffer->tail];
_rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1) % UART_BUFFER_SIZE;
return c;
}
}
数据读取模块的设计思路
数据读取模块的设计遵循以下原则:
- 边界检查:检查缓冲区是否为空,避免读取无效数据
- 高效计算:使用取模运算快速计算下一个尾指针的位置
- 原子操作:确保数据读取和尾指针更新的原子性,避免竞态条件
- 简洁明了:代码简洁明了,减少执行时间
数据读取模块的详细分析
数据读取模块的核心是Uart_read函数,它的主要功能是从接收环形缓冲区中读取一个字符并返回。以下是对该函数的详细分析:
-
缓冲区空检测:
if(_rx_buffer->head == _rx_buffer->tail) { return -1; }通过检查头指针是否等于尾指针,判断缓冲区是否为空。如果为空,则返回-1,表示没有数据可读。
-
数据读取:
unsigned char c = _rx_buffer->buffer[_rx_buffer->tail];从缓冲区的当前尾指针位置读取数据。
-
尾指针更新:
_rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1) % UART_BUFFER_SIZE;使用取模运算计算下一个尾指针的位置,实现环形缓冲区的循环使用。
-
数据返回:
return c;返回读取的数据。
数据读取模块的性能分析
数据读取模块的性能直接影响系统的读取能力。以下是数据读取模块的性能分析:
| 操作 | 时间(时钟周期) | 说明 |
|---|---|---|
| 缓冲区空检测 | ~1 | 简单的比较操作 |
| 数据读取 | ~1 | 直接内存读取 |
| 尾指针更新 | ~2 | 使用取模运算计算下一个尾指针位置 |
| 数据返回 | ~1 | 简单的返回操作 |
| 总计 | ~5 | 整个读取过程的时间 |
数据读取模块的优化建议
- 添加批量读取功能:添加批量读取功能,提高数据读取的效率
/* 添加批量读取功能 */
int Uart_read_bytes(unsigned char *buffer, uint16_t length)
{
uint16_t read = 0;
while(read < length && _rx_buffer->head != _rx_buffer->tail)
{
buffer[read] = _rx_buffer->buffer[_rx_buffer->tail];
_rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1) % UART_BUFFER_SIZE;
read++;
}
return read;
}
- 使用内存屏障:添加内存屏障,确保数据读取和尾指针更新的原子性
/* 使用内存屏障 */
#define memory_barrier() __asm__ __volatile__("dmb\n": : :"memory")
int Uart_read(void)
{
// 如果头指针等于尾指针,说明缓冲区为空
if(_rx_buffer->head == _rx_buffer->tail)
{
return -1;
}
else
{
unsigned char c = _rx_buffer->buffer[_rx_buffer->tail];
memory_barrier(); // 确保数据读取完成
_rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1) % UART_BUFFER_SIZE;
memory_barrier(); // 确保尾指针更新完成
return c;
}
}
- 优化取模运算:当缓冲区大小为2的幂时,可以使用位运算优化取模运算
/* 优化取模运算 */
#define UART_BUFFER_SIZE 128 // 2的幂
#define UART_BUFFER_MASK (UART_BUFFER_SIZE - 1) // 掩码
int Uart_read(void)
{
// 如果头指针等于尾指针,说明缓冲区为空
if(_rx_buffer->head == _rx_buffer->tail)
{
return -1;
}
else
{
unsigned char c = _rx_buffer->buffer[_rx_buffer->tail];
_rx_buffer->tail = (_rx_buffer->tail + 1) & UART_BUFFER_MASK; // 使用位运算替代取模运算
return c;
}
}
- 支持指定缓冲区:修改读取函数,支持从指定缓冲区读取数据
/* 支持指定缓冲区 */
int Uart_read_from(ring_buffer *buffer)
{
// 如果头指针等于尾指针,说明缓冲区为空
if(buffer->head == buffer->tail)
{
return -1;
}
else
{
unsigned char c = buffer->buffer[buffer->tail];
buffer->tail = (unsigned int)(buffer->tail + 1) % UART_BUFFER_SIZE;
return c;
}
}
数据读取模块的常见问题及解决方案
-
缓冲区为空
- 问题:当缓冲区为空时,读取操作会返回-1
- 解决方案 :在读取数据前,使用
IsDataAvailable函数检查缓冲区是否有数据
-
竞态条件
- 问题:在多线程或中断环境中,可能会出现竞态条件
- 解决方案:添加内存屏障;使用原子操作;禁用中断保护
-
内存访问错误
- 问题:内存访问越界可能会导致系统崩溃
- 解决方案:添加边界检查;使用正确的缓冲区大小
-
性能瓶颈
- 问题:数据读取操作可能会成为性能瓶颈
- 解决方案:优化取模运算;添加批量读取功能;使用更快的内存访问方式
数据读取模块的应用场景
数据读取模块在以下场景中特别重要:
- 字符型数据处理:在字符型数据处理场景中,数据读取的效率直接影响系统的处理能力
- 实时数据处理:在实时数据处理场景中,数据读取的及时性直接影响系统的实时性
- 命令解析:在命令解析场景中,数据读取的可靠性直接影响系统的稳定性
- 低功耗系统:在低功耗系统中,数据读取的效率直接影响系统的功耗
数据读取模块的设计总结
数据读取模块是系统处理接收数据的重要部分,它的设计直接影响系统的读取能力和可靠性。一个好的数据读取模块应该具备以下特点:
- 边界检查:检查缓冲区边界,避免读取无效数据
- 高效计算:使用高效的算法计算尾指针位置
- 原子操作:确保数据读取和尾指针更新的原子性
- 错误处理:添加错误检测和处理,提高系统可靠性
- 可扩展性:支持批量读取等高级功能
- 性能优化:优化读取操作,提高系统性能
通过合理设计和优化数据读取模块,可以显著提高系统的读取能力和可靠性,满足各种应用场景的需求。
数据可用检测模块
数据可用检测模块是系统处理接收数据的辅助部分,它负责计算环形缓冲区中可用数据的数量并返回给应用程序。
int IsDataAvailable(void)
{
return (uint16_t)(UART_BUFFER_SIZE + _rx_buffer->head - _rx_buffer->tail) % UART_BUFFER_SIZE;
}
数据可用检测模块的设计思路
数据可用检测模块的设计遵循以下原则:
- 边界处理:正确处理头指针小于尾指针的情况
- 高效计算:使用取模运算快速计算可用数据的数量
- 简洁明了:代码简洁明了,减少执行时间
数据可用检测模块的详细分析
数据可用检测模块的核心是IsDataAvailable函数,它的主要功能是计算接收环形缓冲区中可用数据的数量并返回。以下是对该函数的详细分析:
-
可用数据数量计算:
return (uint16_t)(UART_BUFFER_SIZE + _rx_buffer->head - _rx_buffer->tail) % UART_BUFFER_SIZE;该计算式的巧妙之处在于:
- 当
head >= tail时,UART_BUFFER_SIZE + head - tail的结果会大于UART_BUFFER_SIZE,取模后得到head - tail,即实际可用数据的数量 - 当
head < tail时,UART_BUFFER_SIZE + head - tail的结果会小于UART_BUFFER_SIZE,取模后得到UART_BUFFER_SIZE + head - tail,即实际可用数据的数量
- 当
-
类型转换:
(uint16_t)(...)使用
uint16_t类型转换,确保返回值为无符号整数,避免负数返回值
数据可用检测模块的性能分析
数据可用检测模块的性能直接影响系统的响应速度。以下是数据可用检测模块的性能分析:
| 操作 | 时间(时钟周期) | 说明 |
|---|---|---|
| 可用数据数量计算 | ~5 | 包括加法、减法和取模运算 |
| 类型转换 | ~1 | 简单的类型转换操作 |
| 总计 | ~6 | 整个检测过程的时间 |
数据可用检测模块的优化建议
- 支持指定缓冲区:修改检测函数,支持检测指定缓冲区的可用数据数量
/* 支持指定缓冲区 */
int IsDataAvailableFrom(ring_buffer *buffer)
{
return (uint16_t)(UART_BUFFER_SIZE + buffer->head - buffer->tail) % UART_BUFFER_SIZE;
}
- 使用更快的计算方法:当缓冲区大小为2的幂时,可以使用位运算优化计算
/* 使用位运算优化计算 */
#define UART_BUFFER_SIZE 128 // 2的幂
#define UART_BUFFER_MASK (UART_BUFFER_SIZE - 1) // 掩码
int IsDataAvailable(void)
{
return (_rx_buffer->head - _rx_buffer->tail) & UART_BUFFER_MASK;
}
- 添加缓冲区满检测:添加缓冲区满检测函数,方便应用程序使用
/* 添加缓冲区满检测 */
int IsBufferFull(ring_buffer *buffer)
{
int next_head = (buffer->head + 1) % UART_BUFFER_SIZE;
return (next_head == buffer->tail) ? 1 : 0;
}
- 添加缓冲区空检测:添加缓冲区空检测函数,方便应用程序使用
/* 添加缓冲区空检测 */
int IsBufferEmpty(ring_buffer *buffer)
{
return (buffer->head == buffer->tail) ? 1 : 0;
}
数据可用检测模块的常见问题及解决方案
-
计算错误
- 问题:可用数据数量计算错误,导致应用程序读取无效数据
- 解决方案:确保使用正确的计算式,正确处理头指针小于尾指针的情况
-
性能问题
- 问题:数据可用检测操作可能会成为性能瓶颈
- 解决方案:优化计算方法;使用位运算替代取模运算(当缓冲区大小为2的幂时)
数据可用检测模块的应用场景
数据可用检测模块在以下场景中特别重要:
- 轮询模式:在轮询模式下,应用程序需要定期检查缓冲区是否有数据
- 批量读取:在批量读取场景中,应用程序需要知道缓冲区中有多少数据可读
- 超时处理:在超时处理场景中,应用程序需要知道何时停止等待
数据可用检测模块的设计总结
数据可用检测模块是系统处理接收数据的辅助部分,它的设计直接影响系统的响应速度和可靠性。一个好的数据可用检测模块应该具备以下特点:
- 边界处理:正确处理头指针小于尾指针的情况
- 高效计算:使用高效的算法计算可用数据的数量
- 简洁明了:代码简洁明了,减少执行时间
- 可扩展性:支持检测指定缓冲区的可用数据数量
通过合理设计和优化数据可用检测模块,可以显著提高系统的响应速度和可靠性,满足各种应用场景的需求。
字符串发送模块
字符串发送模块是系统处理发送数据的重要部分,它负责将字符串中的每个字符发送到串口。
void Uart_sendstring (const char *s)
{
while(*s) Uart_write(*s++);
}
字符串发送模块的设计思路
字符串发送模块的设计遵循以下原则:
- 简洁明了:代码简洁明了,减少执行时间
- 边界处理:正确处理字符串结束符
- 高效循环:使用高效的循环遍历字符串中的每个字符
字符串发送模块的详细分析
字符串发送模块的核心是Uart_sendstring函数,它的主要功能是遍历字符串中的每个字符并调用Uart_write函数发送。以下是对该函数的详细分析:
-
字符串遍历:
while(*s) Uart_write(*s++);使用
while循环遍历字符串中的每个字符,当遇到字符串结束符'\0'时,循环结束。 -
字符发送:
Uart_write(*s++);调用
Uart_write函数发送当前字符,然后指针指向下一个字符。
字符串发送模块的性能分析
字符串发送模块的性能直接影响系统的发送能力。以下是字符串发送模块的性能分析:
| 操作 | 时间(时钟周期) | 说明 |
|---|---|---|
| 字符串遍历 | ~2 per char | 包括条件检查和指针自增 |
| 字符发送 | ~10 per char | 包括Uart_write函数调用和内部操作 |
| 总计 | ~12 per char | 发送一个字符的总时间 |
字符串发送模块的优化建议
- 添加长度参数:添加长度参数,避免字符串遍历中的条件检查
/* 添加长度参数 */
void Uart_sendstring_len(const char *s, uint16_t len)
{
while(len--)
{
Uart_write(*s++);
}
}
- 添加格式化发送功能:添加格式化发送功能,方便应用程序使用
/* 添加格式化发送功能 */
void Uart_printf(const char *format, ...)
{
char buffer[256];
va_list args;
va_start(args, format);
vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
Uart_sendstring(buffer);
}
- 添加批量发送功能:添加批量发送功能,提高发送效率
/* 添加批量发送功能 */
void Uart_sendbytes(const unsigned char *data, uint16_t length)
{
while(length--)
{
Uart_write(*data++);
}
}
- 优化循环 :使用
for循环替代while循环,提高循环效率
/* 优化循环 */
void Uart_sendstring (const char *s)
{
for(; *s; s++)
{
Uart_write(*s);
}
}
字符串发送模块的常见问题及解决方案
-
字符串过长:
- 问题:字符串过长可能会导致发送缓冲区溢出
- 解决方案:增大发送缓冲区大小;使用批量发送功能
-
内存不足:
- 问题:在格式化发送时,缓冲区可能会不足
- 解决方案:使用更大的缓冲区;添加缓冲区大小检查
-
性能瓶颈:
- 问题:字符串发送操作可能会成为性能瓶颈
- 解决方案:优化循环;添加批量发送功能;使用DMA发送
字符串发送模块的应用场景
字符串发送模块在以下场景中特别重要:
- 调试信息输出:在调试信息输出场景中,字符串发送的效率直接影响调试的便利性
- 命令响应:在命令响应场景中,字符串发送的及时性直接影响系统的响应速度
- 数据日志:在数据日志场景中,字符串发送的可靠性直接影响数据的完整性
字符串发送模块的设计总结
字符串发送模块是系统处理发送数据的重要部分,它的设计直接影响系统的发送能力和可靠性。一个好的字符串发送模块应该具备以下特点:
- 简洁明了:代码简洁明了,减少执行时间
- 边界处理:正确处理字符串结束符
- 高效循环:使用高效的循环遍历字符串中的每个字符
- 可扩展性:支持格式化发送、批量发送等高级功能
通过合理设计和优化字符串发送模块,可以显著提高系统的发送能力和可靠性,满足各种应用场景的需求。
字符串查找模块
字符串查找模块是系统处理接收数据的高级部分,它负责在接收环形缓冲区中查找指定的字符串。
int Wait_for (char *string)
{
int so_far =0;
int len = strlen (string);
again:
timeout = TIMEOUT_DEF;
while ((!IsDataAvailable())&&timeout); // 等待数据到达
if (timeout == 0) return 0;
while (Uart_peek() != string[so_far]) // 查找字符串的第一个字符
{
if (_rx_buffer->tail != _rx_buffer->head)
{
_rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1) % UART_BUFFER_SIZE; // 跳过不匹配的字符
}
else
{
return 0;
}
}
while (Uart_peek() == string [so_far]) // 查找字符串的其他字符
{
// 现在查找字符串的其他字符
so_far++;
_rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1) % UART_BUFFER_SIZE; // 移动尾指针
if (so_far == len) return 1;
timeout = TIMEOUT_DEF;
while ((!IsDataAvailable())&&timeout);
if (timeout == 0) return 0;
}
if (so_far != len)
{
so_far = 0;
goto again;
}
if (so_far == len) return 1;
else return 0;
}
字符串查找模块的设计思路
字符串查找模块的设计遵循以下原则:
- 超时处理:添加超时处理,避免无限等待
- 边界处理:正确处理缓冲区为空的情况
- 高效查找:使用简单的字符串匹配算法查找指定的字符串
- 状态恢复:当部分匹配失败时,恢复状态并重新开始查找
字符串查找模块的详细分析
字符串查找模块的核心是Wait_for函数,它的主要功能是在接收环形缓冲区中查找指定的字符串。以下是对该函数的详细分析:
-
初始化:
int so_far =0; int len = strlen (string);初始化匹配计数器和获取字符串长度。
-
等待数据:
timeout = TIMEOUT_DEF; while ((!IsDataAvailable())&&timeout); // 等待数据到达 if (timeout == 0) return 0;设置超时计数器,等待数据到达。如果超时,返回0。
-
查找第一个字符:
while (Uart_peek() != string[so_far]) // 查找字符串的第一个字符 { if (_rx_buffer->tail != _rx_buffer->head) { _rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1) % UART_BUFFER_SIZE; // 跳过不匹配的字符 } else { return 0; } }查找字符串的第一个字符,跳过不匹配的字符。如果缓冲区为空,返回0。
-
查找其他字符:
while (Uart_peek() == string [so_far]) // 查找字符串的其他字符 { // 现在查找字符串的其他字符 so_far++; _rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1) % UART_BUFFER_SIZE; // 移动尾指针 if (so_far == len) return 1; timeout = TIMEOUT_DEF; while ((!IsDataAvailable())&&timeout); if (timeout == 0) return 0; }查找字符串的其他字符,移动尾指针。如果找到完整的字符串,返回1。如果超时,返回0。
-
状态恢复:
if (so_far != len) { so_far = 0; goto again; }当部分匹配失败时,恢复状态并重新开始查找。
字符串查找模块的性能分析
字符串查找模块的性能直接影响系统的查找能力。以下是字符串查找模块的性能分析:
| 操作 | 时间(时钟周期) | 说明 |
|---|---|---|
| 字符串长度计算 | ~10 | 包括strlen函数调用 |
| 等待数据 | 可变 | 取决于数据到达的时间 |
| 字符匹配 | ~5 per char | 包括条件检查和尾指针移动 |
| 超时处理 | ~2 per check | 包括超时计数器检查和递减 |
字符串查找模块的优化建议
- 使用KMP算法:使用KMP算法优化字符串查找,减少回溯
/* 使用KMP算法优化字符串查找 */
void compute_lps_array(char *pattern, int m, int *lps)
{
int len = 0; // 最长前缀后缀的长度
int i = 1;
lps[0] = 0; // lps[0] 总是 0
// 计算lps[i] for i = 1 to m-1
while (i < m)
{
if (pattern[i] == pattern[len])
{
len++;
lps[i] = len;
i++;
}
else
{
if (len != 0)
{
len = lps[len-1];
}
else
{
lps[i] = 0;
i++;
}
}
}
}
int Wait_for_kmp(char *string)
{
int m = strlen(string);
int *lps = (int *)malloc(sizeof(int) * m);
compute_lps_array(string, m, lps);
int i = 0; // 模式串的索引
int j = 0; // 文本串的索引
while (1)
{
timeout = TIMEOUT_DEF;
while ((!IsDataAvailable())&&timeout);
if (timeout == 0)
{
free(lps);
return 0;
}
if (_rx_buffer->tail != _rx_buffer->head)
{
char c = _rx_buffer->buffer[_rx_buffer->tail];
if (string[j] == c)
{
i++;
j++;
_rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1) % UART_BUFFER_SIZE;
if (j == m)
{
free(lps);
return 1;
}
}
else
{
if (j != 0)
{
j = lps[j-1];
}
else
{
_rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1) % UART_BUFFER_SIZE;
i++;
}
}
}
else
{
free(lps);
return 0;
}
}
free(lps);
return 0;
}
- 添加非阻塞版本:添加非阻塞版本,避免长时间阻塞
/* 添加非阻塞版本 */
int Check_for(char *string)
{
int so_far =0;
int len = strlen (string);
int start_tail = _rx_buffer->tail;
while (_rx_buffer->tail != _rx_buffer->head)
{
if (Uart_peek() == string[so_far])
{
so_far++;
_rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1) % UART_BUFFER_SIZE;
if (so_far == len)
{
return 1;
}
}
else
{
so_far = 0;
_rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1) % UART_BUFFER_SIZE;
}
}
// 恢复尾指针
_rx_buffer->tail = start_tail;
return 0;
}
- 添加缓冲区查找:添加缓冲区查找函数,方便应用程序使用
/* 添加缓冲区查找 */
int Look_for (char *str, char *buffertolookinto)
{
int stringlength = strlen (str);
int bufferlength = strlen (buffertolookinto);
int so_far = 0;
int indx = 0;
repeat:
while (str[so_far] != buffertolookinto[indx])
{
indx++;
if (indx>stringlength) return 0;
}
if (str[so_far] buffertolookinto[indx])
{
while (str[so_far] buffertolookinto[indx])
{
so_far++;
indx++;
}
}
if (so_far == stringlength);
else
{
so_far =0;
if (indx >= bufferlength) return -1;
goto repeat;
}
if (so_far == stringlength) return 1;
else return -1;
}
- 优化超时处理:优化超时处理,提高系统的响应速度
/* 优化超时处理 */
int Wait_for_with_timeout(char *string, uint16_t timeout_ms)
{
int so_far =0;
int len = strlen (string);
uint32_t start_time = HAL_GetTick();
again:
while (!IsDataAvailable())
{
if (HAL_GetTick() - start_time > timeout_ms)
{
return 0;
}
}
while (Uart_peek() != string[so_far])
{
if (_rx_buffer->tail != _rx_buffer->head)
{
_rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1) % UART_BUFFER_SIZE;
}
else
{
return 0;
}
if (HAL_GetTick() - start_time > timeout_ms)
{
return 0;
}
}
while (Uart_peek() == string [so_far])
{
so_far++;
_rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1) % UART_BUFFER_SIZE;
if (so_far == len) return 1;
while (!IsDataAvailable())
{
if (HAL_GetTick() - start_time > timeout_ms)
{
return 0;
}
}
}
if (so_far != len)
{
so_far = 0;
goto again;
}
if (so_far == len) return 1;
else return 0;
}
字符串查找模块的常见问题及解决方案
-
超时设置不当:
- 问题:超时设置过短可能导致查找失败,过长可能导致系统阻塞时间过长
- 解决方案:根据实际通信速率和数据大小,设置合理的超时时间
-
字符串匹配失败:
- 问题:字符串匹配失败可能导致系统一直查找,消耗大量CPU资源
- 解决方案:使用更高效的字符串匹配算法;添加最大尝试次数限制
-
缓冲区溢出:
- 问题:当查找过程中,新数据不断到达,可能会导致缓冲区溢出
- 解决方案:增大缓冲区大小;提高查找效率
-
内存使用:
- 问题:使用KMP算法时,需要额外的内存存储LPS数组
- 解决方案:根据字符串长度,合理分配内存;使用静态内存
字符串查找模块的应用场景
字符串查找模块在以下场景中特别重要:
- 命令解析:在命令解析场景中,需要查找特定的命令字符串
- 协议处理:在协议处理场景中,需要查找特定的协议帧头和帧尾
- 数据同步:在数据同步场景中,需要查找特定的同步字符
- 事件触发:在事件触发场景中,需要查找特定的事件字符串
字符串查找模块的设计总结
字符串查找模块是系统处理接收数据的高级部分,它的设计直接影响系统的查找能力和可靠性。一个好的字符串查找模块应该具备以下特点:
- 超时处理:添加超时处理,避免无限等待
- 边界处理:正确处理缓冲区为空的情况
- 高效查找:使用高效的字符串匹配算法查找指定的字符串
- 状态恢复:当部分匹配失败时,恢复状态并重新开始查找
- 可扩展性:支持非阻塞版本、带超时版本等高级功能
通过合理设计和优化字符串查找模块,可以显著提高系统的查找能力和可靠性,满足各种应用场景的需求。
数据复制模块
数据复制模块是系统处理接收数据的重要部分,它负责将接收环形缓冲区中的数据复制到应用程序提供的缓冲区中,直到找到指定的字符串为止。
int Copy_upto (char *string, char *buffertocopyinto)
{
int so_far =0;
int len = strlen (string);
int indx = 0;
again:
while (Uart_peek() != string[so_far])
{
buffertocopyinto[indx] = _rx_buffer->buffer[_rx_buffer->tail];
_rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1) % UART_BUFFER_SIZE;
indx++;
while (!IsDataAvailable());
}
while (Uart_peek() == string [so_far])
{
so_far++;
buffertocopyinto[indx++] = Uart_read();
if (so_far == len) return 1;
timeout = TIMEOUT_DEF;
while ((!IsDataAvailable())&&timeout);
if (timeout == 0) return 0;
}
if (so_far != len)
{
so_far =0;
goto again;
}
if (so_far == len) return 1;
else return 0;
}
数据复制模块的设计思路
数据复制模块的设计遵循以下原则:
- 边界处理:正确处理缓冲区为空的情况
- 超时处理:添加超时处理,避免无限等待
- 状态恢复:当部分匹配失败时,恢复状态并重新开始查找
- 数据完整性:确保复制的数据完整,包括终止字符串
数据复制模块的详细分析
数据复制模块的核心是Copy_upto函数,它的主要功能是从接收环形缓冲区中复制数据到目标缓冲区,直到找到指定的字符串为止。以下是对该函数的详细分析:
-
初始化:
int so_far =0; int len = strlen (string); int indx = 0;初始化匹配计数器、字符串长度和目标缓冲区索引。
-
查找并复制:
while (Uart_peek() != string[so_far]) { buffertocopyinto[indx] = _rx_buffer->buffer[_rx_buffer->tail]; _rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1) % UART_BUFFER_SIZE; indx++; while (!IsDataAvailable()); }查找指定字符串的第一个字符,同时将不匹配的字符复制到目标缓冲区。如果缓冲区为空,等待数据到达。
-
匹配并复制:
while (Uart_peek() == string [so_far]) { so_far++; buffertocopyinto[indx++] = Uart_read(); if (so_far == len) return 1; timeout = TIMEOUT_DEF; while ((!IsDataAvailable())&&timeout); if (timeout == 0) return 0; }匹配指定字符串的其他字符,同时将匹配的字符复制到目标缓冲区。如果找到完整的字符串,返回1。如果超时,返回0。
-
状态恢复:
if (so_far != len) { so_far =0; goto again; }当部分匹配失败时,恢复状态并重新开始查找。
数据复制模块的性能分析
数据复制模块的性能直接影响系统的数据处理能力。以下是数据复制模块的性能分析:
| 操作 | 时间(时钟周期) | 说明 |
|---|---|---|
| 字符串长度计算 | ~10 | 包括strlen函数调用 |
| 字符查找与复制 | ~15 per char | 包括条件检查、数据复制和尾指针移动 |
| 等待数据 | 可变 | 取决于数据到达的时间 |
| 超时处理 | ~2 per check | 包括超时计数器检查和递减 |
数据复制模块的优化建议
- 添加长度参数:添加目标缓冲区长度参数,避免缓冲区溢出
/* 添加长度参数 */
int Copy_upto_len(char *string, char *buffertocopyinto, uint16_t max_len)
{
int so_far =0;
int len = strlen (string);
int indx = 0;
again:
while (Uart_peek() != string[so_far] && indx < max_len - 1)
{
buffertocopyinto[indx] = _rx_buffer->buffer[_rx_buffer->tail];
_rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1) % UART_BUFFER_SIZE;
indx++;
while (!IsDataAvailable());
}
if (indx >= max_len - 1)
{
buffertocopyinto[indx] = '\0';
return 0;
}
while (Uart_peek() == string [so_far] && indx < max_len - 1)
{
so_far++;
buffertocopyinto[indx++] = Uart_read();
if (so_far == len)
{
buffertocopyinto[indx] = '\0';
return 1;
}
timeout = TIMEOUT_DEF;
while ((!IsDataAvailable())&&timeout);
if (timeout == 0)
{
buffertocopyinto[indx] = '\0';
return 0;
}
}
if (so_far != len)
{
so_far =0;
goto again;
}
if (so_far == len)
{
buffertocopyinto[indx] = '\0';
return 1;
}
else
{
buffertocopyinto[indx] = '\0';
return 0;
}
}
- 添加超时参数:添加超时参数,允许应用程序指定超时时间
/* 添加超时参数 */
int Copy_upto_timeout(char *string, char *buffertocopyinto, uint16_t timeout_ms)
{
int so_far =0;
int len = strlen (string);
int indx = 0;
uint32_t start_time = HAL_GetTick();
again:
while (Uart_peek() != string[so_far])
{
if (HAL_GetTick() - start_time > timeout_ms)
{
return 0;
}
buffertocopyinto[indx] = _rx_buffer->buffer[_rx_buffer->tail];
_rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1) % UART_BUFFER_SIZE;
indx++;
while (!IsDataAvailable())
{
if (HAL_GetTick() - start_time > timeout_ms)
{
return 0;
}
}
}
while (Uart_peek() == string [so_far])
{
if (HAL_GetTick() - start_time > timeout_ms)
{
return 0;
}
so_far++;
buffertocopyinto[indx++] = Uart_read();
if (so_far == len) return 1;
while (!IsDataAvailable())
{
if (HAL_GetTick() - start_time > timeout_ms)
{
return 0;
}
}
}
if (so_far != len)
{
so_far =0;
goto again;
}
if (so_far == len) return 1;
else return 0;
}
- 添加批量复制功能:添加批量复制功能,提高复制效率
/* 添加批量复制功能 */
int Copy_bytes(char *buffertocopyinto, uint16_t length)
{
int copied = 0;
while (copied < length && IsDataAvailable())
{
buffertocopyinto[copied] = Uart_read();
copied++;
}
return copied;
}
- 优化循环:优化循环结构,减少条件检查
/* 优化循环 */
int Copy_upto_optimized(char *string, char *buffertocopyinto)
{
int so_far = 0;
int len = strlen(string);
int indx = 0;
char *pattern = string;
while (1)
{
// 等待数据
while (!IsDataAvailable())
{
timeout = TIMEOUT_DEF;
while ((!IsDataAvailable()) && timeout);
if (timeout == 0) return 0;
}
// 检查当前字符
char c = Uart_peek();
if (c == *pattern)
{
// 匹配成功,继续匹配下一个字符
pattern++;
so_far++;
buffertocopyinto[indx++] = Uart_read();
if (so_far == len)
{
// 找到完整的字符串
return 1;
}
}
else
{
// 匹配失败,重置匹配状态
pattern = string;
so_far = 0;
buffertocopyinto[indx++] = Uart_read();
}
}
}
数据复制模块的常见问题及解决方案
-
缓冲区溢出:
- 问题:目标缓冲区大小不足,可能导致缓冲区溢出
- 解决方案:添加目标缓冲区长度参数,避免复制超出缓冲区大小的数据
-
超时设置不当:
- 问题:超时设置过短可能导致复制失败,过长可能导致系统阻塞时间过长
- 解决方案:根据实际通信速率和数据大小,设置合理的超时时间
-
数据不完整:
- 问题:当部分匹配失败时,可能会导致复制的数据不完整
- 解决方案:确保在复制过程中正确处理部分匹配失败的情况
-
性能瓶颈:
- 问题:数据复制操作可能会成为性能瓶颈
- 解决方案:优化循环结构;添加批量复制功能;使用更快的内存复制方法
数据复制模块的应用场景
数据复制模块在以下场景中特别重要:
- 命令解析:在命令解析场景中,需要复制完整的命令字符串
- 数据采集:在数据采集场景中,需要复制完整的数据帧
- 协议处理:在协议处理场景中,需要复制完整的协议数据包
- 日志记录:在日志记录场景中,需要复制完整的日志条目
数据复制模块的设计总结
数据复制模块是系统处理接收数据的重要部分,它的设计直接影响系统的数据处理能力和可靠性。一个好的数据复制模块应该具备以下特点:
- 边界处理:正确处理缓冲区为空的情况
- 超时处理:添加超时处理,避免无限等待
- 状态恢复:当部分匹配失败时,恢复状态并重新开始查找
- 数据完整性:确保复制的数据完整,包括终止字符串
- 可扩展性:支持批量复制、带超时复制等高级功能
通过合理设计和优化数据复制模块,可以显著提高系统的数据处理能力和可靠性,满足各种应用场景的需求。
数据提取模块
数据提取模块是系统处理接收数据的高级部分,它负责从指定的缓冲区中提取起始字符串和结束字符串之间的数据,并复制到目标缓冲区中。
void GetDataFromBuffer (char *startString, char *endString, char *buffertocopyfrom, char *buffertocopyinto)
{
int startStringLength = strlen (startString);
int endStringLength = strlen (endString);
int so_far = 0;
int indx = 0;
int startposition = 0;
int endposition = 0;
repeat1:
while (startString[so_far] != buffertocopyfrom[indx]) indx++;
if (startString[so_far] buffertocopyfrom[indx])
{
while (startString[so_far] buffertocopyfrom[indx])
{
so_far++;
indx++;
}
}
if (so_far == startStringLength) startposition = indx;
else
{
so_far =0;
goto repeat1;
}
so_far = 0;
repeat2:
while (endString[so_far] != buffertocopyfrom[indx]) indx++;
if (endString[so_far] buffertocopyfrom[indx])
{
while (endString[so_far] buffertocopyfrom[indx])
{
so_far++;
indx++;
}
}
if (so_far == endStringLength) endposition = indx-endStringLength;
else
{
so_far =0;
goto repeat2;
}
so_far = 0;
indx=0;
for (int i=startposition; i<endposition; i++)
{
buffertocopyinto[indx] = buffertocopyfrom[i];
indx++;
}
}
数据提取模块的设计思路
数据提取模块的设计遵循以下原则:
- 边界处理:正确处理字符串查找的边界情况
- 状态恢复:当部分匹配失败时,恢复状态并重新开始查找
- 数据完整性:确保提取的数据完整,不包含起始字符串和结束字符串
- 简洁明了:代码结构清晰,易于理解和维护
数据提取模块的详细分析
数据提取模块的核心是GetDataFromBuffer函数,它的主要功能是从指定的缓冲区中提取起始字符串和结束字符串之间的数据。以下是对该函数的详细分析:
-
初始化:
int startStringLength = strlen (startString); int endStringLength = strlen (endString); int so_far = 0; int indx = 0; int startposition = 0; int endposition = 0;初始化变量,包括起始字符串长度、结束字符串长度、匹配计数器、索引和位置变量。
-
查找起始字符串:
repeat1: while (startString[so_far] != buffertocopyfrom[indx]) indx++; if (startString[so_far] buffertocopyfrom[indx]) { while (startString[so_far] buffertocopyfrom[indx]) { so_far++; indx++; } } if (so_far == startStringLength) startposition = indx; else { so_far =0; goto repeat1; }查找起始字符串,当找到完整的起始字符串时,记录起始位置。
-
查找结束字符串:
so_far = 0; repeat2: while (endString[so_far] != buffertocopyfrom[indx]) indx++; if (endString[so_far] buffertocopyfrom[indx]) { while (endString[so_far] buffertocopyfrom[indx]) { so_far++; indx++; } } if (so_far == endStringLength) endposition = indx-endStringLength; else { so_far =0; goto repeat2; }查找结束字符串,当找到完整的结束字符串时,记录结束位置。
-
提取数据:
so_far = 0; indx=0; for (int i=startposition; i<endposition; i++) { buffertocopyinto[indx] = buffertocopyfrom[i]; indx++; }提取起始位置和结束位置之间的数据,并复制到目标缓冲区中。
数据提取模块的性能分析
数据提取模块的性能直接影响系统的数据处理能力。以下是数据提取模块的性能分析:
| 操作 | 时间(时钟周期) | 说明 |
|---|---|---|
| 字符串长度计算 | ~10 per string | 包括strlen函数调用 |
| 起始字符串查找 | 可变 | 取决于起始字符串的位置和长度 |
| 结束字符串查找 | 可变 | 取决于结束字符串的位置和长度 |
| 数据提取 | ~5 per char | 包括数据复制和索引自增 |
数据提取模块的优化建议
- 添加长度参数:添加目标缓冲区长度参数,避免缓冲区溢出
/* 添加长度参数 */
int GetDataFromBuffer_len(char *startString, char *endString, char *buffertocopyfrom, char *buffertocopyinto, uint16_t max_len)
{
int startStringLength = strlen (startString);
int endStringLength = strlen (endString);
int so_far = 0;
int indx = 0;
int startposition = 0;
int endposition = 0;
int extracted_len = 0;
repeat1:
while (startString[so_far] != buffertocopyfrom[indx]) indx++;
if (startString[so_far] buffertocopyfrom[indx])
{
while (startString[so_far] buffertocopyfrom[indx])
{
so_far++;
indx++;
}
}
if (so_far == startStringLength) startposition = indx;
else
{
so_far =0;
goto repeat1;
}
so_far = 0;
repeat2:
while (endString[so_far] != buffertocopyfrom[indx]) indx++;
if (endString[so_far] buffertocopyfrom[indx])
{
while (endString[so_far] buffertocopyfrom[indx])
{
so_far++;
indx++;
}
}
if (so_far == endStringLength) endposition = indx-endStringLength;
else
{
so_far =0;
goto repeat2;
}
so_far = 0;
indx=0;
extracted_len = endposition - startposition;
// 检查目标缓冲区大小
if (extracted_len > max_len - 1)
{
extracted_len = max_len - 1;
}
for (int i=startposition; i<startposition + extracted_len; i++)
{
buffertocopyinto[indx] = buffertocopyfrom[i];
indx++;
}
buffertocopyinto[indx] = '\0';
return extracted_len;
}
- 添加返回值:添加返回值,指示提取的数据长度
/* 添加返回值 */
int GetDataFromBuffer_return(char *startString, char *endString, char *buffertocopyfrom, char *buffertocopyinto)
{
int startStringLength = strlen (startString);
int endStringLength = strlen (endString);
int so_far = 0;
int indx = 0;
int startposition = 0;
int endposition = 0;
int extracted_len = 0;
// 查找起始字符串
// ...
// 查找结束字符串
// ...
// 提取数据
so_far = 0;
indx=0;
extracted_len = endposition - startposition;
for (int i=startposition; i<endposition; i++)
{
buffertocopyinto[indx] = buffertocopyfrom[i];
indx++;
}
buffertocopyinto[indx] = '\0';
return extracted_len;
}
- 优化字符串查找:使用更高效的字符串查找算法
/* 优化字符串查找 */
int find_string(char *haystack, char *needle)
{
int haystack_len = strlen(haystack);
int needle_len = strlen(needle);
if (needle_len == 0)
{
return 0;
}
if (needle_len > haystack_len)
{
return -1;
}
for (int i = 0; i <= haystack_len - needle_len; i++)
{
int j;
for (j = 0; j < needle_len; j++)
{
if (haystack[i + j] != needle[j])
{
break;
}
}
if (j == needle_len)
{
return i;
}
}
return -1;
}
void GetDataFromBuffer_optimized(char *startString, char *endString, char *buffertocopyfrom, char *buffertocopyinto)
{
int start_pos = find_string(buffertocopyfrom, startString);
if (start_pos == -1)
{
return;
}
int start_len = strlen(startString);
int end_pos = find_string(buffertocopyfrom + start_pos + start_len, endString);
if (end_pos == -1)
{
return;
}
end_pos += start_pos + start_len;
int end_len = strlen(endString);
int data_len = end_pos - (start_pos + start_len);
strncpy(buffertocopyinto, buffertocopyfrom + start_pos + start_len, data_len);
buffertocopyinto[data_len] = '\0';
}
- 支持从环形缓冲区提取:修改函数,支持从环形缓冲区直接提取数据
/* 支持从环形缓冲区提取 */
int GetDataFromRingBuffer(char *startString, char *endString, char *buffertocopyinto, uint16_t max_len)
{
// 临时缓冲区,用于存储环形缓冲区中的数据
char temp_buffer[UART_BUFFER_SIZE * 2];
int temp_len = 0;
int tail = _rx_buffer->tail;
// 从环形缓冲区复制数据到临时缓冲区
while (tail != _rx_buffer->head)
{
temp_buffer[temp_len++] = _rx_buffer->buffer[tail];
tail = (tail + 1) % UART_BUFFER_SIZE;
}
temp_buffer[temp_len] = '\0';
// 提取数据
return GetDataFromBuffer_len(startString, endString, temp_buffer, buffertocopyinto, max_len);
}
数据提取模块的常见问题及解决方案
-
缓冲区溢出:
- 问题:目标缓冲区大小不足,可能导致缓冲区溢出
- 解决方案:添加目标缓冲区长度参数,避免复制超出缓冲区大小的数据
-
字符串查找失败:
- 问题:起始字符串或结束字符串可能不存在于源缓冲区中
- 解决方案:添加查找失败的处理,返回错误码
-
性能瓶颈:
- 问题:字符串查找操作可能会成为性能瓶颈
- 解决方案:使用更高效的字符串查找算法;添加缓存机制
-
内存使用:
- 问题:当处理大缓冲区时,可能会使用大量内存
- 解决方案:优化内存使用;使用流式处理
数据提取模块的应用场景
数据提取模块在以下场景中特别重要:
- 协议解析:在协议解析场景中,需要提取协议头和协议尾之间的数据
- 数据过滤:在数据过滤场景中,需要提取特定标记之间的数据
- 配置解析:在配置解析场景中,需要提取配置项的值
- 日志分析:在日志分析场景中,需要提取日志中的特定信息
数据提取模块的设计总结
数据提取模块是系统处理接收数据的高级部分,它的设计直接影响系统的数据处理能力和可靠性。一个好的数据提取模块应该具备以下特点:
- 边界处理:正确处理字符串查找的边界情况
- 状态恢复:当部分匹配失败时,恢复状态并重新开始查找
- 数据完整性:确保提取的数据完整,不包含起始字符串和结束字符串
- 错误处理:添加查找失败的处理,返回错误码
- 可扩展性:支持从不同类型的缓冲区提取数据
通过合理设计和优化数据提取模块,可以显著提高系统的数据处理能力和可靠性,满足各种应用场景的需求。
缓冲区清空模块
缓冲区清空模块是系统处理接收数据的辅助部分,它负责清空环形缓冲区中的数据,重置头指针和尾指针,为新的数据接收做准备。
void Uart_flush (void)
{
memset(_rx_buffer->buffer,'\0', UART_BUFFER_SIZE);
_rx_buffer->head = 0;
_rx_buffer->tail = 0;
}
缓冲区清空模块的设计思路
缓冲区清空模块的设计遵循以下原则:
- 彻底清空:确保缓冲区中的所有数据都被清空
- 重置状态:重置头指针和尾指针,恢复缓冲区的初始状态
- 简洁明了:代码简洁明了,减少执行时间
缓冲区清空模块的详细分析
缓冲区清空模块的核心是Uart_flush函数,它的主要功能是清空接收环形缓冲区中的数据并重置指针。以下是对该函数的详细分析:
-
清空缓冲区数组:
memset(_rx_buffer->buffer,'\0', UART_BUFFER_SIZE);使用
memset函数将缓冲区数组中的所有元素设置为'\0',确保缓冲区被彻底清空。 -
重置头指针和尾指针:
_rx_buffer->head = 0; _rx_buffer->tail = 0;将头指针和尾指针都重置为0,恢复缓冲区的初始状态。
缓冲区清空模块的性能分析
缓冲区清空模块的性能直接影响系统的响应速度。以下是缓冲区清空模块的性能分析:
| 操作 | 时间(时钟周期) | 说明 |
|---|---|---|
| 清空缓冲区数组 | ~5 per byte | 包括memset函数调用和内存写入 |
| 重置指针 | ~2 | 包括两个赋值操作 |
| 总计 | ~5*UART_BUFFER_SIZE + 2 | 清空整个缓冲区的总时间 |
缓冲区清空模块的优化建议
- 支持指定缓冲区:修改函数,支持清空指定的缓冲区
/* 支持指定缓冲区 */
void Uart_flush_buffer(ring_buffer *buffer)
{
memset(buffer->buffer,'\0', UART_BUFFER_SIZE);
buffer->head = 0;
buffer->tail = 0;
}
- 支持快速清空:添加快速清空函数,只重置指针,不清空数组
/* 支持快速清空 */
void Uart_flush_fast(void)
{
_rx_buffer->head = 0;
_rx_buffer->tail = 0;
}
- 支持部分清空:添加部分清空函数,只清空指定长度的数据
/* 支持部分清空 */
void Uart_flush_partial(uint16_t length)
{
if (length >= UART_BUFFER_SIZE)
{
// 清空整个缓冲区
Uart_flush();
}
else
{
// 只清空指定长度的数据
int count = 0;
while (count < length && _rx_buffer->tail != _rx_buffer->head)
{
_rx_buffer->buffer[_rx_buffer->tail] = '\0';
_rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1) % UART_BUFFER_SIZE;
count++;
}
}
}
- 优化内存操作:使用更快的内存清空方法
/* 优化内存操作 */
void Uart_flush_optimized(void)
{
// 使用更快的内存清空方法
volatile unsigned char *ptr = _rx_buffer->buffer;
for (int i = 0; i < UART_BUFFER_SIZE; i++)
{
*ptr++ = '\0';
}
_rx_buffer->head = 0;
_rx_buffer->tail = 0;
}
缓冲区清空模块的常见问题及解决方案
-
内存写入开销:
- 问题 :使用
memset函数清空缓冲区可能会产生较大的内存写入开销 - 解决方案:对于不需要彻底清空的场景,使用快速清空函数,只重置指针
- 问题 :使用
-
并发访问:
- 问题:在多线程或中断环境中,可能会出现并发访问的问题
- 解决方案:添加互斥保护;在清空操作前后禁用和启用中断
-
性能瓶颈:
- 问题:当缓冲区较大时,清空操作可能会成为性能瓶颈
- 解决方案:使用快速清空函数;优化内存写入操作
缓冲区清空模块的应用场景
缓冲区清空模块在以下场景中特别重要:
- 初始化:在系统初始化时,清空缓冲区,确保系统从干净的状态开始运行
- 错误恢复:在发生错误时,清空缓冲区,避免错误数据影响后续操作
- 模式切换:在系统模式切换时,清空缓冲区,确保新模式下的数据处理不受旧数据的影响
- 资源释放:在不再需要缓冲区中的数据时,清空缓冲区,释放资源
缓冲区清空模块的设计总结
缓冲区清空模块是系统处理接收数据的辅助部分,它的设计直接影响系统的响应速度和可靠性。一个好的缓冲区清空模块应该具备以下特点:
- 彻底清空:确保缓冲区中的所有数据都被清空
- 重置状态:重置头指针和尾指针,恢复缓冲区的初始状态
- 简洁明了:代码简洁明了,减少执行时间
- 可扩展性:支持清空指定的缓冲区;支持快速清空;支持部分清空
通过合理设计和优化缓冲区清空模块,可以显著提高系统的响应速度和可靠性,满足各种应用场景的需求。
数据预览模块
数据预览模块是系统处理接收数据的辅助部分,它负责查看环形缓冲区中下一个要读取的数据,而不移动尾指针,为应用程序提供数据预览功能。
int Uart_peek()
{
if(_rx_buffer->head == _rx_buffer->tail)
{
return -1;
}
else
{
return _rx_buffer->buffer[_rx_buffer->tail];
}
}
数据预览模块的设计思路
数据预览模块的设计遵循以下原则:
- 边界检查:检查缓冲区是否为空,避免读取无效数据
- 无副作用:只查看数据,不移动尾指针,不改变缓冲区的状态
- 简洁明了:代码简洁明了,减少执行时间
数据预览模块的详细分析
数据预览模块的核心是Uart_peek函数,它的主要功能是查看接收环形缓冲区中下一个要读取的数据。以下是对该函数的详细分析:
-
缓冲区空检测:
if(_rx_buffer->head == _rx_buffer->tail) { return -1; }通过检查头指针是否等于尾指针,判断缓冲区是否为空。如果为空,则返回-1,表示没有数据可读。
-
数据预览:
else { return _rx_buffer->buffer[_rx_buffer->tail]; }从缓冲区的当前尾指针位置读取数据并返回,但不移动尾指针,保持缓冲区的状态不变。
数据预览模块的性能分析
数据预览模块的性能直接影响系统的响应速度。以下是数据预览模块的性能分析:
| 操作 | 时间(时钟周期) | 说明 |
|---|---|---|
| 缓冲区空检测 | ~1 | 简单的比较操作 |
| 数据预览 | ~1 | 直接内存读取 |
| 总计 | ~2 | 整个预览过程的时间 |
数据预览模块的优化建议
- 支持指定缓冲区:修改函数,支持预览指定缓冲区的数据
/* 支持指定缓冲区 */
int Uart_peek_from(ring_buffer *buffer)
{
if(buffer->head == buffer->tail)
{
return -1;
}
else
{
return buffer->buffer[buffer->tail];
}
}
- 支持预览多个字符:添加函数,支持预览多个字符
/* 支持预览多个字符 */
int Uart_peek_n(char *buffer, uint16_t length)
{
if(IsDataAvailable() < length)
{
return -1;
}
int tail = _rx_buffer->tail;
for(int i = 0; i < length; i++)
{
buffer[i] = _rx_buffer->buffer[tail];
tail = (tail + 1) % UART_BUFFER_SIZE;
}
return length;
}
- 支持预览指定位置的数据:添加函数,支持预览指定位置的数据
/* 支持预览指定位置的数据 */
int Uart_peek_at(int offset)
{
if(IsDataAvailable() <= offset)
{
return -1;
}
int pos = (_rx_buffer->tail + offset) % UART_BUFFER_SIZE;
return _rx_buffer->buffer[pos];
}
数据预览模块的常见问题及解决方案
-
缓冲区为空:
- 问题:当缓冲区为空时,预览操作会返回-1
- 解决方案 :在预览数据前,使用
IsDataAvailable函数检查缓冲区是否有数据
-
并发访问:
- 问题:在多线程或中断环境中,可能会出现并发访问的问题
- 解决方案:添加互斥保护;在预览操作前后禁用和启用中断
数据预览模块的应用场景
数据预览模块在以下场景中特别重要:
- 字符串查找:在字符串查找过程中,需要预览下一个字符,判断是否匹配
- 协议解析:在协议解析过程中,需要预览数据,判断协议类型和长度
- 命令解析:在命令解析过程中,需要预览命令的第一个字符,判断命令类型
- 数据过滤:在数据过滤过程中,需要预览数据,判断是否需要过滤
数据预览模块的设计总结
数据预览模块是系统处理接收数据的辅助部分,它的设计直接影响系统的响应速度和可靠性。一个好的数据预览模块应该具备以下特点:
- 边界检查:检查缓冲区是否为空,避免读取无效数据
- 无副作用:只查看数据,不移动尾指针,不改变缓冲区的状态
- 简洁明了:代码简洁明了,减少执行时间
- 可扩展性:支持预览指定缓冲区的数据;支持预览多个字符;支持预览指定位置的数据
通过合理设计和优化数据预览模块,可以显著提高系统的响应速度和可靠性,满足各种应用场景的需求。
代码实现细节
中断处理优化
void Uart_isr (UART_HandleTypeDef *huart)
{
uint32_t isrflags = READ_REG(huart->Instance->SR);
uint32_t cr1its = READ_REG(huart->Instance->CR1);
/* 处理接收中断 */
if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
{
huart->Instance->SR; /* 读取状态寄存器 */
unsigned char c = huart->Instance->DR; /* 读取数据寄存器 */
store_char (c, _rx_buffer); // 存储数据到缓冲区
return;
}
/* 处理发送中断 */
if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
{
if(tx_buffer.head == tx_buffer.tail)
{
// 缓冲区为空,禁用中断
__HAL_UART_DISABLE_IT(huart, UART_IT_TXE);
}
else
{
// 发送下一个字节
unsigned char c = tx_buffer.buffer[tx_buffer.tail];
tx_buffer.tail = (tx_buffer.tail + 1) % UART_BUFFER_SIZE;
huart->Instance->SR;
huart->Instance->DR = c;
}
return;
}
}
中断处理优化的关键点:
- 快速处理:中断处理函数尽可能简短,只做必要的操作
- 状态检查:使用位操作快速检查中断状态
- 早期返回:处理完一种中断后立即返回,减少中断嵌套
- 中断禁用:当发送缓冲区为空时,禁用发送中断,避免不必要的中断触发
环形缓冲区操作优化
void store_char(unsigned char c, ring_buffer *buffer)
{
int i = (unsigned int)(buffer->head + 1) % UART_BUFFER_SIZE;
// 如果头指针即将追上尾指针,说明缓冲区已满,不存储数据
if(i != buffer->tail) {
buffer->buffer[buffer->head] = c;
buffer->head = i;
}
}
环形缓冲区操作优化的关键点:
- 取模运算:使用取模运算实现指针的环形移动
- 缓冲区满检测:通过检查头指针是否即将追上尾指针来检测缓冲区是否已满
- 避免数据覆盖:当缓冲区已满时,不存储新数据,避免覆盖原有数据
超时处理机制
#define TIMEOUT_DEF 500 // 500ms超时
uint16_t timeout;
/* 在SysTick中断中递减超时计数器 */
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
if(timeout >0) timeout--;
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
/* USER CODE END SysTick_IRQn 1 */
}
/* 在等待函数中使用超时 */
int Wait_for (char *string)
{
// ...
timeout = TIMEOUT_DEF;
while ((!IsDataAvailable())&&timeout); // 等待数据到达
if (timeout == 0) return 0;
// ...
}
超时处理机制的关键点:
- 全局超时计数器 :使用全局变量
timeout作为超时计数器 - SysTick中断:在SysTick中断中递减超时计数器
- 超时检测:在等待函数中检查超时计数器是否为0
多UART实例支持
目前代码只支持单个UART实例,但可以通过以下方式扩展为支持多个UART实例:
- 为每个UART创建独立的环形缓冲区
- 修改初始化函数,支持指定UART实例
- 修改中断处理函数,支持多个UART实例
代码优化建议
1. 缓冲区大小配置
/* 修改前 */
#define UART_BUFFER_SIZE 64
/* 修改后 */
#define UART_RX_BUFFER_SIZE 128
#define UART_TX_BUFFER_SIZE 64
- 理由:接收缓冲区和发送缓冲区的需求可能不同,分开配置可以更灵活地调整大小
- 好处:根据实际需求分配内存,避免内存浪费
2. 多UART实例支持
/* 修改前 */
UART_HandleTypeDef huart2;
#define uart &huart2
/* 修改后 */
typedef struct
{
UART_HandleTypeDef *huart;
ring_buffer rx_buffer;
ring_buffer tx_buffer;
} uart_handle_t;
uart_handle_t uart_handles[UART_MAX_INSTANCES];
- 理由:支持多个UART实例,提高代码的可重用性
- 好处:可以在一个项目中使用多个UART,每个UART都有独立的环形缓冲区
3. 错误处理增强
/* 修改前 */
void store_char(unsigned char c, ring_buffer *buffer)
{
int i = (unsigned int)(buffer->head + 1) % UART_BUFFER_SIZE;
// 如果头指针即将追上尾指针,说明缓冲区已满,不存储数据
if(i != buffer->tail) {
buffer->buffer[buffer->head] = c;
buffer->head = i;
}
}
/* 修改后 */
enum {
RING_BUFFER_OK = 0,
RING_BUFFER_FULL = -1,
RING_BUFFER_EMPTY = -2
};
int store_char(unsigned char c, ring_buffer *buffer)
{
int i = (unsigned int)(buffer->head + 1) % UART_BUFFER_SIZE;
// 如果头指针即将追上尾指针,说明缓冲区已满,不存储数据
if(i != buffer->tail) {
buffer->buffer[buffer->head] = c;
buffer->head = i;
return RING_BUFFER_OK;
}
return RING_BUFFER_FULL;
}
- 理由:增强错误处理,返回操作结果
- 好处:调用者可以知道操作是否成功,便于调试和错误处理
4. 中断处理优化
/* 修改前 */
void Uart_isr (UART_HandleTypeDef *huart)
{
// 处理所有中断
}
/* 修改后 */
void Uart_isr (UART_HandleTypeDef *huart)
{
// 快速检查中断类型
uint32_t isrflags = READ_REG(huart->Instance->SR);
// 只处理需要的中断
if (isrflags & (USART_SR_RXNE | USART_SR_TXE | USART_SR_ORE | USART_SR_NE | USART_SR_FE | USART_SR_PE))
{
// 处理具体中断
}
}
- 理由:快速检查中断类型,只处理需要的中断
- 好处:减少中断处理时间,提高系统响应速度
5. 字符串处理优化
/* 修改前 */
int Wait_for (char *string)
{
int so_far =0;
int len = strlen (string);
again:
timeout = TIMEOUT_DEF;
while ((!IsDataAvailable())&&timeout); // 等待数据到达
if (timeout == 0) return 0;
while (Uart_peek() != string[so_far]) // 查找字符串的第一个字符
{
if (_rx_buffer->tail != _rx_buffer->head)
{
_rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1) % UART_BUFFER_SIZE; // 跳过不匹配的字符
}
else
{
return 0;
}
}
// ...
}
/* 修改后 */
int Wait_for (char *string)
{
int so_far =0;
int len = strlen (string);
int start_tail = _rx_buffer->tail;
again:
timeout = TIMEOUT_DEF;
while ((!IsDataAvailable())&&timeout); // 等待数据到达
if (timeout == 0) return 0;
// 使用KMP算法优化字符串查找
while (_rx_buffer->tail != _rx_buffer->head)
{
if (Uart_peek() == string[so_far])
{
so_far++;
_rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1) % UART_BUFFER_SIZE;
if (so_far == len)
return 1;
}
else if (so_far > 0)
{
// KMP算法回退
so_far = get_next(string, so_far);
}
else
{
_rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1) % UART_BUFFER_SIZE;
}
}
// 恢复尾指针
_rx_buffer->tail = start_tail;
return 0;
}
- 理由:使用KMP算法优化字符串查找,减少回溯
- 好处:提高字符串查找的效率,特别是在查找长字符串时
6. 内存屏障
/* 修改前 */
typedef struct
{
unsigned char buffer[UART_BUFFER_SIZE];
volatile unsigned int head;
volatile unsigned int tail;
}
/* 修改后 */
typedef struct
{
unsigned char buffer[UART_BUFFER_SIZE];
volatile unsigned int head;
volatile unsigned int tail;
}
#define memory_barrier() __asm__ __volatile__("dmb\n": : :"memory")
void store_char(unsigned char c, ring_buffer *buffer)
{
int i = (unsigned int)(buffer->head + 1) % UART_BUFFER_SIZE;
if(i != buffer->tail) {
buffer->buffer[buffer->head] = c;
memory_barrier(); // 内存屏障
buffer->head = i;
memory_barrier(); // 内存屏障
}
}
- 理由:在多线程或中断环境中,添加内存屏障确保内存操作的顺序
- 好处:避免编译器优化导致的内存操作重排序,确保数据的一致性
应用场景
1. 串口调试
int main(void)
{
// 初始化
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART2_UART_Init();
// 初始化环形缓冲区
Ringbuf_init();
// 发送欢迎信息
Uart_sendstring("Welcome to STM32 UART Ring Buffer Demo!\r\n");
Uart_sendstring("Please enter a command:\r\n");
while (1)
{
// 检查是否有数据可用
if (IsDataAvailable())
{
// 处理接收到的数据
char buffer[64];
if (Copy_upto("\r\n", buffer))
{
// 发送回显
Uart_sendstring("You entered: ");
Uart_sendstring(buffer);
Uart_sendstring("\r\n");
}
}
}
}
2. 传感器数据采集
void sensor_data_processing(void)
{
// 等待传感器数据
if (Wait_for("DATA:"))
{
// 提取传感器数据
char buffer[64];
if (Copy_upto("\r\n", buffer))
{
// 解析传感器数据
int value = atoi(buffer);
// 处理数据
if (value > 100)
{
Uart_sendstring("Warning: Sensor value too high!\r\n");
}
else
{
Uart_sendstring("Sensor value: ");
Uart_printbase(value, 10);
Uart_sendstring("\r\n");
}
}
}
}
3. 命令解析
void command_parser(void)
{
// 等待命令
if (IsDataAvailable())
{
char buffer[64];
if (Copy_upto("\r\n", buffer))
{
// 解析命令
if (Look_for("LED_ON", buffer))
{
// 打开LED
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
Uart_sendstring("LED turned on!\r\n");
}
else if (Look_for("LED_OFF", buffer))
{
// 关闭LED
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
Uart_sendstring("LED turned off!\r\n");
}
else if (Look_for("STATUS", buffer))
{
// 发送状态
Uart_sendstring("System status: OK\r\n");
}
else
{
// 未知命令
Uart_sendstring("Unknown command!\r\n");
}
}
}
}
性能分析
中断处理时间
| 操作 | 时间(时钟周期) |
|---|---|
| 接收中断处理 | ~20 |
| 发送中断处理 | ~15 |
数据吞吐量
| 波特率 | 最大吞吐量 |
|---|---|
| 9600 | 960 bytes/s |
| 115200 | 11520 bytes/s |
| 921600 | 92160 bytes/s |
内存使用
| 组件 | 内存使用(字节) |
|---|---|
| 接收缓冲区 | 64 |
| 发送缓冲区 | 64 |
| 代码 | ~1000 |
| 总计 | ~1128 |
总结与展望
项目特点
- 高效的环形缓冲区实现:使用环形缓冲区技术,减少数据复制开销
- 中断驱动设计:在中断中快速存储数据,主程序中异步处理
- 丰富的字符串处理函数:提供了多种字符串处理函数,方便应用开发
- 灵活的超时处理机制:支持超时检测,避免无限等待
- 易于集成:代码简洁明了,易于集成到各种STM32项目中
应用价值
本项目的实现对于STM32串口通信的开发具有重要的参考价值:
- 提高串口通信的可靠性:通过环形缓冲区,避免数据丢失
- 减少中断处理开销:中断处理函数简短,减少系统开销
- 简化应用开发:提供了丰富的字符串处理函数,简化应用开发
- 提高系统响应速度:使用中断驱动设计,提高系统响应速度
未来展望
- 多UART实例支持:扩展为支持多个UART实例
- DMA支持:结合DMA,进一步提高数据传输效率
- 动态缓冲区大小:根据实际需求动态调整缓冲区大小
- 更丰富的字符串处理函数:提供更多字符串处理函数,如JSON解析、CSV解析等
- 跨平台支持:适配其他微控制器平台
附录
快速上手指南
-
添加文件 :将
UartRingbuffer.h和UartRingbuffer.c添加到项目中 -
修改中断处理:
- 在
stm32f4xx_it.c中添加外部声明
extern void Uart_isr (UART_HandleTypeDef *huart); extern uint16_t timeout;- 修改SysTick中断处理
void SysTick_Handler(void) { /* USER CODE BEGIN SysTick_IRQn 0 */ if(timeout >0) timeout--; /* USER CODE END SysTick_IRQn 0 */ HAL_IncTick(); /* USER CODE BEGIN SysTick_IRQn 1 */ /* USER CODE END SysTick_IRQn 1 */ }- 修改UART中断处理
void USART2_IRQHandler(void) { /* USER CODE BEGIN USART2_IRQn 0 */ Uart_isr (&huart2); /* USER CODE END USART2_IRQn 0 */ // HAL_UART_IRQHandler(&huart2); /* USER CODE BEGIN USART2_IRQn 1 */ /* USER CODE END USART2_IRQn 1 */ } - 在
-
初始化 :在
main.c中初始化环形缓冲区int main(void) { // 初始化代码 // 初始化环形缓冲区 Ringbuf_init(); // 主循环 while (1) { // 应用代码 } } -
使用示例:
// 发送字符串 Uart_sendstring("Hello, World!\r\n"); // 发送数字 Uart_printbase(12345, 10); Uart_sendstring("\r\n"); // 等待特定字符串 if (Wait_for("OK")) { Uart_sendstring("Received OK!\r\n"); } // 复制数据到缓冲区 char buffer[64]; if (Copy_upto("\r\n", buffer)) { Uart_sendstring("Received: "); Uart_sendstring(buffer); }
常见问题与解决方案
-
缓冲区溢出
- 问题:当数据接收速度超过处理速度时,缓冲区可能溢出
- 解决方案:增大缓冲区大小,或提高数据处理速度
-
字符串查找失败
- 问题:字符串查找函数可能找不到目标字符串
- 解决方案:检查目标字符串是否正确,或增加超时时间
-
多UART冲突
- 问题:当使用多个UART时,可能会出现冲突
- 解决方案:修改代码支持多UART实例
-
中断优先级
- 问题:UART中断优先级设置不当,可能会影响系统性能
- 解决方案:合理设置UART中断优先级,确保关键中断能够及时响应
-
内存使用
- 问题:缓冲区大小设置不当,可能会导致内存浪费或不足
- 解决方案:根据实际需求调整缓冲区大小
代码优化建议
- 根据实际需求调整缓冲区大小
- 添加多UART实例支持
- 增强错误处理
- 优化字符串处理算法
- 添加内存屏障,确保数据一致性
- 结合DMA,进一步提高数据传输效率
参考资料
- STM32F4xx参考手册
- STM32 HAL库文档
- 环形缓冲区原理与实现
- 串口通信协议详解
结论
STM32 UART环形缓冲区是一种高效、可靠的串口通信解决方案,它通过环形缓冲区技术,减少了数据复制开销,提高了串口通信的可靠性和效率。本项目的实现不仅功能完整,而且代码简洁明了,易于集成到各种STM32项目中。
通过本项目的学习,我们可以了解到:
- 环形缓冲区的工作原理和实现方法
- 中断驱动的串口通信设计
- 字符串处理函数的实现
- 超时处理机制的设计
- 代码优化的技巧
这些知识对于嵌入式系统开发,特别是串口通信相关的开发,具有重要的参考价值。希望本项目能够为STM32开发者提供帮助,促进嵌入式系统开发的进步。