☰
STM32舵机时钟:基于PWM与RTC的机械指针时钟设计
2026/10/25 0:25:04 网站建设 项目流程

1. 背景与核心概念

在嵌入式开发中,将实时时钟(RTC)与舵机控制结合起来,是一个非常有代表性的复合型应用场景。它涉及STM32的系统时钟树配置、定时器PWM产生、RTC模块的使用,以及舵机驱动的精确控制。这类项目常见于智能家居中的模拟指针时钟、仿生机械臂的关节角度控制、太阳能追光系统的角度调整等。

“基于STM32舵机时钟”这个标题,背后隐藏着两条技术线索:时钟(时间基准)和舵机(角度输出)。时钟为系统提供时间信息,舵机则将时间信息转化为物理角度。比如,我们可以用两个舵机分别模拟时针和分针,通过STM32读取RTC的当前时间,计算出对应的角度,然后输出PWM驱动舵机旋转到指定位置。这样,一个电子时钟就变成了一个具有机械指针的实体时钟。

对于初学者来说,这个项目既能巩固STM32的基础外设操作(GPIO、定时器、RTC、中断),又能理解实时控制与机械运动的耦合。对于有经验的开发者,则可以在此基础上扩展更多功能,如温度补偿、远程时间同步、多舵机联动等。

本文将从零开始,完整讲解如何搭建一个基于STM32的舵机时钟系统。内容包括:舵机控制原理、STM32定时器PWM配置、RTC时钟读取与角度换算、完整代码实现、常见问题排查以及工程优化建议。无论你是刚开始学习STM32,还是想找一个综合项目练手,本文都能提供参考。

2. 环境准备与版本说明

2.1 硬件清单

  • 主控板:STM32F103C8T6(蓝色小开发板)或任意STM32系列(本例以F103为例,HAL库开发)
  • 舵机:SG90 9g微型舵机 × 2(一个用于时针,一个用于分针)
  • 外部RTC模块:DS3231(高精度,可选)或使用STM32内置RTC(需外部32.768kHz晶振)
  • 电源:5V 2A电源适配器,为舵机供电;STM32通过USB供电
  • 连接线:杜邦线若干
  • 可选:按键、OLED显示屏用于显示数字时间

2.2 软件环境

  • 开发IDE:Keil MDK 5.38(或STM32CubeIDE)
  • 固件库:STM32CubeF1 HAL库(版本1.8.5)
  • 配置工具:STM32CubeMX(用于生成初始化代码)
  • 串口调试工具:SSCOM或SecureCRT(用于输出调试信息)
  • 逻辑分析仪(可选,用于观察PWM波形)

版本说明:本例以STM32F103C8T6和Keil MDK 5环境为例,使用HAL库。如果你使用的是其他系列或标准库,原理相同,只需对应修改外设驱动代码。具体版本号应根据你实际使用的库文件确定,本文重点展示配置思路。

2.3 项目结构

Stm32_Servo_Clock/ ├── Core/ │ ├── Inc/ │ │ ├── main.h │ │ ├── rtc.h │ │ ├── servo.h │ │ └── timer.h │ └── Src/ │ ├── main.c │ ├── rtc.c │ ├── servo.c │ └── timer.c ├── Drivers/ │ └── STM32F1xx_HAL_Driver/ ├── MDK-ARM/(Keil工程文件) └── .ioc(STM32CubeMX配置文件)

3. 核心原理拆解

3.1 舵机控制原理

舵机是一种位置(角度)伺服驱动器。标准舵机(如SG90)通过PWM信号控制旋转角度。典型PWM周期为20ms(50Hz),脉宽(高电平时间)在0.5ms~2.5ms之间,对应0°~180°角度范围。具体对应关系因型号而异,SG90通常为:

  • 0.5ms → 0°
  • 1.0ms → 45°
  • 1.5ms → 90°
  • 2.0ms → 135°
  • 2.5ms → 180°

关键参数:PWM频率(50Hz)、占空比精度(通常8位或16位定时器)。STM32的定时器可以产生高精度的PWM,通过配置ARR(自动重装载值)和CCR(捕获比较值)来调整脉宽。

例如,设置定时器时钟为72MHz,预分频后得到1MHz的计数频率,则ARR=20000-1(20ms周期)。CCR = (脉宽_us) × 1MHz / 1us = 脉宽_us。对于0.5ms脉宽,CCR=500;2.5ms对应CCR=2500。

3.2 时钟系统与时间基准

STM32的时钟树提供了多种时钟源:HSE(外部高速晶振,通常是8MHz)、HSI(内部高速RC振荡器,8MHz)、LSE(外部低速晶振,32.768kHz)、LSI(内部低速RC振荡器,40kHz)。对于RTC(实时时钟),通常使用LSE(32.768kHz)以获得高精度时间基准。

RTC模块可以独立于主系统时钟运行,提供秒、分、时、日期等时间信息。通过初始化RTC并配置闹钟或中断,可以定期触发时间更新。

3.3 角度换算

假设时分针每走一圈,对应舵机旋转0°~360°。但标准舵机旋转范围只有0°~180°,因此时钟指针不能直接使用360°范围。常见实现方式有两种:

  1. 分针:使用一个舵机,每60分钟旋转360°(即每分钟6°)。但舵机只能0~180°,所以需要采用“周期复位”方式:每30分钟从0°走到180°,然后快速复位(或使用两个舵机交替)。
  2. 简化方案:仅显示小时和分钟,但使用两个舵机分别指示小时和分钟,其中小时舵机每12小时旋转360°,分钟舵机每60分钟旋转360°。同样受限于舵机旋转范围,实际工程中通常采用“相对角度”方式,即每次刷新时计算当前时间对应的角度,并控制在0°~180°内。例如,分钟舵机:分钟值 m (0-59) → 角度 = m * 3° (0°~177°),小时舵机:小时值 h (0-23) → 角度 = (h % 12) * 30° + m * 0.5° (0°~330°,但超出180°部分需处理)。更简单的方法是:分针单独用舵机,时针单独用舵机,但每个舵机只负责0~180°范围,通过取模运算实现。

本文采用实用方案:分针舵机直接映射0~180°(对应0~60分钟,即每分钟3°),小时舵机映射0~180°(对应0~12小时,即每小时15°,再加上分钟偏移)。这样两个舵机都能在0°~180°内工作,指针不会超出范围。

4. 完整实战案例

4.1 创建项目结构

使用STM32CubeMX创建一个新工程:

  • 选择芯片:STM32F103C8Tx
  • 配置时钟源:HSE 8MHz,LSE 32.768kHz(用于RTC)
  • 配置定时器:TIM2(产生PWM)通道1、2分别控制两个舵机
  • 配置RTC:激活时钟和日历,使用LSE
  • 配置USART1(用于调试输出)
  • 生成MDK-ARM工程代码

4.2 添加依赖或配置

在CubeMX中完成以下设置:

时钟树:

  • HSE → PLL → 72MHz(系统时钟SYSCLK)
  • LSE → RTC(32.768kHz)

TIM2:

  • 时钟源:内部时钟(72MHz)
  • 预分频器:72-1 → 1MHz计数频率
  • 自动重装载值:20000-1 → 20ms周期
  • 通道1:PWM输出,引脚PA0
  • 通道2:PWM输出,引脚PA1

RTC:

  • 激活时钟和日历
  • 格式:24小时制
  • 不使用闹钟(可后期扩展)

USART1:

  • 模式:异步,波特率115200
  • 引脚:PA9(TX), PA10(RX)

4.3 编写核心代码

4.3.1 舵机控制模块(servo.c)
#include "servo.h" #include "tim.h" // 角度转换为CCR值(0~180°对应脉宽500~2500us) // 线性映射:angle = 0 -> ccr = 500; angle = 180 -> ccr = 2500 uint16_t Servo_AngleToCCR(uint8_t angle) { if (angle > 180) angle = 180; return (uint16_t)(500 + (uint16_t)angle * (2000 / 180)); } // 设置角度(舵机索引0或1) void Servo_SetAngle(uint8_t servoIndex, uint8_t angle) { uint16_t ccr = Servo_AngleToCCR(angle); if (servoIndex == 0) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, ccr); } else if (servoIndex == 1) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, ccr); } } // 初始化PWM void Servo_Init(void) { HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2); // 初始位置归零 Servo_SetAngle(0, 0); Servo_SetAngle(1, 0); HAL_Delay(1000); // 等待舵机到位 }
4.3.2 RTC时间读取模块(rtc.c)
#include "rtc.h" #include "usart.h" RTC_TimeTypeDef gTime; RTC_DateTypeDef gDate; // 读取当前时间(秒、分、时) void RTC_ReadTime(void) { HAL_RTC_GetTime(&hrtc, &gTime, RTC_FORMAT_BIN); HAL_RTC_GetDate(&hrtc, &gDate, RTC_FORMAT_BIN); } // 打印时间到串口(调试用) void RTC_PrintTime(void) { char buf[64]; sprintf(buf, "Time: %02d:%02d:%02d\r\n", gTime.Hours, gTime.Minutes, gTime.Seconds); HAL_UART_Transmit(&huart1, (uint8_t*)buf, strlen(buf), 1000); } // 初始化RTC(CubeMX已生成,此函数仅封装) void RTC_Init(void) { // CubeMX已自动初始化,这里可添加校准等 // 例如:__HAL_RCC_RTC_ENABLE(); 但通常在HAL_RTC_MspInit中已处理 }
4.3.3 主逻辑(main.c)
#include "main.h" #include "rtc.h" #include "servo.h" #include "usart.h" // 角度映射函数 // 分钟:0~59 -> 0~177° uint8_t MinuteToAngle(uint8_t minute) { return (uint8_t)(minute * 3); } // 小时:0~23 -> 0~180°(小时+分钟偏移,确保12小时制) uint8_t HourToAngle(uint8_t hour, uint8_t minute) { uint8_t h12 = hour % 12; // 每小时15°,每分钟0.5°,但为了简化,每3分钟加1°(近似) // 这里使用精确公式:角度 = h12 * 15 + minute * 0.5 float angle = h12 * 15.0f + minute * 0.5f; if (angle > 180) angle = 180.0f; return (uint8_t)angle; } int main(void) { HAL_Init(); SystemClock_Config(); // 由CubeMX生成 MX_GPIO_Init(); MX_USART1_UART_Init(); MX_RTC_Init(); MX_TIM2_Init(); Servo_Init(); RTC_Init(); printf("STM32 Servo Clock Started\r\n"); uint8_t lastMinute = 0xFF; // 上一次的分钟值,用于检测变化 while (1) { RTC_ReadTime(); RTC_PrintTime(); // 每分钟更新一次舵机角度(避免频繁刷新导致抖动) if (gTime.Minutes != lastMinute) { lastMinute = gTime.Minutes; // 设置分针舵机(索引0) Servo_SetAngle(0, MinuteToAngle(gTime.Minutes)); // 设置时针舵机(索引1) Servo_SetAngle(1, HourToAngle(gTime.Hours, gTime.Minutes)); printf("Servo Updated: Minute=%d°, Hour=%d°\r\n", MinuteToAngle(gTime.Minutes), HourToAngle(gTime.Hours, gTime.Minutes)); } HAL_Delay(1000); // 每秒检查一次,但只在分钟变化时更新 } }

4.4 运行与验证

  1. 使用Keil编译工程,下载到STM32开发板。
  2. 连接串口线(PA9 TX, PA10 RX),打开串口助手,波特率115200。
  3. 上电后,串口会输出当前时间,且每过一分钟,舵机角度会更新。
  4. 观察两个舵机(分针、时针)是否指向正确角度。例如,时间12:00时,两个舵机都应指向0°;时间12:30时,分针指向180°,时针指向15°(15°对应30分钟偏移)。

4.5 结果说明

  • 分针舵机每过一分钟转动3°,从0°到177°(对应0~59分钟),然后下一分钟从0°重新开始(由于角度映射取模,实际每分钟角度计算为 minute*3,当minute=60时,minute会重置为0,但我们的代码只在分钟变化时更新,因此分针会从177°直接跳转到0°,产生一个快速回零动作。这是硬件限制所致,可以通过在180°时使用舵机“反向旋转”或设置机械限位来改善,但本文示例保留此现象以简化代码)。
  • 时针舵机每小时转动15°,加上每分钟0.5°的偏移,在12小时内从0°到180°。例如,12:00时0°,1:00时15°,6:00时90°,12:00时180°(但实际12:00应归零?我们用的是12小时制,12:00实际是0°,代码中h12=0,因此角度=0°+minute*0.5,当minute=0时角度0°;当minute=59时角度约29.5°,与12:00不同。但如果直接使用h12,12:00对应0°,12:30对应15°,符合真实时钟——时针指向12和1之间。所以实际效果是正常的。)

对于更精确的时钟,可以增加一个外部RTC模块(如DS3231)以消除温度漂移,并添加一个按键用于时间设置。

5. 常见问题与排查思路

问题现象常见原因解决思路
舵机不转动供电不足(舵机需要5V,STM32板载3.3V无法驱动)外接5V电源,共地,确保电流>500mA
舵机抖动或异响PWM频率不是50Hz;或占空比计算错误导致脉宽越界用逻辑分析仪/示波器测量PWM波形,确认频率50Hz,脉宽在0.5~2.5ms
时间不更新RTC时钟源未配置(LSE未启用或晶振不起振)检查LSE晶振及电容,使用CubeMX确认RTC时钟选择;可在初始化后读取RTC状态
角度与预期不符角度映射公式错误(如分钟角度超过180°)调试时通过串口打印计算出的角度值和换算前的分钟值,确认映射范围
程序卡死或跑飞定时器中断冲突或堆栈溢出检查HAL_Delay是否在中断中调用;减小数组中变量占用;使用调试器单步
外部RTC(DS3231)无法通信I2C引脚配置错误或上拉电阻缺失确认I2C引脚(PB6/PB7)是否被复用,外接4.7kΩ上拉电阻
舵机响应延迟更新频率过高(如每秒都更新)只在时间变化时更新(如每分钟一次),避免在中断中频繁调用HAL_TIM_SET_COMPARE

6. 最佳实践与工程建议

6.1 电源与驱动

  • 舵机瞬间电流较大(SG90启动时可达500mA),绝对不能直接从STM32开发板的3.3V引脚取电。应使用外部5V电源供电,并将舵机电源地与STM32的GND共地。
  • 如果需要同时驱动多个舵机,建议使用舵机驱动板或独立的电源模块,避免电压跌落导致STM32复位。
  • 可以在舵机电源输入端并联一个470μF/16V电解电容,吸收电流尖峰。

6.2 代码健壮性

  • 角度值应做边界检查(0~180),防止CCR溢出。
  • 使用HAL_Delay或定时器中断来避免阻塞,但本例中每分钟更新一次,1秒的HAL_Delay可以接受。更高效的方式是使用RTC秒中断,在中断中标记更新标志,主循环检测标志后执行角度计算。
  • 考虑加入“舵机缓动”功能,即每次角度变化不大时,分多次小步更新,使指针运动更平滑,避免突然跳变。

6.3 时间精度

  • 系统默认使用LSE(32.768kHz)作为RTC时钟源,精度通常优于±20ppm,每天误差约1.7秒。如果要求更高(如年误差<1分钟),可选用带温度补偿的RTC芯片(DS3231,精度±2ppm,年误差约1分钟)。
  • 可以在代码中实现时间校准接口,通过串口接收NTP时间或手动设置。

6.4 功能扩展

  • 添加OLED显示:同时显示数字时间,提升用户体验。
  • 添加WiFi模块(ESP8266/ESP32):自动获取网络时间,同步RTC。
  • 添加闹钟功能:在指定时间触发舵机快速摆动或蜂鸣器。
  • 使用更高精度的舵机(如数字舵机,支持更高分辨率PWM),提升指针定位精度。

6.5 生产环境注意事项

  • 如果项目需要量产,务必进行电磁兼容性(EMC)测试,尤其是舵机产生的噪声可能干扰晶振。
  • 程序应加入看门狗(IWDG),防止舵机卡死导致系统死机。
  • 使用EEPROM或Flash保存RTC校准参数,避免每次上电都需要重新校准。

7. 总结与学习路线

通过本文,你亲手搭建了一个完整的“STM32舵机时钟”项目,涉及了:

  • STM32时钟树配置(系统时钟与RTC时钟)
  • 定时器PWM输出驱动舵机(SG90)
  • RTC实时时钟的读取与时间转换
  • 角度计算与映射算法
  • 串口调试与问题排查技巧

下一步可以继续学习的方向:

  • 深入学习STM32的定时器高级功能(如互补输出、死区插入),用于控制更大功率的舵机或直流电机。
  • 学习FreeRTOS系统,将任务分解为:RTC任务、舵机控制任务、显示任务,提高系统实时性。
  • 探索更复杂的机械结构,如并联机械臂的多舵机协调控制。
  • 结合物联网云平台,实现远程时钟校准与状态监控。

如果你在实践过程中遇到问题,欢迎在评论区留言,我会尽力解答。如果本文对你有帮助,可以收藏备用,也欢迎分享给更多嵌入式爱好者。动手试一试,你会发现将时间“变成”一个真实的机械指针,是一件非常有成就感的事情。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询