1. STM32信号发生器项目概述
这个基于STM32的信号发生器项目,完美结合了Matlab的强大计算能力和STM32的实时控制特性。作为一名电子工程师,我经常需要在项目调试时用到各种波形信号,市面上的信号发生器要么太贵,要么功能不够灵活。于是决定自己动手打造一个多功能信号发生器,支持正弦波、锯齿波、三角波和方波输出,频率范围1Hz-50kHz可调,THD(总谐波失真)控制在1%以内。
项目的核心思路是:先用Matlab生成高精度的波形数据表,然后通过STM32的DAC+DMA组合实现自动波形输出,配合定时器精确控制输出频率。上位机通过串口通信实时调整参数,TFTLCD显示屏直观展示当前波形状态,物理按键实现波形快速切换。这种架构既保证了波形质量,又能灵活调整参数,实测效果堪比专业设备。
2. 硬件架构设计
2.1 核心硬件选型
主控芯片选用STM32F103C8T6,这款Cortex-M3内核的MCU性价比极高,内置12位DAC和DMA控制器,完全满足我们的需求。其他关键硬件包括:
- TFTLCD显示屏:2.4寸IPS屏,240x320分辨率,SPI接口
- 机械按键:用于波形切换和参数调整
- 电平转换电路:将DAC输出的0-3.3V信号放大到0-5V
- USB转串口芯片:CH340G,实现与PC通信
硬件设计时特别注意了电源去耦,每个芯片的VCC都加了0.1μF和10μF的电容,确保DAC输出稳定无毛刺。
2.2 硬件连接示意图
code复制STM32F103C8T6核心板
├── PA4(DAC1_OUT) -> 运放电路 -> 输出端子
├── PA2(USART2_TX) -> CH340G
├── PA3(USART2_RX) -> CH340G
├── PB0-PB3 -> 4个机械按键
└── SPI1 -> TFTLCD显示屏
3. Matlab波形数据生成
3.1 波形生成原理
Matlab负责生成高质量波形数据表,其核心优势在于:
- 内置丰富的数学函数,可精确计算各种波形
- 支持向量化运算,生成大量数据效率极高
- 可直观预览波形效果,便于调试
以正弦波生成为例,关键参数包括:
- 采样率(fs):决定波形的时间分辨率
- 点数(N):影响波形频率分辨率和内存占用
- 频率(f):目标波形频率
3.2 具体实现代码
matlab复制% 正弦波生成函数
function wave_data = gen_sine_wave(fs, N, f)
t = (0:N-1)/fs; % 时间向量
y = sin(2*pi*f*t); % 标准正弦波
wave_data = uint16((y+1)*2047); % 转换到DAC的0-4095范围
end
% 三角波生成函数
function wave_data = gen_triangle_wave(fs, N)
wave_data = uint16(4095 * sawtooth(2*pi*(0:N-1)/N, 0.5));
end
% 方波生成函数
function wave_data = gen_square_wave(fs, N, duty)
wave_data = uint16(4095 * square(2*pi*(0:N-1)/N, duty));
end
% 锯齿波生成函数
function wave_data = gen_sawtooth_wave(fs, N)
wave_data = uint16(4095 * sawtooth(2*pi*(0:N-1)/N));
end
3.3 数据优化技巧
- 点数选择:推荐256点,兼顾内存占用和波形质量
- 归一化处理:将波形数据映射到DAC的0-4095范围
- 数据导出:保存为C语言数组格式,方便直接嵌入STM32工程
matlab复制% 导出C数组
fid = fopen('wave_data.h','w');
fprintf(fid, 'const uint16_t sine_wave[256] = {\n');
for i = 1:32
fprintf(fid, ' ');
for j = 1:8
fprintf(fid, '%4d, ', wave_data((i-1)*8+j));
end
fprintf(fid, '\n');
end
fprintf(fid, '};\n');
fclose(fid);
4. STM32软件设计
4.1 DAC与DMA配置
DAC和DMA的协同工作是本项目的关键,配置要点如下:
c复制// DAC初始化
DAC_ChannelConfTypeDef sConfig = {0};
hdac.Instance = DAC;
HAL_DAC_Init(&hdac);
sConfig.DAC_Trigger = DAC_TRIGGER_T2_TRGO; // 定时器2触发
sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_1);
// DMA初始化
hdma_dac1.Instance = DMA1_Channel3;
hdma_dac1.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_dac1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_dac1.Init.MemInc = DMA_MINC_ENABLE;
hdma_dac1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_dac1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_dac1.Init.Mode = DMA_CIRCULAR; // 循环模式
HAL_DMA_Init(&hdma_dac1);
// 关联DAC和DMA
__HAL_LINKDMA(&hdac, DMA_Handle1, hdma_dac1);
特别注意:DMA必须配置为循环模式(CIRCULAR),这样波形才能连续输出。MemInc要开启,让DMA自动遍历整个波形数组。
4.2 定时器触发配置
定时器控制DAC的更新速率,从而决定输出波形频率:
c复制// 定时器2初始化
htim2.Instance = TIM2;
htim2.Init.Prescaler = 71; // 72MHz/(71+1)=1MHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 9; // 1MHz/(9+1)=100kHz
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_Base_Init(&htim2);
// 配置TRGO事件
TIM_MasterConfigTypeDef sMasterConfig = {0};
sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig);
输出频率计算公式:
code复制实际频率 = 定时器触发频率 / 波形点数
例如:定时器触发100kHz,256点波形 → 100000/256 ≈ 390.6Hz
4.3 波形切换实现
通过按键切换不同波形,核心逻辑如下:
c复制// 波形类型枚举
typedef enum {
WAVE_SINE = 0,
WAVE_TRIANGLE,
WAVE_SQUARE,
WAVE_SAWTOOTH,
WAVE_MAX
} WaveType;
WaveType current_wave = WAVE_SINE;
// 按键处理函数
void Key_Handler(void)
{
if(Key_Scan() == 1) {
current_wave = (current_wave + 1) % WAVE_MAX;
switch(current_wave) {
case WAVE_SINE:
HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)sine_wave, 256, DAC_ALIGN_12B_R);
break;
case WAVE_TRIANGLE:
HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)triangle_wave, 256, DAC_ALIGN_12B_R);
break;
// 其他波形类似
}
LCD_UpdateWaveType(current_wave); // 更新显示
}
}
5. 串口通信协议设计
5.1 通信协议格式
与上位机通信采用简单的文本协议,格式如下:
- 设置频率:FREQ:1000\n (单位Hz)
- 设置幅值:AMP:2000\n (DAC值,0-4095)
- 设置偏移:OFFSET:1000\n (DAC值)
5.2 STM32串口接收实现
c复制#define CMD_BUF_SIZE 32
char cmd_buf[CMD_BUF_SIZE];
uint8_t cmd_index = 0;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1) {
char ch = USART1->DR;
if(ch == '\n') { // 命令结束符
cmd_buf[cmd_index] = '\0';
process_command(cmd_buf);
cmd_index = 0;
} else if(cmd_index < CMD_BUF_SIZE-1) {
cmd_buf[cmd_index++] = ch;
}
}
HAL_UART_Receive_IT(&huart1, &ch, 1); // 重新启用接收中断
}
void process_command(char* cmd)
{
if(strncmp(cmd, "FREQ:", 5) == 0) {
uint32_t freq = atoi(cmd + 5);
set_output_frequency(freq);
}
// 处理其他命令...
}
5.3 频率调整算法
动态调整输出频率需要重新计算定时器周期:
c复制void set_output_frequency(uint32_t freq)
{
if(freq == 0) return;
uint32_t timer_freq = 1000000; // 定时器基础频率1MHz
uint32_t period = (timer_freq / (freq * WAVE_POINTS)) - 1;
__HAL_TIM_SET_AUTORELOAD(&htim2, period);
target_freq = freq;
LCD_UpdateFrequency(freq); // 更新显示
}
6. TFTLCD显示实现
6.1 显示界面设计
LCD界面需要展示以下信息:
- 当前波形类型(文字+图形)
- 输出频率
- 幅值和偏移量
- 系统状态(运行/停止)
6.2 优化刷新策略
为避免屏幕闪烁,采用局部刷新策略:
c复制void LCD_UpdateWaveType(WaveType type)
{
// 只刷新波形类型区域
LCD_SetTextColor(WHITE);
LCD_FillRect(50, 30, 150, 20, WHITE); // 清除原有内容
LCD_SetTextColor(BLACK);
switch(type) {
case WAVE_SINE: LCD_DisplayString(50, 30, "Sine Wave"); break;
case WAVE_TRIANGLE: LCD_DisplayString(50, 30, "Triangle Wave"); break;
// 其他波形...
}
// 简单绘制波形示意图
draw_wave_preview(type);
}
void draw_wave_preview(WaveType type)
{
// 在指定区域绘制简化波形
uint16_t x_start = 50, y_start = 60;
uint16_t width = 150, height = 80;
LCD_DrawRect(x_start, y_start, width, height, BLACK); // 边框
switch(type) {
case WAVE_SINE:
for(int x=0; x<width; x++) {
uint16_t y = y_start + height/2 + (height/2-1)*sin(x*2*3.14/width);
if(x==0) LCD_DrawPixel(x_start+x, y, RED);
else LCD_DrawLine(x_start+x-1, last_y, x_start+x, y, RED);
last_y = y;
}
break;
// 其他波形类似...
}
}
7. 系统优化与实测结果
7.1 性能优化技巧
- 双缓冲技术:准备两个波形缓冲区,DMA传输其中一个时,CPU可以更新另一个,实现无缝切换
- 查表法:预计算常用频率对应的定时器参数,减少实时计算开销
- DMA优先级:提高DMA通道优先级,确保波形输出不受其他中断影响
- 电源滤波:DAC输出端增加RC低通滤波,平滑波形
7.2 实测性能指标
经过优化后,系统达到以下指标:
- 频率范围:1Hz - 50kHz
- 频率分辨率:1Hz (低频段),100Hz (高频段)
- 波形失真度:THD <1% (正弦波@1kHz)
- 幅值精度:±0.5%
- 响应时间:<100ms (参数调整后稳定)
7.3 常见问题排查
-
波形失真严重
- 检查DAC参考电压是否稳定
- 确认波形数据表生成正确
- 测量输出端是否接了合适的负载
-
高频波形不完整
- 降低波形点数(如从256降到128)
- 提高定时器触发频率
- 检查DMA传输速度是否跟得上
-
串口通信不稳定
- 确认双方波特率一致
- 检查硬件连接,特别是地线
- 增加串口协议校验(如CRC)
-
LCD显示异常
- 检查SPI时钟是否过高
- 确认初始化序列正确
- 检查电源电压是否稳定
8. 项目扩展方向
这个基础框架可以进一步扩展:
- 增加波形类型:白噪声、任意波形等
- 添加调制功能:AM/FM调制
- 实现扫频功能:自动频率扫描
- 上位机增强:添加波形编辑和保存功能
- 多通道输出:利用STM32的第二个DAC通道
我在实际调试中发现,DAC输出端增加一级运放缓冲能显著提高带负载能力。另外,当需要输出较高频率时,适当减少波形点数可以提高最大输出频率,但会牺牲一些波形质量,需要根据具体需求权衡。