公司动态

STM32舵机时钟项目:基于PWM与RTC的完整实现

📅 2026/9/1 19:15:56
STM32舵机时钟项目:基于PWM与RTC的完整实现
1. 背景与核心概念1.1 什么是“基于STM32的舵机时钟”很多嵌入式初学者在学完STM32基础外设后会想找一个既有技术深度又能激发兴趣的综合项目。“基于STM32舵机时钟”就是一个非常经典的组合——它把STM32的定时器PWM输出、RTC实时时钟、中断处理、角度换算等知识点融合在一起最终通过舵机模拟时钟指针的旋转实现一个动态的物理时钟。这个项目并不是用数码管或LCD显示时间而是让两只或三只舵机分别代表秒针、分针和时针通过精确控制舵机旋转角度来指示当前时间。当RTC走时主控芯片计算当前时刻各指针应该指向的角度然后通过PWM驱动舵机转到对应位置。这种设计既有视觉冲击力又能锻炼系统级编程思维非常适合作为STM32进阶实战项目。1.2 舵机的基本工作原理舵机Servo是一种位置角度伺服驱动器广泛用于机器人、航模、小型机械臂等场景。常见的模拟舵机如SG90、MG996R通过一根PWM信号线控制角度。PWM的周期通常是20ms50Hz其中高电平脉冲宽度在0.5ms~2.5ms之间对应舵机0°~180°或0°~270°的旋转范围。例如1.5ms脉宽对应中立位置90°。不同品牌和型号的脉宽-角度映射可能略有差异但原理一致。STM32通过定时器输出PWM波只要配置好定时器的时钟、分频和比较值就能精确产生所需的脉宽从而控制舵机角度。1.3 为什么时钟模块需要高精度时钟是时间的度量误差必须可控。STM32内部通常有多个定时器它们可以基于系统时钟SYSCLK或外部晶振工作。如果使用内部RC振荡器HSI精度一般只有±1%~±2%不能满足长时间走时需求。因此在时钟项目中必须使用外部晶振HSE如8MHz作为系统时钟源并启用RTC模块通常使用LSE 32.768kHz晶振来保证走时精度。此外PWM信号的频率和占空比也需要稳定否则舵机会抖动影响指针定位。2. 环境准备与硬件选型2.1 硬件清单在开始写代码之前先列出本项目所需的硬件硬件推荐型号数量说明主控板STM32F103C8T6蓝色小板1性价比高资源足够舵机SG90 (9g)2~3秒针、分针/时针注意力矩外部晶振8MHz 32.768kHz各1确保系统时钟和RTC精度电源模块5V/2A USB供电或锂电池1舵机需要5V供电STM32可3.3V电容470μF电解电容1舵机启动时电流大需滤波面包板及杜邦线若干搭建电路下载调试器ST-Link V21烧录与调试注意事项舵机工作时瞬间电流可达几百毫安如果直接从STM32的3.3V引脚供电会拉低电压导致复位。必须使用独立的5V电源给舵机供电且STM32与舵机共地。2.2 软件环境开发环境Keil MDK 5或STM32CubeIDE固件库STM32标准外设库V3.5或HAL库本文以标准库为例逻辑清晰调试工具ST-Link Utility用于烧录或直接通过IDE下载串口助手用于打印调试信息可选2.3 项目结构Project/ ├── User/ │ ├── main.c │ ├── stm32f10x_it.c │ └── stm32f10x_conf.h ├── FWlib/ │ ├── src/ (标准库源文件) │ └── inc/ (头文件) ├── Output/ │ └── (编译生成文件) └── README.md在Keil中新建工程选择STM32F103C8添加标准库文件配置时钟为外部晶振8MHz系统时钟倍频到72MHzPLL。3. STM32定时器与PWM原理解析3.1 定时器时钟源STM32F103系列有多个定时器高级定时器TIM1、TIM8、通用定时器TIM2~TIM5和基本定时器TIM6、TIM7。通用定时器可以输出PWM且相互独立。我们使用TIM2或TIM3来产生舵机控制信号。定时器的时钟来自APB1或APB2总线。例如TIM2挂载在APB1上如果APB1预分频为2则TIM2的时钟为72MHz当系统时钟为72MHz时。我们需要通过分频得到50Hz的PWM基频。3.2 计算PWM参数舵机需要的PWM周期为20ms50Hz。假设定时器时钟为72MHz经过分频后得到计数时钟。公式如下定时器溢出时间 (预分频值 1) × (自动重装载值 1) / 定时器时钟频率设预分频PSC 71则计数时钟频率 72MHz / (711) 1MHz即1μs计数一次。要得到20ms周期自动重装载值ARR应为ARR 20ms / 1μs - 1 20000 - 1 19999此时PWM频率为 1MHz / 20000 50Hz符合要求。接下来控制占空比即改变比较值CCR。例如脉宽1.5ms对应CCR 1500因为1μs一个计数。SG90舵机常用脉宽范围是0.5ms~2.5ms对应CCR 500~2500角度0°~180°。3.3 定时器配置代码标准库// 文件timer.c #include stm32f10x_tim.h void TIM2_PWM_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; // 1. 使能时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); // 2. 配置PA0为复用推挽输出TIM2_CH1 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // 3. 配置定时器时基 TIM_TimeBaseStructure.TIM_Period 19999; // 自动重装载值 TIM_TimeBaseStructure.TIM_Prescaler 71; // 预分频 TIM_TimeBaseStructure.TIM_ClockDivision 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure); // 4. 配置PWM模式1 TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 1500; // 初始脉宽1.5ms90° TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM2, TIM_OCInitStructure); // 5. 使能预装载 TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM2, ENABLE); // 6. 启动定时器 TIM_Cmd(TIM2, ENABLE); }代码解释第3步设置ARR19999PSC71达到20ms周期。第4步设置比较值初始为1500对应1.5ms脉宽舵机停在90°。后续可通过修改TIM2-CCR1的值来改变角度。4. 舵机控制基础实验4.1 角度与脉宽的换算不同舵机的脉宽范围可能不同但一般SG90的典型值如下0° → 0.5ms → CCR 50090° → 1.5ms → CCR 1500180°→ 2.5ms → CCR 2500因此角度θ0~180对应的CCR为CCR 500 (θ / 180) * 2000或更精确地线性映射。4.2 编写角度控制函数// 文件servo.c #include stm32f10x_tim.h #define SERVO_MIN_PULSE 500 // 0.5ms #define SERVO_MAX_PULSE 2500 // 2.5ms #define SERVO_PERIOD 20000 // 20ms void Servo_SetAngle(uint8_t channel, uint16_t angle) { uint16_t pulse; // 限制角度范围0~180 if (angle 180) angle 180; // 线性映射 pulse SERVO_MIN_PULSE (uint32_t)angle * (SERVO_MAX_PULSE - SERVO_MIN_PULSE) / 180; // 更新比较寄存器 if (channel 1) TIM_SetCompare1(TIM2, pulse); else if (channel 2) TIM_SetCompare2(TIM2, pulse); // 其他通道类似 }主函数中调用示例int main(void) { TIM2_PWM_Init(); while (1) { Servo_SetAngle(1, 0); // 转到0° Delay_ms(1000); Servo_SetAngle(1, 90); // 转到90° Delay_ms(1000); Servo_SetAngle(1, 180); // 转到180° Delay_ms(1000); } }运行效果舵机依次转到0°、90°、180°说明PWM控制成功。4.3 关于舵机抖动问题的处理初学者常遇到舵机抖动原因通常是电源不稳定舵机供电不足加470μF电容可缓解。PWM频率偏移定时器分频或重装载值计算错误导致周期不是20ms。程序卡顿如果主循环中使用了HAL_Delay或其他阻塞函数且PWM更新不及时也会抖动。建议使用定时器中断或DMA来更新占空比但简单项目用轮询也可。5. 基于RTC的时钟舵机控制5.1 RTC配置与时间读取STM32F103的RTC模块需要独立的32.768kHz晶振LSE作为时钟源。配置步骤使能电源和备份域时钟。选择LSE作为RTC时钟。设置RTC预分频器得到1Hz秒中断。读取RTC计数器秒数并转换为当前时间。标准库RTC配置示例简化// 文件rtc.c #include stm32f10x_rtc.h #include stm32f10x_pwr.h #include stm32f10x_bkp.h void RTC_Configuration(void) { // 使能PWR和BKP时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); PWR_BackupAccessCmd(ENABLE); // 允许访问备份域 // 复位备份域可选会清除RTC和BKP数据 // BKP_DeInit(); // 使能LSE外部低速晶振32.768kHz RCC_LSEConfig(RCC_LSE_ON); while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) RESET); // 等待稳定 // 选择LSE作为RTC时钟 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); RCC_RTCCLKCmd(ENABLE); // 等待RTC寄存器同步 RTC_WaitForSynchro(); // 设置预分频器32.768kHz / 32767 ≈ 1Hz实际32767132768 RTC_SetPrescaler(32767); // 等待写操作完成 RTC_WaitForLastTask(); } // 设置初始时间例如12:00:00 void RTC_SetTime(uint8_t hour, uint8_t min, uint8_t sec) { uint32_t seconds hour * 3600 min * 60 sec; RTC_SetCounter(seconds); RTC_WaitForLastTask(); } // 读取当前时间秒数 uint32_t RTC_GetTime(void) { return RTC_GetCounter(); }5.2 时间转换与角度计算拿到秒数后需要转换为时分秒然后计算每个指针的角度。秒针每秒转6°360°/60秒分针每分钟转6°但秒针走1分钟分针走0.1°6°/60秒时针每小时转30°每分钟转0.5°每秒转0.00833°为了精确我们可以在每次更新时计算当前秒、分、时并映射到舵机角度。void Clock_Update(void) { uint32_t total_seconds RTC_GetCounter(); uint8_t hour (total_seconds / 3600) % 24; uint8_t minute (total_seconds % 3600) / 60; uint8_t second total_seconds % 60; // 秒针角度0~59 - 0°~354°但舵机通常只能0~180°因此需要特殊处理 // 这里我们用一个舵机模拟秒针但180°只能覆盖半圈所以需要两个舵机或使用360°舵机。 // 实际项目中可以用两个舵机分别代表分针和时针秒针用LED或数码管显示。 // 为简化我们只控制分针和时针秒针用串口输出。 uint16_t second_angle (uint16_t)(second * 6); // 0~354 uint16_t minute_angle (uint16_t)(minute * 6 second * 0.1); // 0~354 uint16_t hour_angle (uint16_t)(hour % 12 * 30 minute * 0.5); // 0~360 // 由于舵机通常0~180°我们需将角度映射到0~180°例如只显示上半圈或者用减速齿轮 // 此处我们假设分针舵机覆盖0~180°对应实际0~180°时分针各用一只舵机。 // 实际角度可能超过180°需要做模运算或使用360°舵机。这里仅演示思路。 if (minute_angle 180) minute_angle 0; // 简单处理 if (hour_angle 180) hour_angle 0; Servo_SetAngle(1, minute_angle); // 分针 Servo_SetAngle(2, hour_angle); // 时针 }注意普通舵机旋转范围只有180°无法完整显示360°的时钟盘。解决方法有使用360°连续旋转舵机但需要额外控制方式。使用两个舵机分别驱动分针和时针的“半圈”显示配合机械结构。更常见的做法是只制作一个半圆时钟或者使用步进电机。在实际项目中可以简化只显示小时和分钟且只显示0~180°范围比如6点~12点。本文重点在于原理具体机械设计可根据需求调整。5.3 主循环整合int main(void) { Delay_Init(); // 使用SysTick做延时 TIM2_PWM_Init(); RTC_Configuration(); // 设置初始时间例如通过串口或按键此处写死为12:00:00 RTC_SetTime(12, 0, 0); // 开启RTC秒中断可选用于定时更新 // 此处采用主循环轮询每100ms读取一次RTC检测秒变化 uint32_t last_seconds 0; while (1) { uint32_t now RTC_GetTime(); if (now ! last_seconds) { last_seconds now; Clock_Update(); } Delay_ms(100); // 避免频繁查询 } }6. 常见问题与排查思路6.1 舵机不转或抖动问题现象常见原因解决思路舵机完全不动电源未供或共地问题检查舵机电源线红线5V棕线GND确保与STM32共地舵机轻微抖动但不转PWM信号频率不对用示波器或逻辑分析仪测量PWM波形确认周期20ms脉宽在500~2500μs舵机只转到一个角度占空比固定检查代码中是否更新了CCR或者定时器未使能预装载供电后STM32反复复位舵机启动电流过大为舵机单独供电加470μF电解电容不要共用3.3V稳压6.2 RTC不走时或时间不准问题现象常见原因解决思路RTC不走时LSE晶振未起振检查晶振、负载电容通常6~12pF用示波器测32.768kHz引脚时间走快或走慢晶振不准或分频系数错误确认RTC预分频值32767产生1Hz检查LSE频率是否准确掉电后时间丢失未使用备份电池添加纽扣电池CR1220连接到VBAT引脚或者每次上电重新设置时间6.3 角度映射不准确不同舵机的脉宽范围可能不同需要实测。可以用示波器测量脉宽调整SERVO_MIN_PULSE和SERVO_MAX_PULSE。另外机械臂安装时需校准初始位置。7. 最佳实践与工程建议7.1 电源设计舵机供电必须独立推荐使用LM2596降压模块或5V/2A适配器。在舵机电源输入端并联一个大电容470μF以上吸收尖峰电流。STM32与舵机共地但信号线可以串联一个100Ω电阻减少干扰。7.2 代码模块化将PWM、RTC、舵机控制封装成独立模块servo.c, rtc.c, clock.c主函数只负责逻辑组合。这样便于调试和复用。7.3 异常处理在RTC初始化时增加超时判断防止晶振不起振导致死循环。舵机控制函数中若角度超出范围应做限幅处理避免脉宽超出舵机允许范围可能损坏舵机。7.4 使用定时器中断更新为了避免主循环阻塞导致PWM更新延迟建议使用定时器更新中断每20ms触发一次在中断中读取当前时间并计算角度。但要注意中断服务程序不能耗时过长角度计算可放在主循环。7.5 生产环境注意事项如果产品需要批量生产建议选用工业级晶振温度特性好和带过流保护的舵机驱动芯片。程序应加入看门狗防止跑飞导致舵机失控。修改RTC时间时先停止秒中断设置完成后重新开启避免竞争条件。8. 总结与拓展8.1 本文核心要点舵机通过PWM控制STM32定时器可精确产生PWM。时钟项目需要外部晶振保证RTC精度PWM周期20ms占空比线性映射角度。实际项目中普通舵机0~180°范围有限需要结合机械结构或使用360°舵机。电源独立、滤波电容、共地是硬件稳定的关键。8.2 下一步可以尝试的方向增加蓝牙模块如HC-05通过手机APP校准时间。使用OLED显示屏显示电子时间同时用舵机显示指针形成双模时钟。加入高精度DS3231 RTC模块免去晶振配置走时更准。用步进电机代替舵机实现360°无限制旋转配合驱动板可以实现更平滑的指针运动。8.3 动手实践建议如果你刚接触STM32建议先完成单独的PWM控制舵机实验再单独调试RTC最后合并。遇到问题不要怕多用示波器或逻辑分析仪观察波形。网上有很多现成的代码和电路图可以参考但一定要理解原理否则出了问题无从下手。希望本文能帮你顺利搭建起自己的舵机时钟项目如果对你有帮助可以收藏备用也欢迎在评论区交流排错经验。