news 2026/9/6 9:54:54

STM32F103ZET6驱动SG90舵机:从PWM原理到智能小车实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32F103ZET6驱动SG90舵机:从PWM原理到智能小车实战

简介:本资源是一套基于STM32F103ZET6主控芯片的SG90舵机精准控制实践项目,面向已掌握基础PWM输出与串口通信原理的单片机初学者,用于巩固外设协同开发能力并完成典型机电控制闭环训练。压缩包共80个文件(320KB),涵盖36个头文件(.h)定义外设寄存器、中断向量及模块接口,35个源文件(.c)实现GPIO初始化、SysTick定时、USART收发解析、PWM波形生成及SG90角度映射逻辑,另含Keil工程配置(.uvprojx/.uvoptx)、调试脚本(.bat)、固件镜像(.hex)及CMSIS标准库支持文件,结构完整、开箱即用。已有4997人学习下载,项目采用串口助手直接输入角度值(0°–180°)触发舵机响应,代码中已适配SG90脉宽特性,并对atoi转换警告作出明确说明,附带清晰模块划分(APP层含pwm/sg90/time等子目录),便于理解控制流程与快速调试。

1. 项目概述与硬件准备

玩过单片机的人很难绕开舵机,尤其SG90这种9g小舵机,几乎是每个嵌入式玩家入门必摸的元件。但真要说“用STM32F103ZET6把它控制好”,还真不是接三根线、写个PWM就完事的事——电压跌落、脉冲频率、定时器通道映射、上电姿态,随便一个坑都能让你多折腾半天。这篇文章就围绕这个项目把原理、配置、代码和调试手法一次说透。

先说结论:SG90是一个模拟舵机,靠周期20ms的PWM信号控制转角,而STM32F103ZET6作为Cortex-M3内核的主流型号,定时器资源非常丰富,随便挑一个通用定时器就能输出高精度PWM。整套方案做下来,既能理解舵机内部的工作原理,也能顺手掌握定时器PWM的底层配置方式,后续再做机械臂、云台、智能小车甚至简易机器人,这套东西都是绕不开的地基。

1.1 为什么选STM32F103ZET6这块板子

STM32F103ZET6属于F103系列里的“大个子”:STM32F103ZET6中的Z代表144引脚(LQFP144封装),E代表512KB Flash,T是LQFP封装代号,6则代表工作温度范围。相比F103C8T6这种“小蓝板”,ZET6最大的价值是引脚管够——144个引脚里有一大堆GPIO、5个USART、3个SPI、2个I2C,以及8个定时器。做舵机控制时,你完全可以把定时器PWM输出、按键输入、OLED显示、串口调试同时铺开,不需要为引脚打架发愁。

更重要的是,F103ZET6的定时器资源对多舵机场景非常友好。它内部有2个高级定时器TIM1和TIM8(各带6路PWM输出)、4个通用定时器TIM2/3/4/5(各带4路PWM输出)、2个基本定时器TIM6/7。理论上,你最多可以同时驱动几十路舵机——只要功率跟得上。所以拿这块板子入门,后面就算想做一个六自由度机械臂或者双舵机云台,硬件底子也完全够用。

我个人的建议是:如果只是单纯验证“转个角度”,用C8T6甚至最小系统板都行;但如果想通过这个项目把定时器、中断、串口、显示、外部扩展整个串起来,ZET6的开发板(尤其是带板载USB转串口、按键、LED和液晶接口的)会舒服得多,调试时不用来回飞线。

1.2 SG90舵机的基本参数与选型注意

SG90是TowerPro(辉盛)公司推出的9g微型舵机,市场价几块钱到十几块钱不等,常见的有三种版本:普通塑料齿版本(SG90)、金属齿版本(MG90S,同样重9g)和数字舵机版本。不同版本的扭矩、死区表现和耐用性有一定差异,但控制协议完全一样,都是标准PWM。下面这张表是我实测整理的常用参数:

参数SG90典型值备注
工作电压3.5V ~ 6.0V推荐5V供电
空载电流约100mA转动时
堵转电流300mA ~ 800mA卡住瞬间,电源设计的重点
扭矩1.8 kg·cm @ 4.8V约合0.18 N·m,很轻的负载
速度0.12s / 60° @ 4.8V无负载时
控制信号50Hz PWM,脉宽0.5ms~2.5ms对应0°到180°
重量约9g不含线材
线材定义红=VCC,棕=GND,橙=信号不同批次颜色可能不同

市面上还存在所谓SG90 360°连续旋转版本,它的内部去掉了电位器反馈,改成开环电机驱动,PWM只控制转速和方向,不控制绝对角度。买的时候一定要看清楚——如果标题写的是180°舵机,那才是角度控制版本;如果写的是“连续旋转舵机”,那这套程序就不适用了。别等到接上线发现舵机一直转不停才意识到买错了。

1.3 硬件连接与电源方案

SG90三根线的定义是:红色线接电源正极、棕色线接地、橙色线接信号。在STM32F103ZET6开发板上,信号线接到任意一个能输出PWM的GPIO即可,比如PB0(对应TIM3_CH3)或者PA0(对应TIM2_CH1)。需要注意一个常见误区——有些刚接触的朋友会把舵机的电源直接接到单片机开发板的3.3V引脚上,结果舵机要么不转要么极慢,甚至导致单片机反复复位。

原因很简单:SG90堵转时电流能冲到几百毫安甚至更高,而F103的3.3V电源轨通常只供芯片和板载外设使用,根本扛不住这种瞬态大电流。电压一旦被拉低到3.0V以下,单片机自己就先复位了。正确做法是单独给舵机供5V,可以从USB 5V取电,也可以用一块3.7V锂电池升压到5V,或者用独立的5V降压模块,然后和单片机的GND共地。

信号线这一路,IO口输出3.3V高电平,而SG90的驱动板识别高电平的最低阈值为2.5V左右,所以STM32的3.3V信号直接驱动是没问题的。但如果用的是5V供电的单片机(比如Arduino UNO),反而需要考虑把5V信号降为3.3V,防止把舵机信号端击穿,至少要在信号线上串一个1kΩ电阻。这一点新手容易忽略。

如果项目中还会加OLED、蓝牙、无线模块等其他外设,建议把舵机电源和主控电源彻底分开:一路5V专门给舵机,一路通过AMS1117-3.3给主控和外设供电,两个电源的负极接在一起即可。实测下来这种方案能有效避免舵机启动瞬间对其他外设的干扰。

2. 舵机控制原理与PWM信号解析

很多教程把舵机控制简化成一句“给50Hz的PWM,占空比2.5%到12.5%就能控制角度”,这句话表面上对,但如果你不了解背后的信号链路,遇到问题时基本只能靠猜。这一节我尽量把舵机内部的逻辑讲清楚。

2.1 SG90内部结构:电机、减速齿轮与电位器反馈

SG90虽然长得小,但内部是一个典型的闭环伺服系统。打开外壳可以看到:

  • 直流电机:负责输出转矩,转速一般是每分钟一万转以上,直接输出根本不能用于角度控制。
  • 减速齿轮组:把电机的高转速降下来,转变为大扭矩的低速输出。SG90内部的齿轮比通常在1/300左右,所以最终输出轴的速度和扭矩才适合小模型使用。
  • 电位器:输出轴带动电位器同步旋转,电位器的分压值就代表当前输出轴的实际角度。
  • 控制电路板:内部有一块小板子,上面是比较器和驱动H桥,负责对比“目标角度”和“当前角度”的误差,然后驱动电机正转或反转,直到误差归零。

信号链路可以这样理解:外部输入PWM脉冲,舵机内部的脉冲宽度检测电路把它转换成一个参考电压,参考电压的值正比于脉宽(脉宽越宽,参考电压越高);电位器实时反馈的分压值也送到比较器另一路;两者之差就是误差信号。误差为正,电机向一个方向转;误差为负,电机反向转;误差为零,电机停止并锁住。

这也是为什么舵机在达到目标角度后,如果你用手去掰它的输出轴,会感觉到一股明显的对抗力——因为误差出现了,控制电路在努力把它拉回目标位置。意识到这一点,对理解后面的“为什么舵机会抖动”、“为什么卡住会烧电机”都有帮助。

2.2 20ms周期与0.5ms~2.5ms脉宽的对应关系

标准SG90舵机的控制协议是:PWM周期固定为20ms,也就是频率50Hz;高电平脉冲宽度在0.5ms到2.5ms之间变化,对应舵机转角0°到180°。具体对应关系如下:

脉宽(ms)占空比(20ms周期)典型角度(0°基准版本)
0.52.5%
1.05%45°
1.57.5%90°(中位)
2.010%135°
2.512.5%180°

理论上脉宽和角度是线性关系,所以从0°到180°的脉宽映射公式就是:脉宽(ms)=0.5 + 角度/180 × 2.0。如果你用的是-90°到+90°标注的舵机,只是把0°挪到中间的区别,公式本质一样。

有些朋友会好奇,为什么不用死区更小的占空比分辨率?答案是SG90内部已经把PWM信号解调成模拟电压了,占空比微小的变化会被内部的RC滤波器平滑。实际测试中,相邻脉宽相差小于0.01ms时,舵机基本不会动——这就是“死区”。所以你在代码里就算把CCR寄存器做得非常精细,舵机的物理分辨率也就那样,一般5°一档跳变非常明显,1°一档就开始很细腻了。

2.3 为什么周期必须是20ms?能否用其他周期

网上经常会有人讨论:SG90能不能用15ms周期控制?答案是可以转,但不推荐长期使用。原因是SG90内部脉冲宽度检测电路通常假设周期在10ms到30ms之间都能识别,但它的参考电压生成电路是按20ms周期标定的。如果你把周期缩短到10ms,同样的绝对脉宽下,占空比翻倍,内部电路产生的参考电压也会偏高,最终表现是角度偏转。

反过来,如果周期超过30ms甚至更大(比如50ms),舵机会出现典型的“点头”现象——每次脉冲到来时舵机朝目标方向转一点,然后因为没有新的脉冲刷新,内部电位器反馈慢慢把状态“遗忘”,舵机又退回原位。所以周期越短,刷新率越高,舵机跟随越紧;但周期太短(比如5ms)又会进入非标区间,可能导致舵机内部驱动管发烫。

标准做法就是严格保持20ms周期、脉宽0.5ms~2.5ms。这也是为什么用定时器而不用软件延时的原因——定时器可以在后台精确维持这个时序,CPU想干嘛干嘛,而软件延时不但占用CPU,还极其容易因为中断、按键扫描等操作导致时序漂移。我见过有人用delay在中断里做PWM,效果很差,舵机要么抖要么无力,就是这个道理。

3. 定时器PWM的工程配置与代码实现

这一节是重头戏,我会把定时器PWM从原理到代码逐行讲解。工程环境采用STM32标准外设库(如果你用HAL库,后半段我会给对照说明)。项目管理上直接建一个空工程,勾选TIM2、GPIOA、RCC即可。

3.1 定时器时钟树:为什么PSC=71,ARR=20000

在写代码之前,先把时钟关系弄清楚。F103ZET6默认使用外部8MHz晶振,经过PLL倍频到72MHz作为系统时钟SYSCLK。APB1总线的最高频率是36MHz,而TIM2挂在APB1上,有一个特殊规则:当APB1的预分频系数不为1时,定时器时钟是APB1的两倍。多数默认配置下APB1分频为2,所以TIM2的输入时钟是72MHz,也就是每秒钟计数72000000次。

我们需要输出50Hz的PWM,也就是每周期20ms。假设定时器计数周期为N(从0计数到ARR,共N+1个计数点),那么PWM频率 = TIMxCLK / ((PSC+1) × (ARR+1))。

为了尽量提高脉宽调节精度,我选择PSC=71,这样计数时钟变成72MHz/72=1MHz,也就是每1微秒计1个数。再让ARR=20000,这样周期为20000微秒=20ms。脉宽寄存器CCR的值就直接以微秒为单位,比如CCR=1500就表示1.5ms脉宽,对应90°中位。这个设计非常直观,省去了换算的脑子。

3.2 标准外设库方式:初始化代码逐行说明

下面是标准外设库方式的完整初始化代码。以TIM2_CH1输出到PA0为例。

#include "stm32f10x.h" #include "stm32f10x_tim.h" #include "stm32f10x_gpio.h" #include "stm32f10x_rcc.h" void TIM2_PWM_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; // 1. 开启TIM2和GPIOA的时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 2. 配置PA0为复用推挽输出 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. 配置定时器时基:72MHz / 72 = 1MHz,计20000次为20ms TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_Period = 20000 - 1; TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); // 4. 配置PWM模式1:计数值 < CCR时输出高电平 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 1500; // 初始1.5ms脉宽,即90度 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM2, &TIM_OCInitStructure); // 5. 使能TIM2 TIM_Cmd(TIM2, ENABLE); }

每一步都有明确意义:

  • 第2行到第7行把PA0配置为复用推挽输出。这里的“复用”是指GPIO引脚的控制权交给片上外设(TIM2),而不是普通输出模式。推挽模式是常规输出高/低电平的方式,频率设为50MHz是为了匹配PWM波形的上升沿速度,一般默认50MHz不会错。
  • 第3行设置预分频PSC=71,4行设置自动重装载值ARR=19999,8行设置PWM模式1。模式1的核心逻辑是:CNT<CCR时输出有效电平(这里配置为高),CNT≥CCR时输出无效电平(低)。
  • 第6行把初始脉宽设置为1500,也就是1.5ms,舵机初始位置在90°。

注意,这里有一个F103特有的小坑:对于高级定时器TIM1/TIM8,初始化完之后还需要额外调用TIM_CtrlPWMOutputs函数来打开主输出,否则引脚上没有任何波形。但TIM2是通用定时器,不需要这一步。

3.3 角度映射函数与按键控制演示

有了PWM基础,角度控制就是一个线性映射。我习惯写成一个独立函数:

// 角度0~180映射到脉宽500~2500 void Servo_SetAngle(TIM_TypeDef* TIMx, uint8_t channel, uint16_t angle) { uint16_t ccr_value; if (angle > 180) angle = 180; ccr_value = 500 + (uint16_t)(angle * 2000.0f / 180.0f); switch (channel) { case 1: TIM_SetCompare1(TIMx, ccr_value); break; case 2: TIM_SetCompare2(TIMx, ccr_value); break; case 3: TIM_SetCompare3(TIMx, ccr_value); break; case 4: TIM_SetCompare4(TIMx, ccr_value); break; default: break; } }

日常调试中,我习惯配合按键做角度步进测试。比如PA1接一个按键,按下后角度增加30°,用简单的轮询扫描就行(或者用外部中断也行,这里不展开):

// 按键扫描实现:每按一次转30度,循环0->30->...->180->0 void Key_Scan(void) { static uint16_t current_angle = 0; if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == RESET) // 按下(低电平) { Delay_Ms(20); // 简单消抖 if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == RESET) { current_angle += 30; if (current_angle > 180) current_angle = 0; Servo_SetAngle(TIM2, 1, current_angle); while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == RESET); // 等待松开 } } }

按键扫描唯一要注意的是消抖和等待松开的逻辑——如果不等待松开,按住按键时函数会被反复执行,舵机在0°和180°之间来回横跳。另外在实际项目中,按键检测最好放在定时器中断里做节拍扫描,而不是用delay函数,否则舵机PWM本身虽然不受影响(因为PA0由TIM2硬件控制),但主循环里的其他逻辑会被阻塞。

3.4 HAL库版本:三分钟完成移植

现在很多基于STM32CubeMX的工程都默认HAL库,我也给出HAL库的关键配置方式。如果你用CubeMX生成工程,只需要在Pinout视图里把PA0配置为TIM2_CH1,然后在Clock Configuration里确认APB1定时器时钟为72MHz,再按下面配置:

  • PWM频率:50Hz,所以Prescaler=71,Counter Period=20000-1。
  • Pulse初始值:1500(对应1.5ms,90°)。

生成工程后,在main函数里加上这一句启动PWM:

HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);

运行时设置角度:

void Servo_SetAngle_HAL(uint16_t angle) { if (angle > 180) angle = 180; uint32_t pulse = 500 + (uint32_t)(angle * 2000.0f / 180.0f); __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pulse); }

HAL库的特点是封装度高、可读性好,缺点是效率不如标准库,中断回调链条长。但如果你的项目本身就在CubeMX生态里,没必要为了一个舵机单独退回标准库。我个人的项目习惯是:老工程用标准库,新工程以CubeMX + HAL为主,两者都能很好地驱动SG90。

4. 实操过程与核心环节实现

理论说完了,代码也贴了,但真走上实操台的时候还会有很多细节跳出来咬人。这一节我按照实际动手的顺序,把整个调试过程完整走一遍,顺便把容易踩的坑提前标出来。

4.1 从一盏LED到PWM信号:快速验证硬件通路

刚开始调试时,不要直接连舵机。先把PA0接到示波器或者逻辑分析仪上,确认波形是否正常。没有示波器的话,最简单的替代方案是用LED串联一个220Ω电阻接到PA0,观察LED亮度变化。如果PWM正常,LED会随着CCR值的改变而明显变亮变暗。

这个步骤非常重要——有一次我在项目里用的是自己画的PCB板,结果引脚短路导致PA0始终被拉低,舵机一动不动。如果直接怀疑舵机坏了,那就白折腾半天。先用LED把信号链路验一遍,能帮你把问题范围缩小到“主控配置”还是“舵机供电/舵机本身”。

LED验证通过后,再连接舵机。上电顺序建议:先给单片机供电,再给舵机供电。因为如果舵机先上电而信号线悬空,部分舵机会自动转到内部随机位置,给你一种“失控”的感觉。当然,更规范的初始化流程是在主程序最开始就调用Servo_SetAngle(TIM2, 1, 90),让舵机先归中位,然后再执行其他逻辑。

4.2 供电不稳引发的“抖动”和“复位”现象

我多次强调供电问题,是因为这真的是SG90项目里出镜率最高的问题。实测数据如下:SG90在带负载转动时电流大约100mA到200mA,但如果输出轴被卡住或者瞬间加速,电流可以冲到500mA以上。如果你使用USB转TTL模块上的5V给舵机供电,而那个模块本身的稳压能力只有几百毫安,那么在舵机加速瞬间电压会掉得很厉害。

表现最典型的两种现象:

  • 舵机在接近目标角度时来回震荡,像“发抖”一样。这是因为电机需要大电流刹车时,电源电压瞬间跌落,控制电路检测到的电位器反馈电压波动,导致误差信号反复翻转。
  • 单片机反复复位。电流跌落导致3.3V电源轨跟着受牵连,主控芯片电压不足,看门狗复位或直接掉电重启。

解决方案不复杂:在舵机电源两端并联一个大电容,比如470μF电解电容,再并联一个100nF陶瓷电容。电解电容用来提供瞬态能量储备,陶瓷电容用来滤除高频噪声。如果条件允许,用电池或者独立稳压模块供电更好。实测下,同样的代码,供电从USB模块直供改成“AMS1117-5V + 电容组”之后,抖动现象基本消失。

4.3 GPIO复用映射:用错引脚导致PWM输不出

F103的定时器通道和GPIO并不是一对一硬绑定的,它有一个“重映射”机制。比如TIM2_CH1默认映射在PA0,但也可以通过AFIO重映射到PA15。如果你在CubeMX里配置的是PA15,但是代码里初始化的是PA0,波形当然出不来。

实际项目中最容易出错的场景是:初始化了TIM2_CH1,也配置了GPIO,但忘了使用GPIO_PinRemapConfig函数开启重映射。比如你要用PB3作为TIM2_CH2输出(部分开发板的丝印标注为“TIM2_CH2”),但PB3默认功能是JTDO,必须先关闭JTAG复用或者调用重映射配置,否则引脚根本不归定时器管。

一个快速判断方法:把CCR设为0和2047两个极端值,用万用表量引脚电压——如果PWM生效,CCR=0时引脚应该持续低电平,CCR=2047(接近满占空比)时引脚应该持续高电平。如果两种情况电压都没有变化,多半是引脚复用没配对。

4.4 主循环与PWM的关系:为什么舵机不卡,但其他功能卡

因为PWM由定时器硬件产生,所以主循环就算写了个死循环delay,舵机也会按最后设置的脉宽持续运行,不会“断供”。这是使用硬件PWM的最大优势。但要注意,如果你在主循环里频繁调用Delay_Ms来做按键消抖、OLED刷新,整体系统响应会变慢,比如按键按下去要等很久才反应。

想做得更顺手,可以把角度控制放到一个“慢速任务”里,主循环每10ms扫一次按键状态,舵机角度只在按键有效时更新。或者直接上定时器中断:在TIM2的更新中断里做系统时基,每10ms执行一次按键扫描和状态机更新。这样整个系统的实时性会有明显改善。我在后面做智能小车时,就是把舵机控制、电机控制、无线遥控和传感器读取分别拆到不同任务里,主循环只做一个简单调度。

4.5 多舵机驱动:同一块板子同时控制多个SG90

如果项目需要两个以上的舵机,比如做一个双舵机云台,你有两种方案:

方案一是用多个定时器通道。例如TIM2_CH1输出到PA0控制左右,TIM3_CH3输出到PB0控制上下。每个定时器通道独立设置CCR就行,互不干扰。

方案二是用同一个定时器的多个通道。比如TIM2的CH1到CH4分别接四个舵机,初始化时把这几个GPIO都配置成复用推挽,然后分别初始化OC1到OC4。因为四个通道共用同一个时基(周期都是20ms),所以代码量更小,CPU负担也低。

需要提醒的是:多个舵机同时动,瞬时电流是相加的。三个SG90同时从0°转到180°,峰值电流可能接近2A。如果用USB供电,很容易电流不足。多舵机项目我建议直接用2S锂电池(7.4V)加BEC降压到5V或6V,输出电流5A以上的稳压模块,才能吃得稳。

5. 扩展应用:从舵机到智能小车与F407对比

很多朋友做这个项目的下一步,就是把它塞进智能小车里。热词里也明确提到了stm32f103zet6智能小车,那我把这个方向一并说透。

5.1 舵机在智能小车中的角色:转向机构 vs 驱动轮

智能小车里的舵机一般有两种用法。

第一种是作为转向舵机:舵机装配在前轮转向机构上,通过改变前轮摆角来控制小车方向。这种用法SG90完全可以胜任,因为小模型车的前轮负载不大,1.8kg·cm的扭矩在低速下够用。控制逻辑很简单:遥控信号给出目标转向角(比如-45°到+45°),舵机转到对应位置,配合后轮差速驱动实现转弯。

第二种是直接把舵机当驱动电机用——这就很勉强了。SG90转速只有0.12s/60°,换算下来最高转速约83rpm,对小车来说太慢,而且扭矩不足。真要当驱动轮用,至少得用DS3218这种大扭矩数字舵机,或者干脆换TT马达加编码器。

所以如果你正在规划智能小车项目,我强烈建议舵机专职做转向,驱动让直流电机来干。两者分开,调试起来简单很多。控制端我用的主控就是STM32F103ZET6:定时器TIM2输出PWM给舵机转向,TIM3输出PWM给电机驱动模块,TIM4接编码器做测速,串口1接蓝牙模块接收遥控指令。

5.2 常用控制方案:蓝牙遥控与PID闭环

智能小车最常见的控制链路是:手机蓝牙 → 蓝牙模块(HC-05/HM-10) → STM32串口 → 解析指令 → 更新舵机角度和电机PWM。串口解析逻辑可以写成这样:

void UART_Command_Handler(uint8_t cmd) { switch (cmd) { case 'L': Servo_SetAngle(TIM2, 1, 45); break; // 左转 case 'R': Servo_SetAngle(TIM2, 1, 135); break; // 右转 case 'F': Servo_SetAngle(TIM2, 1, 90); // 回正,加速前进 Motor_SetSpeed(2000); break; case 'S': Motor_SetSpeed(0); break; // 刹车 default: break; } }

如果想让小车走直线走得更稳,还需要加入PID闭环。让定时器配合编码器读取实际转速,然后通过增量式PID调整电机PWM。这一步对入门者稍微有些难度,但理解了PID的P、I、D三个参数分别对应“当前误差”“历史累积误差”“误差变化率”之后,调试起来也没那么玄。智能小车项目的价值就在这里——舵机控制只是其中一个子模块,但它是理解整个系统的最小闭环。

5.3 STM32F103ZET6与STM32F407ZET6:怎么选

热词里有关stm32f103zet6和stm32f407zet6的区别,这里我给出一个直白的对比表格,帮助你在不同项目中做选型:

对比项STM32F103ZET6STM32F407ZET6
内核Cortex-M3,72MHzCortex-M4F,168MHz
Flash/RAM512KB / 64KB512KB / 192KB
硬件FPU有(单精度)
定时器资源8个12个(含32位定时器)
DAC2路
通信接口5×USART,3×SPI,2×I2C,1×CAN6×USART,3×SPI,3×I2C,2×CAN
价格(开发板)中高
PWM输出能力够用,最多几十路更强,32位定时器适合高精度低频

结论是:如果只是做舵机控制、智能小车、机械臂,F103ZET6的能力远远溢出,选它完全够用;如果你要跑音频处理、FFT、摄像头识别、IMU姿态解算这些需要浮点运算的算法,那F407的FPU优势就非常明显了。不要把F407当作F103的“升级版”无脑换,因为它的开发复杂度也更高,功耗也更大,不是每个项目都需要那块芯片。

5.4 进一步扩展:云台、机械臂与FreeRTOS

舵机这个基础模块掌握之后,可以往三个方向延伸:云台、机械臂、仿生机器人。云台需要两个舵机,一个控制俯仰,一个控制水平,需要做好机械结构上的重心配平;机械臂则涉及多个舵机的协调运动,需要考虑逆运动学;而如果要做仿生机器人,舵机数量更多,供电和时序复杂程度都会几何级上升。

这时候强烈推荐引入FreeRTOS。把每个舵机的角度控制封装成独立任务,通过消息队列接收指令,主控任务只负责状态机调度。F103ZET6跑FreeRTOS完全没有任何压力,毕竟Cortex-M3本身就是为了这类实时控制场景设计的。我在项目里用FreeRTOS管理两个舵机、一个OLED、一个蓝牙模块时,RAM占用大概只有十几KB,剩余空间还很多。

6. 常见问题与排查技巧实录

这一节把我这些年做舵机项目踩过的坑集中整理出来,按“现象 → 原因 → 解决”的顺序做成速查表,方便你调试时直接对照。

6.1 问题速查表

现象可能原因排查思路与解决方法
舵机完全不动供电不足、信号线没接对、PWM频率不对先测5V供电电压;用LED验证PA0有无波形;核对信号线是否在正确引脚;示波器或逻辑分析仪看PWM周期是否是20ms
舵机只往一个方向转,到极限位置后卡死脉宽超出0.5~2.5ms范围检查CCR值是否越界;用串口打印实际设置的CCR值;注意不要长期堵转
舵机抖动、来回震荡电源纹波大、负载过重、死区过小在电源端并联470μF电容;减轻输出轴负载;检查PWM是否被中断干扰
舵机上电瞬间猛转一下初始化前IO悬空导致舵机收到随机信号初始化时先将信号线设置为低电平,再初始化定时器;然后设置舵机到安全角度
单片机供电正常但反复复位舵机大电流拉低3.3V电源轨舵机单独供电;共地;加电容;不要从板载3.3V取电
用TIM1/TIM8输出时无波形高级定时器主输出未使能调用TIM_CtrlPWMOutputs(TIM1, ENABLE)
某一角度附近明显卡顿电位器磨损或齿轮间隙换一个舵机试试;如果只在特定角度出现,多半是电位器局部磨损

6.2 三个调试中的独家经验

第一个经验:用串口打印状态,而不是凭空猜。调试的时候我把CCR值、目标角度、供电电压全部通过串口打印到电脑上,然后对照舵机实际动作,很快就能定位问题。比如舵机抖动时,如果串口显示CCR值是稳定不变的,那问题一定在供电或机械负载,不在代码。

第二个经验:给舵机做“行程校准”。不同品牌的SG90即使标称180°,实际脉宽范围也会有一点点差异。有些舵机在0.5ms时还没到0°,或者2.5ms时已经超出180°,长期超行程运行会顶到机械限位,导致齿轮损坏或电流过大。所以我每次换新舵机,都会手动测试它的最小脉宽和最大脉宽,然后写进一个校准结构体里。代码里定义成宏或者常量,方便替换。

第三个经验:在主循环里加一个“看门狗”式的超时保护。比如舵机需要5秒内到达目标角度,如果5秒后还没有到位(可以通过电位器ADC读取),就主动把PWM输出关闭,防止舵机堵转发热。这个保护对长时间运行的机械臂项目特别有用,否则一旦机械结构卡住,堵转电流会把舵机烧掉。

6.3 关于舵机寿命与维护

SG90毕竟是几块钱的小舵机,它的减速齿轮是塑料的,长时间高负载运行会磨损、扫齿。如果你的项目需要长时间运转,建议提前换成MG90S金属齿版本,价格也差不了多少,耐用性却提升明显。另外,舵机工作温度较高时手感会变软,这是正常现象,但如果烫手到不能摸,就要检查是不是长时间堵转或者电压过高。

舵机线的接口也要注意,SG90出厂一般是1.25mm端子或杜邦线,插拔多了容易接触不良。我在项目里习惯给舵机线加一个热缩管保护根部,防止弯折断裂。

说在最后

SG90舵机控制看起来是个入门级项目,但真正把它做扎实,你不仅掌握了PWM的原理和定时器的用法,还顺带理解了闭环控制、电源设计、机械限制这些嵌入式开发的通用知识点。我到现在做机械臂、云台甚至一些桌面小机器人,依然会回到这个项目管理定时器配置和舵机脉宽映射,只是代码量更大了、工程结构更规范了,但底层那套东西没变。

最后再分享一个小技巧:如果你的舵机在目标位置附近轻微抖动,但电源和负载都没问题,可以在角度映射函数里加一个“死区判断”——当目标角度与当前角度差值小于2°时,不再更新CCR值。这个“懒惰”策略能显著减少舵机的反复修正,对延长寿命帮助很大。

希望这篇实操记录能帮你在自己的项目里顺利点亮那个9g小舵机。动手试一下,比看一百篇教程都管用。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 15:41:02

LT9211桥接芯片BHK异常排查:从水平消隐期脉冲到保留寄存器误配置

1. 项目背景与整体设计思路U5 平台上调 LT9211 桥接芯片那阵子&#xff0c;我们被一个叫 BHK 的怪问题折磨了整整一周。屏幕在量产测试时偶尔出现瞬间的横纹闪烁&#xff0c;几十秒一次&#xff0c;毫无规律。逻辑分析仪抓 LVDS 输出端&#xff0c;能看到行消隐期莫名其妙多出一…

作者头像 李华
网站建设 2026/9/4 17:01:46

Wiener过程驱动的剩余寿命预测:Matlab实现与工程实践

简介&#xff1a;本资源面向可靠性工程、预测性维护及故障诊断领域的研究人员与工程师&#xff0c;提供基于Wiener维纳过程的剩余使用寿命&#xff08;RUL&#xff09;建模与预测完整解决方案。针对设备退化过程的随机性与非线性特征&#xff0c;资源系统实现四类主流维纳退化模…

作者头像 李华
网站建设 2026/9/4 14:45:38

CPU核心数怎么看?物理核、超线程与选型排查全解析

如果你买电脑或配服务器时被“8核16线程”“16核24线程”这些宣传词绕晕过&#xff0c;这篇文章就是为你准备的。CPU核心数量到底意味着什么&#xff0c;核心越多性能一定越强吗&#xff0c;为什么有时核心数很多但电脑依然卡顿&#xff0c;这些问题我在日常答疑和性能排查中经…

作者头像 李华
网站建设 2026/9/5 13:45:38

搜狐畅游校招3D渲染引擎笔试核心考点解析

搜狐畅游2019校招笔试的3D引擎开发工程师&#xff08;渲染方向&#xff09;这套题&#xff0c;放到今天看依然很有参考价值。虽然年份早了点&#xff0c;但图形学基础、渲染管线和引擎优化这些考点&#xff0c;底层逻辑没怎么变&#xff0c;各家游戏公司在校招里考察的思路也大…

作者头像 李华
网站建设 2026/9/5 20:49:54

基于Python的同态加密电子投票系统:Paillier算法与隐私保护实践

简介&#xff1a;本资源是一个基于Python实现的隐私保护电子投票系统&#xff0c;聚焦同态加密算法在实际场景中的工程落地&#xff0c;面向计算机专业本科生及研究生开展毕业设计、课程设计或科研项目开发。系统完整集成ElGamal半同态加密与整数环上全同态加密方案&#xff0c…

作者头像 李华