作为一名电子爱好者,我一直对信号发生器的设计充满兴趣。这次我决定用单片机打造一个多功能函数信号发生器,能够产生正弦波、三角波、方波和锯齿波四种基本波形,并且具备频率调节和LCD显示功能。这个项目不仅考验对单片机编程的理解,还需要掌握数字信号处理的基本原理。
选择单片机作为核心控制器有几个关键考量:首先,现代单片机性能足够强大,能够满足波形生成的需求;其次,单片机方案成本低廉,适合个人DIY;最重要的是,通过这个项目可以深入理解数字信号生成的底层原理。我最终选择了STM32F103C8T6这款Cortex-M3内核的单片机,它内置12位DAC,能够提供足够的波形分辨率。
经过对比多款单片机,我选择了STM32F103C8T6(俗称"蓝莓板")作为核心控制器,主要基于以下考虑:
提示:如果预算充足,可以考虑STM32F4系列,其内置的DAC性能更优,但初学者用F103已经足够。
DAC输出后需要经过适当的信号调理电路:
我采用了二阶巴特沃斯低通滤波器,截止频率设为50kHz,足以覆盖音频范围。运放选择了常见的NE5532,它具有低噪声、高转换速率的特性。
选用1602字符型LCD显示当前波形参数,通过4位并行接口连接,节省IO口资源。显示内容包括:
正弦波的数学表达式为y = A×sin(2πft + φ),在数字系统中,我们采用查表法实现:
c复制#define SINE_TABLE_SIZE 256
const uint16_t sineTable[SINE_TABLE_SIZE] = {
// 通过Python预先计算:int(2047*sin(2*pi*i/256)+2048)
2048, 2073, 2098, 2123, ..., 2047 // 具体数值省略
};
void generateSineWave(uint32_t frequency) {
static uint32_t phaseAccumulator = 0;
uint32_t phaseIncrement = (frequency * SINE_TABLE_SIZE) / SAMPLE_RATE;
while(1) {
phaseAccumulator += phaseIncrement;
if(phaseAccumulator >= SINE_TABLE_SIZE)
phaseAccumulator -= SINE_TABLE_SIZE;
DAC->DHR12R1 = sineTable[phaseAccumulator];
delayMicroseconds(1000000/SAMPLE_RATE);
}
}
关键点说明:
传统方法是通过线性增减实现,但存在非线性问题。我改用了累加器方案:
c复制void generateTriangleWave(uint32_t frequency) {
static int16_t accumulator = 0;
static int8_t direction = 1;
uint16_t step = (4096 * frequency) / (2 * SAMPLE_RATE);
while(1) {
if(direction) {
accumulator += step;
if(accumulator >= 4095) {
accumulator = 4095;
direction = 0;
}
} else {
accumulator -= step;
if(accumulator <= 0) {
accumulator = 0;
direction = 1;
}
}
DAC->DHR12R1 = accumulator;
delayMicroseconds(1000000/SAMPLE_RATE);
}
}
这种实现方式频率更精确,波形线性度更好。
利用定时器产生PWM波,实现高精度方波:
c复制void initPWMForSquareWave(void) {
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 定时器时钟配置
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
// 基础配置
TIM_TimeBaseStructure.TIM_Period = SystemCoreClock / 100000 - 1; // 初始10kHz
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
// PWM模式配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = TIM_TimeBaseStructure.TIM_Period / 2; // 50%占空比
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC2Init(TIM3, &TIM_OCInitStructure);
TIM_Cmd(TIM3, ENABLE);
}
通过调整TIM_Period值改变频率,TIM_Pulse值改变占空比。
为了实现精确的频率控制,我采用了定时器中断方式:
c复制void TIM2_IRQHandler(void) {
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
static uint16_t sampleIndex = 0;
// 根据当前波形类型输出相应样本
switch(currentWaveform) {
case SINE_WAVE:
DAC->DHR12R1 = sineTable[sampleIndex];
sampleIndex = (sampleIndex + 1) % SINE_TABLE_SIZE;
break;
// 其他波形类似处理
}
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
void setOutputFrequency(uint32_t freq) {
// 计算定时器重载值
uint32_t arrValue = (SystemCoreClock / (SINE_TABLE_SIZE * freq)) - 1;
TIM_SetAutoreload(TIM2, arrValue);
}
这种方法频率精度高,CPU占用率低。
通过旋转编码器实现频率微调:
c复制void handleEncoder(void) {
static int8_t lastState = 0;
int8_t newState = (GPIO_ReadInputDataBit(ENC_PORT, ENC_A) << 1) |
GPIO_ReadInputDataBit(ENC_PORT, ENC_B);
if(lastState == 0x3 && newState == 0x1) {
currentFrequency += frequencyStep;
} else if(lastState == 0x3 && newState == 0x2) {
currentFrequency -= frequencyStep;
}
lastState = newState;
setOutputFrequency(currentFrequency);
updateLCD();
}
1602 LCD两行显示设计:
为避免频繁刷新导致闪烁,采用差异刷新策略:
c复制void updateLCD(void) {
static uint32_t lastFreq = 0;
static WaveformType lastWave = NO_WAVE;
if(lastWave != currentWaveform) {
lcdSetCursor(6, 0);
lcdPrint(waveNames[currentWaveform]);
lastWave = currentWaveform;
}
if(lastFreq != currentFrequency) {
lcdSetCursor(14, 0);
lcdPrintf("%-5lu", currentFrequency);
lastFreq = currentFrequency;
}
}
采用状态机模式组织代码:
c复制typedef enum {
STATE_IDLE,
STATE_RUNNING,
STATE_MENU
} SystemState;
void main(void) {
hardwareInit();
lcdInit();
SystemState state = STATE_IDLE;
while(1) {
switch(state) {
case STATE_IDLE:
if(buttonPressed(START_BUTTON)) {
startOutput();
state = STATE_RUNNING;
}
break;
case STATE_RUNNING:
handleEncoder();
if(buttonPressed(MENU_BUTTON)) {
state = STATE_MENU;
}
break;
case STATE_MENU:
handleMenu();
if(menuExit()) {
state = STATE_RUNNING;
}
break;
}
}
}
波形失真严重
高频时波形不稳定
LCD显示乱码
测试条件:12V供电,室温25℃
| 波形类型 | 频率范围 | 幅度误差 | THD(1kHz) |
|---|---|---|---|
| 正弦波 | 1Hz-20kHz | ±3% | <1% |
| 三角波 | 1Hz-50kHz | ±5% | - |
| 方波 | 1Hz-100kHz | ±2% | - |
| 锯齿波 | 1Hz-30kHz | ±7% | - |
增加幅度调节功能
添加波形存储功能
上位机控制接口
频率扫描功能
这个项目从构思到实现花了约两周时间,期间遇到了不少挑战,特别是高频波形失真的问题让我调试了很久。最终通过优化中断服务程序和调整滤波器参数解决了这个问题。建议初学者可以从简单的方波生成开始,逐步增加功能复杂度。