2. 核心设计思路- 电源架构设计
- 采用线性稳压 + 电荷泵方案:LM1117 系列线性稳压器产生低纹波 + 5V、+3.3V,满足数字 / 模拟电路供电需求;ICL7660 电荷泵将 + 5V 转换为 - 5V,实现单 12V 输入下的正负双电源,避免运放单电源供电的失真问题。
- 线性稳压优势:低纹波、低噪声,适合对电源质量要求高的 DAC、运放等模拟器件。
- 数控可调设计
- 采用STM32+DAC + 运放架构:STM32 控制 MC14725 DAC 输出 0~5V 模拟电压,再通过 LM358 运放放大 / 跟随,实现可调电压输出,同时提高带载能力。
- 8 位 DAC 分辨率 1/256,实现约 19.5mV 步进精度,满足通用可调电源需求。
- PWM 输出设计
- 采用 STM32 高级定时器 TIM1 生成硬件 PWM,PA8 为 TIM1_CH1 复用功能引脚,硬件输出无软件占用 CPU;
- 支持频率、占空比独立编程,适配电机驱动、LED 调光、模拟信号输出等场景;
- PWM 输出引脚增加测试点与限流保护,避免外接负载损坏 IO 口。
四、原理图与 PCB 设计介绍1. 原理图设计使用KiCad软件进行原理图绘画
   打版与迭代五、软件设计与调试1. 开发与调试工具- 1.0 版本:首次打版,验证电源架构、DAC / 运放电路、主控电路的可行性;
- 后续重打原因和优化方向:
- 负电源带载不足:优化 ICL7660 泵电容、布线,增加滤波;
- 运放失真:优化运放电源滤波、反馈网络,更换高精度运放;
- 干扰问题:优化地平面、增加磁珠隔离,进一步抑制数字噪声;
- 开发环境:Keil MDK v5用于代码编写、编译、调试;
- 调试工具:ST-Link/V2 仿真器,用于程序下载、在线调试;
- 辅助工具:串口调试助手、示波器(纹波 / 电压测试)、万用表(电压 / 电流测量)。

步进和按键控制逻辑 DeviceParams dev_params = { .curr_mode = MODE_POWER, .curr_adj = ADJ_POWER, .step_gear = 0, .power_val = 0.0f, .freq_val = 1000, .duty_val = 50.0f, .power_steps = {0.1f, 0.5f, 1.0f}, .freq_steps = {1,10, 50, 100, 500, 1000, 10000}, .duty_steps = {0.1f, 0.5f, 1.0f, 5.0f, 10.0f} };
const char* mode_names[] = { "", "Power", "PWM" };
const char* adj_names[] = { "None", "Voltage", "Frequency", "Duty Cycle" };
static AdjTarget Filter_AdjTarget(WorkMode mode, AdjTarget curr_adj) { switch(mode) { case MODE_POWER: return ADJ_POWER; case MODE_PWM: if(curr_adj == ADJ_POWER) return ADJ_FREQ; return curr_adj; default: return ADJ_NONE; } }
static void Limit_Params(void) { if(dev_params.power_val < -4.4f) dev_params.power_val = -4.4f; if(dev_params.power_val > 4.4f) dev_params.power_val = 4.4f; if(dev_params.freq_val > 150000) dev_params.freq_val = 150000; if(dev_params.freq_val < 1) dev_params.freq_val = 1; if(dev_params.duty_val < 0.0f) dev_params.duty_val = 0.0f; if(dev_params.duty_val > 100.0f) dev_params.duty_val = 100.0f; }
float Get_Curr_Step(void) { switch(dev_params.curr_adj) { case ADJ_POWER: return dev_params.power_steps[dev_params.step_gear % 3]; case ADJ_FREQ: return (float)dev_params.freq_steps[dev_params.step_gear % 7]; case ADJ_DUTY: return dev_params.duty_steps[dev_params.step_gear % 5]; case ADJ_NONE: default: return 0.0f; } }
void App_Init(void) { KEY_Init(); LCD_Init(); LCD_Fill(0, 0, LCD_W, LCD_H, 0xFFFF); Power_Init(); PWM_Init(dev_params.freq_val, dev_params.duty_val); dev_params.curr_adj = Filter_AdjTarget(dev_params.curr_mode, dev_params.curr_adj); LCD_Display_All(); }
void Key_Process(void) { float step = Get_Curr_Step(); if(KEY_Scan(1) == KEY_PRESSED) { if(dev_params.curr_mode == MODE_POWER) dev_params.curr_mode = MODE_PWM; else dev_params.curr_mode = MODE_POWER; dev_params.curr_adj = Filter_AdjTarget(dev_params.curr_mode, dev_params.curr_adj); dev_params.step_gear = 0; LCD_Display_All(); delay_ms(100); } if(KEY_Scan(2) == KEY_PRESSED) { AdjTarget next_adj; switch(dev_params.curr_mode) { case MODE_POWER: next_adj = ADJ_POWER; break; case MODE_PWM: next_adj = (dev_params.curr_adj == ADJ_FREQ) ? ADJ_DUTY : ADJ_FREQ; break; default: next_adj = ADJ_NONE; break; } dev_params.curr_adj = next_adj; dev_params.step_gear = 0; LCD_Display_All(); delay_ms(100); } if(KEY_Scan(3) == KEY_PRESSED) { dev_params.step_gear++; LCD_Display_All(); delay_ms(100); } if(KEY_Scan(4) == KEY_PRESSED) { switch(dev_params.curr_adj) { case ADJ_POWER: dev_params.power_val += step; break; case ADJ_FREQ: dev_params.freq_val += (u32)step; if(dev_params.curr_mode == MODE_PWM) { PWM_Update(dev_params.freq_val, dev_params.duty_val); } break; case ADJ_DUTY: dev_params.duty_val += step; if(dev_params.curr_mode == MODE_PWM) { PWM_Update(dev_params.freq_val, dev_params.duty_val); } break; case ADJ_NONE: break; } Limit_Params(); if(dev_params.curr_mode == MODE_POWER) { Power_Update(); } LCD_Display_All(); delay_ms(100); } if(KEY_Scan(5) == KEY_PRESSED) { switch(dev_params.curr_adj) { case ADJ_POWER: dev_params.power_val -= step; break; case ADJ_FREQ: if(dev_params.freq_val >= (u32)step) { dev_params.freq_val -= (u32)step; if(dev_params.curr_mode == MODE_PWM) { PWM_Update(dev_params.freq_val, dev_params.duty_val); } } break; case ADJ_DUTY: dev_params.duty_val -= step; if(dev_params.curr_mode == MODE_PWM) { PWM_Update(dev_params.freq_val, dev_params.duty_val); } break; case ADJ_NONE: break; } Limit_Params(); if(dev_params.curr_mode == MODE_POWER) { Power_Update(); } LCD_Display_All(); delay_ms(100); } }
PWM初始化 #include "pwm.h" #include "stm32f10x_tim.h"
void PWM_Init(u32 freq, float duty) { GPIO_InitTypeDef gpio; TIM_TimeBaseInitTypeDef tim; TIM_OCInitTypeDef oc;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_TIM1, ENABLE);
gpio.GPIO_Pin = GPIO_Pin_8; gpio.GPIO_Mode = GPIO_Mode_AF_PP; gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &gpio);
u32 psc = 0; u32 arr = 0; if(freq <= 1098) { psc = 7200 - 1; arr = 10000 / freq - 1; } else { psc = 0; arr = 72000000 / freq - 1; }
tim.TIM_Period = arr; tim.TIM_Prescaler = psc; tim.TIM_ClockDivision = 0; tim.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, &tim);
oc.TIM_OCMode = TIM_OCMode_PWM1; oc.TIM_OutputState = TIM_OutputState_Enable; oc.TIM_OCPolarity = TIM_OCPolarity_High; oc.TIM_Pulse = (u32)((arr + 1) * duty / 100.0f); TIM_OC1Init(TIM1, &oc);
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM1, ENABLE); TIM_CtrlPWMOutputs(TIM1, ENABLE); TIM_Cmd(TIM1, ENABLE); TIM_CtrlPWMOutputs(TIM1, ENABLE); }
void PWM_Update (u32 freq, float duty) { if (freq < 1) freq = 1; if (freq > 60000) freq = 60000; if (duty < 0.0f) duty = 0.0f; if (duty > 100.0f) duty = 100.0f;
u32 psc = 0; u32 arr = 0; if(freq <= 1098) { psc = 7200 - 1; arr = 10000 / freq - 1; } else { psc = 0; arr = 72000000 / freq - 1; }
TIM1->PSC = psc; TIM_SetAutoreload(TIM1, arr); TIM_SetCompare1(TIM1, (u32)((arr + 1) * - duty / 100.0f)); }
MCP4725初始化 #include "mcp4725.h" #include "delay.h"
static void I2C_Start(void) { MCP4725_GPIO_PORT->BSRR = MCP4725_SDA_PIN; MCP4725_GPIO_PORT->BSRR = MCP4725_SCL_PIN; delay_us(1); MCP4725_GPIO_PORT->BRR = MCP4725_SDA_PIN; delay_us(1); MCP4725_GPIO_PORT->BRR = MCP4725_SCL_PIN; }
static void I2C_Stop(void) { MCP4725_GPIO_PORT->BRR = MCP4725_SDA_PIN; delay_us(1); MCP4725_GPIO_PORT->BSRR = MCP4725_SCL_PIN; delay_us(1); MCP4725_GPIO_PORT->BSRR = MCP4725_SDA_PIN; }
static void I2C_WriteByte(uint8_t data) { for(uint8_t i=0; i<8; i++) { if(data & 0x80) MCP4725_GPIO_PORT->BSRR = MCP4725_SDA_PIN; else MCP4725_GPIO_PORT->BRR = MCP4725_SDA_PIN; delay_us(1); MCP4725_GPIO_PORT->BSRR = MCP4725_SCL_PIN; delay_us(1); MCP4725_GPIO_PORT->BRR = MCP4725_SCL_PIN; data <<= 1; } MCP4725_GPIO_PORT->BSRR = MCP4725_SDA_PIN; delay_us(1); MCP4725_GPIO_PORT->BSRR = MCP4725_SCL_PIN; delay_us(1); MCP4725_GPIO_PORT->BRR = MCP4725_SCL_PIN; }
void MCP4725_Init(void) { GPIO_InitTypeDef gpio; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); gpio.GPIO_Pin = MCP4725_SCL_PIN | MCP4725_SDA_PIN; gpio.GPIO_Mode = GPIO_Mode_Out_OD; gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(MCP4725_GPIO_PORT, &gpio); I2C_Stop(); } void MCP4725_SetVoltage(float display_val) { uint16_t dac_val;
if(display_val < -8.8f) display_val = -8.8f; if(display_val > 8.8f) display_val = 8.8f;
dac_val = (uint16_t)((display_val + 4.4f) * 4095.0f /13.2f);
if(dac_val > 4095) dac_val = 4095;
I2C_Start(); I2C_WriteByte(0xC0); I2C_WriteByte(0x40); I2C_WriteByte((dac_val >> 4) & 0xFF); I2C_WriteByte((dac_val << 4) & 0xF0); I2C_Stop(); delay_us(50); } 电源调节函数 #include "power.h" #include "mcp4725.h" #include <math.h> #include "app_func.h"
#define POWER_GAIN 0.75f #define POWER_OFFSET 2.2f
void Power_Init(void) { MCP4725_Init(); Power_Update(); }
void Power_Update(void) { float v_out = dev_params.power_val; float v_dac = (v_out / POWER_GAIN) + POWER_OFFSET; if(v_dac < -10.0f) v_dac = -10.0f; if(v_dac > 10.0f) v_dac =10.0f; MCP4725_SetVoltage(v_dac); } 通过5个按键分别控制不同的数据调整 按键1切换模式 按键2切换调节频率还是占空比 按键3在步进切换 按键4和5加减
六、硬件功能展示与说明1. 功能测试与展示 上电测试 LED3 常亮,万用表测量 +5V/3.3V/-5V 稳定在标称值 系统供电正常,固定电源输出稳定 可调电压测试 按键调节时,万用表测量输出 0~5V 连续可调,步进约 19.5mV DAC 与运放工作正常,可调功能实现 纹波测试 示波器测量 +5V / 可调输出纹波峰峰值≤10mV 线性稳压 + 滤波设计有效,纹波满足要求 带载测试 5V 输出带载 1A 时,电压跌落≤0.05V;-5V 带载 100mA 时稳定 电源带载能力符合设计指标 PWM 波形输出测试 示波器接 PA8 引脚,可观测到标准方波,默认频率 1kHz、占空比 50% TIM1 定时器初始化正常,硬件 PWM 输出功能正常 PWM 参数调节测试 按键调节时,PA8 输出 PWM 占空比平滑变化,频率可按需修改 PWM 占空比控制程序运行正常,参数更新实时生效 2. 功能说明 固定电源:+5V/3.3V/-5V 为系统与外设提供稳定供电,满足数字 / 模拟电路需求; 可调电源:数控 0~5V 连续可调,按键操作简单,可扩展显示、闭环控制; PWM 波输出:PA8 引脚可输出可编程硬件 PWM 波形,频率与占空比均可独立调节,可用于信号测试、LED 调光、小型驱动等场景; 状态指示:LED 实时反馈供电状态,异常时可设置闪烁报警; 扩展能力:预留 IO 口支持显示、上位机控制、过流保护等功能升级。 七、设计中遇到的难题与解决方法1. 负电源(-5V)带载不足、纹波大 问题:ICL7660 输出 -5V 带载后电压跌落,纹波大导致运放异常; 原因:泵电容容量不足、布线不合理,电荷泵效率低; 解决:更换 22μF 低 ESR 泵电容,输出端添加 100μF+0.1μF 滤波电容,优化布线缩短走线。 2. 运放输出失真、振荡 问题:DAC 输出正常,但运放输出失真、无法满幅输出; 原因:单电源供电、反馈电阻不合理、电源滤波不足; 解决:为运放提供 ±5V 双电源,重新计算反馈电阻设置合理放大倍数,电源引脚添加去耦电容。 3. 数字干扰模拟电路,DAC 输出噪声大 问题:STM32 运行时,DAC 输出有明显噪声,可调电源纹波大; 原因:数字 / 模拟地共地不合理、布线交叉干扰; 解决:4 层板单独划分地层,数字 / 模拟地分区单点共地,数字线远离模拟线,添加磁珠隔离。 4. 线性稳压器发热严重 问题:LM1117-5.0 发热严重,甚至过热保护; 原因:输入输出压差大(12V→5V,压差 7V)、负载电流大; 解决:增加开关电源预降压(12V→7V),减少线性稳压器功耗,添加散热片。 八、心得体会与建议通过本次项目设计,我完整完成了从硬件到软件的整个开发过程,收获很多。 在硬件上,我学会了稳压电源、负电源、运放和数模混合电路的设计,也懂得了合理布线、接地和滤波对系统稳定性的重要性。在软件上,掌握了 STM32 的 GPIO、DAC 驱动以及 PA8 的 PWM 波形输出编程,实现了电压调节与 PWM 控制的结合。 调试过程中遇到的纹波、干扰、发热、PWM 无输出等问题,让我学会了耐心排查和实际解决问题,提升了动手和工程实践能力。 同时也发现本项目还有很多可以优化的地方,比如增加电压显示、加入过流保护、优化电源效率等。这次设计让我明白理论必须结合实践,也为今后的电子设计积累了宝贵经验。    
|