通过点灯科技云平台控制舵机
293.11.通过点灯科技云平台控制舵机
#define BLINKER_WIFI // 选择 Blinker 的 Wi-Fi 接入方式。
#define LED 4 // 把 ESP32-CAM 板载白色闪光灯所接的 GPIO4 命名为 LED。
#define Servo 12 // 把本课输出舵机控制脉冲的 GPIO12 命名为 Servo;大小写照视频最终代码保留。
#include <Blinker.h> // 引入 Blinker 库的声明。
#include "ESP32_CAM_SERVER.h" // 引入配套摄像头服务文件;这里不展开其内部实现。
char auth[] = "c56be051d11f"; // 视频中的设备 Secret Key;自己复现时必须换成自己的密钥。
char ssid[] = "302"; // 视频中的 Wi-Fi 热点名称;复现时换成自己的网络名称。
char pswd[] = "12345678"; // 视频中的 Wi-Fi 热点密码;不是 Blinker 账号密码。
bool setup_camera = false; // 标记摄像头服务尚未由下方代码启动。
BlinkerButton Button1("led"); // 保留上节的按键组件对象;"led" 是 App 按键的组件键名。
BlinkerSlider Slider0("duoji"); // 建立滑块组件对象;"duoji" 必须与 App 滑块的组件键名一致。
void button1_callback(const String & state) // 定义上节保留的 LED 按键回调;state 保存收到的按键状态。
{ // 开始按键回调。
BLINKER_LOG("get button state: ", state); // 把收到的按键状态写到 Blinker 串口调试日志。
digitalWrite(LED, !digitalRead(LED)); // 读取 GPIO4 当前电平、取反,再写回 GPIO4。
} // 结束按键回调。
void Myservo(int angle) // 定义舵机控制函数;angle 接收滑块传来的目标值。
{ // 开始执行舵机控制函数。
for (int i = 0; i < 5; i++) { // 连续发送 5 个代表同一目标值的控制脉冲。
int pulsewidth = (angle * 11) + 500; // 按课堂公式计算高电平宽度,单位为微秒。
digitalWrite(Servo, HIGH); // 将 GPIO12 拉高,开始本次控制脉冲。
delayMicroseconds(pulsewidth); // 保持高电平 pulsewidth 微秒。
digitalWrite(Servo, LOW); // 将 GPIO12 拉低,结束高电平脉冲。
delayMicroseconds(20000 - pulsewidth); // 保持低电平,使本轮名义周期合计为 20 000 微秒。
} // 结束 5 次脉冲发送。
delay(10); // 发送完 5 个脉冲后再等待 10 毫秒。
} // 结束舵机控制函数。
void slider0_callback(int32_t value) // 定义滑块回调;value 是 App 滑块发来的 32 位有符号整数。
{ // 开始处理本次滑块数据。
int servo=value; // 用小写 servo 临时保存滑块值;它与大写 Servo 引脚宏不是同一标识符。
Myservo(servo); // 把滑块值传给舵机控制函数,生成对应脉宽。
} // 结束滑块回调。
void dataRead(const String & data) // 定义收到未由已绑定组件处理的数据时调用的通用回调。
{ // 开始处理收到的数据。
BLINKER_LOG("Blinker readString: ", data); // 将收到的内容写到串口调试日志。
Blinker.vibrate(); // 向 App 发送手机振动请求。
uint32_t BlinkerTime = millis(); // 读取本次开机以来经过的毫秒数。
Blinker.print("millis", BlinkerTime); // 以 millis 为键把该数值发送给 App。
} // 结束通用数据回调。
void setup() // 定义上电或复位后只执行一次的初始化函数。
{ // 开始初始化。
Serial.begin(115200); // 以 115200 波特率启动串口。
BLINKER_DEBUG.stream(Serial); // 把 Blinker 的调试输出指向串口。
pinMode(LED, OUTPUT); // 将 GPIO4 配置成数字输出。
digitalWrite(LED, LOW); // 启动时让板载闪光灯输出低电平。
pinMode(Servo, OUTPUT); // 将 GPIO12 配置成数字输出,以便生成舵机控制脉冲。
digitalWrite(Servo, LOW); // 让舵机信号脚初始保持低电平。
Blinker.begin(auth,ssid,pswd); // 使用设备密钥、热点名和密码接入 Blinker。
Blinker.attachData(dataRead); // 注册通用数据回调。
Button1.attach(button1_callback); // 把 Button1 的指令与 LED 按键回调关联。
Slider0.attach(slider0_callback); // 把 Slider0 收到的数值与滑块回调关联。
} // 结束初始化。
void loop() // 定义 Arduino 反复执行的主循环。
{ // 开始本轮循环。
Blinker.run(); // 维持 Blinker 通信并处理 App 发来的指令。
if (Blinker.connected() && !setup_camera) // Blinker 已连接且摄像头服务尚未启动时进入。
{ // 开始首次启动摄像头服务的分支。
setupCamera(); // 调用配套文件中的摄像头及本地视频流服务初始化。
setup_camera = true; // 记录已经初始化,避免以后每轮重复调用。
Blinker.printObject("video", "{\"str\":\"mjpg\",\"url\":\"http://"+ WiFi.localIP().toString() + "\"}"); // 向 App 的 video 组件发送 MJPG 格式和本机局域网 URL。
} // 结束首次启动摄像头服务的分支。
} // 结束本轮循环,随后再次进入 loop。
一、通过点灯科技云平台控制舵机
本节承接第 292 节的 ESP32-CAM 视频与 LED 控制程序,在原有业务代码上增加 GPIO12 舵机脉冲输出、BlinkerSlider 滑块对象、滑块回调和回调注册。上方代码按视频最终编译、烧录时的主草图整理:保留课堂中的变量名、公式、5 次脉冲和阻塞式延时,只删除原注释并逐行补充说明;ESP32_CAM_SERVER.h 的内部实现不属于本节业务代码,因此没有抄入。
1.1 课程回顾
- 上一节已经实现:ESP32-CAM 连接手机热点和 Blinker,App 显示摄像头画面,并用键名为
led的按键控制 GPIO4 白色闪光灯。本节没有重写这些功能,而是在同一设备页面加入滑块,使视频、LED 和舵机控制同时存在。 - 本节的完整控制链路是:拖动 App 滑块 → 云端把
duoji对应的整数发给设备 →Slider0匹配该键名 →slider0_callback()被调用 →Myservo()在 GPIO12 输出一组脉冲 → 舵机尝试转到对应位置。 - App 右上角的编辑入口用于添加和配置组件。页面底部可选视频、图片、按键、文字、数据、滑块等组件;组件的界面名称可以自由显示,但用于通信的“组件键名”必须和草图字符串一致。
1.2 点灯科技介绍
- 视频进入点灯科技开发文档的“自定义布局/组件”页面,查看滑动条的声明、回调和
attach()用法。官方接口说明确认:BlinkerSlider用来读取或设置滑动条,回调形参为int32_t value,并在setup()中用attach()注册。Blinker Arduino 组件接口 - 网页示例与视频当前项目的接入方式并不完全相同:视频看到的某个滑块示例使用蓝牙,而本项目在首行定义
BLINKER_WIFI,继续采用 Wi-Fi。因此应提取滑块组件的通用写法,再放进现有 Wi-Fi/ESP32-CAM 草图,不能把另一种接入示例整份替换进来。Blinker 官方 Slider Wi-Fi 示例 Blinker.run()必须在loop()中频繁执行,设备才会持续处理连接和数据。摄像头画面的 URL 仍由原程序发送给video组件;滑块只发送数值,二者是同一设备页面里的不同组件,不代表舵机控制数据包含在 MJPG 视频流中。Blinker 连接与数据处理说明
1.3 通过点灯科技平台控制滑块
1.3.1 声明组件对象
- 视频最终写成
BlinkerSlider Slider0("duoji");:Slider0是程序里的 C++ 对象名,"duoji"才是与 App 通信的数据键名。两者没有“必须同名”的要求;真正必须一致的是代码字符串duoji与 App 的组件键名。原笔记把“任务名、滑块名称都要相同”混为一谈,现已更正。 - 视频曾把这一行漏写字符串的右双引号,形成
BlinkerSlider Slider0("duoji);。编译器因此报告missing terminating " character;最终补成"duoji"后通过编译。这里缺的是右双引号,不是原笔记所说的分号。 - PPT 注释写了“舵机范围 0~180”,但视频在 App 的滑块编辑页实际显示最小值
0、最大值100,随后只修改了组件键名为duoji,没有看到把最大值改成180。因此本次演示实际只能确认 App 发送了当前配置范围内的值,不能据此声称已经覆盖 0~180°。若要覆盖更大的目标范围,应按舵机允许范围设置滑块上限,并先核实机械端点;这是复现时的配置要求,不是视频已经完成的操作。
1.3.2 滑块控制任务函数
- 官方滑块回调的形参类型是
int32_t,所以slider0_callback(int32_t value)会在 App 发来滑块数值时得到一个整数。课堂又用int servo=value;保存一次,再调用Myservo(servo);这个临时变量并不保存舵机真实角度,也没有传感器回读。 Servo与servo区分大小写:视频最终用大写Servo表示 GPIO12 宏,而回调里的小写servo仍是临时变量。若把后者也机械替换成大写,预处理后int Servo=value;会被宏替换成非法的int 12=value;,所以不能根据口述把所有同形文字不加判断地改掉。Myservo()沿用第 288 节原代码:pulsewidth = angle × 11 + 500,单位为微秒;随后高电平保持pulsewidth,低电平保持20000 - pulsewidth,名义周期约 20 ms,并连续发送 5 次。例如 App 值为 0 时高电平为 500 μs;值为 100 时为 1600 μs;视频最后画面显示值 14 时,代码计算为 654 μs。该换算是课堂近似公式,并不等于实物一定精确转到相同数字的角度。- 一次
Myservo()名义上发送约5 × 20 ms = 100 ms的脉冲,之后再delay(10),所以一次滑块回调会占用约 110 ms,期间主循环不能继续频繁执行Blinker.run()。快速连续拖动时可能出现响应滞后;这是原程序的执行特性,本稿不擅自把它改成 PWM、定时器或非阻塞实现。 - 函数只发送 5 个脉冲,随后 GPIO12 维持低电平,并没有持续用约 50 Hz 的脉冲保持位置。负载较大、舵机尚未到位或受到外力时,短暂的一组脉冲不保证持续保持力矩。普通定位舵机与 360° 连续旋转舵机的含义也不同:连续旋转版通常按脉宽控制方向/速度,不能用本例把它定位到任意角度。TowerPro 普通 SG90、TowerPro SG90 360° 版
1.3.3 关联滑块
Slider0.attach(slider0_callback);的作用是注册回调:收到键名duoji的滑块数据后,Blinker 调用slider0_callback()。它不是靠“回调函数名与滑块名相同”来关联;对象、键名和函数各有职责。Button1.attach(...)仍保留,所以 LED 按键没有因本节新增滑块而失效。BlinkerSlider 官方接口pinMode(Servo, OUTPUT);把 GPIO12 设为输出,digitalWrite(Servo, LOW);给信号脚一个明确的初始低电平。Servo只是本草图的宏名,程序没有实例化 Arduino Servo 库对象,也没有调用attach()来绑定舵机;这里的Slider0.attach()是注册 Blinker 回调,不要把两种含义混淆。- 舵机三线应按实物标识核对:信号线接 GPIO12,电源正极接符合舵机规格且电流足够的电源,地线接电源负极;该负极还必须与 ESP32-CAM 的
GND相连,形成共同参考地。GPIO12 只输出控制信号,不能给舵机电机供电。视频沿用前课硬件做演示,但 TowerPro 普通 SG90 标称工作电压为 4.8 V,不能把 ESP32-CAM 的 3.3 V 引脚当成通用舵机电源;更稳妥的做法是按手中舵机规格使用独立稳压电源并与开发板共地。TowerPro SG90 产品页 - GPIO12/MTDI 还是 ESP32 的启动配置脚:对使用 3.3 V Flash 的板子,若外设在复位瞬间把它拉高,可能误选 1.8 V Flash 电压并导致启动或烧录失败;遇到异常可先断开舵机信号线排查。GPIO12 也与 ESP32-CAM 的 microSD
DAT2功能复用,启用四线 SD 模式时不能不加分析地继续独占该脚。Espressif ESP32 启动脚说明、Espressif SD 上拉与复用说明
1.3.4 验证
- **编译前配置:**在 Arduino IDE 选择
AI Thinker ESP32-CAM和实际串口。视频一度出现“缺少 FQBN(完全合格开发板名)”,原因是开发板尚未正确选定,不是舵机函数本身的语法错误。 - **处理视频中的两类代码错误:**初稿用过通用的小写宏
#define servo 12,它会在预处理阶段替换后续头文件里同名的servo标识符,引发库代码编译报错;视频最终将表示引脚的宏及对应引脚引用改为大写Servo,并保留回调临时变量小写servo。另一处错误是Slider0("duoji)缺右双引号,最终改成Slider0("duoji")。不要只按报错行号猜“少分号”,应先读编译器的首个错误信息。 - **替换凭据:**视频在成功编译后发现
auth仍是占位字符串,才换成自己的设备 Secret Key,并再次编译、上传。上方保留视频公开展示的课堂值只是为了忠实记录;复现时必须使用自己的auth、ssid、pswd,真实密钥和密码不应提交到公开仓库。 - **进入下载模式:**使用外置 USB 转串口器时,让 GPIO0 在复位/上电时临时接 GND,芯片才进入串口下载模式。原笔记的“GPIO0 与 D 短接”是识别错误,视频所指应为地线
GND。上传完成后移开 GPIO0—GND 跳线,再复位或重新上电进入正常运行;移开的只是下载模式跳线,板子和舵机仍需要可靠供电。Espressif 下载模式说明 - **保证连接可靠:**视频首次上传时 USB 转串口模块连接松动,重新插接后才继续。若串口突然消失或连接失败,应先检查模块、线缆、COM 口、供电、GPIO0 跳线和复位时机,避免带电反复改舵机接线。
- **配置 App:**在设备页面进入组件编辑,加入滑块,把“组件键名”设为小写
duoji并保存;大小写和拼写都要与BlinkerSlider Slider0("duoji")一致。显示标题、颜色和尺寸只影响界面外观,不代替通信键名。若要使用 0~180 的控制值,还需明确把最大值改为 180;视频画面仍是默认最大值 100。 - **运行验证:**退出下载模式、复位板子,开启与
ssid、pswd对应的热点,等待设备在线。视频末尾摄像头画面正常,滑块由 0 拖到 14 时实物舵机转动,证明duoji → slider0_callback() → Myservo()这条链路生效;但只观察这两个值不能证明全范围、端点精度或“任意角度”都已经校准。
若 App 显示在线但舵机不动,可按链路依次排查:组件键名 → 滑块是否发出数值 → 串口/Blinker 是否收到数据 → GPIO12 是否输出脉冲 → 舵机信号/共地/电源 → 舵机型号与机械负载。若拖动滑块时 ESP32-CAM 重启、视频断流或 Wi-Fi 掉线,优先怀疑舵机启动电流造成供电压降,而不是只修改云平台代码。
核对依据
- 本地
videos/293.11.通过点灯科技云平台控制舵机.mp4的官网页面、Arduino IDE 最终代码、编译/上传过程、App 滑块配置和实物动作;对照transcripts/293.11.通过点灯科技云平台控制舵机.srt与原始notes/md/293.11.通过点灯科技云平台控制舵机.md。代码同时与第 292 节的累积草图、第 288 节的Myservo()课堂源码交叉核对。 - Blinker Arduino 组件接口与官方 Slider Wi-Fi 示例:核对
BlinkerSlider、int32_t回调、attach()和Blinker.run()。 - Espressif ESP32 启动模式与SD 引脚要求:核对 GPIO0 下载条件、GPIO12 启动绑带和 SD 复用风险;TowerPro SG90 产品资料:核对普通 SG90 的额定电压与定位版性质。