舵机控制

Tutorial: 卓越工程师 Category: 嵌入式 Published: 2026-10-23 11:05:29 Views: 1 Likes: 0 Comments: 0

288.6.舵机控制

#define ServoPin 12                         // 将视频选用的 GPIO12 命名为 ServoPin,后面用名字引用该引脚。

void Myservo(int angle)                     // 定义接收目标角度的舵机控制函数;函数名和大小写照视频保留。
{                                           // 开始执行 Myservo 函数中的语句。
    for (int i = 0; i < 5; i++) {           // 按视频写法,连续发送 5 个同一目标角度的控制脉冲。
        int pulsewidth = (angle * 11) + 500; // 按课堂公式计算高电平宽度,单位为微秒。
        digitalWrite(ServoPin, HIGH);       // 把 GPIO12 拉高,开始本次控制脉冲。
        delayMicroseconds(pulsewidth);      // 保持高电平 pulsewidth 微秒。
        digitalWrite(ServoPin, LOW);        // 把 GPIO12 拉低,结束本次高电平脉冲。
        delayMicroseconds(20000 - pulsewidth); // 保持低电平至本轮名义上的 20 000 微秒结束。
    }                                       // 五轮高、低电平发送结束。
    delay(10);                              // 五个脉冲之后再等待 10 毫秒;不是持续输出脉冲。
}                                           // Myservo 函数结束。

void setup() {                              // 定义 Arduino 上电或复位后执行一次的初始化函数。
    pinMode(ServoPin,OUTPUT);               // 将 GPIO12 配置成输出模式,以便发送高、低电平。
}                                           // 初始化结束。

void loop() {                               // 定义 Arduino 不断重复执行的主循环。
    Myservo(0);                             // 向舵机发送表示课堂预设 0° 的一组脉冲。
    delay(2000);                            // 暂停 2000 毫秒,再请求下一个角度。
    Myservo(45);                            // 向舵机发送表示课堂预设 45° 的一组脉冲。
    delay(2000);                            // 暂停约 2 秒。
    Myservo(90);                            // 向舵机发送表示课堂预设 90° 的一组脉冲。
    delay(2000);                            // 暂停约 2 秒。
    Myservo(145);                           // 向舵机发送表示课堂预设 145° 的一组脉冲。
    delay(2000);                            // 暂停约 2 秒;之后 loop 重来,重新请求 0°。
}                                           // 本轮主循环结束,Arduino 框架会再次调用 loop。

一、舵机控制

本节使用 AI Thinker ESP32-CAM 的 GPIO12 驱动视频中的 SG90 舵机。上方是视频最终草图实际写出的业务代码:移除了 PPT 和 IDE 原有注释,仅在每个非空行增加讲解,保留 Myservo、pulsewidth、angle * 11 + 500、发送 5 次和 delay(10) 等原样写法。这里没有调用 Servo 库,也没有使用 ESP32 硬件 PWM;是程序依次拉高、延时、拉低 GPIO 来“软件生成”控制脉冲。以下按原笔记的四个小节展开,并区分课堂演示、公式近似与硬件额定要求。

1.1 舵机控制原理

  • 视频 PPT 画的是可按角度定位的舵机:大约每隔 20 ms = 20 000 μs 发送一个脉冲,也就是约 50 Hz。所谓“高电平宽度”,是这一周期开始后信号维持高电平的时间;剩余时间为低电平。例如高 1.5 ms、低约 18.5 ms,构成一个约 20 ms 的周期。ms 是毫秒,μs 是微秒,1 ms = 1000 μs。PPT 依次画出约 0.5 ms → 0°、1 ms → 45°、1.5 ms → 90°、2 ms → 145°、2.5 ms → 180°,用于理解“脉宽与目标位置有关”。
  • 脉冲宽度占 20 ms 周期的比例也叫占空比,PPT 的 0.5~2.5 ms 对应 2.5%~12.5%;但舵机接收的是脉冲高电平持续多久,不能以为只要占空比相同、随意改变频率就会得到同一位置。0.5/1.5/2.5 ms 与 0/90/180° 是这节课采用的示意映射,不是所有 SG90、所有舵机的普适标定值。不同型号的允许脉宽、机械端点和零位可能不同;越过机械端点可能导致堵转、电流增大。Arduino 官方 Servo 文档也明确指出各厂家端点脉宽并不完全一致。
  • 普通定位舵机内部不是“高电平时才转,低电平时就反转”:内部控制电路读取脉宽,把它解释为目标位置,随后由内部电机和反馈机构尝试转到目标。信号线只是控制输入,不能提供舵机电机所需的工作电流。视频配套硬件清单曾写“SG90(360 度)”,但 TowerPro 将普通 SG90和SG90 360° 连续旋转版列为不同产品;连续旋转版的脉宽主要对应转动方向、速度或停止点,不能按本节公式定位到 45°、90°。应以实物型号和规格确认所用的是定位版,不能只看名称“SG90”。

1.2 控制舵机函数

  • #define ServoPin 12 是预处理宏:编译前把后面出现的 ServoPin 替换成 12。12 指芯片的 GPIO12/IO12,不是第 12 个物理孔位;一旦以后更换信号引脚,只需统一修改宏定义并检查该脚是否可用。Myservo(int angle) 中的 angle 是调用时传入的期望角度,不是舵机实时测量值;该函数也不读取舵机实际位置。

  • 视频实际公式为 pulsewidth = angle * 11 + 500,单位是 μs。其端点是 0° → 500 μs、180° → 2480 μs;系数 11 是为了近似课堂图中的 0.5~2.5 ms 范围,并非精确的 (2500-500)/180 ≈ 11.11。代码的 int 只做整数运算,这里乘以整数 11 不会出现小数结果。看几个代入值更容易理解:

    调用参数 angle代码算得高电平名义低电平 20000 - pulsewidth与视频示意图的关系
    0°500 μs = 0.500 ms19 500 μs对应示意图的 0.5 ms
    45°995 μs = 0.995 ms19 005 μs约 1 ms,不能写成精确 1000 μs
    90°1490 μs = 1.490 ms18 510 μs约 1.5 ms
    145°2095 μs = 2.095 ms17 905 μsPPT 的 2 ms 只是示意近似
    180°2480 μs = 2.480 ms17 520 μs约 2.5 ms;主循环没有调用 180°
  • 每轮 for 先 digitalWrite(ServoPin, HIGH),等待 pulsewidth 微秒,再输出 LOW,等待 20000 - pulsewidth 微秒。这样按代码请求的延时相加是 20 000 μs;实际 GPIO 调用、循环和系统调度也要耗时,因此示波器测到的周期未必精确 20 ms。这里手工延时会占住程序执行;与将来同时运行摄像头、Wi‑Fi 等任务相比,这只是便于理解的入门演示,不能把它等同于持续、精确的硬件 PWM。

  • i 从 0 开始,在 i < 5 时执行,因此依次是 0、1、2、3、4,正好 5 个脉冲,名义发送时间为 5 × 20 ms = 100 ms;退出循环后另有 delay(10),即再等 10 ms。随后 loop() 中每个角度后还有 delay(2000),约 2 秒内没有继续生成该角度的脉冲,引脚维持低电平。故它只是“短暂发送一组定位命令”,不是整整 2 秒持续维持 50 Hz 信号;若舵机未到位、受负载扰动或需要保持力矩,本例不能保证可靠保持。这里是解释原代码行为,不擅自改写成另一套代码。

  • 从公式的设计意图看是覆盖 0~180°,但函数本身没有范围检查,传入异常角度还可能让 20000 - pulsewidth 不合理。实际可转的机械范围和端点脉宽必须按手中舵机规格确认,不要用手硬拧舵盘或长时间顶住机械止挡。

1.3 循环调用函数

  • setup() 只运行一次,pinMode(ServoPin,OUTPUT); 将 GPIO12 配成输出;loop() 会不断从头执行,每次按 0° → 45° → 90° → 145° → 下一轮的 0° 请求位置。每次 Myservo(...) 自身发送约 100 ms 脉冲并等待 10 ms,后面的 delay(2000) 才是约 2 秒的观察间隔;因此相邻目标角度的开始时间并不是严格每隔 2 秒。
  • delay(2000) 的单位是毫秒,而 delayMicroseconds(...) 的单位是微秒;转录中把“两秒”误识为“两毫秒”。代码中的 #define、括号、逗号和分号均用英文半角符号;中文输入法打出的全角括号、逗号等不能替代 C/C++ 语法符号。函数名 Myservo 的大小写也要保持一致,不能写成 MyServo 或 myservo。视频虽然使用 Arduino IDE 编译,草图实际按 C++ 规则处理;这里依 readme.md 要求把课堂业务代码放在 c 代码围栏中。
  • 注意观察“目标值”与“实际转到的角度”不是同一个量。本例没有角度传感器回读,也没有串口打印实际位置;演示中看见舵盘转动,可以验证控制信号产生了效果,但不足以证明每一站都精确到标称角度。

1.4 舵机转动效果

  • **先认清三根线及回路:**视频中的橙线是控制信号,连 ESP32-CAM 的 IO12/GPIO12;红线是舵机电源正极 V+;棕线是地 GND。信号只在相对共同地的电压下有意义,所以舵机地与 ESP32-CAM 地必须相连;舵机电机电流则应走舵机专用电源正极和回路,不要通过 GPIO12 给电机供电。上电前对照实物插头、标识确认三线顺序;接错正负极或把电源误接信号脚有损坏风险,改接线时先断电。
  • 课堂画面与推荐供电要分开看:视频把舵机红线接在 ESP32-CAM 板上的 3.3V,并演示其转动,这只说明该次实物实验出现了转动。TowerPro 普通 SG90 产品页给出的工作电压是 4.8 V,其连续旋转版为 4.8~6 V,均不是 3.3 V;且舵机启动和负载变化会要求足够的供电电流。复现实验应先核实所购舵机的型号、额定电压和供电能力,优先用符合其规格的独立稳压电源供给舵机 V+,将该电源负极与 ESP32-CAM GND 共地,并确认舵机控制输入能识别 ESP32 的 3.3 V 信号。不能因为板上有一个 3.3V 排针就认定它适合给电机供电,也不能把 4.8/5 V 电源正极直接接入 GPIO12。
  • **IO12 不是毫无限制的“空闲脚”:**ESP32 的 GPIO12/MTDI 在复位时也是启动配置脚。对使用 3.3 V Flash 的相关模块,复位瞬间若被外部电路拉高,会误选 1.8 V Flash 供电并可能导致启动、烧录失败;本例舵机信号线接在 IO12 上,遇到启动异常应检查外设在复位时是否影响该脚,必要时先断开舵机信号线排查。此外 GPIO12 也是 SD 卡总线的 DAT2,以后若启用 microSD 的四线数据模式,就不能再不加分析地同时把它当舵机专用输出。Espressif 启动脚说明、SD 总线说明。
  • **按视频的烧录/运行顺序:**烧录前将 GPIO0/IO0 临时接 GND,接好 USB 转串口及板子供电,在 Arduino IDE 中选 AI Thinker ESP32-CAM 与实际 COM 口,上传草图;GPIO0 在复位时为低,芯片才进入串口下载模式。确认 IDE 写入、校验完成后,断电或确保线路安全地拔掉 GPIO0–GND 下载跳线,按复位键(或重新上电)进入正常运行,再接好舵机三线。不能把“拔掉烧录跳线”误作“拔掉供电”:板子和舵机仍需各自合适的电源。若视频那样串口断开/上传失败,先核对 COM 口是否仍存在、串口线与供电、GPIO0 跳线和复位时机;重插 USB 后重新选择端口再试,不是盲目反复烧录。Espressif 下载模式说明。
  • 成功运行时视频展示舵盘按 0、45、90、145 的顺序改变朝向,下一轮再回到 0,持续重复。若舵机抖动或板子复位,先排查电源压降、共地、接线与型号;若只是转个不停而不在角度处停下,尤其要核对是否误用了360° 连续旋转版。实际效果同时受舵机型号、负载、脉宽标定和每次只发送 5 脉冲的限制,不把课堂画面描述成所有器件都能精确复现的保证。

核对依据