通过点灯科技云平台实现小车的控制
295.13.通过点灯科技云平台实现小车的控制
#define BLINKER_WIFI // 选择 Blinker 的 Wi-Fi 接入方式。
#include <Blinker.h> // 引入 Blinker 库的声明。
#include "ESP32_CAM_SERVER.h" // 引入配套摄像头服务文件;这里不展开其内部实现。
#define LED 4 // 把 ESP32-CAM 板载白色闪光灯所接的 GPIO4 命名为 LED。
#define Servo 12 // 把输出舵机控制脉冲的 GPIO12 命名为 Servo;大小写照课堂代码保留。
#define IN1 13 // 把连接 L298N IN1 的 GPIO13 命名为 IN1。
#define IN2 15 // 把连接 L298N IN2 的 GPIO15 命名为 IN2。
#define IN3 14 // 把连接 L298N IN3 的 GPIO14 命名为 IN3。
#define IN4 2 // 把连接 L298N IN4 的 GPIO2 命名为 IN4。
char auth[] = "c56be051d11f"; // 视频公开展示的设备 Secret Key;复现时必须换成自己的密钥。
char ssid[] = "302"; // 视频中的 Wi-Fi 热点名称;复现时换成自己的网络名称。
char pswd[] = "12345678"; // 视频中的 Wi-Fi 热点密码;不是 Blinker 账号密码。
bool setup_camera = false; // 标记摄像头服务尚未由下方代码启动。
BlinkerButton Button1("led"); // 保留 LED 按键组件;"led" 是它在 App 中的组件键名。
BlinkerButton Button2("GoForward"); // 建立前进按键组件;字符串必须与 App 组件键名一致。
BlinkerButton Button3("GoBack"); // 建立后退按键组件;字符串必须与 App 组件键名一致。
BlinkerButton Button4("Left"); // 建立左转按键组件;字符串必须与 App 组件键名一致。
BlinkerButton Button5("Right"); // 建立右转按键组件;字符串必须与 App 组件键名一致。
BlinkerButton Button6("Stop"); // 建立停止按键组件;字符串必须与 App 组件键名一致。
BlinkerSlider Slider0("duoji"); // 保留舵机滑块组件;"duoji" 是 App 滑块的组件键名。
void button1_callback(const String & state) // 定义 LED 按键回调;state 保存收到的按键状态。
{ // 开始 LED 按键回调。
BLINKER_LOG("get button state: ", state); // 把收到的按键状态写到 Blinker 串口调试日志。
digitalWrite(LED, !digitalRead(LED)); // 读取 GPIO4 当前电平、取反,再写回 GPIO4。
} // 结束 LED 按键回调。
void button2_callback(const String & state) // 定义前进按键 Button2 的回调函数。
{ // 开始处理前进按键事件。
BLINKER_LOG("get button state: ", state); // 输出本次前进按键的状态字符串。
if (state == "press" || state == "tap") // 收到按住或轻触事件时进入运动分支。
{ // 开始前进分支。
GoForward(); // 向 L298N 输出 1、0、1、0,使小车前进。
} // 结束前进分支。
else if (state == "pressup") // 收到按键松开事件时进入停止分支。
{ // 开始松开停止分支。
Stop(); // 把四个方向输入都拉低,让两个电机通道停止。
} // 结束松开停止分支。
} // 结束前进按键回调。
void button3_callback(const String & state) // 定义后退按键 Button3 的回调函数。
{ // 开始处理后退按键事件。
BLINKER_LOG("get button state: ", state); // 输出本次后退按键的状态字符串。
if (state == "press" || state == "tap") // 收到按住或轻触事件时进入运动分支。
{ // 开始后退分支。
GoBack(); // 向 L298N 输出 0、1、0、1,使小车后退。
} // 结束后退分支。
else if (state == "pressup") // 收到按键松开事件时进入停止分支。
{ // 开始松开停止分支。
Stop(); // 停止两个电机通道。
} // 结束松开停止分支。
} // 结束后退按键回调。
void button4_callback(const String & state) // 定义左转按键 Button4 的回调函数。
{ // 开始处理左转按键事件。
BLINKER_LOG("get button state: ", state); // 输出本次左转按键的状态字符串。
if (state == "press" || state == "tap") // 收到按住或轻触事件时进入转向分支。
{ // 开始左转分支。
Left(); // 让两侧电机朝相反方向旋转,按课堂接线原地左转。
delay(100); // 阻塞等待 100 毫秒;delay() 的参数单位不是微秒。
Stop(); // 100 毫秒后主动停止,形成一次短促转向。
} // 结束左转分支。
else if (state == "pressup") // 收到按键松开事件时再次进入停止分支。
{ // 开始松开停止分支。
Stop(); // 再次保证两个电机通道停止。
} // 结束松开停止分支。
} // 结束左转按键回调。
void button5_callback(const String & state) // 定义右转按键 Button5 的回调函数。
{ // 开始处理右转按键事件。
BLINKER_LOG("get button state: ", state); // 输出本次右转按键的状态字符串。
if (state == "press" || state == "tap") // 收到按住或轻触事件时进入转向分支。
{ // 开始右转分支。
Right(); // 让两侧电机朝相反方向旋转,按课堂接线原地右转。
delay(100); // 阻塞等待 100 毫秒。
Stop(); // 100 毫秒后主动停止,形成一次短促转向。
} // 结束右转分支。
else if (state == "pressup") // 收到按键松开事件时再次进入停止分支。
{ // 开始松开停止分支。
Stop(); // 再次保证两个电机通道停止。
} // 结束松开停止分支。
} // 结束右转按键回调。
void button6_callback(const String & state) // 定义停止按键 Button6 的回调函数。
{ // 开始处理停止按键事件。
BLINKER_LOG("get button state: ", state); // 输出本次停止按键的状态字符串。
if (state == "press" || state == "tap") // 收到按住或轻触事件时进入停止分支。
{ // 开始主动停止分支。
Stop(); // 立即把 IN1~IN4 全部拉低。
} // 结束主动停止分支。
else if (state == "pressup") // 收到按键松开事件时进入同一停止逻辑。
{ // 开始松开停止分支。
Stop(); // 松开停止键后仍保持停止状态。
} // 结束松开停止分支。
} // 结束停止按键回调。
void slider0_callback(int32_t value) // 定义舵机滑块回调;value 是 App 发来的 32 位有符号整数。
{ // 开始处理本次滑块数据。
int servo=value; // 用小写 servo 临时保存滑块值;它与大写 Servo 宏不同。
Myservo(servo); // 把滑块值传给舵机控制函数,生成对应脉宽。
} // 结束滑块回调。
void Myservo(int 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 dataRead(const String & data) // 定义未由已绑定组件处理的数据所使用的通用回调。
{ // 开始处理收到的数据。
BLINKER_LOG("Blinker readString: ", data); // 将收到的内容写到串口调试日志。
Blinker.vibrate(); // 向 App 发送手机振动请求。
uint32_t BlinkerTime = millis(); // 读取本次开机以来经过的毫秒数。
Blinker.print("millis", BlinkerTime); // 以 millis 为键把该数值发送给 App。
} // 结束通用数据回调。
void GoForward() // 封装小车前进所需的四路方向电平。
{ // 开始设置前进电平。
digitalWrite(IN1, HIGH); // A 通道输入组合的第一位设为 1。
digitalWrite(IN2, LOW); // A 通道输入组合的第二位设为 0。
digitalWrite(IN3, HIGH); // B 通道输入组合的第一位设为 1。
digitalWrite(IN4, LOW); // B 通道输入组合的第二位设为 0。
} // 前进组合最终为 1、0、1、0。
void GoBack() // 封装小车后退所需的四路方向电平。
{ // 开始设置后退电平。
digitalWrite(IN1, LOW); // A 通道输入组合的第一位设为 0。
digitalWrite(IN2, HIGH); // A 通道输入组合的第二位设为 1。
digitalWrite(IN3, LOW); // B 通道输入组合的第一位设为 0。
digitalWrite(IN4, HIGH); // B 通道输入组合的第二位设为 1。
} // 后退组合最终为 0、1、0、1。
void Left() // 封装课堂接线下的原地左转电平。
{ // 开始设置左转电平。
digitalWrite(IN1, HIGH); // 让 A 通道按课堂定义的正向工作。
digitalWrite(IN2, LOW); // 与上一行共同构成 A 通道的 1、0。
digitalWrite(IN3, LOW); // 让 B 通道按课堂定义的反向工作。
digitalWrite(IN4, HIGH); // 与上一行共同构成 B 通道的 0、1。
} // 左转组合最终为 1、0、0、1。
void Right() // 封装课堂接线下的原地右转电平。
{ // 开始设置右转电平。
digitalWrite(IN1, LOW); // 让 A 通道按课堂定义的反向工作。
digitalWrite(IN2, HIGH); // 与上一行共同构成 A 通道的 0、1。
digitalWrite(IN3, HIGH); // 让 B 通道按课堂定义的正向工作。
digitalWrite(IN4, LOW); // 与上一行共同构成 B 通道的 1、0。
} // 右转组合最终为 0、1、1、0。
void Stop() // 封装两个电机通道的停止电平。
{ // 开始设置停止电平。
digitalWrite(IN1, LOW); // 把 A 通道输入 1 拉低。
digitalWrite(IN2, LOW); // 把 A 通道输入 2 拉低。
digitalWrite(IN3, LOW); // 把 B 通道输入 1 拉低。
digitalWrite(IN4, LOW); // 把 B 通道输入 2 拉低。
} // 停止组合最终为 0、0、0、0。
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); // 让舵机信号脚初始保持低电平。
pinMode(IN1, OUTPUT); // 将连接 L298N IN1 的 GPIO13 配置成输出。
digitalWrite(IN1, LOW); // 把 IN1 的初始输出显式设为低电平。
pinMode(IN2, OUTPUT); // 将连接 L298N IN2 的 GPIO15 配置成输出。
digitalWrite(IN1, LOW); // 课堂最终代码仍写 IN1;此处按视频原样保留该复制粘贴错误。
pinMode(IN3, OUTPUT); // 将连接 L298N IN3 的 GPIO14 配置成输出。
digitalWrite(IN1, LOW); // 课堂最终代码仍写 IN1;原意应是初始化刚配置的 IN3。
pinMode(IN4, OUTPUT); // 将连接 L298N IN4 的 GPIO2 配置成输出。
digitalWrite(IN1, LOW); // 课堂最终代码仍写 IN1;原意应是初始化刚配置的 IN4。
Blinker.begin(auth,ssid,pswd); // 使用设备密钥、热点名和密码接入 Blinker。
Blinker.attachData(dataRead); // 注册通用数据回调。
Button1.attach(button1_callback); // 把 LED 按键对象与 LED 回调关联。
Button2.attach(button2_callback); // 把前进按键对象与前进回调关联。
Button3.attach(button3_callback); // 把后退按键对象与后退回调关联。
Button4.attach(button4_callback); // 把左转按键对象与左转回调关联。
Button5.attach(button5_callback); // 把右转按键对象与右转回调关联。
Button6.attach(button6_callback); // 把停止按键对象与停止回调关联。
Slider0.attach(slider0_callback); // 把舵机滑块对象与滑块回调关联。
} // 结束初始化。
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() + "\"}"); // 向 video 组件发送 MJPG 格式和本机局域网 URL。
} // 结束首次启动摄像头服务的分支。
} // 结束本轮循环,随后再次进入 loop。
一、通过点灯科技云平台实现小车的控制
本节承接第 293 节已经完成的 ESP32-CAM 视频、LED 和舵机程序,并把第 294 节的 L298N 方向控制原理写成五个运动函数,再用五个 Blinker 按键远程调用。上方仅收录课堂主草图业务代码;Blinker.h 和 ESP32_CAM_SERVER.h 的库内实现没有抄入。代码保持视频最终版本的函数名、状态字符串、电平组合和 delay(100),并逐行补充初学者注释。
1.1 L298N电机驱动原理
- L298N 含有两组全桥:A 通道由
ENA、IN1、IN2控制,B 通道由ENB、IN3、IN4控制。本课保留 ENA、ENB 跳帽,使两个通道一直处于使能状态;程序只改变四个IN引脚的数字电平,不包含 PWM 调速。 - ESP32-CAM 的 GPIO 只给出逻辑控制信号,电机的大电流经过 L298N 的功率桥和电机电源,不能把直流电机直接接到 GPIO。主控、驱动板与电机电源还必须共地,否则 L298N 无法可靠判断 GPIO 的高、低电平。
GoForward()、GoBack()、Left()、Right()、Stop()是对四次digitalWrite()的封装。调用者不必每次重写四路电平,也能避免只改一侧电机而遗留上一动作状态。digitalWrite()设置的是持续保持的输出状态,不是一个瞬间脉冲。函数返回后,L298N 仍按最后写入的组合驱动电机,直到后续函数再次改写这些 GPIO、芯片复位或掉电。
1.2 直流电机旋转方式
在课堂当前电机安装方向和 OUT 接线下,五个动作对应关系如下。1 表示逻辑高电平,0 表示逻辑低电平,不表示 1 V 与 0 V。
| 函数 | IN1 | IN2 | IN3 | IN4 | A 通道 | B 通道 | 整车动作 |
|---|---|---|---|---|---|---|---|
GoForward() | 1 | 0 | 1 | 0 | 正向 | 正向 | 前进 |
GoBack() | 0 | 1 | 0 | 1 | 反向 | 反向 | 后退 |
Left() | 1 | 0 | 0 | 1 | 正向 | 反向 | 原地左转 |
Right() | 0 | 1 | 1 | 0 | 反向 | 正向 | 原地右转 |
Stop() | 0 | 0 | 0 | 0 | 快速停止 | 快速停止 | 停止 |
“正向”只是本课为当前接线规定的方向,不是电机的绝对属性。若某侧电机的两根 OUT 线对调,或左右通道接反,同一代码组合可能表现为后退或相反方向转弯;应断电后检查布线,而不是把错误接线当作固定真值表。
1.3 引脚初始化
课堂使用的真实映射是:
| L298N 输入 | ESP32-CAM 引脚 | 宏定义 |
|---|---|---|
IN1 | GPIO13 | #define IN1 13 |
IN2 | GPIO15 | #define IN2 15 |
IN3 | GPIO14 | #define IN3 14 |
IN4 | GPIO2 | #define IN4 2 |
原笔记中的“13、14、35”“L1~L4”和“ESP32 的三个 CAN 口”都是识别或理解错误:这里是四个 IN1~IN4 数字输入,对接 ESP32-CAM 的四个 GPIO。pinMode(INx, OUTPUT) 必须先把相应 GPIO 配成输出,随后 digitalWrite() 才是在主动输出电平。
视频最终编译、上传的 setup() 有一处值得特别警惕的复制粘贴错误:配置 IN2、IN3、IN4 后,紧随其后的三行仍写成 digitalWrite(IN1, LOW)。上方代码依照“课程源码原样保留”的要求没有暗改。它之所以没有阻止演示,是每个运动函数都会在动作前重新写满四个输入;但可靠复现时应把四组初始化明确改为:
digitalWrite(IN1, LOW); // 明确初始化 L298N 的 IN1。
digitalWrite(IN2, LOW); // 明确初始化 L298N 的 IN2。
digitalWrite(IN3, LOW); // 明确初始化 L298N 的 IN3。
digitalWrite(IN4, LOW); // 明确初始化 L298N 的 IN4。
这四行是对课堂错误的复现修正建议,不是对上方“视频最终源码”的替换记录。
1.4 前进功能实现
GoForward()依次输出1、0、1、0,两个通道都按课堂定义的正向旋转,小车前进。GoBack()依次输出0、1、0、1,同时翻转两个通道的输入极性,小车后退。后退不是修改宏定义实现的;宏只固定“哪个 GPIO 叫 IN1~IN4”,动作函数才负责写入不同电平。- 两个函数每次都改写全部四路输入,因此从转向直接切换到前进或后退时,不会继续保留上一动作的某一路方向电平。
1.5 停止功能实现
Stop() 把 IN1~IN4 全部写成 LOW。在本课 ENA、ENB 跳帽保持高电平的前提下,每个桥的两路输入同为 0,L298N 数据手册将这种状态归为快速停止,即电机两端被置于同一电位而产生电气制动。它不同于把 ENA/ENB 拉低后的高阻释放、依靠惯性滑行停止;实车仍会因速度、轮胎和地面状况产生少量停止距离。ST L298 数据手册
原笔记中的“停止分装函数”应为“停止封装函数”。这里的封装只是把四条停止语句集中到 Stop(),并不是另一个软件包或“分装”步骤。
1.6 左转功能实现
Left() 输出 1、0、0、1:A 通道按正向转,B 通道按反向转。两侧轮子反向旋转,使小车以两轮之间附近的位置为中心原地转向。课堂把该动作称为左转;实物若向右转,应先核对 Motor A/B 对应哪一侧、各侧电机极性是否与上一节一致。
在 button4_callback() 中,程序不是一直调用 Left(),而是写入一次左转组合,阻塞等待 100 ms,再调用 Stop()。持续旋转的原因是 GPIO/L298N 会保持前一次写入的状态,不是回调函数在内部“无限循环执行”。
1.7 右转功能实现
Right() 输出 0、1、1、0,正好与左转的两侧方向相反:A 通道反向、B 通道正向。原笔记的“n1n2 为底高、n2n3 为高低”应更正为:IN1/IN2 为低/高,IN3/IN4 为高/低。
右转回调也只维持约 100 ms 后主动调用 Stop()。这个时间不是固定转角:电池电压、电机差异、轮胎附着力、车重和地面都会改变一次动作的实际角度,因此这里只能称为开环的短促转向。
1.8 通过点灯科技控制小车
本节在原有 Button1("led") 后增加 Button2~Button6,避免覆盖 LED 按键对象。每个对象包含一个用于通信的组件键名,再用 attach() 绑定自己的回调:
App 按键事件
↓ 按组件键名匹配
BlinkerButton 对象
↓ attach() 注册的回调
button2_callback()~button6_callback()
↓ 调用动作函数
ESP32-CAM GPIO13/15/14/2
↓
L298N IN1~IN4 → 两侧电机
- 官方 Button Wi-Fi 示例把轻触、按下和松开作为不同状态处理,并在
setup()中用attach()注册回调。本课按视频源码直接比较字符串"tap"、"press"、"pressup",没有擅自换成官方示例里的状态宏。Blinker 官方 Button Wi-Fi 示例 - 前进和后退回调在
press或tap时写入运动电平,在收到pressup时停止;停止键在这三类事件中都调用Stop()。串口日志会打印实际收到的state,如果操作方式与预期不同,应先看日志,不应凭中文名称猜测事件。 - 左转、右转在
press或tap分支内执行“转向 →delay(100)→ 停止”,随后松开事件又停止一次。Arduino 的delay()参数单位为毫秒,所以这里是 100 ms,不是转录中可能误听的 100 μs。Arduinodelay()参考 delay(100)是阻塞等待:期间当前回调不能返回,主循环也不能继续调用Blinker.run()。本例等待很短且视频测试成功,但它不是实时转向控制;后续若增加更多联网任务,应考虑用millis()做非阻塞计时,而本稿不改写课堂源码。slider0_callback()、Myservo()、摄像头启动逻辑和 LED 回调来自前面的连续课程,本节继续保留,所以小车按键不是替代摄像头、灯或舵机,而是加入同一设备页面的新控制通道。
1.8.1 代码验证
- 在 Arduino IDE 中选择实际使用的 ESP32-CAM 开发板和串口,使用外置 USB 转串口器时按前课流程让 GPIO0 在复位/上电时临时接 GND,进入下载模式;上传结束后断开 GPIO0—GND 跳线并再次复位,才能正常启动应用。Espressif ESP32 下载模式说明
- 视频完成编译和上传,随后复位开发板。上方代码保留视频公开出现的
auth、ssid、pswd只为忠实记录;自己的设备必须替换为自己的凭据,真实密钥和密码不应提交到公开仓库。 - 上电后应先按“停止”,再短按前进/后退并准备断电。若动作相反,先断开电池,再核对左右电机及 OUT 接线;不要在电机运行或带电时拔插端子。
- 左右转各执行约 100 ms 后自动停止。需要校准转向幅度时可小步调整延时并在空旷地面测试,但这会是对课程程序的后续参数调整,不代表 100 ms 对所有小车都是某个固定角度。
- 视频末尾依次验证了前进、停止、后退、停止、左转和右转,LED 开关也仍然有效;这能证明本次实物上的按键键名、回调注册与四路方向输出已连通,不能据此推断不同电池、地面和接线下也会得到完全相同的速度与转角。
1.9 小车接线
本节视频实物接线应按端子丝印逐根核对:
| 起点 | 终点 | 作用 |
|---|---|---|
ESP32-CAM GPIO13 | L298N IN1 | A 通道方向输入 1 |
ESP32-CAM GPIO15 | L298N IN2 | A 通道方向输入 2 |
ESP32-CAM GPIO14 | L298N IN3 | B 通道方向输入 1 |
ESP32-CAM GPIO2 | L298N IN4 | B 通道方向输入 2 |
ESP32-CAM GND | L298N GND | 建立共同的 0 V 参考点 |
ESP32-CAM 5V | L298N 模块 +5V 节点 | 视频所用模块条件下的 5 V 供电连接 |
- 原笔记的“13→12→……→2 依次向下接入”是错误的。四根方向线只用 GPIO 13、15、14、2,并且必须按
IN1~IN4的对应表接,不能凭开发板针脚排列顺序推断。 - 第 294 节视频保留 L298N 模块的板载 5 V 稳压跳帽,并在其适用输入范围内由模块
+5V节点给 ESP32-CAM 供电。不同兼容板的稳压器和跳帽逻辑可能不同;上电前必须根据手中模块说明与 PCB 丝印确认该端此时是输出还是输入,并用万用表核对极性、电压。摄像头与 Wi-Fi 可能产生电流峰值,若反复重启或视频断流,应检查电池压降和稳压能力,必要时使用电流能力足够的独立 5 V 降压电源,但仍需共地。Ai-Thinker ESP32-CAM 原理图 - ENA、ENB 跳帽需保持安装,才能符合本节“只用 IN1~IN4 控制方向”的前提;若取下跳帽却没有另接高电平或 PWM,使能端未开启时电机不会按表格动作。
- GPIO13、15、14、2 与 ESP32-CAM 的 microSD/SDIO 线路复用,本课占用它们控制电机后,不能直接同时使用 microSD 四线模式。GPIO2、GPIO15 还是启动配置脚,外接 L298N 若在复位瞬间影响其电平,可能造成烧录或启动异常;遇到问题先断电、暂时拔下四根 IN 控制线排查。Espressif SDIO 引脚表、ESP32 启动模式说明
- 改线前先断开电池,避免电机端短路、正负极接反以及 USB 转串口器与 L298N 的 5 V 电源互相反向供电。GPIO 只能接 L298N 的逻辑输入,绝不能接电机电源或电机输出端。
1.10 点灯科技云平台配置
视频在设备页面进入编辑模式,新增五个普通按键组件,并把它们摆成上、下、左、右和中央停止的方向控制区。真正参与程序匹配的是“组件键名”,不是 C++ 对象名、显示中文或图标名称:
| 功能 | 程序对象 | App 组件键名(必须精确匹配) | 视频最终显示文本 | 建议图标 |
|---|---|---|---|---|
| 前进 | Button2 | GoForward | 前进 | 上箭头 |
| 后退 | Button3 | GoBack | 后退 | 下箭头 |
| 左转 | Button4 | Left | 左转 | 左箭头 |
| 右转 | Button5 | Right | 右转 | 右箭头 |
| 停止 | Button6 | Stop | 停止 | 停止/暂停符号 |
组件键名区分大小写,应逐字与 BlinkerButton ButtonX("...") 中的字符串一致。Button2~Button6 只是草图内变量名,不必写进 App;中文显示文本、图标和颜色则只影响界面表达。
1.10.1 文本标签修改
- 向页面加入五个按键,分别打开编辑页。
- 按上表填写
GoForward、GoBack、Left、Right、Stop组件键名。视频配置时曾发现一处名称填错并返回修正,这也说明通信依赖键名精确匹配。 - 把“显示文本1”分别改为“前进、后退、左转、右转、停止”。这些中文可以改成其他易懂文字,不会改变程序匹配;若修改的是组件键名,就必须同步修改代码字符串并重新烧录。
- 保存组件与页面布局,等待设备上线后再测试。原有摄像头、LED 和舵机组件仍留在同一页面。
1.10.2 图标修改
- 图标库中可分别选择上、下、左、右箭头与停止/暂停类图标;颜色和尺寸也可按界面需要调整。视频最终页面用方向图形直观表示动作。
- 原笔记所说“图标命名必须与代码一致”不正确:图标是视觉资源,不参与
BlinkerButton的键匹配。必须一致的是组件键名。 - “go for walk”也是转录误识别。程序前进键名是大小写准确的
GoForward;后退、左转、右转、停止依次为GoBack、Left、Right、Stop。
1.10.3 控制效果展示
- 点击“前进”后,
Button2回调调用GoForward();收到松开事件或点击“停止”后调用Stop()。 - “后退”走
Button3 → GoBack();“左转”和“右转”各执行约 100 ms 的反向轮转后主动停止。 - 视频实测前进、停止、后退、左转、右转均有响应,并再次操作了 LED 按键,证明新增五个按键没有覆盖
Button1("led")。 - App 显示已响应不等于电机供电一定正常。若串口能看到按键状态但车轮不动,应依次检查:组件键名、回调注册、GPIO 电平、IN1~IN4 顺序、ENA/ENB 跳帽、共地、电机电源及端子接触。
核对依据
- 本地
videos/295.13.通过点灯科技云平台实现小车的控制.mp4中 Arduino IDE 最终代码、编译/上传、接线、App 五个按键配置及实车演示;对照transcripts/295.13.通过点灯科技云平台实现小车的控制.srt、原始notes/md/295.13.通过点灯科技云平台实现小车的控制.md,并与第 293 节累积草图、第 294 节 L298N 接线和真值表交叉核对。 - Blinker 官方 Button Wi-Fi 示例与Blinker Arduino 组件接口:核对按键的轻触、按下、松开状态分类、回调注册与
Blinker.run()处理方式。 - ST L298 数据手册:核对双全桥结构、方向输入、使能条件和快速停止;Arduino
delay()参考:核对delay(100)的毫秒单位。 - Ai-Thinker ESP32-CAM 原理图、Espressif SDIO 引脚表与启动模式说明:核对电源连接、GPIO2/13/14/15 的 microSD 复用与 GPIO2/GPIO15 的启动注意事项。