外设配置
外设配置
本文档基于源码
include/core/PeripheralTypes.h、src/core/PeripheralManager.cpp、data/config/peripherals.json、include/core/FeatureFlags.h、include/core/interfaces/ISensorDriver.h、include/core/DriverRegistry.h和platformio.ini整理,用于说明外设配置系统支持的所有类型、引脚要求、参数含义、支持状态以及最佳实践。
当前版本提示
- Lite 环境(
esp32c3-F4R0、esp32c6-F4R0)保留 UART/I2C/SPI、GPIO、基础传感器、OLED/TM1637、NeoPixel 和外设执行核心能力。 - Standard 环境(
esp32-F4R0、esp32s3-F8R0)增加 Modbus、以太网、4G、RFID、红外和更多 I2C 传感器;如需文件、日志、多用户、RuleScript 或 LoRa,请使用 Full 环境。 - 外设配置保存到
/config/peripherals.json,可在 Web 的“配置导入/导出”中按“外设配置”单独备份和恢复。 - 出厂
data/config/peripherals.json作为安全模板发布,硬件外设默认enabled: false;文档示例中的enabled: true表示完成接线核对后的目标状态。 - 传感器数据要作为外设执行事件触发来源时,需要先在外设执行中配置传感器采集动作,系统会将采集结果缓存为
ds:<外设ID>_<字段>数据源。 - Modbus 子设备由通信协议页管理,作为虚拟外设参与设备控制和外设执行,不占用本地 GPIO。
外设配置的 Web 入口如下。新增外设时先选类型,再填引脚和类型参数;批量迁移时优先通过文件管理或配置导入导出备份 peripherals.json。


接入新模块时,不建议从 JSON 批量导入后立刻启用。更稳的顺序是先确认硬件和版本支持,再通过页面保存禁用配置,单项验证后再进入外设执行规则。
如果不确定应该选择哪个 type,先按上图从开关量、模拟量、总线设备、RS485 从站和显示输出五类入口收敛范围,再回到后文的类型 ID 表确认字段。
外设配置不是单个表单字段,而是一条从 Web 表单、JSON 文件、校验逻辑、运行时驱动到规则消费者的链路。现场导入配置后,建议按图中的顺序检查 id、type、pins、params、enabled 和版本能力开关,确认每一层都能解释当前运行状态。
如果只想快速判断某类外设需要哪些字段,可以先看参数矩阵,再回到后文的类型 ID 表和字段说明。矩阵里的“启用前检查”适合作为现场接线核对清单。
目录
- 1. 总览
- 2. 外设类型 ID 映射总表
- 3. 通信接口
- 4. GPIO 接口
- 5. 模拟信号接口
- 6. 调试接口
- 7. 专用外设
- 8. Modbus 外设
- 9. 虚拟/逻辑外设
- 10. 配置结构与字段说明
- 11. 功能编译开关(FeatureFlags)
- 12. 常见问题与排错
- 13. 最佳实践
- 14. 传感器驱动扩展(ISensorDriver)
1. 总览
外设配置系统采用"类型驱动 + 引脚占用校验 + 运行时状态"的三层模型:
- 配置层:
data/config/peripherals.json,冷启动从 LittleFS 加载。 - 管理层:
PeripheralManager单例,负责 CRUD、引脚冲突检测、硬件初始化/释放、通用读写接口。 - 驱动层:按类型分派到底层:
- GPIO 系列 →
pinMode/digitalWrite/ledcWrite - I²C/SPI/UART →
Wire/SPI/Serial - LCD/OLED → U8g2(
LCDManager) - TM1637 → 自写 bit-bang(
SevenSegmentDriver) - Modbus → 通过回调委托给
ModbusHandler
- GPIO 系列 →
约定:所有硬件操作统一经过 PeripheralManager,禁止在业务代码里直接 pinMode/digitalWrite,以避免引脚占用冲突和状态不一致。
2. 外设类型 ID 映射总表
| ID | 枚举常量 | 分类 | 显示名(中文) | 实现状态 | 引脚数 |
|---|---|---|---|---|---|
| 0 | UNCONFIGURED | —— | 未配置 | 占位 | 0 |
| 1 | UART | 通信接口 | 串口 | ✅ 已实现 | 2 (RX,TX) |
| 2 | I2C | 通信接口 | I2C 总线 | ✅ 已实现 | 2 (SDA,SCL) |
| 3 | SPI | 通信接口 | SPI 总线 | ✅ 已实现 | 4 (MISO,MOSI,SCK,CS) |
| 4 | CAN | 通信接口 | CAN 总线 | ⚠️ 配置框架就绪,驱动 TODO | 2 |
| 5 | USB | 通信接口 | USB 接口 | ⚠️ 配置框架就绪,驱动 TODO | 2 |
| 11 | GPIO_DIGITAL_INPUT | GPIO | 数字输入 | ✅ 已实现 | 1 |
| 12 | GPIO_DIGITAL_OUTPUT | GPIO | 数字输出 | ✅ 已实现 | 1 |
| 13 | GPIO_DIGITAL_INPUT_PULLUP | GPIO | 数字输入(上拉) | ✅ 已实现 / 按键默认类型 | 1 |
| 14 | GPIO_DIGITAL_INPUT_PULLDOWN | GPIO | 数字输入(下拉) | ✅ 已实现 | 1 |
| 15 | GPIO_ANALOG_INPUT | GPIO | 模拟输入 | ✅ 已实现(analogRead) | 1 |
| 16 | GPIO_ANALOG_OUTPUT | GPIO | 模拟输出 | ✅ 已实现(LEDC PWM 模拟) | 1 |
| 17 | GPIO_PWM_OUTPUT | GPIO | PWM 输出 | ✅ 已实现(LEDC) | 1 |
| 18 | GPIO_INTERRUPT_RISING | GPIO | 中断(上升沿) | 🟡 ISR 骨架存在,事件队列 TODO | 1 |
| 19 | GPIO_INTERRUPT_FALLING | GPIO | 中断(下降沿) | 🟡 ISR 骨架存在,事件队列 TODO | 1 |
| 20 | GPIO_INTERRUPT_CHANGE | GPIO | 中断(变化) | 🟡 ISR 骨架存在,事件队列 TODO | 1 |
| 21 | GPIO_TOUCH | GPIO | 电容触摸 | ⚠️ 芯片依赖;ESP32 classic 支持 | 1 |
| 26 | ADC | 模拟信号 | ADC | ✅ 已实现 | 1 |
| 27 | DAC | 模拟信号 | DAC | ✅ 已实现(仅 GPIO25/26,ESP32 classic) | 1 |
| 31 | JTAG | 调试 | JTAG 调试 | 🔘 仅类型标记,芯片级固定 | 4 |
| 32 | SWD | 调试 | SWD 调试 | 🔘 仅类型标记,芯片级固定 | 2 |
| 36 | LCD | 专用外设 | LCD/OLED 显示屏 | ✅ 已实现(U8g2:SSD1306/SH1106 等) | 2+ |
| 37 | SDIO | 专用外设 | SD 卡接口 | ⚠️ 配置框架就绪,驱动 TODO | 6 |
| 38 | SENSOR | 专用外设 | 通用传感器 (DHT/DS18B20) | ✅ 已实现(FASTBEE_ENABLE_SENSOR_DRIVER) | 1 |
| 39 | CAMERA | 专用外设 | 摄像头 | ⚠️ 配置框架就绪,驱动 TODO | 8 |
| 40 | ETHERNET | 专用外设 | 以太网 | ⚠️ 配置框架就绪,驱动 TODO | 4 |
| 41 | PWM_SERVO | 专用外设 | 舵机 | ✅ 已实现(LEDC) | 1 |
| 42 | STEPPER_MOTOR | 专用外设 | 步进电机 | ✅ 已实现(ULN2003 四相半步,非阻塞 Ticker) | 4 (IN1,IN2,IN3,IN4) |
| 43 | ENCODER | 专用外设 | 编码器 | ⚠️ 配置框架就绪,驱动 TODO | 2 |
| 44 | ONE_WIRE | 专用外设 | 单总线 | ✅ 通过 SENSOR 驱动链路实现(DS18B20) | 1 |
| 45 | NEO_PIXEL | 专用外设 | WS2812B 灯珠/灯带 | ✅ 已实现(ESP32 RMT,默认配置禁用) | 1 |
| 46 | RESERVED_46 | 兼容保留 | 保留位 | 🔒 旧版蜂鸣器类型占位,UI 不再展示 | 0 |
| 47 | SEVEN_SEGMENT_TM1637 | 专用外设 | TM1637 4 位数码管 | ✅ 已实现(FASTBEE_ENABLE_SEVEN_SEGMENT) | 2 (CLK,DIO) |
| 51 | MODBUS_DEVICE | Modbus | Modbus 子设备 | ✅ 已实现(不占 GPIO) | 0 |
| 60 | DEVICE_EVENT | 虚拟 | 设备事件发射源 | ✅ 已实现(无硬件) | 0 |
图例:✅ 完整实现且默认启用 / 🟡 部分实现 / ⚠️ 仅配置占位 / 🔒 默认禁用 / 🔘 仅类型标记
3. 通信接口
3.1 UART (type=1)
| 字段 | 含义 | 取值 |
|---|---|---|
pins[0] | RX(接收) | 任意有效 GPIO |
pins[1] | TX(发送) | 任意有效 GPIO |
params.baudRate | 波特率 | 1 ~ 5000000,常用 9600/115200 |
params.dataBits | 数据位 | 5~8 |
params.stopBits | 停止位 | 1 或 2 |
params.parity | 校验位 | 0=无 / 1=奇 / 2=偶 |
示例:
{ "id": "uart0", "name": "串口0-调试", "type": 1, "enabled": true,
"pins": [1, 3],
"params": { "baudRate": 115200, "dataBits": 8, "stopBits": 1, "parity": 0 } }注意:ESP32 默认 Serial 映射 GPIO1/3(USB 调试)。Modbus RTU 通常使用 Serial2(GPIO16/17)。
3.2 I2C (type=2)
| 字段 | 含义 | 取值 |
|---|---|---|
pins[0] | SDA | 推荐 GPIO21 |
pins[1] | SCL | 推荐 GPIO22 |
params.frequency | 时钟频率 | 仅支持 100000 / 400000 / 1000000 |
params.address | 从机地址 | 0~127,主机模式为 0 |
params.isMaster | 是否主机 | true(推荐) |
示例:
{ "id": "i2c", "name": "I2C总线", "type": 2, "enabled": false,
"pins": [21, 22],
"params": { "frequency": 100000, "address": 0, "isMaster": true } }3.3 SPI (type=3)
| 字段 | 含义 |
|---|---|
pins[0] | MISO |
pins[1] | MOSI |
pins[2] | SCK |
pins[3] | CS |
params.frequency | 1 ~ 80 MHz |
params.mode | 0~3 |
params.msbFirst | true = MSB 先 |
3.4 CAN / USB(未实现驱动)
配置数据会被保存,但 setupHardware 会打印 not yet implemented。使用前需业务层自行实现。
4. GPIO 接口
4.1 数字输入 / 输出 (type=11~14)
| 类型 | 说明 | 典型用途 |
|---|---|---|
11 GPIO_DIGITAL_INPUT | 高阻输入,外部需上/下拉 | 编码器 A/B 相 |
12 GPIO_DIGITAL_OUTPUT | 推挽输出 | 继电器、LED |
13 GPIO_DIGITAL_INPUT_PULLUP | 内部上拉 | 按键(按下接 GND) |
14 GPIO_DIGITAL_INPUT_PULLDOWN | 内部下拉 | 按键(按下接 VCC) |
按键事件:只有 type=13/14 会被
PeriphExecScheduler::checkButtonEvents扫描,支持button_click/button_double_click/button_long_press_2s/5s/10s。
4.2 模拟输入 (type=15) / ADC (type=26)
- ESP32 classic:ADC1 (GPIO32~39) 推荐用于 WiFi 同时工作;ADC2 与 WiFi 冲突。
analogRead(pin)返回 0~4095(12 位)。
4.3 PWM 输出 (type=17) / 模拟输出 (type=16) / 舵机 (type=41)
| 参数 | 说明 | 约束 |
|---|---|---|
pwmChannel | LEDC 通道 | 0 ~ CHIP_MAX_PWM_CH-1(ESP32 为 16) |
pwmFrequency | 频率 Hz | freq × 2^resolution ≤ 80MHz |
pwmResolution | 分辨率位数 | 1~16 |
defaultDuty | 默认占空比 | 0 ~ (2^resolution - 1) |
频率/分辨率组合限制(示例):
- 1kHz × 13 位 = 8.192M ✅
- 40kHz × 12 位 = 163.84M ❌
4.4 中断 (type=18/19/20)
当前实现:isrHandler 仅记录触发引脚号,尚未接入 FreeRTOS 队列分发给主循环。上层业务使用按键事件(type=13/14)替代。
4.5 触摸 (type=21)
仅 CHIP_HAS_TOUCH 芯片有效(ESP32 classic 支持 T0~T9)。
5. 模拟信号接口
5.1 DAC (type=27)
- 硬件限制:ESP32 classic 仅 GPIO25/26 有真实 DAC;其他芯片/引脚会被拒绝。
- 输出范围:8 位(0~255),对应 0V~VDD。
5.2 ADC (type=26)
与 GPIO_ANALOG_INPUT 等价,可用 params.attenuation(0~3)与 params.resolution(9~12)区分。
6. 调试接口
JTAG=31 / SWD=32 为类型标记位。ESP32 的 JTAG 引脚固定为 GPIO12~15,开启后会影响这些引脚的普通 GPIO 功能。
7. 专用外设
7.1 LCD/OLED 显示屏 (type=36)
实现状态:✅ 完整实现。Lite / Standard / Full 默认启用(
FASTBEE_ENABLE_LCD=1)。
支持的控制器(由 U8g2 库覆盖):
- SSD1306(128×64 / 128×32 OLED,I²C 0x3C)
- SH1106(128×64 OLED)
- 其他 U8g2 支持的字符/图形 LCD
参数:
| 字段 | 含义 | 取值 |
|---|---|---|
pins[0] | SDA(I²C)/ MOSI(SPI) | |
pins[1] | SCL(I²C)/ SCK(SPI) | |
pins[2] | CS(仅 SPI) | 可选 |
pins[3] | DC(仅 SPI) | 可选 |
params.width | 宽 | 128 |
params.height | 高 | 64 / 32 |
params.interface | 接口 | 0=并口 / 1=SPI / 2=I2C(默认) |
示例:
{ "id": "oled_display", "name": "OLED显示屏", "type": 36, "enabled": true,
"pins": [23, 22],
"params": { "width": 128, "height": 64, "interface": 2 } }相关规则动作:ACTION_DISPLAY_CUSTOM(OLED 自定义显示,支持多行文本 + 变量插值)。详见 oled-usage-guide.md。
7.2 TM1637 数码管 (type=47)
实现状态:✅ 已实现,自写 bit-bang 驱动(
SevenSegmentDriver),Lite / Standard / Full 默认启用。
| 字段 | 含义 |
|---|---|
pins[0] | CLK |
pins[1] | DIO |
params.brightness | 亮度 0~7 |
约束:CLK/DIO 与其他外设不可共用引脚。由按键引脚冲突的历史教训,务必为数码管单独分配 GPIO。
7.3 通用传感器 (type=38) / 单总线 (type=44)
| 子类 | 引脚 | 说明 |
|---|---|---|
| DHT11/DHT22 | 1 个 DATA | 温湿度 |
| DS18B20 | 1 个 DQ(1-Wire) | 温度 |
通过外设执行动作 ACTION_SENSOR_READ 读取并缓存。需启用 FASTBEE_ENABLE_SENSOR_DRIVER(默认开)。
7.4 保留位 (type=46)
旧版专用蜂鸣器类型已移除,type=46 仅作为历史编号保留,Web 不再提供新增入口。
7.5 WS2812B / NeoPixel 灯珠 (type=45)
使用 ESP32 RMT 外设发送 WS2812B GRB 时序,不依赖第三方 NeoPixel 库。pins[0] 接 WS2812B 的 DIN,默认测试模板为 ws2812b,GPIO4,1 颗灯珠,亮度 64,默认禁用。
| 字段 | 含义 | 默认值 / 范围 |
|---|---|---|
pins[0] | DIN 数据引脚 | 有效输出 GPIO |
params.count | 灯珠数量 | 默认 1,最大 64 |
params.brightness | 全局亮度 | 默认 64,范围 0~255 |
外设执行通过 ACTION_CALL_PERIPHERAL 控制:
| actionValue 示例 | 行为 |
|---|---|
{"periphId":"ws2812b","action":"color","value":"#ff0000"} | 显示红色 |
{"periphId":"ws2812b","action":"off"} | 熄灭 |
{"periphId":"ws2812b","action":"rainbow"} | 推进一次赤橙黄绿青蓝紫循环 |
{"periphId":"ws2812b","action":"brightness","value":"96"} | 设置亮度 |
7.6 舵机 (type=41)
使用 LEDC 50Hz PWM,脉宽 0.5ms~2.5ms 对应 0°~180°。
7.7 步进电机 (type=42)
面向 28BYJ-48 + ULN2003 一类四相步进电机驱动板,pins[0..3] 按顺序接 ULN2003 的 IN1, IN2, IN3, IN4。驱动采用 8 拍半步序列,通过 Ticker 非阻塞输出,不会在外设执行动作里长时间阻塞 Web 服务。
| 字段 | 含义 | 默认值 / 范围 |
|---|---|---|
pins[0] | IN1 | 有效 GPIO |
pins[1] | IN2 | 有效 GPIO |
pins[2] | IN3 | 有效 GPIO |
pins[3] | IN4 | 有效 GPIO |
params.stepsPerRevolution | 每圈步数 | 默认 2048 |
params.speed | 默认转速 RPM | 默认 8,最大 30 |
外设执行可通过 ACTION_CALL_PERIPHERAL 控制该外设,支持 forward、reverse、stop、faster、slower、setSpeed、direction 等动作。
安全提醒:经典 ESP32 的 GPIO9/10/11 通常被 Flash SPI 占用,固件会拒绝在当前芯片保留引脚上启用步进电机,避免误配置导致重启。GPIO 11/10/9/13 更适合 ESP32-S3;经典 ESP32 建议换成空闲 GPIO。
7.8 SDIO / 摄像头 / 以太网 / 编码器
当前仅保存配置,底层驱动未实现。需要使用请自行扩展 PeripheralManager::setupHardware。
8. Modbus 外设
8.1 MODBUS_DEVICE (type=51)
虚拟外设,不占用本地 GPIO,通过 RS485 / Modbus TCP 总线通信。
params.modbus.* 字段 | 含义 | 取值 |
|---|---|---|
slaveAddress | 从站地址 | 1~247 |
deviceType | 设备类型 | 0=继电器 / 1=PWM / 2=PID 等 |
controlProtocol | 控制协议 | 0=线圈 FC05 / 1=寄存器 FC06 |
coilBase | 线圈基地址 | |
pwmRegBase | PWM 寄存器基地址 | |
ncMode | 常闭模式(状态反转) | bool |
注意事项:
- Modbus 外设不通过
peripherals.json持久化,由protocol.json统一管理。 - 物理总线(RS485)需要一条 UART + DE 控制引脚,DE 引脚不可被按键或其他 GPIO 外设复用。
- 参见历史 Lesson:Modbus dePin 与按键引脚不可复用。
9. 虚拟/逻辑外设
9.1 DEVICE_EVENT (type=60)
- 无引脚、无硬件。
- 仅作为规则系统中的"事件发射源",触发后通过 MQTT
DEVICE_EVENT主题上报。 - 配置时
pinCount=0,pins可省略,校验只校验id/name非空。
典型用途:系统状态变更、用户动作、组合事件(如"连续三次按键单击触发警报")。
10. 配置结构与字段说明
参数矩阵适合和本章一起使用:先确认通用字段 id、type、pins、enabled,再按外设类型检查 params 中的地址、量程、校准、频率或动作边界。
peripherals.json 顶级结构:
{
"peripherals": [
{
"id": "唯一ID(英文/数字/下划线)",
"name": "显示名",
"type": 36, // PeripheralType 枚举值
"enabled": true, // 仅 enabled=true 的外设会占用引脚
"pins": [21, 22], // 按顺序排列,未使用位设为 255 或省略
"params": { // 类型特定参数(见各章)
"width": 128,
"height": 64,
"interface": 2
}
}
]
}10.1 通用约束
id必须全局唯一;改名(name)可直接 PUT,改 ID 需先 DELETE 后 POST。pins[]最大 8 个,pinCount自动从非 255 的数量推断。- 禁用 (
enabled=false) 的外设不占引脚——允许多外设声明同一引脚但只启用其中一个。 - 保留引脚(Flash SPI、Boot、USB D+/D-)由
ChipConfig.h定义,validatePinForType会拒绝越界。
10.2 引脚冲突检测
- 增加或启用外设时,
checkPinConflict会扫描所有已启用的非 Modbus 外设。 - 若检测到残留缓存(
pinToPeripheral与peripherals不一致),会自动rebuildPinMapping一次并重试。
11. 功能编译开关(FeatureFlags)
关键开关位于 include/core/FeatureFlags.h,可在 platformio.ini 的 build_flags 中覆盖:
| 宏 | 档位默认值 | 说明 |
|---|---|---|
FASTBEE_ENABLE_LCD | 1 | U8g2 LCD/OLED 驱动 |
FASTBEE_ENABLE_SEVEN_SEGMENT | 1 | TM1637 驱动 |
FASTBEE_ENABLE_NEOPIXEL | 1 | WS2812B / NeoPixel 灯带 |
FASTBEE_ENABLE_LED_SCREEN | Lite=0,Standard/Full=1 | LED 屏扩展入口 |
FASTBEE_ENABLE_SENSOR_DRIVER | 1 | DHT/DS18B20 |
FASTBEE_ENABLE_MODBUS | 1 | Modbus RTU/TCP |
FASTBEE_ENABLE_PERIPH_EXEC | 1 | 外设执行规则(定时/按键/事件) |
11.1 预设环境(来自 platformio.ini)
| 预设 | LCD | TM1637 | NeoPixel | CoAP |
|---|---|---|---|---|
slim / Lite | ✅ | ✅ | ✅ | ❌ |
standard | ✅ | ✅ | ✅ | ❌ |
minimal | ❌ | ❌ | ❌ | ❌ |
full | ✅ | ✅ | ✅ | ✅ |
12. 常见问题与排错
12.1 "引脚 X 已被外设 '<未知>' 占用"
原因:pinToPeripheral 缓存残留(已在 addPeripheral 中加入自动 rebuildPinMapping 回退)。 排查:
- 确认
peripherals.json中是否真的存在该引脚的启用外设。 - 串口观察日志
stale pin mapping detected, rebuilding cache。 - 若仍报冲突,说明目标外设确实启用中,需先禁用或改引脚。
12.2 按键事件长时间运行后失效
历史根因(已修复):
dispatchAsync自愈条件盲区(startTime缺失时永久 skip)。dispatchByRuleId锁超时无重试,checkTimerTriggers持锁 100ms 期间按键事件被丢弃。
当前实现:dispatchAsync 检测 startTimeMissing || stuck>60s 自动清理;dispatchByRuleId 3 次 × 50ms 重试。详见 Lesson:FastBee-Arduino按键失效自愈机制修复。
12.3 TM1637 与按键共用引脚
禁止。TM1637 bit-bang 驱动会频繁翻转 CLK/DIO,与按键扫描冲突导致双向失效。 方案:为 TM1637 单独分配 2 个 GPIO(典型 GPIO18/19)。
12.4 Modbus dePin 与按键/GPIO 冲突
Modbus RTU 的 RS485 DE(Direction Enable)引脚由 protocol.json 中 modbus.dePin 配置,不得与 peripherals.json 中任一启用外设的引脚重复。重复会导致通信方向切换异常或按键失效。
12.5 DAC 写入失败
仅 ESP32 classic 的 GPIO25 / GPIO26 支持硬件 DAC。ESP32-S3/C3 不支持,启用会返回 DAC not supported on this chip。
12.6 OLED 不亮
排查顺序:
- I²C 地址:大多数 SSD1306 为
0x3C,少数0x3D(需手动在LCDManager中调整)。 - 引脚:SDA/SCL 顺序不可颠倒。
- 电源:0.96" OLED 典型工作电压 3.3V,电流 20mA 内。
params.interface必须为2(I²C)。
12.7 LCD vs LED 容易混淆
| LCD/OLED (type=36) | LED 灯带 (type=45, NeoPixel) | |
|---|---|---|
| 用途 | 字符/图形显示 | 多彩像素灯 |
| 接口 | I²C / SPI | RMT(单信号线 WS2812B) |
| 驱动库 | U8g2 | Adafruit NeoPixel |
| 默认启用 | ✅ | ✅ |
如果你要驱动 "一只 LED 单灯",应使用 GPIO_DIGITAL_OUTPUT (type=12) 或 GPIO_PWM_OUTPUT (type=17);如果要 "WS2812 灯带",使用 NEO_PIXEL (type=45);显示屏才是 LCD (type=36)。
13. 最佳实践
13.1 命名
id用 snake_case 英文(如oled_display、key1、tm1637_01),便于规则引擎脚本引用。name用中文/英文均可,面向用户展示。
13.2 引脚分配策略
- 优先使用安全引脚:GPIO4/5/16/17/18/19/21/22/23/25/26/27/32/33。
- 保留:GPIO0(Boot)、GPIO1/3(UART0 调试)、GPIO6-11(Flash)。
- 输入专用:GPIO34~39,只能做输入。
- 为按键预留独立 GPIO,不与 TM1637/OLED/Modbus DE 共用。
13.3 配置演进
- 修改外设类型或引脚时,先在 Web UI 禁用 → 保存 → 重新启用,避免热切换时残留中断。
- 规则配置 (
periph_exec.json) 引用的外设 ID 变更时,需同步更新targets[]。
13.4 调试手段
- 串口日志:
Peripheral Manager: ...前缀可过滤。 - Web → 外设管理:一览所有外设、实时状态、引脚占用。
pio device monitor -p COM6 -b 115200。
13.5 扩展新外设
- 在
PeripheralTypes.h添加枚举值(遵循区段 ID 规则)。 - 更新
getPeripheralTypeName/parsePeripheralType/getPeripheralPinCount。 - 在
PeripheralManager::setupHardware添加初始化分支。 - 在
web-src/pages/modals.html添加<option>及data-i18n。 - 在
web-src/i18n/i18n-zh-CN.js/i18n-en.js添加翻译键。 - (可选)在
web-src/modules/runtime/periph-exec-form.js添加规则动作支持。
提示:若要扩展的是传感器类外设(需周期性读取温度/湿度/光照等数值),推荐优先使用第 14 章的
ISensorDriver驱动接口,无需侵入PeripheralManager核心代码。
14. 传感器驱动扩展(ISensorDriver)
本章节描述本次优化新增的传感器驱动抽象接口与热插拔注册机制,目标是把传感器硬件读取逻辑从
PeripheralManager/PeriphExec的硬编码分支中解耦,便于后续新增 SHT31、BMP280、SCD41 等器件时不再修改核心调度层。
14.1 设计目标
- 将"硬件初始化 + 周期读取 + 多通道输出"封装到独立驱动类。
- 使用静态注册宏在
main()之前完成自注册,新增驱动只需"新建一个头文件 + 包含一次"。 - 驱动注册表使用固定容量静态数组(
MAX_SENSOR_DRIVERS = 8),零动态分配、零碎片。 - 支持最多 4 通道读取(如 DHT 的温度 + 湿度),通道具名(name/unit)方便前端渲染。
14.2 核心接口
头文件:include/core/interfaces/ISensorDriver.h
struct SensorReading {
bool success = false;
float values[4] = {0}; // 最多 4 通道
uint8_t channelCount = 0;
unsigned long timestamp = 0;
// 语义化访问
float temperature() const { return channelCount > 0 ? values[0] : NAN; }
float humidity() const { return channelCount > 1 ? values[1] : NAN; }
};
class ISensorDriver {
public:
virtual ~ISensorDriver() = default;
virtual const char* getName() const = 0; // 驱动类型名,如 "sht31"
virtual uint8_t getChannelCount() const = 0; // 通道数 1~4
virtual const char* getChannelName(uint8_t ch) const = 0; // "temperature" / "humidity" 等
virtual const char* getChannelUnit(uint8_t ch) const = 0; // "℃" / "%" / "lux" 等
virtual bool init(uint8_t pin, const char* params = nullptr) = 0; // params 为 JSON 字符串,可选
virtual bool read(SensorReading& reading) = 0;
virtual void deinit() = 0;
virtual unsigned long getMinInterval() const { return 1000; } // 最小采样间隔 (ms)
};14.3 注册机制
头文件:include/core/DriverRegistry.h
| 要素 | 说明 |
|---|---|
DriverRegistry::getInstance() | 单例,全局唯一 |
registerDriver(name, factory) | 由静态构造函数自动调用,通常不需要手动调用 |
createDriver(name) | 按名称创建驱动实例,返回 ISensorDriver*,失败返回 nullptr |
hasDriver(name) | 判断驱动是否已注册 |
FASTBEE_REGISTER_SENSOR(name, Class) | 宏,在文件末尾使用一次即可完成自注册 |
注册表内部是 std::array<SensorDriverEntry, 8>,满载时 registerDriver 返回 false(构建期可见),避免运行时失败。
14.4 编写自定义驱动
以 include/peripherals/drivers/SHT31Driver.h 为模板:
#include "core/interfaces/ISensorDriver.h"
#include "core/DriverRegistry.h"
class MySensorDriver : public ISensorDriver {
public:
const char* getName() const override { return "my_sensor"; }
uint8_t getChannelCount() const override { return 2; }
const char* getChannelName(uint8_t ch) const override {
return ch == 0 ? "temperature" : "humidity";
}
const char* getChannelUnit(uint8_t ch) const override {
return ch == 0 ? "℃" : "%";
}
bool init(uint8_t pin, const char* params) override {
// TODO: 初始化 I2C / OneWire / 引脚
return true;
}
bool read(SensorReading& r) override {
r.values[0] = 25.3f;
r.values[1] = 60.0f;
r.channelCount = 2;
r.success = true;
r.timestamp = millis();
return true;
}
void deinit() override {}
unsigned long getMinInterval() const override { return 2000; }
};
// 自动注册:在任意 .cpp 包含此头文件一次即可
FASTBEE_REGISTER_SENSOR("my_sensor", MySensorDriver);关键点:
- 驱动头文件放在
include/peripherals/drivers/目录。 - 需要被
src/main.cpp(或任一被链接的.cpp)显式#include一次,触发静态对象构造 → 完成注册。 - 使用方:
ISensorDriver* drv = DriverRegistry::getInstance().createDriver("my_sensor");
14.5 与现有传感器逻辑的关系
- 当前状态:
PeriphExec中对DHT11/DHT22/DS18B20等的硬编码读取分支仍在使用,未迁移到ISensorDriver,功能与兼容性不受影响。 - 建议策略:
- 新增器件(SHT31/BMP280/SCD41/BH1750 等)优先使用
ISensorDriver。 - 存量器件待完整回归后按需迁移,不强制。
- 新增器件(SHT31/BMP280/SCD41/BH1750 等)优先使用
- 容量提升:若 8 种驱动不够,修改
DriverRegistry.h中的MAX_SENSOR_DRIVERS常量并重新编译。
参考文档
oled-usage-guide.md— OLED 自定义显示规则modbus-usage-guide.md— Modbus 使用指南periph-exec-flow.md— 外设执行规则流程script-guide.md— 规则脚本手册
