Day 26:IIO (Part 4) - 帮感应器写驱动程序!以 TCRC5000 为例

这篇将会综合前面的 GPIO 与 IIO 的知识,帮一个常见的红外线感测器 -- TCRC5000 实作 Linux 上的 IIO 驱动程序。

简介:TCRC5000

这是一个红外线感应模组。以红外线作为距离感应,当距离小於阈值时,输出高电位; 反之则输出低电位。而这个阈值大小可以透过调整其上的可变电阻更动。更多叙述可以参考 OSOYOO 公司底下的 Product description 一节。

硬体配置

VCC 接 Raspberry Pi 的 5V,GND 接 Raspberry Pi 的任意 GND; OUTGPIO17。如图所示:

装置树

这个 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";
            };
        };
    };
};

Step 1:资料结构

为了方便,把这个资料结构中需要的硬体资源相关的资料结构,包成一个结构体:

struct tcrc5000 {
    struct gpio_desc *gpiod;
    struct mutex mutex;
    struct device *dev;
};

这边的 gpiod 就是读取时对应的 GPIO 所对应的 GPIO Descriptor。除此之外,还配置一个 mutex 来保护。避免 IIO 的 sysfs 介面有不同的执行单元同时读取。

Step 2:申请 iio_dev 与 GPIO

首先在 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);
    ...
}

Step 3:给定 iio_chan_spec

因为感应器现在只有一道输出 (就是那个 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)
};

Step 4:读取资料

这边就是实作 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;
}

Step 5:填写 iio_info 与 iio_dev

实作完之後,填写对应的 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);

附注:安装模组时出现 Unknown symbol

在尝试 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 生成 Windows 执行档教学 (Pyinstaller, PowerShell)

壹、前言 将 python 程序封装,使用者将更便於使用,而不需担心设定 python 环境 mac...

什麽是痛点?

痛点用在很多应用场景,在叙述使用者经验时,就是在替代用户在使用上不满,或是离开产品服务的关键要点。现...

Day 0x14 - 订单查询 (Part2 : View)

0x1 前言 昨天把 Controller 跟 Route 建立好了,今天来针对回覆内容做更新,并简...

DAY 13 接下来的实作

接下来的天数,预计要来实作上面已经教学的 sass 方法,毕竟学习是一回事,真正拿来实作则是另一回事...

Day 14 [Python ML、Pandas] 引索、选择和给值

Introduction 为了让资料更好的处理,这边要学到如何切割资料 import pandas ...