在嵌入式开发中,信号发生器是常见的测试工具。使用STM32F103的DAC(数字模拟转换器)配合DMA(直接内存访问)和定时器,可以构建一个低成本、高性能的正弦波信号发生器。这个方案特别适合音频测试、传感器校准等场景。
STM32F103C8T6(俗称"蓝莓派")是性价比极高的Cortex-M3内核MCU,内置12位DAC模块。相比外接专用信号发生器芯片,这种方案有三大优势:
实测在72MHz主频下,使用DMA+DAC可以稳定输出0-100kHz的正弦波,完全满足大多数嵌入式场景需求。我曾在智能家居项目中用类似方案生成门铃提示音,效果比专用音频芯片更稳定。
首先在RCC选项卡启用外部高速时钟(HSE),然后在Clock Configuration配置界面:
注意:DAC时钟挂在APB1总线上,超频可能导致输出不稳定
在Analog选项卡启用DAC1:
在DMA Settings添加DMA通道:
定时器负责触发DAC转换,相当于波形的"节拍器":
关键计算公式:
code复制定时器频率 = 系统时钟 / (Prescaler + 1) / (Period + 1)
正弦波频率 = 定时器频率 / 波形点数
原始方法使用浮点运算生成波形,虽然直观但效率低。改进方案:
c复制#define POINTS 256
uint16_t sine_wave[POINTS];
// 预计算正弦表(Q12定点数优化)
void gen_sine_table(void) {
for(int i=0; i<POINTS; i++) {
float radian = 2 * 3.1415926f * i / POINTS;
sine_wave[i] = 2048 * (1 + sinf(radian)); // 0-3.3V输出
}
}
实测发现,使用sinf()函数比sin()快3倍,且精度足够。若追求极致性能,可预先计算好波形存入Flash。
通过修改定时器周期实现变频:
c复制// 频率计算公式(72MHz主频示例)
uint32_t calc_timer_period(uint32_t freq_hz) {
return (72000000 / (POINTS * freq_hz)) - 1;
}
// 动态调整示例
void set_frequency(uint32_t freq_hz) {
htim2.Instance->ARR = calc_timer_period(freq_hz);
htim2.Instance->EGR = TIM_EGR_UG; // 立即更新
}
实际测试时发现,低于20Hz时会出现可闻噪声,建议添加下限检查。
标准单缓冲模式在更新波形时会出现断裂。改进方案:
c复制uint16_t wave_buf[2][POINTS]; // 双缓冲
uint8_t active_buf = 0;
// DMA完成回调函数
void HAL_DAC_ConvCpltCallback(DAC_HandleTypeDef* hdac) {
active_buf ^= 1; // 切换缓冲
HAL_DAC_Start_DMA(hdac, DAC_CHANNEL_1,
(uint32_t*)wave_buf[active_buf], POINTS, DAC_ALIGN_12B_R);
}
// 更新波形时操作非活跃缓冲
void update_waveform(void) {
uint8_t inactive_buf = active_buf ^ 1;
// 填充wave_buf[inactive_buf]...
}
常见坑点:DMA传输要求缓冲区地址对齐。解决方法:
c复制__attribute__((aligned(4))) uint16_t wave_buffer[POINTS];
或者使用编译器指令:
c复制#pragma pack(push, 4)
uint16_t wave_buffer[POINTS];
#pragma pack(pop)
DAC默认输出0-3.3V,通过代码调节幅值:
c复制void set_amplitude(uint16_t max_mv) {
float scale = max_mv / 3300.0f;
for(int i=0; i<POINTS; i++) {
wave_buffer[i] = 2048 + (int)(2047 * scale * sinf(...));
}
}
如需更大电压,建议使用运放电路:
DAC输出会有量化台阶,推荐二阶RC滤波:
code复制截止频率计算:
fc = 1 / (2π√(R1R2C1C2))
常用值:
无输出:
波形畸变:
频率不准:
不同配置下的实测性能对比:
| 波形点数 | 最大频率 | CPU占用率 |
|---|---|---|
| 64 | 150kHz | <1% |
| 128 | 75kHz | <1% |
| 256 | 37kHz | <1% |
| 512 | 18kHz | 2% |
通过修改波形数组实现多种波形输出:
c复制typedef enum {
WAVE_SINE,
WAVE_TRIANGLE,
WAVE_SAWTOOTH
} WaveType;
void gen_waveform(WaveType type) {
switch(type) {
case WAVE_SINE: /* 正弦波代码 */ break;
case WAVE_TRIANGLE: /* 三角波代码 */ break;
case WAVE_SAWTOOTH: /* 锯齿波代码 */ break;
}
}
结合此方案实现简易电子琴:
c复制// 音符频率表(单位Hz)
const uint16_t note_freq[] = {
262, 294, 330, 349, 392, 440, 494 // C4到B4
};
void play_note(uint8_t note_idx) {
set_frequency(note_freq[note_idx]);
}
实际项目中,可以添加ADSR包络控制实现更自然的音效。