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/开关量)、帧协议应答、周期上报输入状态
febmdLinux 主控,systemd 守护进程独占 UART、1kHz 同步循环、端口占用仲裁、IPC 服务、HTTP 服务
libfebmc业务进程内(C 共享库)IPC 封装,提供 C 风格连接/读写/占用 API,线程安全
febm(Python)业务进程内(ctypes 绑定)Pythonic 对象接口,内置自动心跳与 RAII 占用管理
HTTP 服务febmd 内嵌Web 状态页 + REST API,供浏览器与远程脚本监控调试

端口模型:FEB 把从机上的所有 IO 抽象为 6 类端口,统一编址、统一同步:

类型名称方向数据类型访问权限
BOBool Output主机 → 从机布尔独占写(占用令牌)
BIBool Input从机 → 主机布尔任意客户端共享读
IOInt Output主机 → 从机int32独占写(占用令牌)
IIInt Input从机 → 主机int32任意客户端共享读
FOFloat Output主机 → 从机float32独占写(占用令牌)
FIFloat Input从机 → 主机float32任意客户端共享读

同步数据流——一个 1ms 周期内的完整闭环如图 2 所示:

图 2 一个同步周期内的端口数据流(1kHz 闭环)

2. FEB 从机设计细节

2.1 硬件与固件概述

FEBS 从机基于 ESP32-S3 等嵌入式 MCU 平台构建,固件以独立软件模块(SysFebs)形式 集成在从机应用工程中。从机在系统中承担三个角色:

  1. 端口物理层:直接驱动继电器/风扇/使能引脚(BO/IO/FO),采集开关量输入(BI) 与模拟量/数字传感器(II/FI,经 ADC、I2C、SPI 等);
  2. 协议应答器:监听 UART 帧,响应主机的配置查询(GetCfg)与周期同步(SyncState);
  3. 数据源:把本地采集到的物理量实时打包为端口值,随 SyncState 响应帧上报。

从机软件分层如图 3 所示:

图 3 FEBS 从机软件分层

2.2 通信帧协议

主从之间采用定制的轻量二进制帧协议,结构固定、解析开销极小,如下所示:

偏移012345 … N
内容0x950x10CMDLEN_LLEN_HDATA[LEN]
含义帧头(固定)指令码数据长度(uint16 小端)有效数据

指令码定义:

CMD名称方向用途
0x01GetCfg主 → 从上电配对:请求从机自描述配置
0x01GetCfg 响应从 → 主返回 JSON 配置(设备信息 + 端口数量)
0x10SyncState主 → 从周期同步请求,DATA 携带全部输出端口值(OUT_TAB)
0x10SyncState 响应从 → 主携带全部输入端口值(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[] 端口元数据(每端口的 nameunit), 主机界面可直接显示 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 直接对接
JSONcJSON(单文件静态链接)零外部依赖,嵌入友好
HTTPlibmicrohttpd轻量 GNU 库,HTTP/1.1,静态页面编译期内嵌
串口原生 termios完全控制波特率/帧格式/线路电平(DTR/RTS/HUPCL)
IPCUnix 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 -9IPC 连接关闭(内核 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 上下文 自动连接/释放,Occupancy RAII 对象退出即释放占用,并内置 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 基础设施。

Avatar photo

作者 skyate

发表回复