【星火计划】基于HK32F030MF4P6的低成本舵机测试仪设计与实现最近在玩航模和机器人经常需要调试舵机。网上卖的舵机测试仪动辄几十块功能也就那样。作为一个爱折腾的电子爱好者我寻思着自己做一个成本还能压到最低。于是就有了这个小项目——一个总成本不到5块钱的舵机测试仪核心用的是一颗国产的HK32F030MF4P6单片机。这个测试仪功能一点不弱能输出4路PWM信号支持50Hz的模拟舵机信号和125Hz的数字舵机信号有手动调节、自动旋转和回中模式还能用数码管实时显示当前的脉冲宽度。无论是调试普通的舵机还是测试无刷电调都够用了。下面我就把这个项目的硬件设计、软件思路和制作过程掰开揉碎了讲给你听。就算你是刚接触嵌入式的新手跟着做也能做出来。1. 项目核心硬件选型与成本控制做这个项目的初衷之一就是“低成本”。咱们先来看看怎么用最少的钱凑齐所有必需的零件。1.1 主控芯片HK32F030MF4P6整个项目的大脑我选择了航顺芯片的HK32F030MF4P6。这是一颗基于ARM Cortex-M0内核的32位单片机性价比极高。为什么选它首先当然是便宜单价不到1块钱。其次它性能足够主频最高32MHz内存和Flash也够用最关键的是它有多路定时器可以非常方便地生成我们需要的PWM信号。对于这种控制类的小项目它再合适不过了。它的任务读取电位器的旋钮位置、处理按键操作、计算并生成精确的PWM脉冲、驱动数码管显示。基本上所有的“思考”和“控制”工作都由它来完成。1.2 其他关键元件清单与作用除了主控其他元件也都是常见的廉价货。我把它们列了个表你可以一目了然地看到每样东西是干嘛的以及大概花了多少钱。元件数量单价约在项目中的作用HK32F030MF4P61片0.9元主控MCU负责所有逻辑与信号生成0.28寸4位共阳数码管1个0.8元显示当前的PWM脉冲宽度值单位微秒TM1637驱动芯片1片0.5元驱动数码管节省MCU的IO口简化编程6x6mm轻触开关2个0.1元“模式切换”键和“频率切换”键立式两脚轻触开关1个0.1元电源开关AMS1117-3.3稳压芯片1片0.15元将输入的5-6V电压稳定到3.3V给MCU和部分电路供电AO3400 MOS管2个0.2元作为电平转换或电源开关使用增强驱动能力或隔离10k立式可调电位器1个0.4元手动模式下通过旋转来调节PWM脉冲宽度电阻电容等若干0.5元构成电源滤波、上拉下拉、限流等基础电路注意以上价格是当时的市场参考价可能会有浮动。PCB和外壳是利用了立创的免费打样服务所以没算进成本里这里再次感谢立创EDA提供的便利。总成本核算把所有元件的费用加起来大约是4元左右。这可比市面上成品便宜太多了而且自己做的用起来更有成就感。1.3 电路设计思路整个电路的框架很清晰我画了个简单的框图帮你理解5-6V输入 │ ▼ ┌───AMS1117-3.3───┐ │ │ │ ▼ ▼ ▼ 电源开关 MCU 其他3.3V器件 (HK32F030) │ ┌────────┼────────┐ │ │ │ ▼ ▼ ▼ 电位器 TM1637 PWM输出 (ADC读取) 驱动数码管 (4路) │ ┌────────┼────────┐ │ │ │ ▼ ▼ ▼ 按键1 按键2 AO3400等 (模式切换)(频率切换) 驱动/隔离电源部分输入5-6V可以直接用舵机测试常用的BEC或电池经过AMS1117-3.3降压得到稳定的3.3V给MCU和数码管驱动芯片供电。信号输入电位器连接到MCU的一个ADC模数转换引脚。MCU通过读取ADC值就知道旋钮转到了什么位置从而对应改变PWM的宽度。两个按键连接到MCU的普通IO口配置成输入模式并启用内部上拉电阻。用来切换工作模式和信号频率。信号输出与显示PWM输出MCU的定时器产生4路PWM信号经过AO3400等电路进行适当的电平转换或驱动增强后输出给舵机。数码管显示MCU通过两根线CLK, DIO与TM1637芯片通信控制它来驱动4位数码管显示当前的脉冲宽度比如“1500”表示1500微秒。2. 核心原理PWM信号与舵机控制要想做好这个测试仪必须得明白舵机是怎么被控制的。这里的关键就是PWM脉冲宽度调制信号。2.1 舵机需要什么样的PWM舵机不是看你给的电平高低而是看你给的脉冲的宽度。它期待一个周期性的信号在每个周期里都有一个高电平脉冲。周期频率常见的有50Hz周期20ms和125Hz周期8ms。50Hz通常用于模拟舵机125Hz用于反应更快的数字舵机。我们的测试仪通过按键可以切换这两种频率。脉冲宽度高电平时间这个宽度决定了舵机转动的角度。通常范围是800微秒us到2200微秒us。800us对应舵机的最小角度比如0度或-45度取决于舵机。1500us对应舵机的中间位置中位。2200us对应舵机的最大角度比如180度或45度。所以我们的MCU任务就是产生一个周期固定20ms或8ms但高电平脉冲宽度可在800-2200us之间精确变化的方波信号。2.2 HK32F030如何产生PWMHK32F030内部有高级定时器和通用定时器它们都能很方便地产生PWM。我们以通用定时器为例说说思路初始化定时器设置定时器的时钟源、预分频器PSC和自动重装载值ARR。ARR决定了计数器的上限和时钟频率一起共同决定了PWM的周期。比如要产生50Hz20ms周期的PWM如果系统时钟是32MHz经过适当的分频让计数器每计数一次代表1微秒那么ARR就需要设置为20000对应20000us即20ms。配置PWM输出通道将某个定时器的通道比如TIM1_CH1对应的GPIO引脚设置为复用推挽输出模式。设置捕获/比较寄存器CCRx这个值决定了高电平脉冲的宽度。定时器从0开始计数当计数值小于CCRx时输出高电平大于等于CCRx但小于ARR时输出低电平。所以改变CCRx的值就等于改变了脉冲宽度。当CCRx1500时就输出了1500us的高电平脉冲。动态调节在程序里我们根据电位器ADC的读数映射出一个800到2200之间的值然后实时更新到对应定时器的CCRx寄存器里PWM的脉宽就跟着变了。3. 软件设计让硬件“活”起来硬件搭好了接下来就是写程序让整个系统按照我们的想法工作。3.1 程序主流程程序跑起来后会一直在一个大循环里做以下几件事int main(void) { // 1. 初始化所有硬件 System_Init(); // 系统时钟、延时函数初始化 GPIO_Init(); // 按键、LED指示等GPIO初始化 ADC_Init(); // 初始化ADC用于读取电位器电压 TIM_PWM_Init(); // 初始化定时器配置PWM输出 TM1637_Init(); // 初始化数码管驱动芯片 // 2. 变量定义 uint16_t adc_value 0; uint16_t pulse_width_us 1500; // 默认脉宽为中位1500us uint8_t mode MODE_MANUAL; // 默认手动模式 uint8_t freq FREQ_50HZ; // 默认50Hz频率 // 3. 主循环 while(1) { // 3.1 扫描按键更新模式和频率 mode Check_Mode_Key(mode); freq Check_Freq_Key(freq); // 3.2 根据当前模式计算目标脉冲宽度 if(mode MODE_MANUAL) { // 手动模式读取电位器ADC值映射到800-2200us adc_value ADC_GetValue(); pulse_width_us Map_ADC_to_Pulse(adc_value); // 例如800 adc_value * 1400 / 4095 } else if(mode MODE_AUTO) { // 自动模式让脉宽在800-2200之间循环变化模拟舵机自动来回转 pulse_width_us Auto_Sweep_Logic(); } else if(mode MODE_CENTER) { // 回中模式固定输出1500us pulse_width_us 1500; } // 3.3 根据选择的频率更新定时器的ARR值改变周期 Update_PWM_Frequency(freq); // 3.4 更新PWM的脉宽更新CCRx寄存器 Update_PWM_Width(pulse_width_us); // 3.5 更新数码管显示 TM1637_Display(pulse_width_us); // 3.6 加入一小段延时避免程序跑得太快 Delay_ms(10); } }3.2 关键功能代码片段这里挑几个核心的函数看看具体怎么实现。1. 初始化PWM输出以TIM1通道1为例void TIM1_PWM_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct; TIM_OCInitTypeDef TIM_OCInitStruct; // 1. 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_TIM1, ENABLE); // 2. 配置GPIOA.8为复用推挽输出TIM1_CH1 GPIO_InitStruct.GPIO_Pin GPIO_Pin_8; GPIO_InitStruct.GPIO_Mode GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStruct); // 3. 配置定时器基础产生50Hz PWM (周期20ms) // 假设系统时钟32MHz预分频32-1则定时器时钟为1MHz (1us计数一次) TIM_TimeBaseStruct.TIM_Prescaler 32 - 1; TIM_TimeBaseStruct.TIM_Period 20000 - 1; // 自动重装载值对应20000us 20ms TIM_TimeBaseStruct.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseStruct.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM1, TIM_TimeBaseStruct); // 4. 配置PWM模式 TIM_OCInitStruct.TIM_OCMode TIM_OCMode_PWM1; // PWM模式1 TIM_OCInitStruct.TIM_OutputState TIM_OutputState_Enable; // 使能输出 TIM_OCInitStruct.TIM_Pulse 1500; // 初始脉冲宽度1500us (CCR1值) TIM_OCInitStruct.TIM_OCPolarity TIM_OCPolarity_High; // 输出高电平有效 TIM_OC1Init(TIM1, TIM_OCInitStruct); // 初始化通道1 // 5. 使能定时器 TIM_Cmd(TIM1, ENABLE); TIM_CtrlPWMOutputs(TIM1, ENABLE); // 对于高级定时器TIM1需要此命令 }提示TIM_Pulse这个成员就是设置捕获/比较寄存器CCR1的值改变它就能改变脉宽。TIM_Period是ARR寄存器的值改变它就能改变PWM的频率周期。2. 按键扫描与模式切换按键处理我一般用状态机的方式可以有效地消抖。uint8_t Check_Mode_Key(uint8_t current_mode) { static uint8_t key_state 0; static uint32_t press_time 0; if(GPIO_ReadInputDataBit(MODE_KEY_PORT, MODE_KEY_PIN) 0) { // 按键按下低电平 if(key_state 0) { // 首次检测到按下 key_state 1; press_time Get_Tick(); // 记录按下时刻 } else if(key_state 1) { // 持续按下检查是否超过消抖时间 if((Get_Tick() - press_time) 20) { // 消抖约20ms key_state 2; // 确认为有效按下 } } } else { // 按键释放 if(key_state 2) { // 之前是有效按下状态 // 执行模式切换手动 - 自动 - 回中 - 手动... current_mode; if(current_mode MODE_CENTER) { current_mode MODE_MANUAL; } // 这里可以加一个提示音或LED闪烁表示模式已切换 } key_state 0; // 重置状态 } return current_mode; }4. 制作、调试与使用心得硬件焊接和程序烧录都完成后就可以上电测试了。上电流程接上5-6V电源打开电源开关。数码管应该会亮起显示一个初始值比如1500。此时系统处于手动模式、50Hz频率。你可以旋转电位器看到数码管显示的数字在800-2200之间变化同时用示波器或舵机连接输出口能看到PWM脉宽随之变化。按下“模式切换”键可以切换到自动旋转模式舵机会自己往复运动。再按一次切换到回中模式舵机会回到1500us的中位。在任何模式下按“频率切换”键可以在50Hz和125Hz之间切换。注意切换时舵机可能会抖动一下这是正常的。调试中可能遇到的坑PWM无输出首先检查定时器和对应GPIO的时钟是否使能。然后检查GPIO是否配置成了正确的复用功能模式。最后用万用表或示波器量一下引脚。数码管不亮或显示乱码检查TM1637的接线CLK DIO是否正确电源是否稳定。我遇到过因为上拉电阻没接导致通信失败的情况。电位器调节不线性ADC读取的值映射到脉宽时计算公式要仔细。确保ADC最大值比如4095对应2200us最小值0对应800us。可以用printf通过串口把ADC原始值和计算后的脉宽打印出来调试。舵机抖动或不动检查电源是否足够。单个舵机工作电流可能就几百mA电源要能提供。同时检查PWM信号的高电平电压是否是舵机需要的通常是3.3V或5V我们的电路通过AO3400做了电平转换应该能兼容5V舵机。这个自制的舵机测试仪我已经用了好一阵子调试四轴飞行器的舵机、测试新买的数字舵机都非常方便。最重要的是总共花了不到一顿早餐的钱就实现了一个实用的小工具这种成就感是买成品无法比拟的。希望这个详细的分享能帮你做出自己的那一台。