这篇将会综合前面的 GPIO 与 IIO 的知识,帮一个常见的红外线感测器 -- TCRC5000 实作 Linux 上的 IIO 驱动程序。
这是一个红外线感应模组。以红外线作为距离感应,当距离小於阈值时,输出高电位; 反之则输出低电位。而这个阈值大小可以透过调整其上的可变电阻更动。更多叙述可以参考 OSOYOO 公司底下的 Product description 一节。
VCC
接 Raspberry Pi 的 5V,GND
接 Raspberry Pi 的任意 GND
; OUT
接 GPIO17
。如图所示:
这个 Device Tree Overlay 的部分跟前几天 GPIO 的部分几乎一样,除了把名称更换掉,以及 GPIO 由输入改为输出以外:
/dts-v1/;
/plugin/;
/ {
compatible="brcm,brcm2835";
fragment@0 {
target = <&gpio>;
__overlay__ {
tcrc5000: tcrc5000_gpio_pins {
brcm,pins = <0x11>;
brcm,function = <0x0>;
brcm,pull = <0x1>;
};
};
};
fragment@1 {
target-path = "/";
__overlay__ {
tcrc5000 {
tcrc5000-gpios = <&gpio 0x11 0x0>;
compatible = "tcrc5000";
status = "ok";
pinctrl-0 = <&tcrc5000>;
pinctrl-names = "default";
};
};
};
};
为了方便,把这个资料结构中需要的硬体资源相关的资料结构,包成一个结构体:
struct tcrc5000 {
struct gpio_desc *gpiod;
struct mutex mutex;
struct device *dev;
};
这边的 gpiod
就是读取时对应的 GPIO 所对应的 GPIO Descriptor。除此之外,还配置一个 mutex
来保护。避免 IIO 的 sysfs
介面有不同的执行单元同时读取。
首先在 probe
中,先帮各种资料结构配置空间。为了方便资源管理,这边使用 devm_*
系列的函数 (因为所这个模组中的资源都是以 devm_*
函数配置的,所以就没有实作 remove
)。在 devm_gpiod_get_index
当中 ,tcrc5000
的参数是搭配装置树使用的。因为前面处理装置树时,这个感测器所使用的 GPIO 编号,是放在这个感测器对应的装置节点的 tcrc5000-gpios
属性中,所以就可以直接用 *gpiod_get_index
函数找出「前缀 (也就是 tcrc5000
) 对应的第 0 个 GPIO」:
static int tcrc5000_probe(struct platform_device *pdev)
{
...
iio = devm_iio_device_alloc(dev, sizeof(struct tcrc5000));
tcrc5000 = iio_priv(iio);
tcrc5000->dev = dev;
tcrc5000->gpiod = devm_gpiod_get_index(dev, "tcrc5000", 0, GPIOD_IN);
mutex_init(&tcrc5000->mutex);
...
}
因为感应器现在只有一道输出 (就是那个 OUT
)。所以在 iio_chan_spec
中就只给宣告一道输出。又因为这是属於接近相关的感测器,所以种类为 IIO_PROXIMITY
:
#define IIO_CHANNEL_DEFINE(num) {\
.type = IIO_PROXIMITY,\
.indexed = 1,\
.channel = (num),\
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),\
}\
static const struct iio_chan_spec tcrc5000_channels[] = {
IIO_CHANNEL_DEFINE(0)
};
这边就是实作 iio_info
中的 read_raw
函数。现在的数值是从 Raspberry Pi 上的其中一个 GPIO 读取,那麽就把那个 GPIO 的输入用 gpiod_get_value
读取,接着存到 *val0
当中 (也就是最终会在 sysfs
档案中出现的值)。最後用回传值 IIO_VAL_INT
提醒现在回传的东西是一个整数:
static int tcrc5000_read_raw (struct iio_dev *iio, struct iio_chan_spec const *chan,
int *val0, int *val1, long mask)
{
struct tcrc5000 *tcrc5000;
struct gpio_desc *gpiod;
tcrc5000 = iio_priv(iio);
gpiod = tcrc5000 -> gpiod;
mutex_lock(&tcrc5000->mutex);
(*val0) = gpiod_get_value(gpiod);
mutex_unlock(&tcrc5000->mutex);
return IIO_VAL_INT;
}
实作完之後,填写对应的 iio_info
:
struct iio_info tcrc5000_info = {
.read_raw = tcrc5000_read_raw,
};
并且回到 probe
当中,把剩下的初始化做完:
static int tcrc5000_probe(struct platform_device *pdev)
{
...
...
iio -> name = pdev->name;
iio -> info = &tcrc5000_info;
iio -> modes = INDIO_DIRECT_MODE;
iio -> channels = tcrc5000_channels;
iio -> num_channels = ARRAY_SIZE(tcrc5000_channels);
return devm_iio_device_register(dev, iio);
}
装置树叠加的步骤,以及 Makefile 都与之前相同,仅有档案名称不同而已,这边就不再重复内容。安装上模组之後,可以在 /sys/bus/iio/devices/
底下找到对应的装置节点。以这边为例,是 iio:device1
:
$ cd /sys/bus/iio/devices/iio\:device1
$ ls
dev in_proximity0_input name power subsystem uevent
在距离不同的状况下,in_proximity0_input
的档案内容会有所不同。如果距离低於阈值,那麽结果将会是 0:
$ cat in_proximity0_input
0
反之,结果会是 1:
$ cat in_proximity0_input
1
如果执行以下的 python
程序:
import os
from time import sleep
iio_oneshot_path = "/sys/bus/iio/devices/iio:device1/in_proximity0_input"
while 1:
fd = open(iio_oneshot_path, "r")
res = fd.read()
print(res.strip())
sleep(0.025)
fd.close()
就可以观察到「读值随距离产生变化」的结果。实验的影片在 这个连结中。
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/iio/consumer.h>
#include <linux/iio/iio.h>
#include <linux/of.h>
#include <linux/gpio/consumer.h>
#include <linux/mutex.h>
struct tcrc5000 {
struct gpio_desc *gpiod;
struct mutex mutex;
struct device *dev;
};
#define IIO_CHANNEL_DEFINE(num) {\
.type = IIO_PROXIMITY,\
.indexed = 1,\
.channel = (num),\
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),\
}\
static const struct iio_chan_spec tcrc5000_channels[] = {
IIO_CHANNEL_DEFINE(0)
};
static int tcrc5000_read_raw (struct iio_dev *iio, struct iio_chan_spec const *chan,
int *val0, int *val1, long mask)
{
struct tcrc5000 *tcrc5000;
struct gpio_desc *gpiod;
tcrc5000 = iio_priv(iio);
gpiod = tcrc5000 -> gpiod;
mutex_lock(&tcrc5000->mutex);
(*val0) = gpiod_get_value(gpiod);
mutex_unlock(&tcrc5000->mutex);
return IIO_VAL_INT;
}
struct iio_info tcrc5000_info = {
.read_raw = tcrc5000_read_raw,
};
static int tcrc5000_probe(struct platform_device *pdev)
{
struct device *dev = &(pdev-> dev);
struct iio_dev *iio;
struct tcrc5000 *tcrc5000;
iio = devm_iio_device_alloc(dev, sizeof(struct tcrc5000));
if (!iio) {
dev_err(dev, "Failed to allocate IIO/.\n");
return -ENOMEM;
}
tcrc5000 = iio_priv(iio);
tcrc5000->dev = dev;
tcrc5000->gpiod = devm_gpiod_get_index(dev, "tcrc5000", 0, GPIOD_IN);
if (IS_ERR(tcrc5000->gpiod)) {
dev_err(dev, "Failed to get gpio descriptor.\n");
return PTR_ERR(tcrc5000 -> gpiod);
}
mutex_init(&tcrc5000->mutex);
iio -> name = pdev->name;
iio -> info = &tcrc5000_info;
iio -> modes = INDIO_DIRECT_MODE;
iio -> channels = tcrc5000_channels;
iio -> num_channels = ARRAY_SIZE(tcrc5000_channels);
return devm_iio_device_register(dev, iio);
}
static const struct of_device_id tcrc5000_ids[] = {
{.compatible = "tcrc5000",},
{}
};
static struct platform_driver tcrc5000_driver = {
.driver = {
.name = "tcrc5000",
.of_match_table = tcrc5000_ids,
},
.probe = tcrc5000_probe
};
MODULE_LICENSE("GPL");
module_platform_driver(tcrc5000_driver);
在尝试 IIO 的其他功能的时候,有时候安装模组时会出现类似下面,Unknown symbol ... (err -2)
的讯息:
[ 3508.195974] tcrc5000: Unknown symbol devm_iio_triggered_buffer_setup (err -2)
除了可能是 License 不相容之外,另外一个可能的原因是:在编译核心的时候,一部分的功能在配置时被设为以「模组」形式编译 (也就是 m
选项),而不是直接编在核心中 (y
选项)。比如说如果去查询核心的配置:
CONFIG_IIO=m
CONFIG_IIO_BUFFER=y
CONFIG_IIO_BUFFER_CB=m
# CONFIG_IIO_BUFFER_HW_CONSUMER is not set
CONFIG_IIO_KFIFO_BUF=m
CONFIG_IIO_TRIGGERED_BUFFER=m
# CONFIG_IIO_CONFIGFS is not set
CONFIG_IIO_TRIGGER=y
CONFIG_IIO_CONSUMERS_PER_TRIGGER=2
# CONFIG_IIO_SW_DEVICE is not set
# CONFIG_IIO_SW_TRIGGER is not set
就会发现这当中,CONFIG_IIO_TRIGGERED_BUFFER
被设为 m
。这时如果把对应的模组安装回去:
$ sudo modprobe industrialio-triggered-buffer
就可以顺利载入模组了。
>>: DAY26-ASP.NET 加入RWD响应式网页 事前准备(先了解rwd)
壹、前言 将 python 程序封装,使用者将更便於使用,而不需担心设定 python 环境 mac...
痛点用在很多应用场景,在叙述使用者经验时,就是在替代用户在使用上不满,或是离开产品服务的关键要点。现...
0x1 前言 昨天把 Controller 跟 Route 建立好了,今天来针对回覆内容做更新,并简...
接下来的天数,预计要来实作上面已经教学的 sass 方法,毕竟学习是一回事,真正拿来实作则是另一回事...
Introduction 为了让资料更好的处理,这边要学到如何切割资料 import pandas ...