先把命令和实际完成分开
只控制一个架空、无负载的小齿轮电机,先验证请求、驱动输入和真实转动三个层次。PWM duty不是转速反馈;COAST请求不是已观测到轴停止。这是后续小车的单电机基线,不包含无线控制或轮子触地运行。
参考:[6] Pololu2130:载板pinout、SLP默认上拉、开漏FLT与默认无斩波限流[7] TI DRV8833 SLVSAR1E:输入阈值、H桥真值、快衰减与保护[8] Pololu992 LP6V:电机绕组/减速比/规格条件[9] Pololu992数值条件与无编码器规格
指定驱动、电机和电源
固定Pololu #2130 DRV8833 carrier(md17a/0J3762)与#992 100:1 Micro Metal Gearmotor LP6V无编码器版,不能换成堵转电流数倍的HP/HPCB同外形电机。#992原规格6V空载130rpm/40mA、堵转0.36A是厂商条件/推算值;本例动力固定5.0V,实际转速/电流都须测,不按6V数值补表。驱动载板典型1.2A连续/2A峰值依温度与散热,绝非整系统保额;本例不并联双桥、不做堵转资格。
独立稳压5.0V电源,额定≥1A,台架电流限制先设0.5A并架空无载;若限流启动失败先查波形/机械,不自行无限提高。低压总开关可以立刻断动力。主控只桥USB供电,外部5V不接主控5V/3V3;电机回流直接回电源地,另短线将载板GND与主控GND公共参考。按载板方向核VIN/VMM,供电接有反接保护的VIN,本例VMM不接。
载板AISEN/BISEN默认接地,可调电流斩波默认没有启用;其过流保护不能当精确0.5A调节器,实验电源限流也不等于实时绕组峰值限制。本文不切板走线/加sense电阻。驱动附近VIN/GND并470µF/≥10V与100nF陶瓷,电机两端近焊100nF/≥25V陶瓷抑噪;断电焊接且不让电容脚碰外壳/相邻端子,电容不替代合格供电。
载板和电机引线/排针须可靠焊牢并绝缘,不靠把未焊排针插着导电。电机固定在台架,轴/齿轮无轮子和负载,线缆避开转轴。FLT高只是未被拉低,不证明动力存在、温度合格或轴已停止。固定TI SLVSAR1E的UVLO正文写欠压时nFAULT低,而保护表的Error Report又标None;两处不一致,本例不以FLT代替VIN存在/幅值检测,需独立量供电并保留真实低压现象。
| 主控 / 驱动 | 连接 | 默认和注意 |
|---|---|---|
| GPIO16/J1-9 | AIN1;节点另10k到GND | 第一PWM输入,启动LOW |
| GPIO17/J1-10 | AIN2;节点另10k到GND | 第二PWM输入,启动LOW;按v1.1固定排针表 |
| GPIO4/J1-4 | FLT;同点外10k到3V3 | 载板nFAULT开漏,绝不拉到电机5V |
| GPIO5/J1-5 | 正向按钮到GND;外10k到3V3 | LOW有效 |
| GPIO6/J1-6 | 反向按钮到GND;外10k到3V3 | LOW有效;同时按为冲突 |
| AOUT1 / AOUT2 | #992两个电机端子 | 调换两线只改变本地正反定义;断电换线 |
| BIN1 / BIN2 | 接GND | 未用第二桥;BOUT1/2留空 |
| SLP / AISEN / BISEN | 保持原载板配置不外接 | SLP板上拉高;00仅COAST,芯片仍供电 |
| VIN / GND | 独立5.0V与动力地 | VMM留空;另与主控共地,不并5V电源轨 |
参考:[1] IDF5.5.5 LEDC API:计时、分辨率、duty与stop[2] 固定LEDC实现:stop/重新update与fade服务前置[3] GPIO模式、电平与外部上拉/下拉接口[4] 64位微秒单调时钟[5] 官方DevKitC1 v1.1固定GPIO与互斥供电选项[6] Pololu2130:载板pinout、SLP默认上拉、开漏FLT与默认无斩波限流[7] TI DRV8833 SLVSAR1E:输入阈值、H桥真值、快衰减与保护[8] Pololu992 LP6V:电机绕组/减速比/规格条件[9] Pololu992数值条件与无编码器规格
H桥真值与本例选择
DRV8833的00为两输出高阻COAST/快衰减,10/01分别一个方向,11为低侧刹车/慢衰减。本例只使用00、PWM/0或0/PWM,永不主动用11刹车。换方向前先对另一通道ledc_stop(...,0),再设本方向duty/update;两个输入不会有意同时高。GPIO外10k下拉及芯片内部下拉给ROM/复位阶段00,不依赖软件先跑到app_main。
请求20kHz/10bit/APB固定时钟,duty从16每100ms加16,最高256/1024=25%,不将它称为25%转速。低占空比可能克服不了静摩擦,不能把不转直接当故障或通过提高至100%掩盖。输出斩波仍有绕组电感/回生与电源动态,源表平均数不是峰值资格。
参考:[1] IDF5.5.5 LEDC API:计时、分辨率、duty与stop[2] 固定LEDC实现:stop/重新update与fade服务前置[3] GPIO模式、电平与外部上拉/下拉接口[4] 64位微秒单调时钟[5] 官方DevKitC1 v1.1固定GPIO与互斥供电选项[6] Pololu2130:载板pinout、SLP默认上拉、开漏FLT与默认无斩波限流[7] TI DRV8833 SLVSAR1E:输入阈值、H桥真值、快衰减与保护[8] Pololu992 LP6V:电机绕组/减速比/规格条件[9] Pololu992数值条件与无编码器规格
从默认停止到每次受限运行
启动时按住任何方向键都不跑;必须两键全松稳定50ms,再保持单一方向稳定并持续500ms才开始。本次最多请求运行3秒,持续按住不自动重新开始。松开任一有效方向、按另一方向或两键冲突,在下一次owner处理时请求COAST;冲突即使一拍也作废使能,需要全松重新武装。
软件另留至少200ms COAST调度间隔与全松/新500ms保持,但没有编码器,不能据此证明电机已物理停转。读者必须先释放、目视/仪器确认轴静止,再操作另一键。禁止把“程序等了200ms”写成机械停车时间或允许满速直接反向。正负方向是本台端子定义,不承诺统一顺时针。
FLT采样LOW、时钟倒退或owner相邻运行间隔>250ms会撤本地状态;FLT故障解除也不能沿用旧按住重启,须全松再武装。API错误请求两输入LOW后锁闭至手工重启。主循环请求10ms tick,所有停机检查都只在owner运行时执行,不保证卡死期间PWM及时停止,不提供独立硬件急停。
参考:[1] IDF5.5.5 LEDC API:计时、分辨率、duty与stop[2] 固定LEDC实现:stop/重新update与fade服务前置[3] GPIO模式、电平与外部上拉/下拉接口[4] 64位微秒单调时钟[5] 官方DevKitC1 v1.1固定GPIO与互斥供电选项[6] Pololu2130:载板pinout、SLP默认上拉、开漏FLT与默认无斩波限流[7] TI DRV8833 SLVSAR1E:输入阈值、H桥真值、快衰减与保护[8] Pololu992 LP6V:电机绕组/减速比/规格条件[9] Pololu992数值条件与无编码器规格
架空验证顺序
observations.csv分别记requested_direction/duty和measured_current/PWM/observed_rotation/coast_to_observed_stop。无编码器时可用光电/视频方法另记条件,未测就留空。低侧COAST不吸收惯量为零,不能挂重物/坡面验证安全停车。
- 电机先不接载板输出,外部5V关;USB刷写。检查GPIO16/17默认LOW、FLT外上拉3V3以及两按钮逻辑。再接动力但电机仍断开,用仪器检查AIN1/AIN2对GND的单方向PWM/另一输入0、释放后00。普通接地示波器地夹只接公共GND,不能夹任何AOUT电机端;两电机端差分电压仅用合适差分测量,不将H桥输出误当接地端。
- 全部断电,可靠接#992到AOUT1/2,固定轴无负载、5V/0.5A限流,先不按键。检查上电默认不运行并保留真实输入/电流记录。
- 按正常重新武装流程短按住正向,关联串口请求、两输入波形与实际转动,释放看COAST后惯性。记录电源跌落、FLT、主控reset reason;不以日志代填真实rpm。
- 等实际完全静止后再全松/按反向。另检查双键冲突、启动已按住、持续超过3秒、松开重试;不要用手阻挡轴作故障注入。
- FLT读线检查只在载板FLT未连接时,另用预装测试开关把主控GPIO4输入拉GND(串1k)模拟LOW;这是输入夹具,不是实际过热/过流实验。完成后断电恢复真实FLT连接,不能把driver输出与测试地硬短对打。
- 结束释放全键,实际确认输入00和轴停,再断动力、最后断USB。电容放电后再改线。
参考:[1] IDF5.5.5 LEDC API:计时、分辨率、duty与stop[2] 固定LEDC实现:stop/重新update与fade服务前置[3] GPIO模式、电平与外部上拉/下拉接口[4] 64位微秒单调时钟[5] 官方DevKitC1 v1.1固定GPIO与互斥供电选项[6] Pololu2130:载板pinout、SLP默认上拉、开漏FLT与默认无斩波限流[7] TI DRV8833 SLVSAR1E:输入阈值、H桥真值、快衰减与保护[8] Pololu992 LP6V:电机绕组/减速比/规格条件[9] Pololu992数值条件与无编码器规格
诊断与恢复
本轮实际app205552B、12组同源主机测试通过;实际驱动、电机、PWM、电源、FLT、惯性和复位结果全部pending。没有测得转速、停车距离或电流收益。
| 现象 | 检查顺序 | 恢复 |
|---|---|---|
| 请求有但不转 | VIN/GND、限流灯、AIN波形、AOUT端与电机、静摩擦 | 松键断电查线;不把FLT高当电源已到 |
| 正反相反 | 本地端子方向定义 | 断电换电机两线或改标注,不带电拨线 |
| 立刻FLT低 | 电源欠压/短路/温升/过流与真实状态 | 停机断动力查原因;不短接FLT高/取消检查 |
| MCU重启/PWM抖 | 主控3V3、地回流、VIN跌落、绕组干扰 | 先改善电源/焊接/抑噪,不关闭brownout |
| 持续按住3秒后不再跑 | 运行期限/全松重武装 | 正常保护条件,先全松并确认物理停转 |
| 双键/切向后无动作 | 冲突作废、需全松与新单键保持 | 停机后按正常步骤重来,不能直接反向 |
参考:[1] IDF5.5.5 LEDC API:计时、分辨率、duty与stop[2] 固定LEDC实现:stop/重新update与fade服务前置[3] GPIO模式、电平与外部上拉/下拉接口[4] 64位微秒单调时钟[5] 官方DevKitC1 v1.1固定GPIO与互斥供电选项[6] Pololu2130:载板pinout、SLP默认上拉、开漏FLT与默认无斩波限流[7] TI DRV8833 SLVSAR1E:输入阈值、H桥真值、快衰减与保护[8] Pololu992 LP6V:电机绕组/减速比/规格条件[9] Pololu992数值条件与无编码器规格
固定工程、构建与恢复
只支持官方ESP32-S3-DevKitC-1 v1.1 / ESP32-S3-WROOM-1-N8R8,ESP-IDF5.5.5(commit b774170ff46c393eeb5e495ea37936038d3f4f4f),8MiB DIO80MHz/PSRAM关闭,PM关闭,UART0桥口115200。GPIO19/20原生USB与OPI专用脚未用于外设。实际工具Python3.12.13、GCC14.2.0 esp-14.2.0_20260121;无额外managed组件,不需要安装另一个SDK。
激活自己安装的固定IDF环境,进入解压工程根,使用新的配置目录:
REPLACE_PORT换成本板桥接串口。已有sdkconfig优先于defaults;复用旧配置时要核target/8MB/DIO/PSRAM=n/PM=n及实际UART。串口退出Ctrl+]。BOOT+RESET经桥口恢复ROM下载,先断执行器外部电源、松开全部按钮,再刷回已保存的正常固件,不改eFuse、不盲擦Flash。没有实板上传、运动、波形、电流或故障复位记录。
idf.py -B build-local -D SDKCONFIG=build-local/sdkconfig build
idf.py -B build-local -D SDKCONFIG=build-local/sdkconfig -p REPLACE_PORT flash monitor参考:[1] IDF5.5.5 LEDC API:计时、分辨率、duty与stop[2] 固定LEDC实现:stop/重新update与fade服务前置[3] GPIO模式、电平与外部上拉/下拉接口[4] 64位微秒单调时钟[5] 官方DevKitC1 v1.1固定GPIO与互斥供电选项[6] Pololu2130:载板pinout、SLP默认上拉、开漏FLT与默认无斩波限流[7] TI DRV8833 SLVSAR1E:输入阈值、H桥真值、快衰减与保护[8] Pololu992 LP6V:电机绕组/减速比/规格条件[9] Pololu992数值条件与无编码器规格
纯逻辑与真实物理分开
host_check.c直接包含生产策略头,在macOS arm64以clang ASan/UBSan执行合成按键和64位时刻;它不模拟LEDC、GPIO、电机/舵机、惯量或电源。读者可执行:
主循环有自己的单写者,不使用硬件fade,因此用ledc_set_duty再ledc_update_duty;固定5.5.5的ledc_set_duty_and_update会要求fade服务已安装,不能不读前置条件就替换。ledc_stop把该通道输出停在LOW;下一次set+update会重新使能信号,这是固定源码事实,不是示波器结果。APB时钟与PM关闭保证配置的时钟前提,不保证实物抖动、电源或机械响应。软件只在owner实际运行时检查时间;任务阻塞/CPU挂死期间PWM硬件可能继续输出,不能把250ms迟到检查或软件看门狗当独立硬件急停。
每份observations.csv只有表头。自己台架必须保留板号/模块修订、完整源与镜像SHA、接线照片、供电设定及真实波形/现象/失败行。名义频率、量化误差和主机模型不是本站运动、电流、温升或电源稳定性数据。
cc -std=c11 -Wall -Wextra -Werror -fsanitize=address,undefined -fno-omit-frame-pointer host_check.c -o /tmp/actuator-host
/tmp/actuator-host参考:[1] IDF5.5.5 LEDC API:计时、分辨率、duty与stop[2] 固定LEDC实现:stop/重新update与fade服务前置[3] GPIO模式、电平与外部上拉/下拉接口[4] 64位微秒单调时钟[5] 官方DevKitC1 v1.1固定GPIO与互斥供电选项[6] Pololu2130:载板pinout、SLP默认上拉、开漏FLT与默认无斩波限流[7] TI DRV8833 SLVSAR1E:输入阈值、H桥真值、快衰减与保护[8] Pololu992 LP6V:电机绕组/减速比/规格条件[9] Pololu992数值条件与无编码器规格
许可和上游
应用、策略头、主机夹具、图文为本站原创MIT,见LICENSE;SDK按其原许可证,未打包SDK或编译产物。固定源码依据ESP-IDF5.5.5 components/esp_driver_ledc/include/driver/ledc.h、src/ledc.c、gpio.h及esp_timer.h;板卡依据官方v1.1固定指南。完整上游链接和器件资料范围见THIRD_PARTY.md/versions.json。
参考:[1] IDF5.5.5 LEDC API:计时、分辨率、duty与stop[2] 固定LEDC实现:stop/重新update与fade服务前置[3] GPIO模式、电平与外部上拉/下拉接口[4] 64位微秒单调时钟[5] 官方DevKitC1 v1.1固定GPIO与互斥供电选项[6] Pololu2130:载板pinout、SLP默认上拉、开漏FLT与默认无斩波限流[7] TI DRV8833 SLVSAR1E:输入阈值、H桥真值、快衰减与保护[8] Pololu992 LP6V:电机绕组/减速比/规格条件[9] Pololu992数值条件与无编码器规格
实际构建的完整主程序与共享策略
下面两个文件与完整下载工程逐字对应。LEDC/GPIO由单个owner使用,策略头也直接供主机夹具调用;文件名注释只是分隔,不在构建时拼成一个文件。
// ---- main/main.c ----
/* SPDX-License-Identifier: MIT */
#include <stdio.h>
#include <inttypes.h>
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "motor_policy.h"
static esp_err_t coast(void){esp_err_t a=ledc_stop(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_0,0);esp_err_t b=ledc_stop(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_1,0);return a==ESP_OK?b:a;}
static esp_err_t drive(uint8_t dir,uint16_t duty){
if((dir!=1&&dir!=2)||!duty||duty>256)return ESP_ERR_INVALID_ARG;
/* Opposite input is always stopped LOW first. No11 braking state is used. */
ledc_channel_t selected=dir==1?LEDC_CHANNEL_0:LEDC_CHANNEL_1,opposite=dir==1?LEDC_CHANNEL_1:LEDC_CHANNEL_0;
esp_err_t e=ledc_stop(LEDC_LOW_SPEED_MODE,opposite,0);
if(e==ESP_OK)e=ledc_set_duty(LEDC_LOW_SPEED_MODE,selected,duty);
return e==ESP_OK?ledc_update_duty(LEDC_LOW_SPEED_MODE,selected):e;
}
void app_main(void){
gpio_config_t input={.pin_bit_mask=(1ULL<<GPIO_NUM_4)|(1ULL<<GPIO_NUM_5)|(1ULL<<GPIO_NUM_6),.mode=GPIO_MODE_INPUT};ESP_ERROR_CHECK(gpio_config(&input));
ledc_timer_config_t timer={.speed_mode=LEDC_LOW_SPEED_MODE,.duty_resolution=LEDC_TIMER_10_BIT,.timer_num=LEDC_TIMER_0,.freq_hz=20000,.clk_cfg=LEDC_USE_APB_CLK};ESP_ERROR_CHECK(ledc_timer_config(&timer));
for(int i=0;i<2;i++){ledc_channel_config_t c={.gpio_num=i?GPIO_NUM_17:GPIO_NUM_16,.speed_mode=LEDC_LOW_SPEED_MODE,.channel=i?LEDC_CHANNEL_1:LEDC_CHANNEL_0,.timer_sel=LEDC_TIMER_0,.duty=0,.hpoint=0};ESP_ERROR_CHECK(ledc_channel_config(&c));}
ESP_ERROR_CHECK(coast());printf("V07 REQUEST20kHz/10bit; SDK frequency=%"PRIu32"; GPIO16/17 -> DRV8833 AIN1/2. FLT4/forward5/reverse6 external10k pulls. Coast only, no measured RPM or shaft stop.\n",ledc_get_freq(LEDC_LOW_SPEED_MODE,LEDC_TIMER_0));
Motor state={0};uint8_t last=0;uint16_t duty=0;unsigned reason=0;bool api_fault=false;
for(;;){
uint8_t buttons=(gpio_get_level(GPIO_NUM_5)==0?1:0)|(gpio_get_level(GPIO_NUM_6)==0?2:0);
motor_tick(&state,buttons,gpio_get_level(GPIO_NUM_4)==0,(uint64_t)esp_timer_get_time());
if(!api_fault&&(state.active!=last||state.duty!=duty)){
esp_err_t e=state.active?drive(state.active,state.duty):coast();
if(e!=ESP_OK){api_fault=true;(void)coast();printf("MOTOR_API_FAULT=%s; coast requested; reset after diagnosis\n",esp_err_to_name(e));}
else printf("MOTOR_REQUEST dir=%u duty=%u/1024; actual rotation/current unmeasured\n",state.active,state.duty);
}
if(reason!=state.reason){reason=state.reason;printf("STOP_REASON=%u; no power removal or measured stop feedback\n",reason);}
last=state.active;duty=state.duty;vTaskDelay(pdMS_TO_TICKS(10));
}
}
// ---- main/motor_policy.h ----
/* SPDX-License-Identifier: MIT */
#pragma once
#include <stdint.h>
#include <stdbool.h>
typedef struct {bool initialized,eligible,holding;uint8_t candidate,stable,active;uint16_t duty;uint64_t changed,pressed,started,stepped,last,stopped;unsigned reason;} Motor;
static inline void motor_coast(Motor *s,uint64_t now,unsigned reason){if(s->active)s->stopped=now;s->active=0;s->duty=0;s->eligible=false;s->holding=false;s->reason=reason;}
/* buttons1/2 are directions,0 release,3 conflict; fault means nFAULT sampled low.
Coast is both control inputs0, not brake/physical stopped or power isolation. */
static inline void motor_tick(Motor *s,uint8_t buttons,bool fault,uint64_t now){
if(!s->initialized)*s=(Motor){.initialized=true,.candidate=buttons,.stable=3,.changed=now,.last=now,.stopped=now};
bool gap=now<s->last||now-s->last>250000;s->last=now;
if(gap){motor_coast(s,now,5);s->stopped=now;s->candidate=buttons;s->stable=3;s->changed=now;}
if(fault||buttons>3){motor_coast(s,now,4);s->changed=now;s->stable=3;s->candidate=buttons;return;}
if(buttons==3){motor_coast(s,now,2);s->changed=now;s->stable=3;s->candidate=3;return;}
if(s->active&&buttons!=s->active)motor_coast(s,now,buttons==3?2:1);
if(buttons!=s->candidate){s->candidate=buttons;s->changed=now;}
if(now>=s->changed&&now-s->changed>=50000&&s->stable!=s->candidate){
s->stable=s->candidate;
if(s->stable==0){s->eligible=true;s->holding=false;}
else if((s->stable==1||s->stable==2)&&s->eligible){s->pressed=now;s->holding=true;}
else{s->holding=false;s->eligible=false;}
}
if(s->active&&now-s->started>=3000000)motor_coast(s,now,3);
if(!s->active&&(buttons==1||buttons==2)&&s->stable==buttons&&s->eligible&&s->holding&&now-s->pressed>=500000&&now-s->stopped>=200000){s->active=buttons;s->eligible=false;s->holding=false;s->duty=16;s->started=now;s->stepped=now;s->reason=0;}
if(s->active&&now-s->stepped>=100000){s->stepped=now;if(s->duty<256)s->duty+=16;}
}参考:[1] IDF5.5.5 LEDC API:计时、分辨率、duty与stop[2] 固定LEDC实现:stop/重新update与fade服务前置[3] GPIO模式、电平与外部上拉/下拉接口[4] 64位微秒单调时钟
默认构建的目标、时钟和资源范围
8MiB Flash、PSRAM关闭、PM关闭与UART0为本例实际配置。改defaults后重用旧sdkconfig不一定生效,须核最终生成值;OPI模组的存在不等于程序已经使用PSRAM。
# ---- sdkconfig.defaults ----
CONFIG_IDF_TARGET="esp32s3"
CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
CONFIG_ESPTOOLPY_FLASHMODE_DIO=y
CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
# CONFIG_SPIRAM is not set
# CONFIG_PM_ENABLE is not set
CONFIG_ESP_CONSOLE_UART_DEFAULT=y
CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200
CONFIG_FREERTOS_HZ=1000参考:[1] IDF5.5.5 LEDC API:计时、分辨率、duty与stop[2] 固定LEDC实现:stop/重新update与fade服务前置[5] 官方DevKitC1 v1.1固定GPIO与互斥供电选项
原样策略的合成检查全文
本文件运行在主机,输入的是合成按键与时间;assert通过不证明电机、舵机、LEDC波形或电源已经实际验证。编译命令在上面的“纯逻辑与真实物理分开”一节。
// ---- host_check.c ----
/* SPDX-License-Identifier: MIT */
#include <assert.h>
#include <stdio.h>
#include "main/motor_policy.h"
static uint64_t now;
static void steps(Motor *s,unsigned buttons,bool fault,unsigned count){for(unsigned i=0;i<count;i++){motor_tick(s,buttons,fault,now);now+=10000;}}
static void start(Motor *s,unsigned dir){steps(s,0,false,8);steps(s,dir,false,60);assert(s->active==dir&&s->duty>0);}
int main(void){
Motor m={0};steps(&m,1,false,100);assert(!m.active);puts("PASS startup direction held does not run");
start(&m,1);assert(m.active==1);puts("PASS released then stable500ms forward request");
steps(&m,1,false,150);assert(m.duty==256);puts("PASS bounded16-step ramp at100ms to256/1024");
steps(&m,2,false,1);assert(!m.active&&!m.duty);steps(&m,2,false,100);assert(!m.active);puts("PASS opposite request first coasts and requires both released; never direct reversal");
start(&m,2);puts("PASS fresh release and reverse arm");
steps(&m,0,false,1);assert(!m.active);puts("PASS raw release coasts at next owner tick");
start(&m,1);steps(&m,3,false,1);assert(!m.active);steps(&m,2,false,100);assert(!m.active);puts("PASS even one conflicting button sample inhibits until both released");
start(&m,1);steps(&m,1,true,1);assert(!m.active&&m.reason==4);steps(&m,1,false,100);assert(!m.active);puts("PASS nFAULT sampled low stops and no held rearm");
start(&m,1);steps(&m,1,false,310);assert(!m.active&&m.reason==3);steps(&m,1,false,100);assert(!m.active);puts("PASS3s maximum requested run no automatic repeat");
start(&m,1);now+=250001;steps(&m,1,false,1);assert(!m.active&&m.reason==5);puts("PASS250ms late owner gap requests coast, no hardware timing claim");
steps(&m,0,false,8);steps(&m,1,false,2);steps(&m,0,false,2);steps(&m,1,false,2);assert(!m.active);puts("PASS chatter shorter than50ms cannot arm");
now=UINT64_C(5)*UINT64_C(4294967296)*1000;m=(Motor){0};start(&m,2);assert(m.active==2);puts("PASS full64bit timer over repeated millisecond wraps");
puts("12 same-source synthetic groups passed; not H-bridge, motor, current, physical stop or supply simulation");
}参考:[1] IDF5.5.5 LEDC API:计时、分辨率、duty与stop[2] 固定LEDC实现:stop/重新update与fade服务前置[4] 64位微秒单调时钟
资料来源与版本说明
实际读固定LEDC/GPIO/计时实现与板针脚,厂商部件页及手册另留原文与摘要;编译、同源合成与真实机械/电气分开。FS90厂家/销售方电流差异与DRV8833 UVLO正文/表格差异均明确,不将规格当本站测量。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。