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,但使用这些精确值时会产生大量噪声,换成自定义的整数频率却没有噪声,这是为什么?
原因分析
- 采样与缓冲区的匹配逻辑:你的采样周期是
0.5e-4秒,对应采样率为20kHz(1/0.00005 = 20000),DAC缓冲区总时长为500 * 0.00005 = 0.025秒(25ms)。 - 整数频率的连续性:以240Hz为例,其周期为
1/240 ≈ 4.1667ms,25ms刚好是该周期的6倍(25 / (1/240) = 6),意味着缓冲区中刚好包含整数个正弦波周期,缓冲区首尾的信号相位完全一致。DMA每次刷新缓冲区时,信号不会出现跳变,因此无噪声。 - 精确频率的不连续性:以真实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 += [信号]操作后声音完全混乱,充满噪声,请问原因是什么?实现干净的多音符叠加需要注意哪些要点?
噪声原因
- 相位不连续的叠加放大:每个精确频率的音符本身就存在缓冲区首尾相位不连续的问题,多个这样的信号叠加后,相位突变的影响会被放大,导致噪声更明显。
- 除以0的潜在错误:当没有激活的音符时,
active_notes为0,执行y /= active_notes会得到NaN(非数值),DAC转换NaN会产生随机值或固定错误电平,引发噪声。 - 信号削波风险:多个正弦波叠加后,
y的峰值可能超过1.0(比如3个峰值为1的正弦波叠加,峰值可达3),即使除以active_notes后回到[-1,1]范围,中间计算过程中可能出现浮点精度丢失,导致信号失真。 - 实时计算的精度误差:多次调用
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
相关产品推荐
相关产品推荐

