从Python发送WAV数据时,STM32F401RE的 UART音频处理会丢失采样

编程语言 2026-07-12

我正在使用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))

解决方案

你在这里遇到了多重问题。

  1. 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波特/秒)也应该是支持的。

  1. 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波特率下也很容易实现。

  1. 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外设添加中断处理程序以正确处理。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章