8 个版本 (4 个重大更改)
0.5.2 | 2023年2月3日 |
---|---|
0.5.1 | 2022年2月5日 |
0.5.0 | 2022年1月31日 |
0.4.1 | 2022年1月28日 |
0.1.0 | 2018年11月22日 |
#143 在 Unix API
每月3,139 次下载
用于 pluto-sdr
780KB
18K SLoC
Rust Linux 工业I/O
Rust 库 crate,用于使用 Linux 工业I/O (IIO) 子系统,主要用于在用户空间中从 Linux 系统输入和输出模拟数据。请参阅 IIO Wiki。
当前版本是对用户空间 C 库的包装,libiio。后续版本可能直接访问内核 ABI 接口。
要在应用程序中使用,请将以下内容添加到 Cargo.toml:
[dependencies]
industrial-io = "0.5"
预发布说明
这是 crate 的预发布版本。API 正在稳定化,但仍在积极开发中,在最终发布之前可能还会发生变化。
此初始开发工作包装了 特定 版本(v0.21)的 libiio。它假设库已在构建系统和目标系统上预先安装。
贡献
我们欢迎对该项目的贡献。请记住以下几点
- 请将所有拉取请求(Pull Requests)针对存储库的
develop
分支。我们希望在发布之间保持 master 分支相对稳定,并在 develop 分支中进行集成和测试。 - 请将单个拉取请求(Pull Requests)限制在一个主题。
- 请勿在其他更新中重新格式化代码。任何代码格式化都应该在单独的提交或 PR 中进行。格式化规范在
.rustfmt.toml
中,目前需要 nightly 发布。
欢迎对任何涉及不同 IIO 设备的调整或反馈的贡献。如果您测试、工作或在使用特定 IIO 硬件或驱动程序时遇到任何问题,请告诉我们。
还要求提供不同硬件的新示例。
最新消息
总的来说,正在努力使此 crate 达到生产质量。这包括
- 全面覆盖 libiio API - 或者尽可能多。
- 一组完整的工作示例。
- 单元测试和持续集成
要了解此项目的最新公告,请关注
Twitter: @fmpagliughi
0.5.2 版本新增内容
0.5.1 版本新增内容
iio_info_rs
工具现在支持网络和 URI 上下文。- PR #19 macOS 构建在搜索 Homebrew 框架(libiio 库)时区分 Intel 和非 Intel 构建。
- PR #20 修复一些 clippy 建议。特别是更干净的原指针转换等。
基础知识
C libiio 库为 Linux 工业级 I/O 子系统提供了一个用户空间接口,用于与(可能高速)模拟硬件接口,例如 A/D 转换器、D/A 转换器、加速度计、陀螺仪等。此包提供了一个相当薄的、安全的、围绕 C 库的包装。
要访问任何物理设备,您必须首先创建一个 Context
,它可以使用几种后端之一,其中最常见的是
- "local" - 访问本地机器上的硬件
- "network" - 访问运行 IIO 网络守护进程的远程机器上的硬件,
iiod
还有 USB 和串行设备的后端。
默认上下文将使用本地机器,但可以通过 IIOD_REMOTE
环境变量来覆盖,当设置时,为网络上下文提供主机名。
创建的给定上下文可以查询其中包含的设备及其提供通道的信息。上下文、设备和它们的通道包含 属性,用于读取和设置数据以及收集它的参数。
可以通过 触发 设备开始数据采样和/或输出,这些触发器可以是硬件或软件定时器,用于收集定期数据,或基于外部事件(如 GPIO 输入)的触发器。注意,某些设备可以自我触发。
库还可以使用内核中的环形缓冲区以更高的速度和更低的抖动收集数据。
在 examples/ 目录中有许多应用程序。
硬件和驱动程序的特殊之处
Linux IIO 子系统和 libiio 抽象了大量不同类型的硬件,这些硬件具有相当不同的功能集。在硬件的不同功能和为它们编写的驱动程序之间,当使用新设备开始时,应用程序通常会看到奇怪和意外的结果。示例应用程序不保证在所有不同类型的硬件上都能立即使用。但它们为最常见的使用提供了一个相当好的模板。在处理新设备时通常需要进行一些修改和实验。
实现细节
Rust 工业级 I/O 库是 C libiio 的一个相当薄的包装,添加了一些功能,使其更具 Rust 风格。
库包装器
要对libiio进行任何操作,应用程序必须首先创建一个Context
,以便操作本地设备上的硬件(即本地上下文),或者通过网络连接与远程设备上的硬件进行通信。与库中的其他操作相比,创建本地上下文是一个相当繁重的操作,因为它将扫描硬件并在内存中建立本地表示。
因此,上下文是创建时的硬件快照。任何在上下文外部添加的硬件(如另一个进程创建新的hrtimer)都不会反映在其中。需要创建新的上下文来重新扫描硬件。
但是,在上下文中查找硬件非常高效,因为它只需搜索上下文中的数据结构。像ctx.find_device('adc0')
这样的调用只需在硬件设备列表中查找字符串匹配,而底层库调用的返回指针只是对现有上下文数据结构的引用。
当声明新的Rust硬件结构时(例如Device
或Channel
),不会创建或销毁任何内容。因此,可以通过复制库结构的指针轻松克隆Rust结构。
Rust的Context
对象只是一个对InnerContext
的引用计数智能指针。这使得在不同对象(设备、通道等)之间共享C上下文并管理其生命周期变得容易。实际上,InnerContext
封装了C上下文。当它超出作用域时(通常当最后一个引用消失时),C上下文将被销毁。克隆InnerContext
会创建C库上下文的完整副本。
这导致了对“克隆”上下文的某些混淆。因为Context
只是一个对内部上下文的线程安全、引用计数的智能指针,克隆它只是创建了一个新的、共享的内部/C上下文指针。这使得在不同由它创建的多个Device
对象之间共享上下文并保证其生命周期变得容易。实际上,克隆Context
并将副本发送到另一个线程将在两个线程之间共享InnerContext
(以及C上下文)。
然而,当使用单独的线程来管理每个设备时,为每个线程创建一个完全独立的C上下文可能更有效率。为此,需要创建Context
的“深度”克隆。这实际上是InnerContext
的克隆,并在该内部上下文周围创建新的智能指针。因此,它是对InnerContext
的克隆,它实际上创建了C库上下文的副本。有关详细信息,请参阅下一节。
线程安全
此库的早期版本(v0.4.x及之前)是在一个错误的认识下编写的,即底层libiio不是线程安全的。有关该库的一些公开信息有些误导,但随着库维护者的输入和额外发布的信息,线程限制正在逐渐从这个库中解除。
从v0.5开始,现在可以实现以下功能
InnerContext
(它实际上封装了C库上下文)现在是Sync
,除了是Send
之外。它可以在线程之间共享。Context
现在通过一个Arc
来指向其InnerContext
。因此,这些指向内部上下文的引用可以被发送到不同的线程,并且这些线程可以共享同一个上下文。- 持有
Context
引用的Device
对象现在是Send
。它们可以被移动到创建上下文的不同线程中。 - 目前,
Channel
和Buffer
对象仍然是!Send
和!Sync
,并且需要与Device
在同一个线程中存在,但这些限制可能会随着我们确定哪些特定操作不是线程安全的而放宽。 Buffer::refill()
函数现在接收一个可变引用&mut self
,为放宽对缓冲区的线程限制做准备。缓冲区肯定不能被两个不同的线程同时填充。
即使有了这些新的线程能力,当由 IIO 上下文描述的物理设备可以被不同的线程操作时,通常仍然希望为每个线程使用一个单独的 Context
实例。有两种方法可以实现这一点
- 简单地在每个线程中使用相同的 URI 等创建一个新的
Context
对象。如果在这两个上下文创建之间添加或删除了新的硬件,它们可能不会完全相同。但这可能是罕见的。 - 创建一个上下文,然后克隆其
InnerContext
对象并将其发送到其他线程。然后每个都可以用来在该线程中创建一个新的Context
实例。
第二种选项效率更高,可以以多种方式实现。
一种方法是对上下文进行“深度”克隆并将其发送到另一个线程
let ctx = Context::new()?;
let thr_ctx = ctx.try_deep_clone()?;
thread::spawn(move || {
let dev = thr_ctx.find_device("somedevice")?
// ...
});
这会复制一个内部上下文,该上下文克隆了 C 库上下文。然后它将唯一的引用发送到另一个线程,给它对该 C 上下文的独占访问权。
或者,为了明确克隆内部上下文,可以这样做
let ctx = Context::new()?;
let cti = ctx.try_clone_inner()?;
thread::spawn(move || {
let thr_ctx = Context::from(cti);
let dev = thr_ctx.find_device("somedevice")?
// ...
});
在这里,内部上下文被克隆到 cti
对象,该对象被移动到线程中,并用于创建新的上下文对象 thr_ctx
。此过程是在库的早期版本(v0.5.0 之前)中需要的。
在线程和进程之间共享设备的另一种方法是运行本地机器上的 IIO 网络守护进程,并允许它控制本地上下文。然后多个客户端应用程序可以使用网络上下文从 localhost 访问它。守护进程将序列化对设备的访问,并允许多个客户端共享它。客户端的每个线程仍然需要一个单独的网络上下文。
需要注意的是,尽管 Rust 可以在一个进程内强制执行线程安全,但整体 IIO 子系统是暴露给其他进程的。不同的设备和驱动程序可能会以不同的方式暴露访问权限。确保使用 IIO 的进程之间不相互干扰的责任在于系统设计者。
测试 crate
用户空间IIO库的一个优点是,如果您在较新的Linux主机上进行开发,可以开始实验而无需在板上进行开发。您可以在嵌入式板上运行IIO服务器守护进程,然后使用此软件包通过网络连接与其通信。当您的应用程序运行正常时,您可以将其编译为目标板,本地测试,并部署。
另外,您还可以在开发主机上使用模拟的“虚拟”上下文进行测试。这是一个模拟几个设备的内核模块。它可以从主机机器的本地上下文中使用,以进行库的初始开发和测试。下面将详细介绍将其加载到内核中的方法。
BeagleBone
可以使用多个制造商的板子轻松尝试工业I/O子系统。BeagleBone Black和Green具有板载AM335X A/D转换器,而BeagleBone AI具有可用于模拟输入的STM触摸屏芯片。
BeagleBone的IIO库支持单个和缓冲采样,但无外部触发支持。板上的最新Debian 9.x IoT发行版已将IIO编译到内核中,可以直接使用 - 虽然用户空间库 libiio 应该升级(见下文)。
Linux开发主机
如Ubuntu 18.04等几个现代Linux发行版已为内核编译了IIO模块,如 dummy 上下文。这些可以加载到内核中,例如
$ sudo modprobe iio_dummy
$ sudo modprobe iio_trig_hrtimer
加载后,可以使用 configfs 创建设备和触发器。此存储库中包含的 load_dummy.sh 脚本可以用来加载模块并配置设备,适用于基本实验或运行单元测试。
$ sudo ./load_dummy.sh
macOS
该软件包也与macOS兼容,但仅提供网络上下文。可以从源代码构建或从社区homebrew公式安装libiio框架。
brew install tfcollins/homebrew-formulae/libiio
安装C库
在目标板上安装 libiio v0.21。如果您在Linux主机上进行开发,请在那里安装相同版本的库,以便在主机上进行一些开发。
检查目标版本
如果您使用的是BeagleBone Black,旧版库可能已随发行版发货。安装板的最新发行版。(此测试使用 Debian 9.5 2018-10-07 4GB SD IoT)。
登录到板并检查版本
$ iiod --version
0.21
如果版本低于0.21,请删除Debian软件包并从源安装。
首先,删除现有的库和实用程序
$ sudo apt-get purge libiio-utils libiio0
从源码构建
安装构建的先决条件
$ sudo apt-get install cmake flex bison libxml2-dev libserialport-dev
然后下载库源代码并构建
$ cd /tmp
$ wget https://github.com/analogdevicesinc/libiio/archive/v0.21.tar.gz
$ tar -xf v0.21.tar.gz
$ cd libiio-0.21/
$ mkdir build ; cd build
$ cmake .. && make && sudo make install
然后检查安装的版本是否正确
$ iiod --version
0.21
构建Rust软件包
这是一个围绕C库的相当标准的Rust包装项目。它包含一个不安全的 "-sys" 子软件包来包装C库API,以及在主软件包中包含的更高级别的安全Rust库。该软件包还包含通过库与IIO设备接口的实用程序。要构建库和实用程序
$ cargo build
该库可以以更少的依赖关系构建。要仅构建库
$ cargo build --lib --no-default-features
还有许多示例应用程序。它们都可以使用以下方法构建
$ cargo build --examples
依赖关系
~1.9–2.5MB
~54K SLoC