尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

自供电机器人实战:基于ESP32的能量采集与智能任务调度

自供电机器人实战:基于ESP32的能量采集与智能任务调度 最近在折腾自供电机器人项目时发现一个很有意思的方向让机器人自己寻找光照、自己充电再把储备的能量分配给它想执行的任务。开源社区里这样一个原型被命名为 Rodeo它的定位很直接——一台试图“赚取自己电费”的机器人。本文不针对某个特定仓库做源码分析而是以 Rodeo 这类自供电机器人为目标从核心概念、系统设计、硬件选型、代码实现到运行验证完整拆解一台自供电巡检机器人的实现思路。如果你之前接触过巡检小车、太阳能充电板、ESP32 或 Arduino这篇文章会很好上手如果你是零基础也没关系文中会把每个模块的设计原因讲清楚。学完后你能理解自供电机器人最关键的三个问题能量怎么采集、能量怎么估算、任务怎么根据电量动态调度并且能拿到一套完整的可运行示例代码。1. 项目背景为什么机器人要自己赚电1.1 “自供电机器人”到底解决什么问题传统机器人的电量来源是外接电源或者手动更换电池。这种模式在实验室里没有问题但一旦把机器人放到温室大棚、露天园区、屋顶平台这类场景问题就来了电池容量撑不起长时间连续运行人工定期充电成本高也不够“机器人”巡检过程中突然电量不足任务只能中断还可能回不到充电点。Rodeo 这类自供电机器人的思路是不在外部充电桩上“依赖别人”而是把能量采集模块直接装到机器人身上利用太阳能板把环境光照转换为电能存入电池然后由电池统一供电给电机、传感器和主控。简单说它并不是真的去“赚钱”而是把自己运行所需的电能通过能量采集和智能调度“赚”出来。从专业定义上看自供电机器人属于能量采集与能量感知机器人Energy Harvesting and Energy-Aware Robot的范畴。它把能源问题从一个一次性约束变成了一个动态资源管理问题能量不再是“满就干活、没电就停”而是像操作系统里的 CPU 时间片一样可以被估算、分配和回收。1.2 一台自供电机器人由哪几部分组成一个完整自供电机器人原型至少包含三个核心子系统。第一个是能量采集系统。最常见的是太阳能光伏板通过升压、稳压电路把光能转成可用的充电电流。室内光照较弱时也可以换成光能、风能甚至温差发电但原理相似。第二个是能量存储系统。机器人任务不是匀速消耗的光照贡献也不稳定所以必须有电池作为缓冲。锂聚合物电池、18650 锂电池是轻量原型中最常用的选择。第三个是能量管理系统。这是最容易被新手忽略的部分。能量管理系统负责实时采样电池电压、电流、电量并根据当前电量决定机器人该执行任务、寻找光照、保持待机还是进入深度睡眠。没有这一层太阳能板只是给电池充了个寂寞机器人仍然可能突然关机。1.3 为什么这类项目值得动手实践自供电机器人把硬件、嵌入式、控制、能耗优化多个知识点揉在了同一个项目里。对学习者来说它比单纯点灯、驱动电机更有挑战性你需要用电压估算电量用状态机处理不同的电源阶段用“电量预算”来决定是否执行一个任务还要处理电机启动瞬间造成的电压跌落。这些坑恰好是真实嵌入式工程里最容易踩到、也最值得积累经验的。2. 系统设计思路与技术选型2.1 整体工作流程在写代码之前先把机器人的一天规划出来。一台“自己赚电”的巡检机器人典型工作流程如下机器人处于待机状态定期采样电池电压、电流和光敏传感器数据如果电量充足且光照一般机器人执行巡检任务比如前进、转向、采集环境数据如果光照很强且电量没有充满机器人优先停在光照下充电如果电量掉到警戒线附近机器人停止巡检转向寻找光照最强的方向如果电量已经极低系统进入深度睡眠只保留定时唤醒检查电压的能力电量重新上升后机器人恢复到待机或执行任务状态。这个流程的本质是一个以电量为核心的状态机。光照只是触发条件之一最终决策依据仍然是“当前有多少电未来我能补多少电”。2.2 硬件选型说明Rodeo 类自供电机器人没有统一硬件标准下面这套选型是常见的低成本示例你完全可以根据手头模块调整模块作用选型建议主控运行状态机与控制逻辑ESP32自带 ADC、I2C、深度睡眠太阳能板把光能转为电能6V / 5W 左右的单晶硅板充电管理给锂电池安全充电TP4056 或带升压的太阳能充电模块电池存储电能3.7V 18650 锂电池或锂电池组电流电压采样实时监测电池状态INA219 模块I2C 接口光敏传感器判断光照方向和强度两个光敏电阻或环境光传感器电机驱动驱动直流电机DRV8833 或 L298N电机提供动力普通 TT 直流减速电机这套硬件选型的核心是 ESP32。它自带 WiFi 和蓝牙方便以后做数据上报内置 ADC 可以采样模拟量深度睡眠电流可以做到很低适合太阳能供电场景。如果只做最小验证把 ESP32 换成 Arduino Nano 也可以但后续接入网络上报会麻烦一些。2.3 能耗模型与任务分配自供电系统代码设计的关键是“能耗预算”。每个任务不是想执行就执行需要先估算成本任务能耗 任务执行功率 × 任务持续时间比如巡检任务需要电机运行 5 秒电机功率约 800mW那么一次巡检预估消耗800mW × 5s 4000mJ而当前可用能量可以通过电池电压和实测功率粗估。当“剩余能量预算”覆盖不了任务成本时系统就拒绝执行高级任务转而去寻找光照或等待充电。这是自供电机器人区别于普通小车的地方任务调度必须电量驱动而不是纯时间驱动。2.4 软件分层架构为了避免把所有逻辑堆在loop()里建议按下面四层拆分驱动层INA219、光敏电阻、电机驱动等硬件访问电源管理层采样、电量估算、状态机迁移调度层根据电量预算判断能否执行任务行为层巡检、寻找阳光等具体业务动作。这样的好处是换一块新开发板时只需要改驱动层调整充电策略时只改电源管理层增加新业务任务时只加行为层。3. 环境准备与项目结构3.1 开发环境本文示例使用 PlatformIO ESP32 开发板 Arduino 框架。PlatformIO 的好处是依赖管理和命令行编译都比较干净。你也可以用 Arduino IDE代码本身兼容。必需的开发环境工具说明PlatformIO IDE 或命令行编译烧录、串口监控ESP32 开发板本文以 ESP32 DevKit 为例USB 数据线供电与烧录INA219、光敏电阻、电机驱动硬件模块3.2 PlatformIO 工程配置项目根目录下的platformio.ini是核心配置文件[env:esp32dev] platform espressif32 board esp32dev framework arduino monitor_speed 115200 lib_deps adafruit/Adafruit INA219这里没有锁定精确版本号建议以安装时 PlatformIO 自动解析到的可用版本为准。Adafruit INA219 库负责把 INA219 模块的电流电压采样转换成可读数值。3.3 项目目录结构为了模块清晰代码按功能拆分rodeo_robot/ ├── platformio.ini └── src/ ├── main.cpp ├── energy/ │ ├── EnergyMonitor.h │ └── EnergyMonitor.cpp ├── power/ │ ├── PowerStateMachine.h │ └── PowerStateMachine.cpp ├── task/ │ ├── TaskScheduler.h │ └── TaskScheduler.cpp ├── sensor/ │ ├── LightSensor.h │ └── LightSensor.cpp ├── motion/ │ ├── MotorController.h │ └── MotorController.cpp └── behavior/ ├── BehaviorController.h └── BehaviorController.cpp下面逐个实现这些模块。4. 核心模块代码实现4.1 能量采集与监测模块首先实现 EnergyMonitor它负责 INA219 初始化、电压电流采样和简单的电量估算。// src/energy/EnergyMonitor.h #ifndef ENERGY_MONITOR_H #define ENERGY_MONITOR_H #include Arduino.h #include Adafruit_INA219.h struct EnergySnapshot { float busVoltage; // 总线电压单位 V float currentMillis; // 电流单位 mA float powerMillis; // 功率单位 mW float batteryPercent; // 估算电量单位 % }; class EnergyMonitor { public: bool begin(); EnergySnapshot sample(); private: float estimateSoc(float voltage); Adafruit_INA219 ina219; }; #endif// src/energy/EnergyMonitor.cpp #include EnergyMonitor.h bool EnergyMonitor::begin() { Wire.begin(21, 22); // ESP32 I2C 引脚按你的板子调整 if (!ina219.begin()) { Serial.println([EnergyMonitor] INA219 init failed.); return false; } ina219.setCalibration_16V_400mA(); return true; } EnergySnapshot EnergyMonitor::sample() { EnergySnapshot snap; snap.busVoltage ina219.getBusVoltage_V(); snap.currentMillis ina219.getCurrent_mA(); snap.powerMillis snap.busVoltage * snap.currentMillis; snap.batteryPercent estimateSoc(snap.busVoltage); return snap; } float EnergyMonitor::estimateSoc(float voltage) { const float minVoltage 3.0f; // 锂电池截止电压 const float maxVoltage 4.2f; // 锂电池满电电压 if (voltage minVoltage) return 0.0f; if (voltage maxVoltage) return 100.0f; return (voltage - minVoltage) / (maxVoltage - minVoltage) * 100.0f; }这里有两个关键点需要说明。第一电量估算用的是简单的开路电压比例法。锂电池放电曲线并不是一条直线初中段电压变化很小所以这种方式只能得到粗略电量工程上更精确的做法是库仑计配合电压校准。本文用比例法是为了先把逻辑跑通。第二currentMillis 0通常表示电流方向是“流入电池”也就是在充电。但这个符号取决于 INA219 与电池的接线极性如果发现符号反了在接线层面调整即可不需要改代码逻辑。4.2 电源状态机自供电机器人的大脑是电源状态机。这里定义了五个状态状态含义HIBERNATE电量极低深度睡眠CHARGING正在充电等待电量恢复IDLE待机等待调度决策RUNNING执行任务SEEKING_LIGHT寻找光照最强的方向// src/power/PowerStateMachine.h #ifndef POWER_STATE_MACHINE_H #define POWER_STATE_MACHINE_H #include ../energy/EnergyMonitor.h enum class RobotState : uint8_t { HIBERNATE 0, CHARGING, IDLE, RUNNING, SEEKING_LIGHT }; class PowerStateMachine { public: explicit PowerStateMachine(EnergyMonitor* monitor); void update(); RobotState getState() const; const char* stateName() const; void forceState(RobotState s); private: void setState(RobotState s); EnergyMonitor* monitor; RobotState state; unsigned long lastChangeMs; }; #endif// src/power/PowerStateMachine.cpp #include PowerStateMachine.h PowerStateMachine::PowerStateMachine(EnergyMonitor* monitor) : monitor(monitor), state(RobotState::IDLE), lastChangeMs(0) {} void PowerStateMachine::update() { EnergySnapshot snap monitor-sample(); float soc snap.batteryPercent; bool charging snap.currentMillis 0; // 注意充电极性 switch (state) { case RobotState::HIBERNATE: if (soc 85.0f) { setState(RobotState::IDLE); } break; case RobotState::CHARGING: if (soc 92.0f) { setState(RobotState::IDLE); } else if (!charging soc 45.0f) { setState(RobotState::SEEKING_LIGHT); } break; case RobotState::IDLE: if (soc 20.0f) { setState(RobotState::SEEKING_LIGHT); } else if (charging soc 90.0f) { setState(RobotState::CHARGING); } break; case RobotState::RUNNING: if (soc 18.0f) { setState(RobotState::SEEKING_LIGHT); } else if (charging soc 30.0f) { setState(RobotState::CHARGING); } break; case RobotState::SEEKING_LIGHT: if (soc 5.0f) { setState(RobotState::HIBERNATE); } else if (soc 60.0f charging) { setState(RobotState::CHARGING); } break; } } void PowerStateMachine::setState(RobotState s) { if (state s) return; state s; lastChangeMs millis(); Serial.print([PowerStateMachine] state - ); Serial.println(stateName()); } const char* PowerStateMachine::stateName() const { switch (state) { case RobotState::HIBERNATE: return HIBERNATE; case RobotState::CHARGING: return CHARGING; case RobotState::IDLE: return IDLE; case RobotState::RUNNING: return RUNNING; case RobotState::SEEKING_LIGHT: return SEEKING_LIGHT; } return UNKNOWN; } void PowerStateMachine::forceState(RobotState s) { setState(s); }状态机的阈值需要仔细设计。如果 20% 就去找光、5% 才休眠中间留了足够长的“找光窗口”。要注意这些数字并不是越高越好阈值太高会导致机器人频繁放弃任务阈值太低又可能导致机器人在找光路上电量耗尽。实际调参时需要通过串口日志观察一整天数据再决定。4.3 基于电量预算的任务调度状态机回答的是“现在该充电還是活动”而调度器回答的是“当前电量能不能运行某个任务”。// src/task/TaskScheduler.h #ifndef TASK_SCHEDULER_H #define TASK_SCHEDULER_H #include Arduino.h struct TaskDef { const char* name; uint32_t requiredPowerMw; // 预计执行功耗 mW uint32_t durationMs; // 预计持续时间 ms uint8_t priority; // 优先级数字越小越紧急 }; class TaskScheduler { public: TaskScheduler(float availablePowerMw, float batteryPercent); void updateBudget(float availablePowerMw, float batteryPercent); bool canRun(const TaskDef task) const; private: float availablePowerMw; float batteryPercent; }; #endif// src/task/TaskScheduler.cpp #include TaskScheduler.h TaskScheduler::TaskScheduler(float availablePowerMw, float batteryPercent) : availablePowerMw(availablePowerMw), batteryPercent(batteryPercent) {} void TaskScheduler::updateBudget(float availablePowerMw, float batteryPercent) { this-availablePowerMw availablePowerMw; this-batteryPercent batteryPercent; } bool TaskScheduler::canRun(const TaskDef task) const { float runningCost task.requiredPowerMw * task.durationMs; // mJ float availableEnergy availablePowerMw * 3600.0f; // 当前功率一小时能量 return runningCost availableEnergy batteryPercent 15.0f; }这里的预算模型做了简化把“当前功率持续一小时产生的能量”当作可用能量。真实场景中可用能量应该来自剩余电池容量单位是 mWh 或 J而不是当前功率推算的一小时值。但作为教学原型这个模型能直观体现“电量不足就拒绝任务”的思想。4.4 光敏传感器与电机驱动寻找阳光依赖一对光敏电阻。左右两个光敏电阻的值就是机器人判断光照方向的依据。// src/sensor/LightSensor.h #ifndef LIGHT_SENSOR_H #define LIGHT_SENSOR_H #include Arduino.h class LightSensor { public: void begin(int leftPin, int rightPin); int readLeft(); int readRight(); private: int leftPin; int rightPin; }; #endif// src/sensor/LightSensor.cpp #include LightSensor.h void LightSensor::begin(int leftPin, int rightPin) { this-leftPin leftPin; this-rightPin rightPin; } int LightSensor::readLeft() { return analogRead(leftPin); } int LightSensor::readRight() { return analogRead(rightPin); }电机驱动采用典型的 H 桥控制核心代码把“前进、左转、右转、停止”封装成四个方法// src/motion/MotorController.h #ifndef MOTOR_CONTROLLER_H #define MOTOR_CONTROLLER_H #include Arduino.h class MotorController { public: void begin(int pwmLeft, int in1, int in2, int pwmRight, int in3, int in4); void forward(int speed); void turnLeft(int speed); void turnRight(int speed); void stop(); private: int pwmLeft, in1, in2, pwmRight, in3, in4; }; #endif// src/motion/MotorController.cpp #include MotorController.h void MotorController::begin(int pwmLeft, int in1, int in2, int pwmRight, int in3, int in4) { this-pwmLeft pwmLeft; this-in1 in1; this-in2 in2; this-pwmRight pwmRight; this-in3 in3; this-in4 in4; pinMode(pwmLeft, OUTPUT); pinMode(in1, OUTPUT); pinMode(in2, OUTPUT); pinMode(pwmRight, OUTPUT); pinMode(in3, OUTPUT); pinMode(in4, OUTPUT); } void MotorController::forward(int speed) { analogWrite(pwmLeft, speed); digitalWrite(in1, HIGH); digitalWrite(in2, LOW); analogWrite(pwmRight, speed); digitalWrite(in3, HIGH); digitalWrite(in4, LOW); } void MotorController::turnLeft(int speed) { analogWrite(pwmLeft, speed * 0.7); digitalWrite(in1, LOW); digitalWrite(in2, HIGH); analogWrite(pwmRight, speed); digitalWrite(in3, HIGH); digitalWrite(in4, LOW); } void MotorController::turnRight(int speed) { analogWrite(pwmLeft, speed); digitalWrite(in1, HIGH); digitalWrite(in2, LOW); analogWrite(pwmRight, speed * 0.7); digitalWrite(in3, LOW); digitalWrite(in4, HIGH); } void MotorController::stop() { digitalWrite(in1, LOW); digitalWrite(in2, LOW); digitalWrite(in3, LOW); digitalWrite(in4, LOW); }4.5 行为控制器与主程序行为控制器把光敏传感器、电机和调度器组合成两个业务动作执行巡检任务、寻找阳光。// src/behavior/BehaviorController.h #ifndef BEHAVIOR_CONTROLLER_H #define BEHAVIOR_CONTROLLER_H #include Arduino.h #include ../sensor/LightSensor.h #include ../motion/MotorController.h #include ../task/TaskScheduler.h class BehaviorController { public: BehaviorController(LightSensor* light, MotorController* motor, TaskScheduler* scheduler); void runInspection(); void seekSunlight(); private: LightSensor* light; MotorController* motor; TaskScheduler* scheduler; }; #endif// src/behavior/BehaviorController.cpp #include BehaviorController.h BehaviorController::BehaviorController(LightSensor* light, MotorController* motor, TaskScheduler* scheduler) : light(light), motor(motor), scheduler(scheduler) {} void BehaviorController::runInspection() { TaskDef inspectionTask{inspection, 800, 5000, 2}; if (!scheduler-canRun(inspectionTask)) { Serial.println([Behavior] energy not enough for inspection.); return; } motor-forward(120); delay(4000); motor-turnRight(100); delay(800); motor-stop(); } void BehaviorController::seekSunlight() { int left light-readLeft(); int right light-readRight(); Serial.printf([Behavior] L%d R%d\n, left, right); if (right left 30) { motor-turnRight(120); } else if (left right 30) { motor-turnLeft(120); } else { motor-forward(80); } delay(600); motor-stop(); }最后是主程序。它负责初始化所有模块并在loop()里周期性地更新状态机、打印日志、分发行为。// src/main.cpp #include Arduino.h #include energy/EnergyMonitor.h #include power/PowerStateMachine.h #include task/TaskScheduler.h #include sensor/LightSensor.h #include motion/MotorController.h #include behavior/BehaviorController.h EnergyMonitor monitor; PowerStateMachine powerMachine(monitor); LightSensor lightSensor; MotorController motor; TaskScheduler* scheduler nullptr; BehaviorController* behavior nullptr; unsigned long lastLogMs 0; unsigned long lastIdleCheckMs 0; void setup() { Serial.begin(115200); delay(300); if (!monitor.begin()) { Serial.println([Main] EnergyMonitor init failed, stop.); while (1) delay(1000); } lightSensor.begin(32, 35); motor.begin(13, 26, 27, 15, 12, 14); EnergySnapshot snap monitor.sample(); scheduler new TaskScheduler(snap.powerMillis, snap.batteryPercent); behavior new BehaviorController(lightSensor, motor, scheduler); Serial.println([Main] Rodeo robot initialized.); powerMachine.forceState(RobotState::IDLE); } void loop() { powerMachine.update(); RobotState state powerMachine.getState(); if (millis() - lastLogMs 5000) { EnergySnapshot snap monitor.sample(); Serial.printf([Main] V%.2f I%.2f P%.2f SOC%d%% State%s\n, snap.busVoltage, snap.currentMillis, snap.powerMillis, static_castint(snap.batteryPercent), powerMachine.stateName()); lastLogMs millis(); } switch (state) { case RobotState::RUNNING: behavior-runInspection(); break; case RobotState::SEEKING_LIGHT: behavior-seekSunlight(); break; case RobotState::HIBERNATE: // ESP32 深度睡眠定时唤醒检查电压 esp_deep_sleep(10 * 60 * 1000000ULL); break; case RobotState::CHARGING: // 充电状态只更新日志不执行任务 break; case RobotState::IDLE: if (millis() - lastIdleCheckMs 10000) { EnergySnapshot snap monitor.sample(); scheduler-updateBudget(snap.powerMillis, snap.batteryPercent); if (snap.batteryPercent 40.0f) { powerMachine.forceState(RobotState::RUNNING); } else { powerMachine.forceState(RobotState::SEEKING_LIGHT); } lastIdleCheckMs millis(); } break; } }注意代码中esp_deep_sleep(10 * 60 * 1000000ULL)表示休眠 10 分钟后由定时器唤醒。唤醒后程序会重新从setup()开始执行而不是回到loop()的下一行这是深度睡眠和普通delay()最大的区别。5. 运行与验证5.1 编译烧录在项目根目录执行pio run编译成功后连接开发板并上传pio run -t upload然后打开串口监控pio device monitor -b 1152005.2 串口现象正常启动后可以在串口看到类似下面的输出[PowerStateMachine] state - IDLE [Main] Rodeo robot initialized. [Main] V3.92 I-45.20 P-177.18 SOC92% StateCHARGING [Main] V4.01 I-10.30 P-41.30 SOC97% StateCHARGING [Main] V4.15 I120.80 P501.32 SOC100% StateRUNNING第一行中的负电流说明电池正在被太阳能板充电。如果光照稳定、电量充足状态会从充电切换到运行。此时可以看到巡检动作日志[Behavior] L512 R480 [Main] V4.10 I180.20 P738.82 SOC98% StateRUNNING当电量下降到阈值以下状态机会切到SEEKING_LIGHT机器人开始转向光照更强的方向。5.3 数据验证方法要判断一个自供电机器人是否设计合理不要只看它能不能跑而要记录三组数据数据项采集方式判断标准充电功率串口日志中的功率绝对值晴朗时最好大于待机功耗待机功耗关闭行为只保留采样和日志越小越好ESP32 可降频状态迁移频率记录状态切换时间戳避免频繁在充电和找光之间抖动如果发现机器人总是在充电和找光之间反复横跳说明状态机阈值缺少滞回区也就是进入某个状态的阈值和离开该状态的阈值太接近。这时需要把阈值拉开比如 18% 进入找光、30% 才恢复执行任务。6. 常见问题与排查问题现象常见原因解决思路INA219 初始化失败I2C 接线错误或地址不对检查 SDA/SCL 引脚确认模块地址 0x40电量显示跳变很大开路电压估算法本身精度有限增加滤波或改用库仑计电流方向判断反了充电电流显示为正值调整 INA219 与电池接线极性机器人一直进入深度睡眠充电功率不足或休眠阈值太高降低休眠阈值检查太阳能板是否被遮挡光敏电阻读数抖动环境光变化快、模拟引脚无滤波连续采样取平均加移动平均电机启动瞬间系统复位电机启动电流过大导致电压跌落电机驱动单独供电或在电源端加大电容深度睡眠后无法定时唤醒没有正确配置唤醒源检查esp_sleep_enable_timer_wakeup配置最需要提前预防的是电机导致系统复位。直流电机的启动电流往往是额定电流的好几倍如果和主控共用同一路电源瞬间电压跌落很可能让 ESP32 重启。工程上建议把电机驱动电源和逻辑电源分开或者在电池端并联一个大容量电容。7. 最佳实践与工程建议7.1 电气层面的建议自供电机器人对供电稳定性要求很高。不要把太阳能板直接接到电池上必须经过充电管理模块。ESP32 的 ADC 参考电压不够精确时电压采样必须校准否则电量估算会整体偏移。太阳能板的输出电压、电池电压、主控工作电压并不一致因此系统至少需要两种电压轨一种给电池充电一种给主控和传感器供电。电机驱动建议独立供电避免启动电流干扰主控。7.2 软件层面的建议状态机是自供电系统的核心所有状态迁移都要考虑“滞回”。例如进入充电状态的条件是电量低于 90%离开充电状态的条件是电量高于 92%两个阈值之间的差值能有效防止临界抖动。日志要有分级思想。调试阶段可以每 5 秒打印一次完整电压电流数据但带上电池后高频日志本身会消耗大量电能。正式运行时建议把日志频率降到 60 秒一次或者只在状态切换时打印。7.3 生产环境部署的注意事项如果需要把这类原型放到真实农田、屋顶或园区部署安全边界就很重要。首先要确保电池充电管理芯片具备过充、过放、过流保护其次要设置软件层面的电量保护带不能等到 0% 才关机最后所有涉及远程控制、手动强制状态切换的命令都应该有明确的授权机制和测试环境验证流程避免在无人看管状态下发生意外。8. 总结与下一步通过 Rodeo 这个自供电机器人原型本文完整实现了能量采集监测、电源状态机、电量预算调度和基础行为控制。你可能已经掌握的最重要思维是机器人不能只按时间执行任务还必须按能量执行任务。任何动作在发起之前都要先回答“这点电够不够”。接下来值得深入的方向有三个第一把开路电压估算法升级为库仑计让电量数据更可信第二引入非阻塞调度或 RTOS把delay()改成状态机驱动让机器人在等待期间也能响应电源变化第三加网络上报模块把电压、电流、状态迁移数据实时上传到服务端通过长时间数据分析和调参让机器人的自供电策略越来越贴近真实光照环境。硬件项目最忌只看代码不动手。你可以先用一块 ESP32、一个太阳能充电模块、一个 INA219 和两个电机搭出最小系统把本文代码下载到板子上放在窗台边运行一个下午观察电量曲线和状态切换是否合理。每次调试都记录一组数据这些数据会比任何理论都更有说服力。祝你早日让你的 Rodeo 真正“赚到自己的电”。
返回列表