从Python发送WAV数据时,STM32F401RE的 UART音频处理会丢失采样
我正在使用STM32F401RE Nucleo开发板进行简单的音频降噪。
我有 .wav 音频文件,它们是:
- 单声道
- 16位 PCM
- 16 kHz
- 大约7–8秒长
Python读取音频样本并逐个通过UART发送到STM32。
STM32应用一个简单的移动平均滤波器,并把处理后的样本发送回Python。
Python然后把滤波后的样本重新拼接成一个新的 .wav 文件。
然而重建的音频文件不正确:
- 时长只有大约2–4秒
- 输出的音频大多是噪声/故障,而不是语音
Python的示例输出:
Total samples in file: 130672
Filtered samples received: 72021
因此看起来在UART传输或处理过程中丢失了大量样本。
我想了解如何在不丢失数据的前提下,正确地通过UART将音频样本流式传输给STM32。
STM32 Code (filter + UART processing)
uint8_t rx_char;
uint8_t buffer[50];
uint8_t index = 0;
int16_t x0 = 0;
int16_t x1 = 0;
int16_t x2 = 0;
int16_t x3 = 0;
HAL_UART_Receive(&huart2, &rx_char, 1, HAL_MAX_DELAY);
if(rx_char == '\r' || rx_char == '\n')
{
buffer[index] = '\0';
int16_t value = (int16_t)atoi((char*)buffer);
x3 = x2;
x2 = x1;
x1 = x0;
x0 = value;
int32_t sum = (2*(int32_t)x0 + (int32_t)x1 + (int32_t)x2);
int16_t result = sum / 4;
char msg[50];
sprintf(msg,"\r\nResult: %d\r\n\r\n",result);
HAL_UART_Transmit(&huart2,(uint8_t*)msg,strlen(msg),HAL_MAX_DELAY);
memset(buffer,0,sizeof(buffer));
index = 0;
}
else
{
if(index < 49)
buffer[index++] = rx_char;
}
Python Code
import wave
import numpy as np
import serial
import time
ser = serial.Serial('COM9',115200,timeout=1)
filtered_samples = []
file_path = r"recordings/raw_enrollment_16k/user_1/sample_1.wav"
with wave.open(file_path,'rb') as w:
frames = w.readframes(w.getnframes())
samples = np.frombuffer(frames,dtype=np.int16)
print("Total samples in file:", len(samples))
for s in samples:
ser.write((str(int(s)) + "\n").encode())
time.sleep(0.0005)
while ser.in_waiting:
response = ser.readline().decode(errors='ignore').strip()
if response:
try:
value = int(response.split()[-1])
filtered_samples.append(value)
except:
pass
print("Filtered samples received:", len(filtered_samples))
解决方案
你在这里遇到了多重问题。
- UART带宽太低
你的音频流是:
16 kHz * 16 bit = 256 kbit/s raw data
但你并非发送原始样本。你发送的是类似ASCII数字的内容
-12345\n
这通常大约每个样本6–7字节。UART也会发送起始位和停止位,因此实际带宽大约是
16000 samples/s * 7 bytes * 10 bits ≈ 1.12 Mbit/s
如果你的UART运行在例如115200波特率,这要慢一个数量级。因此样本必须在某处被丢弃。
因此,使用更高的波特率。在STM32上,即使是1 兆波特或2 兆波特(2,000,000波特/秒)也应该是支持的。
- ASCII编码极其低效
这个数据流管线成本很高。
Python
int → 字符串 → UART TX
STM32
UART RX → atoi() → 移动平均滤波 → sprintf() → UART TX
Python
UART RX → 字符串 → int
这两者,尤其是 atoi(),以及 sprintf(),在这里都很慢且不必要。请避免在样本处理循环中进行任何printf风格的格式化。
相反,将样本以二进制的 int16_t 直接通过数据线发送。
Python:
ser.write(np.int16(s).tobytes())
STM32:
int16_t sample;
HAL_UART_Receive(&huart2, (uint8_t*)&sample, sizeof(sample), HAL_MAX_DELAY);
以同样的方式返回结果:
HAL_UART_Transmit(&huart2, (uint8_t*)&result, sizeof(result), HAL_MAX_DELAY);
这将所需带宽降至大约
16000 * 2 bytes * 10 bits ≈ 320 kbit/s
在甚至921600波特率下也很容易实现。
- HAL_UART_Receive() 在轮询模式下并不适合流式传输
你正在使用阻塞轮询:
HAL_UART_Receive(..., HAL_MAX_DELAY)
为了获得更高的吞吐量,至少应使用
HAL_UART_Receive_IT()
或者理想地
HAL_UART_Receive_DMA()
轮询UART访问模式会导致如果内部UART缓冲区(可能只有1 个字符,或最多16字符FIFO)已满,而CPU未在 HAL_UART_Receive() 调用中主动运作,数据将被丢失。这是一种UART的“溢出”——接收到的字符因为缓冲区未清空而无法存储。
UART DMA常用于流式数据(如音频)。使用中断(IT)或直接内存访问(DMA)模式来接收UART数据 会大幅降低CPU负载,因为要么是在新字符出现并存入缓冲区时才对CPU进行极短时间的中断(中断),要么根本不需要存储,因为DMA已经完成。
请注意,要把UART设置为中断或DMA模式,你应该回到STM32CubeMX配置器。它将需要不同的初始化,并需要为UART外设添加中断处理程序以正确处理。