马上注册,结交更多好友,享用更多功能,让你轻松玩转社区
您需要 登录 才可以下载或查看,没有账号?立即注册
×
嵌入式开发中,我们经常要处理外部设备的数据流,比如从高精度ADC通过SPI总线采集音频数据。假设我们有个96kHz采样的ADC,数据源源不断涌入,处理器需要高效接收并处理这些数据,同时避免数据丢失或处理冲突。这时乒乓缓冲区是非常简单高效的解决方案,像打乒乓球一样在两个缓冲区间来回切换,今天我们就来详细聊聊这个常用的技术。 乒乓缓冲区的核心思路 乒乓缓冲区的核心思想并不复杂:通过两个缓冲区轮流工作,一个负责接收数据,另一个交给CPU处理,互不干扰。就像在工地上两台挖掘机轮流挖土,一台挖完休息,另一台接着上,节奏紧凑又不乱套。
假设我们用一块STM32外接了个高精度ADC,通过SPI总线采集96kHz的音频数据。ADC每次采样完成后会触发一个DataReady中断,通知处理器可以读取数据了。 如果用传统的单缓冲区,处理器得频繁响应中断,逐个读取样本,效率低还容易出乱子。这时候,DMA就派上用场了,配合乒乓缓冲区,可以让整个流程丝滑无比。
基本流程是这样的: - ADC触发DataReady中断。
- DMA自动通过SPI发送读取命令和足够的0xFF空字节,抓取一个样本。
- 样本存入当前缓冲区(比如A),重复上述步骤,直到A缓冲区装满N个样本。
- 缓冲区满后触发DMAComplete中断,通知CPU可以处理A的数据。
- 与此同时,DMA切换到B缓冲区,继续接收下一波样本。
这套流程的好处是,数据采集几乎全由硬件搞定,CPU主循环几乎不用操心,省下来的算力可以专注处理算法,比如做信号处理、压缩或者存到SD卡。听起来是不是有点像工厂流水线?采集和处理各司其职,互不干扰。 为什么需要乒乓缓冲区? 如果只有一个缓冲区,问题就来了。假设你正在对缓冲区的数据做算法处理(比如FFT变换),这时候ADC又来了新样本,直接写入缓冲区,数据就可能被覆盖,算法结果直接GG。一种解决办法是每次都把缓冲区数据复制一份再处理,但这太浪费资源了,在资源紧张的MCU上,动辄几KB的复制操作可不是闹着玩的。
乒乓缓冲区的妙处就在于,它用两个缓冲区轮流接力。一个缓冲区满后,DMA自动切换到另一个缓冲区继续接收数据,同时CPU处理已经填满的那个缓冲区。这样采集和处理完全并行,互不干扰,效率拉满。 乒乓缓冲区的实现细节 实现乒乓缓冲区并不复杂,核心在于DMA的配置和中断处理逻辑。以下是具体步骤,用课程配套的STM32F411开发环境来举例: 1. 初始化两个缓冲区 分配两个大小为N的缓冲区,N是你算法需要的样本数。比如要做512点的FFT,那就分配两个512样本的缓冲区,分别命名为ping和pong。可以用数组定义,比如: uint16_t ping_buffer[512];
uint16_t pong_buffer[512];
2. 配置DMA和SPI 初始化SPI的DMA通道,设置第一个缓冲区(比如A)为目标地址。每次DataReady中断触发,DMA自动从SPI读取一个样本存到A缓冲区。STM32的HAL库里可以用HAL_SPI_TransmitReceive_DMA来配置,指定A缓冲区的地址和样本长度。 3. DMA完成中断处理 当A缓冲区装满N个样本后,DMA会触发DMAComplete中断。在中断处理函数中,切换缓冲区并通知CPU: void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) {
if (current_buffer == ping_buffer) {
// 切换到B缓冲区
HAL_SPI_TransmitReceive_DMA(&hspi, pong_buffer, 512);
// 通知CPU处理A缓冲区
signal_algorithm_to_process(ping_buffer);
current_buffer = pong_buffer;
} else {
// 切换到A缓冲区
HAL_SPI_TransmitReceive_DMA(&hspi, ping_buffer, 512);
// 通知CPU处理B缓冲区
signal_algorithm_to_process(pong_buffer);
current_buffer = ping_buffer;
}
}
这里用了个current_buffer变量追踪当前使用的缓冲区,切换时直接改目标地址,CPU则处理另一个缓冲区的数据。 4. 算法处理 算法部分在主循环或单独的任务中运行,接收到信号后处理对应的缓冲区数据。因为采集和处理是分开的,算法有足够时间跑完,不用担心被新数据打断。 时间线分析 为了更直观,我们来画个时间线看看乒乓缓冲区怎么工作。假设N=8(为了简化,实际可能用更大的N,比如512):
- 0~T1:ADC触发8次DataReady中断,DMA把样本存到Ping缓冲区。
- T1:Ping缓冲区满,触发DMAComplete中断,DMA切换到Pong缓冲区,算法开始处理Ping的数据。
- T1~T2:Pong缓冲区接收新样本,同时算法处理Ping的数据。
- T2:Pong缓冲区满,DMA切换回Ping,算法开始处理Pong的数据。
这就像乒乓球比赛,Ping和Pong轮流上场。相比单一缓冲区,乒乓缓冲区避免了数据冲突,算法处理时间也更灵活。 乒乓缓冲区和环形缓冲区的关系 有经验的工程师可能会说,这不就是个两元素的环形缓冲区吗?完全正确。乒乓缓冲区本质上是个简化的环形缓冲区,固定用两个缓冲区切换。如果你的算法处理时间比数据采集时间长,可能需要更大的环形缓冲区(比如4个或更多缓冲区),避免数据覆盖。 实际应用场景 乒乓缓冲区在嵌入式开发中用途广泛,不只限于音频处理。比如: - 显示驱动:很多显示屏驱动用双缓冲(Double Buffering),一个缓冲区准备显示数据,另一个显示到屏幕,切换后继续准备下一帧。
- 视频采集:摄像头数据流处理也常用乒乓缓冲区,一个缓冲区存新帧,另一个处理或编码。
- 通信协议:像UART或I2C数据流,也可以用乒乓缓冲区实现高效接收和处理。
|