You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

STM32F4 DAC生成音符的噪声问题与多音符叠加异常咨询

STM32F4 DAC生成音符的问题排查与解决

我正在使用STM32F4开发板,通过DAC生成音符,核心代码如下:

生成单音符的函数:

float playNote(float noteFrequency, float t)
{
    float w = 2 * PI * noteFrequency;
    return sinf(w * t);
}

填充DAC缓冲区的初始代码(缓冲区大小500):

void getData4Dac(uint16_t* dac_buff, uint8_t note)
{
    uint16_t n;
    float t = 0;

    for(n=0; n<(DAC_BUFF_SIZE); n++)
    {
        switch (note)
        {
            case 0:
                y = 0;       // 静音
                break;
            case 1:
                y = playNote(NOTE_C, t);
                break;
            case 2:
                y = playNote(NOTE_D, t);
                break;
            case 3:
                y = playNote(NOTE_E, t);
                break;
            case 4:
                y = playNote(NOTE_F, t);
                break;
            case 5:
                y = playNote(NOTE_G, t);
                break;
            case 6:
                y = playNote(NOTE_A, t);
                break;
            case 7:
                y = playNote(NOTE_H, t);
                break;
            case 8:
                y = playNote(NOTE_C2, t);
                break;
            default:
                y = 0;
                break;
        }

        dac_buff[n] = (uint16_t)(y * 2047 + 2048);
        t = t + 0.5e-4;
    }
}

问题一:精确频率产生噪声,自定义整数频率无噪声的原因

我定义的音符频率如下:

#define NOTE_C   240
#define NOTE_D   280
#define NOTE_E   320
#define NOTE_F   360
#define NOTE_G   400
#define NOTE_A   440
#define NOTE_H   480
#define NOTE_C2  520

4八度的真实音符频率范围是261.63Hz至493.88Hz,但使用这些精确值时会产生大量噪声,换成自定义的整数频率却没有噪声,这是为什么?

原因分析

  1. 采样与缓冲区的匹配逻辑:你的采样周期是0.5e-4秒,对应采样率为20kHz(1/0.00005 = 20000),DAC缓冲区总时长为500 * 0.00005 = 0.025秒(25ms)。
  2. 整数频率的连续性:以240Hz为例,其周期为1/240 ≈ 4.1667ms,25ms刚好是该周期的6倍(25 / (1/240) = 6),意味着缓冲区中刚好包含整数个正弦波周期,缓冲区首尾的信号相位完全一致。DMA每次刷新缓冲区时,信号不会出现跳变,因此无噪声。
  3. 精确频率的不连续性:以真实C4音261.63Hz为例,其周期约为3.822ms,25ms约为该周期的6.54倍,不是整数。这导致缓冲区结束时的信号相位与开始时不一致,DMA切换缓冲区时会产生信号突变,表现为明显的噪声。

问题二:多音符叠加后声音混乱有噪声的原因及解决要点

我尝试用以下代码实现多音符叠加:

for(n=0; n<(DAC_BUFF_SIZE); n++)
{
    active_notes = 0;
    y = 0;
    for(i = 0; i < NUMBER_OF_NOTES; i++)
    {
        if(note_durations[i] > 0)
        {
            active_notes++;
            y += sinf(2 * PI * note_frequencies[i] * (1 << octave) * t);
            //y = sinf(2 * PI * note_frequencies[i] * (1 << octave) * t);
        }
    }
    y /= active_notes;
    t += 0.5e-4;
    dac_buff[n] = (uint16_t)(y*2047 + 2048);
}

但执行y += [信号]操作后声音完全混乱,充满噪声,请问原因是什么?实现干净的多音符叠加需要注意哪些要点?

噪声原因

  1. 相位不连续的叠加放大:每个精确频率的音符本身就存在缓冲区首尾相位不连续的问题,多个这样的信号叠加后,相位突变的影响会被放大,导致噪声更明显。
  2. 除以0的潜在错误:当没有激活的音符时,active_notes为0,执行y /= active_notes会得到NaN(非数值),DAC转换NaN会产生随机值或固定错误电平,引发噪声。
  3. 信号削波风险:多个正弦波叠加后,y的峰值可能超过1.0(比如3个峰值为1的正弦波叠加,峰值可达3),即使除以active_notes后回到[-1,1]范围,中间计算过程中可能出现浮点精度丢失,导致信号失真。
  4. 实时计算的精度误差:多次调用sinf实时计算正弦值,会累积浮点运算误差,叠加后这些误差会被放大,影响信号纯净度。

实现干净多音符叠加的要点

  • 保证信号连续:让每个音符的频率满足频率 = k * 采样率 / 缓冲区长度(k为整数),确保缓冲区中包含整数个信号周期。以你的参数为例,采样率20kHz、缓冲区500,频率需为40*kHz(20000/500=40),比如240Hz(k=6)、280Hz(k=7),可以将真实频率近似为最接近的符合该规则的数值。
  • 避免除以0:在执行除法前判断active_notes是否为0,若为0直接设置y=0。
  • 控制信号振幅:叠加前限制单个音符的振幅(比如每个音符用0.5*sinf(...)),避免叠加后峰值超过1.0;或者叠加后对y进行钳位处理,确保其范围在[-1,1]之间:
    y = fminf(fmaxf(y, -1.0f), 1.0f);
    
  • 使用预计算正弦表:提前计算常用频率的正弦波样本存入数组,实时叠加时直接查表,减少浮点运算的误差和CPU负载,提升信号纯净度。
  • 同步缓冲区更新:确保在DMA完成当前缓冲区传输后再更新下一个缓冲区,避免传输过程中修改缓冲区数据导致信号断裂。

完整main函数代码:

#include "stm32f4xx.h"
#include "usart.h"
#include "delay.h"
#include "cs43l22.h"
#include "dac.h"
#include "math.h"

#define PI             3.1415
#define DAC_BUFF_SIZE  500

#define NOTE_C         230
#define NOTE_D         280
#define NOTE_E         320
#define NOTE_F         360
#define NOTE_G         400
#define NOTE_A         440
#define NOTE_H         480
#define NOTE_C2        520

#define NOTE_Cis       17.32
#define NOTE_Dis       19.45
#define NOTE_Fis       23.12
#define NOTE_Gis       25.96
#define NOTE_Ais       29.14

extern volatile int8_t octave;
extern volatile uint8_t note_durations[NUMBER_OF_NOTES];
static float note_frequencies[NUMBER_OF_NOTES];

uint16_t dac_buff[DAC_BUFF_SIZE];
uint32_t btime = 0;

void getData4Dac(uint16_t * dac_buff, int8_t octave);
void initNotes(void);
void updateNoteDurations(void);

int main(void)
{
    initUSART2(USART2_BAUDRATE_921600);
    enIrqUSART2();

    initDmaDAC1(dac_buff, DAC_BUFF_SIZE);
    getData4Dac(dac_buff, octave);

    initCS43L22(10, 48000);
    initSYSTIM();
    initNotes();
    printUSART2("PLAY NOW: ");

    while(1)
    {
        if(chk4TimeoutSYSTIM(btime, 1000) == SYSTIM_TIMEOUT)
        {
            updateNoteDurations();
            btime = getSYSTIM();
        }
        while((SPI3->SR & 0x00000002) == 0)
            ;

        SPI3->DR = 0x00;

        chkBuffUSART2();
        getData4Dac(dac_buff, octave);
    }
}

void getData4Dac(uint16_t* dac_buff, int8_t octave)
{
    uint8_t active_notes;
    uint16_t n, i;
    float y = 0, t = 0;

    for(n=0; n<(DAC_BUFF_SIZE); n++)
    {
        active_notes = 0;
        y = 0;
        for(i = 0; i < NUMBER_OF_NOTES; i++)
        {
            if(note_durations[i] > 0)
            {
                active_notes++;
                y += sinf(2 * PI * note_frequencies[i] * t); // 移除octave处理
            }
        }
        if(active_notes > 0) {
            y /= active_notes;
        } else {
            y = 0;
        }
        t += 0.5e-4;
        dac_buff[n] = (uint16_t)(y*2048 + 2048);
    }
}

void initNotes()
{
    uint8_t i;
    for(i = 0; i < NUMBER_OF_NOTES; i++)
    {
        note_durations[i] = 0;
    }
    note_frequencies[0] = NOTA_C;
    note_frequencies[1] = NOTA_Cis;
    note_frequencies[2] = NOTA_D;
    note_frequencies[3] = NOTA_Dis;
    note_frequencies[4] = NOTA_E;
    note_frequencies[5] = NOTA_F;
    note_frequencies[6] = NOTA_Fis;
    note_frequencies[7] = NOTA_G;
    note_frequencies[8] = NOTA_Gis;
    note_frequencies[9] = NOTA_A;
    note_frequencies[10] = NOTA_Ais;
    note_frequencies[11] = NOTA_H;
    note_frequencies[12] = NOTA_C2;
}

void updateNoteDurations()
{
    uint8_t i;
    for(i = 0; i < NUMBER_OF_NOTES; i++)
    {
        if(note_durations[i] > 0)
        {
            note_durations[i]--;
        }
    }
}

内容的提问来源于stack exchange,提问作者user9599100

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 17:47:01