TasmotaSerial:ESP8266/ESP32 多实例串口库的软件模拟与硬件回退实现详解
【免费下载链接】TasmotaAlternative firmware for ESP8266 and ESP32 based devices with easy configuration using webUI, OTA updates, automation using timers or rules, expandability and entirely local control over MQTT, HTTP, Serial or KNX. Full documentation at项目地址: https://gitcode.com/GitHub_Trending/ta/Tasmota
TasmotaSerial 是 Tasmota 固件内置的串口驱动库,为 ESP8266 提供"软件串口 + 硬件串口回退"方案,为 ESP32 提供双 UART 硬件串口、为 ESP32-S2 提供单 UART 硬件串口,并允许同一时刻激活多个实例。本文以其 README.md 为主线,结合 TasmotaSerial.h 与 TasmotaSerial.cpp 源码,以及 Tasmota 固件中数十个调用方(如 Serial Bridge、能量计量、传感器驱动等),系统讲解其设计原理、构造函数参数、API 用法、时序局限与实战注意事项,读完即可在自己的固件或传感器接入方案中正确选型与使用。
一、库定位与核心能力
按 library.properties 的官方描述,TasmotaSerial 是"for ESP8266 and ESP32 的软件串行实现,带硬件串行回退"(Implementation of software serial with hardware serial fallback for ESP8266 and ESP32),架构覆盖esp8266,esp32两个平台,类别为 Signal Input/Output。该库基于 Peter Lerup 的 EspSoftwareSerial v3.4.3 演化而来(见 TasmotaSerial.h 头注释),由 Tasmota 作者 Theo Arends 维护并深度定制。
README 声明了三条平台能力:
| 平台 | 串口策略 |
|---|---|
| ESP8266 | 软件串口为主,特定引脚组合自动回退到硬件串口 |
| ESP32 | 双 UART 硬件串口(UART0/UART1/UART2 动态分配) |
| ESP32-S2 | 单 UART 硬件串口 |
同时明确两条关键特性:
- 允许多个实例同时活跃(
Allows for several instances to be active at the same time.):这使 Tasmota 固件可以在同一颗芯片上同时驱动多个串口外设,例如一个 UART 跑 Zigbee 协调器、另一个跑 Modbus 电表。 - 时序精度有代价(中断定时存在不精确性,重流量下可能产生位错误):因为 ESP 芯片始终有其他活动(WiFi 栈、TCP/IP、任务调度)在运行,软件串口依赖 GPIO 中断与 CPU 周期计数
ESP.getCycleCount()采样,繁忙时易出现位错。
二、为什么需要 TasmotaSerial:硬件串口不够用
ESP8266 只有 1 个 UART(UART0,TX=GPIO1/RX=GPIO3),且 UART0 通常被固件日志与刷机占用;UART1(TX=GPIO2)只能发送不能接收。而 Tasmota 需要同时对接 WiFi 日志调试、串口桥、Tuya MCU、PZEM 电表、Zigbee 协调器等外设,原生串口远不够用。TasmotaSerial 的价值正是:
- 把任意合法 GPIO变成软件串口,按需扩展收发通道;
- 在引脚恰好是硬件串口引脚时自动回退到硬件串口,获得更稳定时序;
- 在 ESP32 上直接把多 UART 控制器分配给不同外设,互不干扰。
从仓库使用面看,TasmotaSerial 被固件大量引用:TasmotaSerial符号出现在 support.ino、xdrv_08_serial_bridge.ino、xdrv_23_zigbee_9_serial.ino、xdrv_10_scripter.ino、xdrv_52_3_berry_serial.ino 以及能量计量驱动 xnrg_02_cse7766.ino、xnrg_03_pzem004t.ino 等数十个文件中,是固件串口基础设施。
三、API 速览与构造函数
TasmotaSerial 继承自 ArduinoStream,对外接口与 HardwareSerial 基本一致,可以直接替代。完整方法见 TasmotaSerial.h:
TasmotaSerial(int receive_pin, int transmit_pin, int hardware_fallback = 0, int nwmode = 0, int buffer_size = TM_SERIAL_BUFFER_SIZE, bool invert = false); ~TasmotaSerial(); bool begin(uint32_t speed = TM_SERIAL_BAUDRATE, uint32_t config = SERIAL_8N1); // 默认 9600, 8N1 void end(); bool hardwareSerial(); // 当前是否运行在硬件串口上 bool isValid(); // 构造是否成功 int peek(); size_t write(uint8_t byte); int read(); size_t read(char* buffer, size_t size); int available(); void flush(); bool overflow(); // 缓冲溢出标志(读取后自动清除) void setTransmitEnablePin(int tx_enable_pin); // RS-485 方向控制 void clearTransmitEnablePin(); size_t setRxBufferSize(size_t size); size_t getRxBufferSize(); void setReadChunkMode(bool mode); void rxRead(); // ESP8266 软件接收中断处理 uint32_t getLoopReadMetric(); // 连续字节采样命中统计 #ifdef ESP32 uint32_t getUart(); // 当前 UART 编号 HardwareSerial *getesp32hws(); int32_t setConfig(uint32_t config); // 运行时改数据位/校验/停止位(RS-485 动态改校验) #endif3.1 构造函数参数详解
以 TasmotaSerial.cpp 的实现为准:
| 参数 | 含义 | 说明 |
|---|---|---|
receive_pin | 接收引脚 | 可为-1表示只发不收 |
transmit_pin | 发送引脚 | 可为-1表示只收不发 |
hardware_fallback | 硬件回退模式 | 0强制软件串口;1标准回退;2回退且启用引脚交换(ESP8266Serial.swap()) |
nwmode | 接收中断模式 | 0FALLING 边沿(逐位采样);非 0 为 CHANGE 边沿(状态机采样),当前仅支持 8 位数据 |
buffer_size | 接收缓冲字节数 | 默认TM_SERIAL_BUFFER_SIZE = 64(见 TasmotaSerial.h) |
invert | 信号反相 | true用于 RS-485 等反相电平场景 |
ESP8266 引脚合法性检查:isValidGPIOpin()只接受-1..5与12..15(TasmotaSerial.cpp),即 GPIO6~11(flash 占用)不可用;发送端额外允许 GPIO16(transmit_pin == 16特例)。
ESP8266 硬件回退判定(TasmotaSerial.cpp):
hardware_fallback == 1:引脚组合为(RX=GPIO3, TX=GPIO1)或其中一端为-1时回退到 UART0;hardware_fallback == 2:引脚组合为(RX=GPIO13, TX=GPIO15)(或单端-1)时回退 UART0 并执行Serial.swap()启用引脚交换。
ESP32 构造行为:构造阶段只校验引脚合法性(GPIO_IS_VALID_GPIO/GPIO_IS_VALID_OUTPUT_GPIO),一律标记为硬件串口模式,真正的 UART 分配推迟到begin()。
四、硬件串口回退机制(ESP8266)
软件串口时序不稳、占用 CPU,因此能走硬件就尽量走硬件。hardware_fallback参数正是为此设计:
TasmotaSerial swSer(3, 1, 1); // RX=GPIO3, TX=GPIO1 → 自动使用硬件 UART0 TasmotaSerial swSer(13, 15, 2); // RX=GPIO13, TX=GPIO15 → 硬件 UART0 + swap() TasmotaSerial swSer(14, 12, 0); // 普通 GPIO → 纯软件串口回退到硬件后,begin()内部调用Serial.begin(speed, (SerialConfig)config, SERIAL_FULL, m_tx_pin, m_invert)再按需Serial.swap()(TasmotaSerial.cpp),并可用setRxBufferSize()把接收缓冲提升到 256 字节以上(小于 256 时强制为 256)。注意回退硬件串口与调试日志共用 UART0,因此end()中刻意保留Serial不关闭(源码注释 "Keep active for logging",TasmotaSerial.cpp)。
五、ESP32 多 UART 动态分配
ESP32 上 TasmotaSerial 不再做位级模拟,而是包装HardwareSerial并按需分配 UART 控制器,README 中"dual UART"即指 UART1/UART2 可同时被两个实例占用。
5.1 分配策略
freeUart()(TasmotaSerial.cpp)用全局位图tasmota_serial_uart_bitmap记录已占用 UART:
- 若用户恰好选择 SOC 默认的 RX0/TX0 引脚,则复用 UART0(保持调试口可用);
- 否则从高编号向低编号(
SOC_UART_HP_NUM-1递减)查找第一个空闲 UART。注释明确"我们更倾向于 UART1 和 UART2,把 UART0 留给调试"(TasmotaSerial.cpp)。
begin()时优先new HardwareSerial(m_uart),仅当分配到的就是 UART0 时直接引用全局Serial对象(TasmotaSerial.cpp)。因此hardwareSerial()在 ESP32 上恒为true,但返回值语义是"是否占用 UART0"(TasmotaSerial.cpp)。
5.2 IDF 版本兼容处理
头文件针对 IDF 5.2 的 UART 计数变化做了适配(TasmotaSerial.h):IDF 5.2 起SOC_UART_NUM把 LP UART 也计入(影响 ESP32-C6、ESP32-P4),因此预定义SOC_UART_HP_NUM为旧版语义的"高性能 UART 数量",避免把 LP UART 误当成可用的串口通道。
5.3 ESP32 启动时序与 RX 阈值优化
Esp32Begin()(TasmotaSerial.cpp)做了三件事:
- 先把 RX/TX 引脚设为
INPUT_PULLUP,规避 IDF #14787 引入的"新 RX 保持低电平而非浮空"问题(Tasmota v14.5.0 / Core 3.1.1 / IDF 5.3.2 起生效); - 按波特率设置 RX FIFO 满阈值:
≤9600时设为 10 字节(10 字符约 10ms),否则 120 字节(19200 下约 60ms、76800 下约 15ms); - 波特率低于 115200 时把 RX 超时设为 6 字符(76800 下超时约 1ms),提升小包响应速度。
5.4 运行时改配置(RS-485 场景)
ESP32 版提供setConfig()(TasmotaSerial.cpp),可在串口运行中通过uart_set_word_length/uart_set_parity/uart_set_stop_bits动态切换数据位、校验位与停止位,专为 RS-485 半双工总线在收/发方向使用不同奇偶校验的需求设计。
六、软件串口实现原理(ESP8266)
当不满足硬件回退条件时,TasmotaSerial 退化为位级软件串口,核心是中断采样 + CPU 周期计数精确延时。
6.1 发送:位时序生成
write()与内部_fast_write()(TasmotaSerial.cpp)用ESP.getCycleCount()与预计算的m_bit_time(CPU_MHz * 1000000 / speed)逐位翻转 GPIO:
- 起始位拉低 → 依次发送
m_data_bits个数据位(LSB 先出)→ 停止位拉高; - 高速(≥9600)时发送期间
cli()/sei()关中断保证波形干净; - 低速时发送循环内
optimistic_yield(1)让出 CPU,防止看门狗超时(TM_SERIAL_WAIT_SND宏)。
6.2 接收:边沿采样
构造函数在 RX 引脚挂attachInterruptArg(FALLING 或 CHANGE,取决于nwmode),中断服务rxRead()(IRAM_ATTR,TasmotaSerial.cpp)在起始位触发后按m_bit_time等间隔采样数据位并移入缓冲。为提高吞吐,高速模式(m_very_high_speed,≥50000 波特)会尝试连续接收整行字节(一次中断读多个字节),并通过setReadChunkMode(true)强制开启;FALLING 模式还会用m_bit_follow_metric统计连续字节命中率,可用getLoopReadMetric()观测。
nwmode非 0 时走 CHANGE 边沿状态机分支(当前仅支持 8 位数据),逐位累加、按字节完成边界归档,适合对起始位边沿抖动不敏感的低速链路。
6.3 缓冲与溢出
软件串口使用环形缓冲(m_in_pos/m_out_pos,容量默认 64 字节),中断写入、read()/available()读取。缓冲写满时置m_overflow = true并停止接收,overflow()返回并清除该标志。setRxBufferSize()可在运行中重分配缓冲(硬件串口要求 >256 才生效;ESP32 上会先TSerial->end()再重建,delay(10)等待队列清理,见 TasmotaSerial.cpp)。
七、官方示例:回环测试
仓库自带 examples/swsertest/swsertest.ino,展示了最小可用代码:
#include <TasmotaSerial.h> TasmotaSerial swSer(14, 12); // RX=GPIO14, TX=GPIO12,纯软件串口 void setup() { Serial.begin(115200); // 硬件串口用于调试输出 swSer.begin(); // 默认 9600, 8N1 Serial.println("\nTasmota serial test started"); for (char ch = ' '; ch <= 'z'; ch++) { swSer.write(ch); // 发送测试字符 } swSer.println(""); } void loop() { while (swSer.available() > 0) { Serial.write(swSer.read()); // 软件串口 → 调试串口 } while (Serial.available() > 0) { swSer.write(Serial.read()); // 调试串口 → 软件串口 } }将 GPIO12 与 GPIO14 短接即可形成回环,观察字符是否逐字节往返,用于验证波特率、引脚与固件烧录环境。
八、Tasmota 固件中的实际调用模式
以 Serial Bridge 驱动 xdrv_08_serial_bridge.ino 为范本,固件中的典型用法是:
TasmotaSerial *SerialBridgeSerial = nullptr; ... SerialBridgeSerial = new TasmotaSerial(Pin(GPIO_SBR_RX), Pin(GPIO_SBR_TX), HARDWARE_FALLBACK, // 回退模式 0, // FALLING 边沿软件接收 MIN_INPUT_BUFFER_SIZE, // 256 字节缓冲 Settings->flag3.sb_receive_invert); // SetOption69 反相 if (SetSSerialBegin()) { ... }其中SetSSerialBegin()调用SerialBridgeSerial->begin(Settings->sbaudrate * 300, ConvertSerialConfig(Settings->sserial_config))(xdrv_08_serial_bridge.ino),注意波特率以 300 为步进存储。串口格式通过SSerialConfig命令设置,取值范围 0..23,对应 tasmota.h 中定义的枚举:
TS_SERIAL_5N1, TS_SERIAL_6N1, TS_SERIAL_7N1, TS_SERIAL_8N1, // 0..3 TS_SERIAL_5N2, TS_SERIAL_6N2, TS_SERIAL_7N2, TS_SERIAL_8N2, // 4..7 TS_SERIAL_5E1, TS_SERIAL_6E1, TS_SERIAL_7E1, TS_SERIAL_8E1, // 8..11 TS_SERIAL_5E2, TS_SERIAL_6E2, TS_SERIAL_7E2, TS_SERIAL_8E2, // 12..15 TS_SERIAL_5O1, TS_SERIAL_6O1, TS_SERIAL_7O1, TS_SERIAL_8O1, // 16..19 TS_SERIAL_5O2, TS_SERIAL_6O2, TS_SERIAL_7O2, TS_SERIAL_8O2 // 20..23覆盖 5/6/7/8 数据位 × N/E/O 校验 × 1/2 停止位共 24 种组合,校验非法值回退 8N1。同样的枚举与校验逻辑还复用于 xdrv_63_modbus_bridge.ino 与 xdrv_72_pipsolar.ino,可见 TasmotaSerial 是这些串口驱动的公共底座。
其他代表性调用方包括:Tuya MCU 驱动 xdrv_16_tuyamcu_v2.ino、Zigbee 串口层 xdrv_23_zigbee_9_serial.ino、Xiaomi BLE 网关串口 xdrv_52_3_berry_serial.ino、能量计量 xnrg_02_cse7766.ino(CSE7766 计量芯片)、xnrg_03_pzem004t.ino(PZEM-004T 电表)、传感器 xsns_18_pms5003.ino(PM2.5 传感器)等,覆盖"单实例固定引脚"与"模板配置引脚"两种接入方式。
九、多实例与共享注意点
README 强调"可同时激活多个实例",这对固件与自研工程均有实际意义:
- ESP32:多个
TasmotaSerial实例会被分配到不同 UART 控制器,实例间互不干扰;分配失败(无空闲 UART)时begin()返回false并把m_valid置为false,调用方需检查返回值。 - ESP8266:软件串口每个实例占用一个 GPIO 中断槽,接收中断(
rxRead)运行在 IRAM 中,实例越多、波特率越高,对主循环与 WiFi 的 CPU 抢占越明显。中断处理依赖 GPIO 中断回调列表tms_obj_list[16](TasmotaSerial.cpp),同一引脚不可被两个实例重复占用。 - UART0 冲突:回退到 UART0 的实例与调试日志共享通道,串口桥场景下注意日志干扰;ESP32 上若占用 UART0,
end()会真正关闭它,需按源码注释预期处理。
十、时序局限与工程建议(README 警示落地)
README 的警告——"ESP 始终有其他活动,中断定时存在不精确性,重度数据流量下可能产生位错误"——在工程上对应以下规则:
- 波特率上限:软件串口设计支持到 115200(头文件注释),更高波特率位时间过短,中断抖动导致的采样误差占比急剧放大;115200 以上建议使用硬件串口或提高 RX 缓冲。
- 缓冲与流量匹配:默认 64 字节缓冲适合低速遥测(如 9600 波特传感器),突发大数据帧应调大缓冲(硬件串口 ≥256、软件串口按需
setRxBufferSize)并尽快read()取走数据,避免环形缓冲溢出置位overflow。 - 中断时长控制:高速连续采样模式(
setReadChunkMode(true))以单次中断读取更多字节换取吞吐,但中断占用过久可能触发硬件看门狗;对吞吐要求不高的链路保持默认单字节模式更稳。 - 电平与极性:RS-485 等反相总线通过构造参数
invert或 Tasmota 的SetOption69控制接收反相;RS-485 半双工请配合setTransmitEnablePin()使用方向控制引脚。 - 平台选型:能用硬件就用硬件——ESP8266 优先
hardware_fallback=1/2引脚组合,ESP32 优先非 UART0 引脚,把软件模拟留给非关键 GPIO。
十一、快速上手清单
- 引入头文件
#include <TasmotaSerial.h>,工程需在platformio.ini中将本库加入lib_deps(Tasmota 固件默认已内置在lib/default); - 按"接收引脚、发送引脚、回退模式"构造实例,检查
isValid(); begin(波特率, 串口格式)启动,ESP8266 软件串口默认 9600、8N1;- 主循环用
available()/read()消费数据,必要时overflow()检测丢包; - 涉及 RS-485 时配置
setTransmitEnablePin()与invert,ESP32 可用setConfig()运行时切换校验; - 压测验证:参考 swsertest.ino 做回环测试,确认在高流量下无位错、无溢出后再接入生产外设。
十二、结语
TasmotaSerial 以"软件模拟为主、硬件回退为辅、ESP32 全硬件化"的分层设计,成为 Tasmota 固件串口生态的公共底座:Serial Bridge、Tuya MCU、Zigbee、Modbus、能量计量与众多传感器驱动都建立在其上。理解它的构造参数、回退判定与中断时序边界,既能让你在 Tasmota 中正确接线与配置,也能帮助你在自研 ESP8266/ESP32 固件中复用它,在有限 UART 资源下稳定扩展多个串口外设。更多实现细节可继续阅读 TasmotaSerial.cpp 与 TasmotaSerial.h。
【免费下载链接】TasmotaAlternative firmware for ESP8266 and ESP32 based devices with easy configuration using webUI, OTA updates, automation using timers or rules, expandability and entirely local control over MQTT, HTTP, Serial or KNX. Full documentation at项目地址: https://gitcode.com/GitHub_Trending/ta/Tasmota
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考