FEB(Field Expansion Bus,域扩展总线) 是一套自研的轻量级嵌入式 IO 扩展与实时同步系统, 由 FEBS 从机(前端扩展板)与 FEBM 主机管理器(Linux 守护进程)两部分组成。 本文面向公开渠道发布,系统性地介绍 FEB 的设计动机、总体架构与主从两端的设计细节。
文档版本:V1.1 适用范围:FEB 总线协议 V1/V2、febmd 1.x、FEBS 固件 1.x
1. 概述
1.1 开发目的
在移动机器人、无人平台与各类智能装备的开发中,主控计算机(通常是运行 Linux 的 SoC 平台,如 RK3588)除了运行核心算法(感知、定位、规划、控制)之外,还需要与 大量”周边”硬件打交道:
- 执行器:继电器、风扇、电源使能、指示灯、电磁阀……
- 传感器:系统温度、母线电压/电流、电池容量、三轴加速度计/陀螺仪、按键与 开关量输入……
如果把这些 IO 全部直接接到主控 SoC 上,会遇到一系列工程问题:
| 问题 | 具体表现 |
|---|---|
| 引脚资源紧张 | SoC 的 GPIO/ADC 引脚数量有限,且大多与其他功能(UART、SPI、PWM)复用,扩展余量不足 |
| 并发访问失控 | 多个业务进程各自直接操作硬件设备,缺少统一仲裁:谁都能写,冲突后难以定位责任方 |
| 实时性无保障 | 通用 Linux 用户态进程的调度延迟在毫秒级波动,难以稳定维持 kHz 级的周期性 IO 同步 |
| 监控调试困难 | IO 状态散落在各进程中,没有全局视图;现场排障只能逐个进程翻日志 |
| 可移植性差 | 换一块主控板,底层 IO 代码需要重新适配;业务逻辑与硬件细节耦合 |
FEB 的核心思路是:把 IO 扩展与采集整体下沉到一块专用从机板(FEBS), 主控与从机之间只用一条 UART 连线,即可完成全部端口状态的 1kHz 实时同步; 主机侧由一个统一的守护进程(febmd)独占串口并提供并发安全的服务化接口, 业务进程通过本地 IPC 或 HTTP 远程访问,无需关心任何硬件细节。
设计目标与达成指标:
| 目标 | 说明 |
|---|---|
| 高频实时同步 | 1ms(1kHz)周期完成全部端口的双向同步,采用实时调度(SCHED_FIFO)与绝对时间睡眠抑制抖动 |
| 自描述即插即用 | 从机上电自动上报设备类型、序列号与端口配置,主机动态分配资源,无需人工对表 |
| 并发安全 | 输出端口独占写(占用令牌 + 心跳保活),输入端口共享读,客户端崩溃自动回收 |
| 多语言易集成 | 提供 C 客户端库(libfebmc)与 Python 绑定(febm),另有 HTTP REST API 供任意语言/远程调用 |
| 可观测可调试 | 内嵌 Web 状态页与 REST API,实时查看同步质量、端口值与占用关系;支持受鉴权保护的调试写入 |
| 弱网容错 | 从机断线自动重连重配对;febmd 意外退出由 systemd 自动拉起,业务侧透明恢复 |
1.2 FEB 主从架构概述
FEB 采用典型的”主从 + 服务化”两级架构:
- 从机(FEBS):运行在专用的前端扩展板(如 ESP32-S3)上,直接驱动和采集 物理端口,通过 UART 与主机通信,只做一件事——忠实地、周期地交换端口状态;
- 主机(FEBM):运行在 Linux 主控上,以守护进程
febmd形式存在,独占串口, 维护与从机镜像的端口状态表,并向所有业务进程提供 IPC / HTTP / 多语言客户端库 三种访问方式。
系统总体架构如图 1 所示:

图 1 FEB 系统总体架构
各组件职责:
| 组件 | 运行位置 | 职责 |
|---|---|---|
| FEBS 从机 | 前端扩展板(ESP32-S3) | 端口物理驱动(继电器/ADC/开关量)、帧协议应答、周期上报输入状态 |
| febmd | Linux 主控,systemd 守护进程 | 独占 UART、1kHz 同步循环、端口占用仲裁、IPC 服务、HTTP 服务 |
| libfebmc | 业务进程内(C 共享库) | IPC 封装,提供 C 风格连接/读写/占用 API,线程安全 |
| febm(Python) | 业务进程内(ctypes 绑定) | Pythonic 对象接口,内置自动心跳与 RAII 占用管理 |
| HTTP 服务 | febmd 内嵌 | Web 状态页 + REST API,供浏览器与远程脚本监控调试 |
端口模型:FEB 把从机上的所有 IO 抽象为 6 类端口,统一编址、统一同步:
| 类型 | 名称 | 方向 | 数据类型 | 访问权限 |
|---|---|---|---|---|
| BO | Bool Output | 主机 → 从机 | 布尔 | 独占写(占用令牌) |
| BI | Bool Input | 从机 → 主机 | 布尔 | 任意客户端共享读 |
| IO | Int Output | 主机 → 从机 | int32 | 独占写(占用令牌) |
| II | Int Input | 从机 → 主机 | int32 | 任意客户端共享读 |
| FO | Float Output | 主机 → 从机 | float32 | 独占写(占用令牌) |
| FI | Float Input | 从机 → 主机 | float32 | 任意客户端共享读 |
同步数据流——一个 1ms 周期内的完整闭环如图 2 所示:

图 2 一个同步周期内的端口数据流(1kHz 闭环)
2. FEB 从机设计细节
2.1 硬件与固件概述
FEBS 从机基于 ESP32-S3 等嵌入式 MCU 平台构建,固件以独立软件模块(SysFebs)形式 集成在从机应用工程中。从机在系统中承担三个角色:
- 端口物理层:直接驱动继电器/风扇/使能引脚(BO/IO/FO),采集开关量输入(BI) 与模拟量/数字传感器(II/FI,经 ADC、I2C、SPI 等);
- 协议应答器:监听 UART 帧,响应主机的配置查询(GetCfg)与周期同步(SyncState);
- 数据源:把本地采集到的物理量实时打包为端口值,随 SyncState 响应帧上报。
从机软件分层如图 3 所示:

图 3 FEBS 从机软件分层
2.2 通信帧协议
主从之间采用定制的轻量二进制帧协议,结构固定、解析开销极小,如下所示:
| 偏移 | 0 | 1 | 2 | 3 | 4 | 5 … N |
|---|---|---|---|---|---|---|
| 内容 | 0x95 | 0x10 | CMD | LEN_L | LEN_H | DATA[LEN] |
| 含义 | 帧头(固定) | 指令码 | 数据长度(uint16 小端) | 有效数据 | ||
指令码定义:
| CMD | 名称 | 方向 | 用途 |
|---|---|---|---|
0x01 | GetCfg | 主 → 从 | 上电配对:请求从机自描述配置 |
0x01 | GetCfg 响应 | 从 → 主 | 返回 JSON 配置(设备信息 + 端口数量) |
0x10 | SyncState | 主 → 从 | 周期同步请求,DATA 携带全部输出端口值(OUT_TAB) |
0x10 | SyncState 响应 | 从 → 主 | 携带全部输入端口值(IN_TAB),与请求一一对应 |
2.3 自描述配置(GetCfg)
从机上电后并不依赖任何人工配置,而是通过 GetCfg 应答上报一份 JSON 自描述:
{
"info": {"type": "skyate_250316", "serial": 1234567890, "proto": 2},
"BoolPort": {"Input": 4, "Output": 7},
"IntPort": {"Input": 0, "Output": 0},
"FloatPort": {"Input": 16, "Output": 0},
"ports": [
{"type": "Bo", "name": "sw0", "unit": ""},
{"type": "Bo", "name": "fan", "unit": ""},
{"type": "Fi", "name": "sys_temp","unit": "C"},
{"type": "Fi", "name": "vbus", "unit": "V"}
]
}
要点:
- 设备身份:
type(设备型号)与serial(唯一序列号)使主机可识别与审计接入设备; - 端口数量:三类端口的输入/输出数量,主机据此动态分配镜像状态表,从机升级 增减端口对主机完全透明;
- 协议版本:
proto: 2表示携带ports[]端口元数据(每端口的name与unit), 主机界面可直接显示sys_temp (°C)这样的友好标签;旧版从机(无proto字段) 依然兼容,显示退化为Fi#0形式; - 即插即用:主机收到配置后立即进入同步状态,全程无需人工介入。
2.4 高频同步(SyncState)
配对成功后,主机以 1ms 周期发送 SyncState 请求(携带输出数据),从机在同一毫秒内 完成”接收 → 解析 → 驱动输出 → 采集输入 → 应答”的完整闭环,时序如图 4 所示:

图 4 SyncState 1kHz 同步时序
带宽与实时预算(以 1.5 Mbps、BO×7 / BI×4 / FI×16 的典型配置为例):
| 项目 | 数值 |
|---|---|
| 每周期上行帧长 | 6 B(帧头 5 + OUT_TAB 1) |
| 每周期下行帧长 | 70 B(帧头 5 + IN_TAB 65) |
| 线路利用率(全双工,按方向独立计) | 上行约 4%,下行约 47%,余量充足 |
| 单周期往返耗时 | @1.5 Mbps 典型约 0.5 ms(含完整帧传输,随帧长/波特率变化) |
| 端到端延迟(从机变化 → 主机可读) | < 2 ms |
波特率选择:16550 类 UART 的波特率来自整数分频,若目标速率不能整除会产生失真, 超过 ±3% 即可能通信失败。FEB 默认采用 1.5 Mbps——在常见 24 MHz UART 时钟下 分频恰为 1(0% 失真),且带宽余量最大;主机侧打开串口时会自动核算实际失真率并在 超限时告警,从机制约协议与主机两侧必须使用一致速率。
2.5 状态表编码
SyncState 帧中的端口数据区(OUT_TAB / IN_TAB)采用紧凑的定长布局:
- Bool 端口(BO/BI):每端口 1 bit,LSB 在前,按字节打包,字节数 = ⌈数量/8⌉;
- Int 端口(IO/II):每端口 4 字节,小端序 int32;
- Float 端口(FO/FI):每端口 4 字节,IEEE 754 单精度,小端序。
以 BO×7、BI×4、FI×16 为例,状态表布局如图 5 所示:

图 5 OUT_TAB / IN_TAB 状态表布局示例
该布局由双方在 GetCfg 配对时根据端口数量独立计算得出,天然一致,无需逐端口 协商;定长帧也让从机端解析可以做到零动态内存分配,满足 MCU 上的确定性要求。
2.6 典型外设接口能力
以实验室车载 FEBS 从机为例,一条 UART 背后聚合的物理接口能力:
| 端口组 | 数量 | 典型用途 |
|---|---|---|
| BO(开关量输出) | 7 | 模式开关、电源使能、散热风扇、RTK 模组使能 |
| BI(开关量输入) | 4 | 急停、舱门/线束在位检测、模式选择拨码 |
| FI(模拟/数据输入) | 16 | 系统温度、母线电压/电流/功率、电池 SOC/SOH/剩余容量、三轴加速度计、三轴陀螺仪 |
3. FEB 主机设计细节
3.1 软件总体结构
febmd 是一个 C11 编写的 Linux 守护进程(约 3000 行,CMake 构建,仅依赖 cJSON 与 libmicrohttpd 两个轻量库),采用”多线程 + 共享数据层”模型,如图 7 所示:

图 6 febmd 线程模型与共享数据层
技术栈选型:
| 组件 | 选择 | 理由 |
|---|---|---|
| 语言 | C11 + CMake | 系统级守护进程首选,与 POSIX 串口/线程 API 直接对接 |
| JSON | cJSON(单文件静态链接) | 零外部依赖,嵌入友好 |
| HTTP | libmicrohttpd | 轻量 GNU 库,HTTP/1.1,静态页面编译期内嵌 |
| 串口 | 原生 termios | 完全控制波特率/帧格式/线路电平(DTR/RTS/HUPCL) |
| IPC | Unix Domain Socket + JSON | 跨语言、进程隔离、权限可控,短消息往返 < 100 μs |
3.2 同步循环(SyncLoop)
SyncLoop 是整个系统的心脏,负责与从机维持 1kHz 的状态同步。其内部是一个三态状态机 (图7):

图 7 SyncLoop 同步状态机
实时性保障措施:
| 措施 | 实现方式 | 效果 |
|---|---|---|
| 实时调度 | SyncLoop 线程 SCHED_FIFO 优先级 95(由 systemd LimitRTPRIO=95 授权) | 内核优先调度,普通负载无法抢占 |
| 绝对时间睡眠 | clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME) 累加 deadline | 周期无累积漂移,不受 NTP 校时影响 |
| CPU 亲和性 | SyncLoop 绑定固定核心(可配置 cpu_affinity) | 避免跨核迁移带来的 cache/调度抖动 |
| 临界区极短 | 互斥锁采用优先级继承 + 自适应自旋;锁内仅做内存拷贝,UART 收发在锁外 | 持锁时间 < 50 μs,1ms 周期余量充足 |
| 失败降频 | 配对失败时以 1s 间隔重试 | 空闲期不浪费 CPU |
波特率自检:febmd 打开串口时按 baud_base 核算目标速率的整数分频失真, 失真 > 2% 时升级为 ERROR 日志并给出可整除的候选速率——把”波特率配错”这类 隐性问题在启动阶段就暴露出来,而不是表现为莫名的丢帧。
同步质量统计:每次同步的往返时延(RTT)进入 256 深度滑动窗口,经 HTTP API 暴露成功率、RTT 均值/最大/最小等指标,作为系统健康度的量化依据。
3.3 端口占用与并发安全
FEB 遵循严格的单写者原则:每个输出端口同一时刻只允许一个客户端持有写权限, 从机制上杜绝”两个进程同时抢一个继电器”这类事故。
权限模型:
| 操作 | 输出端口(BO/IO/FO) | 输入端口(BI/II/FI) |
|---|---|---|
| 读取 | 所有客户端,无需占用 | 所有客户端,无需占用 |
| 写入 | 必须持有该端口的独占占用令牌(token) | 不允许(值只能来自从机采集) |
占用生命周期管理:febmd 维护占用表与”端口 → token”反向索引,完整的申请、 心跳、崩溃回收与再次分配时序如图 8所示:

图 8 端口占用申请、心跳与自动回收时序
自动回收机制覆盖所有异常场景:
| 场景 | 检测方式 | 释放延迟 |
|---|---|---|
| 客户端正常退出 | IPC 连接关闭 | 立即 |
| 客户端崩溃 / kill -9 | IPC 连接关闭(内核 FIN) | 立即 |
| 客户端被冻结(SIGSTOP/调试断点) | 心跳超时(15s)+ kill(pid,0) 存活复核 | ≤ 15s |
| febmd 自身重启 | 输出状态清零,客户端检测到断连后自动重新申请占用并重写输出 | 秒级 |
写入路径的并发设计——”脏标志 + 暂存区”:
客户端写入只更新暂存区(pending)并置位脏标志(dirty),临界区仅几条赋值语句; SyncLoop 在每个周期开始时批量搬运暂存区 → 输出状态表并清脏标志,然后组装帧发送。 同一周期内同一端口的多次写自然收敛为最后一次值(last-write-wins), 既保证了数据一致性,又把总线负载降到最低。
3.4 IPC 服务与多语言客户端
IPC 协议:Unix Domain Socket(默认 /var/run/febm/febm.sock,权限 0660), 请求-响应模式,消息为 Content-Length 头 + JSON 体。IPC 线程用 epoll 管理所有 客户端连接,支持数十个进程并发;连接断开自动释放其名下全部占用令牌。
统一响应结构携带错误码,常用错误码:
| code | 含义 |
|---|---|
| 0 | 成功 |
| 1003 / 1004 | 无效令牌 / 令牌过期(心跳超时已被回收) |
| 2001 | 从机未连接 |
| 2002 | 端口索引越界 |
| 3001 | 端口已被其他客户端占用(响应附带冲突清单) |
| 3002 | 客户端数已达上限 |
客户端库:协议解析全部收敛在 febmd 内,客户端库只是极薄的 IPC 封装—— 这让多语言绑定几乎零成本:
- C(libfebmc):C 风格 API(
febmc_connect / read_port / occupancy_request / write_port / heartbeat ...),内部以MSG_PEEK高效解析 Content-Length, 线程安全(多任务可并发调用,库内串行化 IO); - Python(febm):ctypes 绑定 libfebmc,提供 Pythonic 接口——
with上下文 自动连接/释放,OccupancyRAII 对象退出即释放占用,并内置 3s 自动心跳线程, 业务代码无需关心任何保活细节。
from febm import Client, PortType
with Client() as c: # 自动连接 + 自动心跳
temp = c.read_float(PortType.FI, 0) # 读系统温度(共享读)
with c.occupancy_request([(PortType.BO, 5)], # 申请风扇端口占用
info="温控风扇") as occ:
occ.write_bool(PortType.BO, 5, True) # 开风扇
# with 退出 / 进程崩溃 → 占用自动释放
并发规模约束:单连接最多同时持有 32 个端口、8 个令牌;febmd 全局最多 64 个客户端——对一台主控上的全部业务进程而言余量充分。
3.5 HTTP 监控与调试
febmd 内嵌 HTTP 服务(默认端口 8765,可选仅绑定回环),提供 Web 状态页与 REST API:
| 方法 & 路径 | 说明 |
|---|---|
GET / | Web 状态页:连接状态、同步质量、全部端口实时值、占用关系 |
GET /api/status | 从机状态 + 同步统计(成功率、RTT 等)JSON |
GET /api/ports | 全部端口当前值(含 name/unit 元数据与占用者信息) |
GET /api/ports/{type}/{index} | 单端口详情 |
GET /api/occupancy | 当前占用表(token、进程 PID、描述、端口清单) |
POST /api/debug/write | 调试写入(绕过占用机制,需独立 debug_token 鉴权,默认关闭) |
Web 页面 200ms 轮询刷新,开发人员无需任何客户端代码即可总览总线健康度; 调试写入仅供开发环境联调硬件使用——绕过占用是它的价值,独立令牌鉴权与 默认关闭开关则控制其风险。
3.6 可靠性设计
| 环节 | 设计 |
|---|---|
| 从机断线 | 连续 5 次同步失败判定离线 → 状态表清零 → 自动降频重发 GetCfg 重新配对,线路恢复后秒级自愈 |
| febmd 崩溃 | systemd Restart=on-failure 2s 自动拉起;业务客户端感知 IPC 断连后自动重连 |
| febmd 重启的输出语义 | 重启后输出端口清零(安全默认值),客户端重连后需重写输出——占用于业务侧是显式的”重新使能”动作 |
| 串口独占 | febmd 独占打开 UART 设备,部署时停用内核 console/getty 抢占;打开时显式控制 DTR/RTS 电平并默认禁用 HUPCL,避免关闭串口的边沿误复位从机 |
| 日志 | syslog + 文件双通道,四级日志(DEBUG/INFO/WARN/ERROR),关键事件(配对成功/失败、占用异常回收)均带源码定位 |
| 无硬件开发 | 提供 PTY 仿真从机(feb_slave_sim.py)与端到端测试脚本,全套功能可在无实机环境下开发回归 |
3.7 关键性能指标(实测)
| 指标 | 实测值 |
|---|---|
| 同步成功率 | ≥ 99.99%(1kHz 连续运行) |
| SyncState 往返时延 | @1.5 Mbps 典型约 0.5 ms(含完整帧传输,随帧长/波特率变化) |
| 输入变化到主机可读延迟 | < 2 ms |
| SyncLoop CPU 占用 | < 5% @ 1kHz(单从机) |
| febmd 内存占用 | 基础约 2 MB,每从机 +200 KB |
| IPC 短消息往返 | < 100 μs |
| 从机断线恢复时间 | 秒级(自动重新配对) |
结语
FEB 用”一条 UART + 一个守护进程”解决了嵌入式平台上 IO 扩展的老大难问题: 从机以自描述、零动态内存的固件设计保证了确定性同步;主机以实时调度、占用仲裁 与服务化接口保证了并发安全与工程效率。二者共同构成了实验室各类移动平台 “主控—前端”之间的标准 IO 基础设施。
