飞仙边缘控制器本地 GPIO 高性能访问库
本 SDK 为飞仙软件内部边缘控制器产品提供统一的 C/C# 编程接口,支持毫秒级响应、硬件原子批量操作和复杂的输入信号滤波。
- 高性能: 底层 C 语言直接操作内核 ioctl,零拷贝开销。
- 输入滤波: 内置滑动窗口、计数、时间阈值三种工业级滤波算法,直接在底层线程处理,输出纯净信号。
- 信号反相: 自动将硬件 Active Low 电平转换为应用层 Active High 语义(有信号=1,无信号=0)。
- 原子操作: 支持
WriteBatch硬件原子操作,完美同步控制多路信号(如步进电机方向+脉冲)。 - 多架构支持: 提供
linux-arm(RK3506),linux-arm64(RK3588),linux-x64全平台支持。 - CI/CD 集成: 完整的 GitLab CI 流水线,自动交叉编译并打包。
外壳丝印顺序 (I00 ~ I19) 与 SDK 索引直接对应,已由 BSP 设备树原生支持。
| 外壳丝印 (Silk Screen) | SDK 索引 (API Index) | 说明 (Note) |
|---|---|---|
| I00 - I19 | 0 - 19 |
直接映射,SDK 无额外重排 |
关于信号反相: 硬件采用 NPN 传感器 + 上拉电阻设计(Active Low),SDK 已自动处理反相。应用层读到
true表示“有信号输入”,false表示“无信号”。
| 硬件型号 (Driver Name) | 芯片平台 | IO 规格 | 说明 |
|---|---|---|---|
RK3506_20I12O |
Rockchip RK3506 | 20I 12O | 推荐: 适用于设备树已修正的新系统 (1:1映射) |
RK3506_20I12O_REMAP |
Rockchip RK3506 | 20I 12O | 兼容: 适用于旧版设备树 (需 SDK 软件重排) |
项目包含一键构建脚本,支持在 Linux (LXC/Docker) 或 macOS (Docker) 环境下编译。
# 赋予执行权限
chmod +x build_all.sh
# 编译 C# Wrapper 和 C Native Lib (自动识别环境)
./build_all.sh构建产物将位于 release/linux-arm/ 目录下:
FXLocalGPIOSDKWrapper.dll: .NET 8.0 封装库libfxlocalgpio.so: C 语言底层库 (ARM32)ConsoleDemo: 可执行演示程序
引用 FXLocalGPIOSDKWrapper.dll,然后编写如下代码:
using FXLocalGPIO.SDK;
// 1. 初始化 (必须指定驱动名)
using var gpio = new FXLocalGPIOSDK("RK3506_20I12O");
// 2. 配置输入滤波 (可选)
gpio.ConfigureFilter(0, InputFilterConfig.StrongDebounce);
// 3. 注册输入变化事件
gpio.OnReadDataChanged += (index, val) => {
Console.WriteLine($"Pin {index} changed to {val}");
};
// 4. 控制输出
gpio.Write(0, true); // 点亮 OUT0
// 5. 批量控制 (原子操作)
// 同时将 OUT0 置高, OUT1 置低
gpio.WriteBatch(0x03, 0x01);
// 6. 保持程序运行
await Task.Delay(-1);更多详细用法请参考 用户指南 (User Guide)。
ReadBatch() 示例:
uint inputs = gpio.ReadBatch(); // 例如返回 0x00000015
// 解析各个输入:
bool in0 = (inputs & 0x01) != 0; // true (bit0 = 1)
bool in1 = (inputs & 0x02) != 0; // false (bit1 = 0)
bool in2 = (inputs & 0x04) != 0; // true (bit2 = 1)
bool in4 = (inputs & 0x10) != 0; // true (bit4 = 1)- 统一接口: 屏蔽底层驱动差异(ioctl/mmap/sysfs),上层统一使用
Read/Write。 - 事件驱动: 内置高性能轮询线程(默认 1ms),提供类似 PLC 的输入中断体验。
- 硬件原子操作:
WriteBatch使用内核 IOCTL,多个引脚在同一时钟周期内同时变化。 - 自动资源管理: 完整支持
using用于自动释放底层句柄。
| 场景 | 推荐方法 | 说明 |
|---|---|---|
| 单个 IO 控制 | Write(idx, val) / Read(idx) |
代码简洁,无需位运算 |
| 多个 IO 需要同时变化 | WriteBatch(mask, val) |
如:电机驱动的使能+方向必须同步翻转,避免中间状态 |
| 批量状态查询 | ReadBatch() |
一次系统调用获取所有输入,效率更高 |
| 高频轮询 | ReadBatch() |
减少 IOCTL 调用次数,降低 CPU 开销 |
示例:电机使能 + 方向同步切换
// 假设 out0 = 使能, out1 = 方向
// 错误做法(可能产生毛刺):
gpio.Write(0, true); // 先使能
gpio.Write(1, true); // 再设方向 — 中间有几微秒"使能但方向错误"的状态!
// 正确做法(原子操作):
gpio.WriteBatch(0x03, 0x03); // 使能 + 正转 同时生效您可能会注意到,本 SDK 与 FXEtherCATSDK 在错误处理上存在差异:
- EtherCAT SDK:
int ret = ReadSlave(..., out val);(返回错误码) - FXLocalGPIO:
bool val = Read(0);(直接返回值,出错抛异常)
原因说明:
- 场景不同:EtherCAT 是远程总线通讯,网络干扰导致偶发失败是常态,调用者需要频繁检查
ret并重试。 - 本地 IO 稳定性:本地 GPIO 访问的是板载寄存器,极其稳定。只要设备初始化成功,运行时几乎不可能失败(除非硬件物理损坏)。
- 开发体验:对于本地 IO,强制用户每次都检查错误码会造成代码冗余。我们选择**“相信硬件”**,一旦发生底层错误(如返回 -1),SDK 会直接抛出
IOException,从逻辑上阻断错误的程序流程,更符合 .NET 现代开发习惯。
FXLocalGPIO 的设计是硬件无关的。fx_local_gpio.c 负责通用的业务逻辑(如轮询、滤波、回调分发),而具体的硬件操作通过 gpio_driver_t 接口抽象。
如果您需要支持新的硬件型号(例如 RK3507_32I32O),请按照以下步骤操作:
在 src/drivers/ 目录下创建一个新的驱动文件(如 rk3507_drv.c),并实现 gpio_driver.h 中定义的接口:
// 定义驱动实例
gpio_driver_t rk3507_driver = {
.name = "RK3507_32I32O",
.init = rk3507_init,
.deinit = rk3507_deinit,
.set_output = rk3507_set_output,
.set_batch_output = rk3507_set_batch_output, // 可选,支持批量写入
.get_input = rk3507_get_input,
.get_all = rk3507_get_all // 必须高效实现,用于 1ms 轮询
};在 src/fx_local_gpio.c 的 fx_gpio_init 函数中添加新驱动的匹配逻辑:
extern gpio_driver_t rk3507_driver; // 声明外部变量
int fx_gpio_init(const char* driver_name) {
if (strcasecmp(driver_name, "RK3507_32I32O") == 0) {
current_driver = &rk3507_driver;
}
// ... 其他驱动
}在 CMakeLists.txt 中将新的 .c 文件加入编译列表。
滤波算法完全复用!
SDK 的核心滤波逻辑(防抖、计数、滑动窗口)实现在通用层 (fx_local_gpio.c)。只要您的驱动能正确上报 get_all 数据,新机型就能直接享受到所有高级滤波功能,无需重复开发。
// 必须指定硬件型号
if (fx_gpio_init("RK3506_20I12O") != 0) {
printf("Hardware not supported!\n");
return -1;
}
fx_gpio_register_callback(my_cb);
fx_gpio_start_monitoring(1); // 1ms 监控周期
// 单点写
fx_gpio_set_output(0, 1);
// 批量写: mask=0x03 (out0+out1), value=0x01 (out0高, out1低)
fx_gpio_write_batch(0x03, 0x01);
// 批量读
int inputs = fx_gpio_read_batch(); // 返回位掩码
fx_gpio_deinit();
return 0;
}