【串口技术系列文档 09】FIFO管理与流控

本文目标:掌握FIFO缓冲区设计、环形缓冲区实现和硬件/软件流控机制


### 目录

  • [@TOC(目录)](#目录 @TOC 硬件FIFO配置 FIFO深度与触发级别 软件环形缓冲区 完整实现 线程安全设计 硬件流控(RTS/CTS) 工作原理 配置代码 软件流控(XON/XOFF) 协议定义 缓冲区大小计算 公式推导 场景化配置 流控失效问题排查 常见原因 实际案例:大文件传输)
  • [硬件FIFO配置](#目录 @TOC 硬件FIFO配置 FIFO深度与触发级别 软件环形缓冲区 完整实现 线程安全设计 硬件流控(RTS/CTS) 工作原理 配置代码 软件流控(XON/XOFF) 协议定义 缓冲区大小计算 公式推导 场景化配置 流控失效问题排查 常见原因 实际案例:大文件传输)
  • [FIFO深度与触发级别](#目录 @TOC 硬件FIFO配置 FIFO深度与触发级别 软件环形缓冲区 完整实现 线程安全设计 硬件流控(RTS/CTS) 工作原理 配置代码 软件流控(XON/XOFF) 协议定义 缓冲区大小计算 公式推导 场景化配置 流控失效问题排查 常见原因 实际案例:大文件传输)
  • [软件环形缓冲区](#目录 @TOC 硬件FIFO配置 FIFO深度与触发级别 软件环形缓冲区 完整实现 线程安全设计 硬件流控(RTS/CTS) 工作原理 配置代码 软件流控(XON/XOFF) 协议定义 缓冲区大小计算 公式推导 场景化配置 流控失效问题排查 常见原因 实际案例:大文件传输)
  • [完整实现](#目录 @TOC 硬件FIFO配置 FIFO深度与触发级别 软件环形缓冲区 完整实现 线程安全设计 硬件流控(RTS/CTS) 工作原理 配置代码 软件流控(XON/XOFF) 协议定义 缓冲区大小计算 公式推导 场景化配置 流控失效问题排查 常见原因 实际案例:大文件传输)
  • [线程安全设计](#目录 @TOC 硬件FIFO配置 FIFO深度与触发级别 软件环形缓冲区 完整实现 线程安全设计 硬件流控(RTS/CTS) 工作原理 配置代码 软件流控(XON/XOFF) 协议定义 缓冲区大小计算 公式推导 场景化配置 流控失效问题排查 常见原因 实际案例:大文件传输)
  • [硬件流控(RTS/CTS)](#目录 @TOC 硬件FIFO配置 FIFO深度与触发级别 软件环形缓冲区 完整实现 线程安全设计 硬件流控(RTS/CTS) 工作原理 配置代码 软件流控(XON/XOFF) 协议定义 缓冲区大小计算 公式推导 场景化配置 流控失效问题排查 常见原因 实际案例:大文件传输)
  • [工作原理](#目录 @TOC 硬件FIFO配置 FIFO深度与触发级别 软件环形缓冲区 完整实现 线程安全设计 硬件流控(RTS/CTS) 工作原理 配置代码 软件流控(XON/XOFF) 协议定义 缓冲区大小计算 公式推导 场景化配置 流控失效问题排查 常见原因 实际案例:大文件传输)
  • [配置代码](#目录 @TOC 硬件FIFO配置 FIFO深度与触发级别 软件环形缓冲区 完整实现 线程安全设计 硬件流控(RTS/CTS) 工作原理 配置代码 软件流控(XON/XOFF) 协议定义 缓冲区大小计算 公式推导 场景化配置 流控失效问题排查 常见原因 实际案例:大文件传输)
  • [软件流控(XON/XOFF)](#目录 @TOC 硬件FIFO配置 FIFO深度与触发级别 软件环形缓冲区 完整实现 线程安全设计 硬件流控(RTS/CTS) 工作原理 配置代码 软件流控(XON/XOFF) 协议定义 缓冲区大小计算 公式推导 场景化配置 流控失效问题排查 常见原因 实际案例:大文件传输)
  • [协议定义](#目录 @TOC 硬件FIFO配置 FIFO深度与触发级别 软件环形缓冲区 完整实现 线程安全设计 硬件流控(RTS/CTS) 工作原理 配置代码 软件流控(XON/XOFF) 协议定义 缓冲区大小计算 公式推导 场景化配置 流控失效问题排查 常见原因 实际案例:大文件传输)
  • [缓冲区大小计算](#目录 @TOC 硬件FIFO配置 FIFO深度与触发级别 软件环形缓冲区 完整实现 线程安全设计 硬件流控(RTS/CTS) 工作原理 配置代码 软件流控(XON/XOFF) 协议定义 缓冲区大小计算 公式推导 场景化配置 流控失效问题排查 常见原因 实际案例:大文件传输)
  • [公式推导](#目录 @TOC 硬件FIFO配置 FIFO深度与触发级别 软件环形缓冲区 完整实现 线程安全设计 硬件流控(RTS/CTS) 工作原理 配置代码 软件流控(XON/XOFF) 协议定义 缓冲区大小计算 公式推导 场景化配置 流控失效问题排查 常见原因 实际案例:大文件传输)
  • [场景化配置](#目录 @TOC 硬件FIFO配置 FIFO深度与触发级别 软件环形缓冲区 完整实现 线程安全设计 硬件流控(RTS/CTS) 工作原理 配置代码 软件流控(XON/XOFF) 协议定义 缓冲区大小计算 公式推导 场景化配置 流控失效问题排查 常见原因 实际案例:大文件传输)
  • [流控失效问题排查](#目录 @TOC 硬件FIFO配置 FIFO深度与触发级别 软件环形缓冲区 完整实现 线程安全设计 硬件流控(RTS/CTS) 工作原理 配置代码 软件流控(XON/XOFF) 协议定义 缓冲区大小计算 公式推导 场景化配置 流控失效问题排查 常见原因 实际案例:大文件传输)
  • [常见原因](#目录 @TOC 硬件FIFO配置 FIFO深度与触发级别 软件环形缓冲区 完整实现 线程安全设计 硬件流控(RTS/CTS) 工作原理 配置代码 软件流控(XON/XOFF) 协议定义 缓冲区大小计算 公式推导 场景化配置 流控失效问题排查 常见原因 实际案例:大文件传输)
  • [实际案例:大文件传输](#目录 @TOC 硬件FIFO配置 FIFO深度与触发级别 软件环形缓冲区 完整实现 线程安全设计 硬件流控(RTS/CTS) 工作原理 配置代码 软件流控(XON/XOFF) 协议定义 缓冲区大小计算 公式推导 场景化配置 流控失效问题排查 常见原因 实际案例:大文件传输)

硬件FIFO配置

FIFO深度与触发级别

典型UART控制器的FIFO深度:

  • 基础型:16字节(16550兼容)
  • 增强型:64/128字节(STM32、NXP)
  • 高性能:256/512字节(部分工业级芯片)
c 复制代码
// STM32 UART FIFO配置
void uart_fifo_config(void) {
    // 使能FIFO
    UART->CR1 |= USART_CR1_FIFOEN;
    
    // 配置接收FIFO触发级别
    UART->CR3 &= ~USART_CR3_RXFTCFG;
    UART->CR3 |= USART_CR3_RXFTCFG_1_8;  // 1/8满触发(2字节)
    // 选项:1/8, 1/4, 1/2, 3/4, 7/8, Full
    
    // 配置发送FIFO触发级别
    UART->CR3 &= ~USART_CR3_TXFTCFG;
    UART->CR3 |= USART_CR3_TXFTCFG_1_2;  // 1/2空触发
    
    // 使能FIFO阈值中断
    UART->CR3 |= USART_CR3_RXFTIE;
}

触发级别选择原则:

  • 接收:低阈值(1/8-1/4)→ 低延迟,防止溢出
  • 发送:中等阈值(1/2)→ 平衡中断频率和响应速度
  • 高速传输:高阈值(3/4-7/8)→ 减少中断次数

软件环形缓冲区

完整实现

c 复制代码
#define RING_BUF_SIZE 256  // 必须是2的幂(优化取模运算)

typedef struct {
    uint8_t buffer[RING_BUF_SIZE];
    volatile uint16_t head;  // 写指针
    volatile uint16_t tail;  // 读指针
} ring_buffer_t;

// 初始化
void ring_buf_init(ring_buffer_t *rb) {
    rb->head = 0;
    rb->tail = 0;
}

// 写入单字节(中断安全)
bool ring_buf_put(ring_buffer_t *rb, uint8_t data) {
    uint16_t next = (rb->head + 1) & (RING_BUF_SIZE - 1);
    
    if (next == rb->tail) {
        return false;  // 缓冲区满
    }
    
    rb->buffer[rb->head] = data;
    
    // 内存屏障:确保数据写入在指针更新之前完成
    __DMB();  // ARM Cortex-M
    
    rb->head = next;
    return true;
}

// 读取单字节
bool ring_buf_get(ring_buffer_t *rb, uint8_t *data) {
    if (rb->head == rb->tail) {
        return false;  // 缓冲区空
    }
    
    *data = rb->buffer[rb->tail];
    
    __DMB();
    
    rb->tail = (rb->tail + 1) & (RING_BUF_SIZE - 1);
    return true;
}

// 批量写入
uint16_t ring_buf_write(ring_buffer_t *rb, const uint8_t *data, uint16_t len) {
    uint16_t written = 0;
    
    for (uint16_t i = 0; i < len; i++) {
        if (!ring_buf_put(rb, data[i])) {
            break;
        }
        written++;
    }
    
    return written;
}

// 获取可用空间
uint16_t ring_buf_free(ring_buffer_t *rb) {
    return (rb->tail - rb->head - 1) & (RING_BUF_SIZE - 1);
}

// 获取已存储数据量
uint16_t ring_buf_available(ring_buffer_t *rb) {
    return (rb->head - rb->tail) & (RING_BUF_SIZE - 1);
}

// 清空缓冲区
void ring_buf_flush(ring_buffer_t *rb) {
    rb->tail = rb->head;
}

线程安全设计

关键点:

  1. 单生产者-单消费者:无需加锁(中断写+线程读)
  2. volatile修饰符:防止编译器优化
  3. 内存屏障:确保多核/乱序执行正确性
  4. 原子操作:head/tail更新必须原子
c 复制代码
// 多生产者环境(需要互斥锁)
typedef struct {
    uint8_t buffer[RING_BUF_SIZE];
    volatile uint16_t head;
    volatile uint16_t tail;
    mutex_t lock;  // 多生产者保护
} mp_ring_buffer_t;

bool mp_ring_buf_put(mp_ring_buffer_t *rb, uint8_t data) {
    mutex_lock(&rb->lock);
    
    uint16_t next = (rb->head + 1) & (RING_BUF_SIZE - 1);
    if (next == rb->tail) {
        mutex_unlock(&rb->lock);
        return false;
    }
    
    rb->buffer[rb->head] = data;
    rb->head = next;
    
    mutex_unlock(&rb->lock);
    return true;
}

硬件流控(RTS/CTS)

工作原理

复制代码
设备A                    设备B
TX  ------------------>  RX
RX  <------------------  TX
RTS ------------------>  CTS  (A请求发送,询问B是否准备好)
CTS <------------------  RTS  (B允许A发送)

流程:

  1. 设备A置RTS为低电平(请求发送)
  2. 设备B接收缓冲区有空间,置CTS为低电平(允许发送)
  3. 设备A开始发送数据
  4. 设备B缓冲区将满,置CTS为高电平(停止发送)
  5. 设备A停止发送,等待CTS恢复低电平

配置代码

c 复制代码
void uart_rts_cts_init(void) {
    // 使能RTS/CTS硬件流控
    UART->CR3 |= USART_CR3_RTSE | USART_CR3_CTSE;
    
    // 配置RTS阈值(接收缓冲区剩余空间)
    #define RTS_THRESHOLD (RING_BUF_SIZE / 4)
    
    // GPIO配置
    GPIO_InitTypeDef gpio = {
        .Pin = RTS_PIN | CTS_PIN,
        .Mode = GPIO_MODE_AF_PP,
        .Pull = GPIO_NOPULL,
        .Speed = GPIO_SPEED_FREQ_HIGH,
        .Alternate = GPIO_AF7_USART1
    };
    HAL_GPIO_Init(GPIOA, &gpio);
}

// 软件控制RTS(手动管理)
void uart_check_rts(ring_buffer_t *rb) {
    if (ring_buf_free(rb) < RTS_THRESHOLD) {
        GPIO_SetBits(RTS_PORT, RTS_PIN);  // 停止对方发送
    } else {
        GPIO_ResetBits(RTS_PORT, RTS_PIN);  // 允许对方发送
    }
}

软件流控(XON/XOFF)

协议定义

c 复制代码
#define XON  0x11   // Ctrl+Q,恢复发送
#define XOFF 0x13   // Ctrl+S,暂停发送

static volatile bool tx_paused = false;

// 接收到流控字符
void uart_process_flow_control(uint8_t ch) {
    if (ch == XOFF) {
        tx_paused = true;
    } else if (ch == XON) {
        tx_paused = false;
        uart_resume_tx();  // 恢复发送
    }
}

// 发送前检查
void uart_tx_with_flow_control(uint8_t data) {
    while (tx_paused) {
        // 等待XON或超时
    }
    uart_send(data);
}

// 接收缓冲区管理
void uart_rx_check_buffer(ring_buffer_t *rb) {
    uint16_t free = ring_buf_free(rb);
    
    if (free < 16 && !xoff_sent) {
        uart_send(XOFF);
        xoff_sent = true;
    } else if (free > 64 && xoff_sent) {
        uart_send(XON);
        xoff_sent = false;
    }
}

注意事项:

  • XON/XOFF占用数据通道,不适合二进制传输
  • 需要协议层识别流控字符
  • 存在延迟(发送XOFF后对方可能还发几个字节)

缓冲区大小计算

公式推导

复制代码
最小缓冲区 = 数据速率 × 最大处理延迟

例:波特率115200,最大延迟50ms
数据速率 = 115200 / 10 = 11520 字节/秒
最小缓冲区 = 11520 × 0.05 = 576 字节

建议:实际值 × 2(安全裕量)→ 1024字节

场景化配置

c 复制代码
// 低速命令接收(9600 bps)
#define CMD_RX_BUF_SIZE 64

// 高速数据传输(921600 bps)
#define DATA_RX_BUF_SIZE 2048

// GPS NMEA语句(4800 bps,最长82字节)
#define GPS_RX_BUF_SIZE 128

// 日志输出(异步缓冲)
#define LOG_TX_BUF_SIZE 4096

流控失效问题排查

常见原因

  1. CTS信号未连接
c 复制代码
// 检测CTS状态
if (UART->ISR & USART_ISR_CTS) {
    // CTS有效,可以发送
}
  1. 流控阈值设置不当
c 复制代码
// 错误:阈值过高
#define RTS_THRESHOLD (RING_BUF_SIZE - 4)  // 仅剩4字节才停止

// 正确:预留足够缓冲
#define RTS_THRESHOLD (RING_BUF_SIZE / 4)
  1. 中断延迟导致溢出
c 复制代码
// 使能DMA减少中断延迟
UART->CR3 |= USART_CR3_DMAR;

实际案例:大文件传输

需求:通过串口传输1MB固件,115200bps,确保无丢失

方案设计:

c 复制代码
#define FILE_RX_BUF_SIZE 4096  // 4KB缓冲区
#define FLOW_LOW_WATER  1024   // 剩余1KB发送XOFF
#define FLOW_HIGH_WATER 3072   // 恢复到3KB发送XON

typedef struct {
    ring_buffer_t rb;
    uint32_t total_bytes;
    uint32_t lost_bytes;
    bool flow_stopped;
} file_transfer_t;

void file_transfer_task(file_transfer_t *ft) {
    uint8_t data[512];
    
    while (1) {
        // 检查缓冲区并发送流控
        uint16_t available = ring_buf_available(&ft->rb);
        
        if (available > FLOW_LOW_WATER && !ft->flow_stopped) {
            uart_send(XOFF);
            ft->flow_stopped = true;
        } else if (available < FLOW_HIGH_WATER && ft->flow_stopped) {
            uart_send(XON);
            ft->flow_stopped = false;
        }
        
        // 读取并写入Flash
        uint16_t len = ring_buf_read(&ft->rb, data, sizeof(data));
        if (len > 0) {
            flash_write(ft->total_bytes, data, len);
            ft->total_bytes += len;
        }
        
        osDelay(10);
    }
}

实测结果:

  • 传输速度:11.5 KB/s
  • 传输时间:约90秒
  • 丢包率:0%
  • CPU占用:<5%

延伸阅读:\[08-中断驱动vs轮询vs DMA]、\[10-低功耗与唤醒机制]

上一篇 :\[08-中断驱动vs轮询vs DMA]

下一篇:\[10-低功耗与唤醒机制]


✨ 感谢你的阅读 ✨

技术之路漫漫,分享是为了更好地交流。

如果本文的内容对你有启发,希望能得到你的支持:

👍 点赞 | ⭐ 收藏 | 👀 关注

如果你在调试过程中遇到了其他问题,

欢迎在 评论区 💬 留言,我们一起探讨。

关注我,一起交流底层开发的那些事儿!🚀


相关推荐
硅基手札1 小时前
【串口技术系列文档 08】中断驱动vs轮询vs DMA
驱动开发·stm32·单片机·嵌入式硬件·mcu·计算机外设
尼喃1 小时前
OVP芯片完整方案:前级瞬态钳位、持续过压隔离与后端保护
嵌入式硬件
不会代码的小猴1 小时前
认识HAL库
单片机·嵌入式硬件
莫小墨1 小时前
数字地、模拟地、功率地
嵌入式硬件
Zw-awa1 小时前
把 AO 接回来:黑线和白底之间到底差了多少
stm32
BSD_HY1 小时前
薄膜开关矩阵按键的GPIO扩展芯片选型与中断响应延迟
单片机·线性代数·矩阵
xunyan62342 小时前
51 单片机LED闪烁(SDCC + Notepad++ + STC-ISP/普中ISP)
单片机·notepad++·接口隔离原则
桀人2 小时前
USART帧格式
网络·stm32·单片机·嵌入式硬件
qq_448011162 小时前
如何烧录TAU804的固件
单片机·嵌入式硬件