先把作品拆成识别、请求与真实落箱三层
本实验仅处理称重≤1g的轻纸卡:S3-EYE识别固定深底浅色圆/方轮廓,电脑桥发出一次有限舵机请求,DevKit带动架空小纸挡片把手动放下的一张卡导向A/B盒。没有自动送料、位置、落箱或卡住传感器;预测正确、ACK接受与真实落箱分别记,不能互相替代。
本站已完成三配置构建、生产同源策略、真实OS双PTY桥和原模型目标自检诊断。当前实际S3 QEMU参考仍失败、STOP且0预测;实板全空。完整工程提供从停止门禁到实物验收的路径,未通过模型参考自检不得带真实舵机继续,也不能拿128个合成CPU成绩冒充相机准确率。
参考:[1] ESP-DL3.3.8 Model::test / run / tensor入口[2] ESP-DL3.3.8 实际参考比较实现[7] 官方S3-EYE固定修订指南[10] Pololu2818 FS90位置舵机与3.3V信号
锁定两块不同的S3板,不能把同号GPIO接错板
视觉板只限官方ESP32-S3-EYE-MB_V2.2+OV2640,8MiB Flash/DIO80与8MiB OPI PSRAM80,原生Micro-USB Serial/JTAG19/20;没有USB-UART桥。GPIO3不配置,LCD/SD/麦克风/RF不初始化。SCCB4/5、VSYNC6、HREF7、PCLK13、XCLK15,D0..7为11/9/8/10/12/18/17/16,完整配置在下载工程。
动作板是官方ESP32-S3-DevKitC-1 v1.1/WROOM-1-N8R8,PSRAM/PM关闭,电脑连接UART桥口(UART0 43/44,115200)。GPIO5/6仅属于这块动作板;EYE的5/6已连接相机。严禁将actuator固件刷入EYE,GPIO6与OV2640 VSYNC网冲突;刷写前按板壳标签核对工程/真实端口,刷错先断电进入ROM恢复。两板之间无GPIO线和5V/3V3线,分别由电脑USB供电,电脑运行本地可信双串口桥。
| 器材 | 唯一范围 |
|---|---|
| 位置舵机 | Pololu2818 / FEETECH FS90 / FS90-C V1.0;不是FS90R |
| 外供 | 独立5.0V额定≥1A,限流1A,正极物理断电开关 |
| 标记卡 | 深色轻纸卡上浅色圆/方轮廓;固定纸材/照明/距离/焦点,≤1g |
| 开发环境 | IDF5.5.5、Python3.12.13、GCC14.2.0;pyserial3.5 |
参考:[4] esp32-camera2.1.8 帧/归还/时间戳API[7] 官方S3-EYE固定修订指南[8] S3-EYE MB2.2实际原理图[9] DevKitC-1 v1.1 固定引脚/供电指南
先断电接线与空轴,外供正极不进开发板
DevKit GPIO5/J1-5接常开ARM按钮到GND,并另10k上拉到本板3V3/J1-2;GPIO6/J1-6接FS90信号,并另10k下拉GND。外供正极经物理开关仅到FS90红线,棕/黑线直接回外供负极,DevKit GND/J1-22低阻共地;470µF≥10V电解并100nF放在舵机旁,注意极性。不要让电机回流经过开发板供电轨,不并两板5V/3V3正极。
产品页明确FS90支持3.3V信号。原厂手册列900–2100µs范围、700mA@4.8V/800mA@6V堵转电流;Pololu600mA@6V为其测值且可变。本实验5V≥1A/限流1A是设计规格,不是本站测得5V堵转电流。先拔连杆空轴试1500/1400/1600,初次1500可能跳动,确认无硬限位再断电装极轻挡片;不要手堵轴,不把A/B请求直接当实际方向。
参考:[9] DevKitC-1 v1.1 固定引脚/供电指南[10] Pololu2818 FS90位置舵机与3.3V信号[11] FS90-C V1.0原厂规格/电流/死区[12] Pololu规格值来源区别[13] IDF5.5.5 LEDC duty/update/stop
固定依赖、模型身份与三种构建上下文
IDF5.5.5/b774170…;ESP-DL3.3.8/77a8a624…、dl_fft0.8.0、esp32-camera2.1.8/b0556a78…、esp_jpeg1.3.1、esp_new_jpeg1.0.2,标准Registry配置与dependencies.lock随包提供。本站仅克隆同版本已装缓存,不改SDK/组件源码。
camera/main/shape-s3.espdl沿用V05原7296B/c495b5de…模型;CMake对完整SHA检查,更换模型被拒绝。model-reference包含原训练结果、版本和训练源码供追溯,不重训、不把打印模板称新训练集。三最终配置分别是EYE原生USB、同源UART诊断、DevKit桥UART0;UART诊断仅供QEMU,不表示EYE存在UART桥,勿接占用LCD的43/44。首次缩进警告Exit2与后续成功都保留,编译不等于运行或机械成功。
- 备份原Flash/分区并安装固定IDF5.5.5环境。
- 在camera内执行idf.py set-target esp32s3、idf.py build,使用EYE原生USB刷写/监视;确认参考测试和PSRAM,退出monitor。
- 在actuator内以同SDK构建,用DevKit UART桥刷写/监视;先断舵机外供检查默认LOW与ARM。
- 诊断UART使用单独build-uart和独立sdkconfig,完整命令见README;不得沿用原生USB sdkconfig冒称切换成功。
# ---- camera/main/idf_component.yml ----
dependencies:
idf: "==5.5.5"
espressif/esp-dl: "==3.3.8"
espressif/dl_fft: "==0.8.0"
espressif/esp32-camera: "==2.1.8"
espressif/esp_jpeg: "==1.3.1"
espressif/esp_new_jpeg: "==1.0.2"参考:[1] ESP-DL3.3.8 Model::test / run / tensor入口[3] ESP-DL3.3.8 TensorBase与指数[4] esp32-camera2.1.8 帧/归还/时间戳API[7] 官方S3-EYE固定修订指南[9] DevKitC-1 v1.1 固定引脚/供电指南
模型test失败就STOP,中央ROI也要同分布
启动先检查8MiB PSRAM,再实际model->test。参考失败直接STOP,不初始化相机、不产生CAMERA_READY/RESULT。输入必须INT8/NHWC[1,16,16,1]/标量指数−7,输出INT8[1,2]/标量指数−32..16;形状、dtype或逐通道指数不符同样停止。不存在skip-test、人工分数或手动COMMIT的正式桥选项。
OV2640灰度160×120取中央64×64(x48..111/y28..91),每4像素取中心点缩到16×16,pixel/255后按指数−7四舍五入并饱和127。固定训练背景15..44、浅色轮廓185..245;白底黑线或实心图形不等价。本包打印深底浅轮廓只是摆位帮助,先用V01照片固定完整轮廓在ROI内约占一半到三分之二,回刷V10后再测。镜头曝光、纸纹、旋转、阴影仍是未证明的域差异。
参考:[1] ESP-DL3.3.8 Model::test / run / tensor入口[2] ESP-DL3.3.8 实际参考比较实现[3] ESP-DL3.3.8 TensorBase与指数[4] esp32-camera2.1.8 帧/归还/时间戳API[6] esp32-camera2.1.8 OV2640灰度格式
把低置信拒动写成可检查的logit差值
输出是两个int8量化logits和标量指数e,不做softmax。主机计算abs(raw0−raw1)×2^e;相等或差值<2.0发CANCEL,满足门限才取较大者:circle=0→A,square=1→B。raw越界、指数越界或非规范整数不是有效预测。
2.0是本实验固定的未校准拒动门限,不能称概率、百分比置信度或准确率保证;大差值也可能错。固定门限后才做连续50个实体尝试,不能看错误后在同run改阈值并删除坏行。生产像素与门限头直接用于同源桌面检查。
// ---- shared/decision.h ----
/* SPDX-License-Identifier: MIT */
#pragma once
#include <stdint.h>
#include <stddef.h>
#include <math.h>
namespace v10 {
/* Uncalibrated logit margin, not a probability or camera accuracy claim. */
inline int decide(int a,int b,int exponent){if(a < -128||a>127||b < -128||b>127||exponent < -32||exponent>16)return -1;double gap=ldexp((double)(a>b?a-b:b-a),exponent);if(a==b||gap<2.0)return -1;return b>a?1:0;}
/* Central64x64 nearest-neighbour to16x16. No histogram/contrast correction. */
inline bool pixels(const uint8_t*src,size_t len,unsigned w,unsigned h,int8_t*out){if(!src||!out||w!=160||h!=120||len!=19200)return false;for(unsigned y=0;y<16;y++)for(unsigned x=0;x<16;x++){unsigned p=src[(28+y*4+2)*160+(48+x*4+2)];unsigned q=(p*128U+127U)/255U;out[y*16+x]=(int8_t)(q>127?127:q);}return true;}
}参考:[1] ESP-DL3.3.8 Model::test / run / tensor入口[3] ESP-DL3.3.8 TensorBase与指数
帧、主机与动作板各自计时,不做跨板相减
固定驱动用本板esp_timer_get_time在DMA帧启动时写fb.timestamp,属于启动近似而不是精确曝光时刻。CAPTURE解析后只接受本机请求之后、非未来且帧龄≤500ms的帧;最多取四次,每个非空帧恰一次return,归还后再推理,推理后复核总耗时≤1.5s与帧龄。fb_get可阻塞更久,这些检查不保证硬实时返回。
主机只以自己的monotonic等待关联RESULT≤1.6s,已排队消息>200ms拒绝;动作板以自己的64位微秒钟检挑战至COMMIT≤2s、距本板最后收到当前SESSION/PING>300ms撤会话(重复同会话PING也刷新,不从物理拔线瞬间计时),owner回拨/间隔>250ms恢复运行时撤销。没有servo_now减eye_timestamp。即使相机或电脑阻塞,过期token也不能在数分钟或完整32位周期后复活。
参考:[4] esp32-camera2.1.8 帧/归还/时间戳API[5] esp32-camera2.1.8 DMA帧启动时间戳[14] IDF5.5.5 UART非阻塞FIFO发送[15] IDF5.5.5 USB Serial/JTAG驱动[16] IDF5.5.5 64位单调微秒计时
一次性请求、整批失效预检与可信本地边界
ASCII协议带版本1、boot/session/strictseq/一次性token和CRC16-CCITT-FALSE,完整规范见README。桥先观察实际CAMERA_READY再建动作SESSION;需要新的REQUEST/token,才向当前camera boot请求新帧。重复COMMIT不叠加运动,旧会话/过期/错误token拒绝。随机标识与CRC不是密码学认证,仅用于本机可信双串口,不对远程开放。
主机Bridge.wait响应等待阶段每次读取两端完整批次,先通查STOP、boot变化、过期和非预期REJECT,再选择匹配结果;保留原RESULT后新boot仍发COMMIT的反例及修正。迟到旧行也可能保守停止,不承诺自动忽略所有旧消息继续。ACK阶段才观察到失效时COMMIT可能已经在飞或接受,不能瞬间撤回;ACK只表示状态机接受,不是波形/位置/落箱。非法/溢出行丢弃直到真实LF;部分行空闲>200ms也持续丢尾到LF,不能把旧后缀接成新命令。idle只检查STOP、READY的boot变化和视觉READY间隔,不把wait的所有校验覆盖到任意op。
参考:[14] IDF5.5.5 UART非阻塞FIFO发送[15] IDF5.5.5 USB Serial/JTAG驱动[16] IDF5.5.5 64位单调微秒计时
ARM、有限脉宽与CPU停调度的真实边界
新SESSION后必须先松开ARM稳定50ms,再新按住1s;启动按住不武装,每轮消费后重来。动作第一秒请求1500µs,随后每100ms至多5µs到1400(A)/1600(B),约3–4s保持路由,再回1500,总6s停止PWM。50Hz/14bit的三个duty为1147/1229/1311;约1.22µs量化和10µs原厂死区意味着5µs命令步进不等于平滑物理转动。
原始ARM松开在下次owner采样停止,不等释放消抖。只有一个owner操作LEDC;API故障锁存、撤会话时尽力发STOP,停止PWM不依赖串口送达。信号LOW不是断5V、刹车、到位或回中证明;CPU完全停调度时LEDC可维持旧波形,恢复后才能检查250ms间隔。必须保留外供正极物理开关,出现顶住/异响/发热先断电再清卡,不能靠串口心跳当硬件急停。
// ---- actuator/main/main.cpp ----
/* SPDX-License-Identifier: MIT */
#include <cstdio>
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "esp_timer.h"
#include "esp_random.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "actuator.h"
#include "transport.h"
static uint64_t nonce(){uint64_t n;do{n=((uint64_t)esp_random()<<32)|esp_random();}while(!n);return n;}
static esp_err_t apply(bool active,uint16_t pulse){if(!active)return ledc_stop(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_0,0);esp_err_t e=ledc_set_duty(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_0,v10::duty(pulse));return e==ESP_OK?ledc_update_duty(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_0):e;}
extern "C" void app_main(){
gpio_config_t in={};in.pin_bit_mask=1ULL<<GPIO_NUM_5;in.mode=GPIO_MODE_INPUT;ESP_ERROR_CHECK(gpio_config(&in));
ledc_timer_config_t t={};t.speed_mode=LEDC_LOW_SPEED_MODE;t.duty_resolution=LEDC_TIMER_14_BIT;t.timer_num=LEDC_TIMER_0;t.freq_hz=50;t.clk_cfg=LEDC_USE_APB_CLK;ESP_ERROR_CHECK(ledc_timer_config(&t));
ledc_channel_config_t c={};c.gpio_num=GPIO_NUM_6;c.speed_mode=LEDC_LOW_SPEED_MODE;c.channel=LEDC_CHANNEL_0;c.timer_sel=LEDC_TIMER_0;c.duty=0;c.hpoint=0;ESP_ERROR_CHECK(ledc_channel_config(&c));ESP_ERROR_CHECK(apply(false,1500));
printf("V10 FS90 POSITION SERVO ONLY; external5V; signal is not power removal. No encoder/jam sensor. UART0 bridge115200.\n");
if(v10::transport_init()!=ESP_OK)return;
v10::Actuator s(nonce());v10::Lines lines;v10::Tx tx;bool was=false,fault_reported=false;uint16_t prior=0;uint64_t prior_session=0;
for(;;){
uint64_t now=(uint64_t)esp_timer_get_time();s.tick(gpio_get_level(GPIO_NUM_5)==0,now);
if(lines.expire(now))s.revoke(10);
char input[64];int n=v10::read_bytes(input,sizeof input);if(n<0)s.api_fault();
for(int k=0;k<n;k++){v10::Msg m;int rc=lines.feed(input[k],m,(uint64_t)esp_timer_get_time());if(rc<0)s.revoke(10);else if(rc>0){
/* Resample raw ARM and deadlines at every command, not a stale loop sample. */
now=(uint64_t)esp_timer_get_time();s.tick(gpio_get_level(GPIO_NUM_5)==0,now);
auto answer=s.receive(m,now,nonce());if(!tx.queue(answer))s.api_fault();
}}
/* Stop/heartbeat also checked after parsing. No other task owns this channel. */
s.tick(gpio_get_level(GPIO_NUM_5)==0,(uint64_t)esp_timer_get_time());
if(s.active!=was||(s.active&&s.pulse!=prior)){if(apply(s.active,s.pulse)!=ESP_OK){s.api_fault();(void)apply(false,1500);}was=s.active;prior=s.pulse;}
/* Explicit STOP is already known to the bridge; no unsolicited echo may
poison its next SESSION. Local timeout/parse/gap/API faults still report. */
if((prior_session&&!s.session&&s.reason!=7)||(s.fault&&!fault_reported)){
if(!tx.queue({v10::STOP,s.boot,0,0,0,(int)s.reason,0,0}))s.api_fault();
fault_reported=s.fault;
}
prior_session=s.session;
if(!tx.poll())s.api_fault();
if(s.fault){(void)apply(false,1500); /* sticky RESET-only API fault */}
vTaskDelay(pdMS_TO_TICKS(10));
}
}参考:[10] Pololu2818 FS90位置舵机与3.3V信号[11] FS90-C V1.0原厂规格/电流/死区[13] IDF5.5.5 LEDC duty/update/stop[14] IDF5.5.5 UART非阻塞FIFO发送[16] IDF5.5.5 64位单调微秒计时
按层运行,再记录50个连续尝试
先不接舵机外供完成模型与镜头层,失败停此;再按V06空轴完成电源/波形/ARM层,确认实际A/B方向;最后断电装纸挡片并以每次一张、架空轻纸卡整合。不是自动送料:识别时纸卡在独立托盘,收到请求后只有目视确认挡片方向且手离开行程,才在约3–4s保持窗手动放开一张卡;来不及就跳过并记录,不追卡或伸手。
安装pyserial3.5,退出monitor,执行tools/bridge.py --camera实际EYE端口 --actuator实际DevKit桥端口 --output run50.csv。固定奇数circle/偶数square,25/25交错,输入truth确认才发新请求。低分放人工复核盒;高分也逐项填observed_bin、jam、recovery、note。每轮STOP并重新会话/ARM;错误或jam记录落盘后结束run,诊断后另起文件,部分run与journal必须保留。
CSV与.events.jsonl排他创建、每事件flush+fsync,不能覆盖/补造;观察不完整时journal仍留attempt_begin/result。analyze_run.py要求顺序1..50与固定truth,少于50仅报告计数,不给准确率;全部50包含reject、unavailable、误分、卡住和错误。endToEndOver50同时要求预测/请求/真实落箱正确且无jam/error,分母恒50。工具只能分析操作者输入,不能认证其物理真实性。所有中断run也要与完整50run并列报告attempt/jam/error与中断原因,不能只挑完整run或把其比例当全部尝试总体表现;部分run不补造、不删除。
# ---- tools/analyze_run.py ----
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""All50 attempts remain denominator. Partial runs have no accuracy claims."""
import argparse,csv,json,math
FIELDS=['sample','truth','prediction','raw0','raw1','exponent','margin','command','observed_bin','jam','error','recovery','camera_boot','actuator_boot','session','physical_observer_note']
def analyze(path):
with open(path,newline='')as f:
reader=csv.DictReader(f)
if reader.fieldnames!=FIELDS:raise ValueError('schema mismatch')
rows=list(reader)
if len(rows)>50:raise ValueError('at most50')
for n,r in enumerate(rows,1):
if r['sample']!=str(n):raise ValueError('continuous sample1..N required; no delete/reorder')
if r['truth']!=('circle'if n%2 else 'square'):raise ValueError('preassigned odd circle/even square')
if r['prediction']not in ['circle','square','reject','unavailable']or r['command']not in ['routeA','routeB','none','none_or_unconfirmed']:raise ValueError('result vocabulary')
if r['observed_bin']not in ['A','B','review','unknown']or r['jam']not in ['yes','no']:raise ValueError('physical observation required')
if any(not r[x]for x in ['recovery','physical_observer_note','camera_boot','actuator_boot','session']):raise ValueError('missing provenance/observation')
if r['prediction']in ['circle','square','reject']:
raw=[int(r[k])for k in ['raw0','raw1','exponent']]
if any(str(v)!=r[k]for v,k in zip(raw,['raw0','raw1','exponent']))or not(-128<=raw[0]<=127 and -128<=raw[1]<=127 and -32<=raw[2]<=16):raise ValueError('score contract')
gap=math.ldexp(abs(raw[0]-raw[1]),raw[2]);margin=float(r['margin'])
pred='reject'if raw[0]==raw[1]or gap<2.0 else('square'if raw[1]>raw[0]else'circle')
if not math.isfinite(margin)or margin!=gap or pred!=r['prediction']:raise ValueError('prediction/margin inconsistent with raw logits')
command='none'if pred=='reject'else('routeA'if pred=='circle'else'routeB')
if r['command']!=command:raise ValueError('command inconsistent with prediction')
elif not r['error']:raise ValueError('unavailable needs recorded error')
complete=len(rows)==50
correct=sum(r['prediction']==r['truth']for r in rows)
routed=sum(r['command']in ['routeA','routeB']for r in rows)
end_to_end=sum(r['prediction']==r['truth']and r['command']==('routeA'if r['truth']=='circle'else'routeB')and r['observed_bin']==('A'if r['truth']=='circle'else'B')and r['jam']=='no'and not r['error']for r in rows)
return {'attempts':len(rows),'qualified50':complete,'requestedRoutes':routed,'rejectedOrUnavailable':sum(r['prediction']in ['reject','unavailable']for r in rows),'jamRows':sum(r['jam']=='yes'for r in rows),'errorRows':sum(bool(r['error'])for r in rows),'predictionCorrectOver50':correct/50 if complete else None,'endToEndOver50':end_to_end/50 if complete else None,'scope':'Only supplied operator CSV; does not authenticate physical origin, infer confidence calibration or detect unreported jams.'}
def main():
p=argparse.ArgumentParser(description=__doc__);p.add_argument('csv');a=p.parse_args()
try:print(json.dumps(analyze(a.csv),indent=2))
except (ValueError,OSError)as e:p.exit(2,str(e)+'\n')
if __name__=='__main__':main()参考:[7] 官方S3-EYE固定修订指南[10] Pololu2818 FS90位置舵机与3.3V信号[13] IDF5.5.5 LEDC duty/update/stop[16] IDF5.5.5 64位单调微秒计时
桌面夹具、当前QEMU失败与未实测表分开
生产wire/decision/actuator头的335条ASan/UBSan断言覆盖规范整数、CRC、旧尾、像素、门限、重放、时限、ARM、长运行与PWM请求;完整Bridge.trial/wait的18组批次覆盖RESULT/ACK前后失效与过期/错token/串口错误。双真实OS PTY走生产Port/Bridge、生产策略actorpeer和明确camera桩,六场景及空闲旧尾均通过;这些都不是模型推理、USB电气或物理分拣。
本次同一真实UART镜像在S3 QEMU仍参考28与−128不匹配,ESP_FAIL→V10 STOP,不进入相机初始化、0RESULT。simulation保持failed;不能将构建和受控成功分数测试改成相机已成功。observations的连续50、电源波形、相机层、故障表全部仅表头,真实镜头表现、50连续成功率、方向、电流、动作耗时与卡住恢复仍待现场执行。
- 做到这里,你应该看到
- 固定模型参考必须先在目标通过;再做相机域与机构层。当前软件完整交付不等于实物分拣通过。
参考:[1] ESP-DL3.3.8 Model::test / run / tensor入口[2] ESP-DL3.3.8 实际参考比较实现[4] esp32-camera2.1.8 帧/归还/时间戳API[5] esp32-camera2.1.8 DMA帧启动时间戳[13] IDF5.5.5 LEDC duty/update/stop
故障恢复以旧证据与新会话为起点
参考FAIL先保持舵机外供断开,查完整模型SHA/PSRAM/运行库与V05逐层诊断;不要绕过test。相机初始化失败断电检查板修订/原装排线。低分或错分留样本/光照/raw值,不能在同run偷偷改阈值。CRC、boot变化、串口错、pending过期先松ARM/断外供,保CSV+journal;退出桥,诊断并RESET两板后重新启动新run。
API故障须诊断后RESET,继续PING不能清锁存。纸挡片顶住、卡片卡住或掉错先物理断舵机正极,再移卡并写明jam/误分/恢复。程序没有传感器证明卡已清除,GPIO信号LOW也不证明轴停止。完成本项目后可用E10把固定编译、策略和桥接拒动测试纳入后续维护,所有物理结果继续独立留原始记录。
- 如果卡住了
- 模型STOP、物理异响或无法确认的运动都先停止本轮;保留部分run及失败,不补成50成功。
参考:[1] ESP-DL3.3.8 Model::test / run / tensor入口[10] Pololu2818 FS90位置舵机与3.3V信号[11] FS90-C V1.0原厂规格/电流/死区[13] IDF5.5.5 LEDC duty/update/stop[14] IDF5.5.5 UART非阻塞FIFO发送
资料来源与版本说明
固定板图、FS90原厂规格与实际安装API逐项核对;构建、受控主机夹具、QEMU失败与空物理记录分开,不把软件接受命令称已完成分拣。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。
- ESP-DL3.3.8 Model::test / run / tensor入口
- ESP-DL3.3.8 实际参考比较实现
- ESP-DL3.3.8 TensorBase与指数
- esp32-camera2.1.8 帧/归还/时间戳API
- esp32-camera2.1.8 DMA帧启动时间戳
- esp32-camera2.1.8 OV2640灰度格式
- 官方S3-EYE固定修订指南
- S3-EYE MB2.2实际原理图
- DevKitC-1 v1.1 固定引脚/供电指南
- Pololu2818 FS90位置舵机与3.3V信号
- FS90-C V1.0原厂规格/电流/死区
- Pololu规格值来源区别
- IDF5.5.5 LEDC duty/update/stop
- IDF5.5.5 UART非阻塞FIFO发送
- IDF5.5.5 USB Serial/JTAG驱动
- IDF5.5.5 64位单调微秒计时