diff --git a/IMX6ULL/doc_pic/14_IIO/02.1_iio_buffer的核心要素与体验.tif b/IMX6ULL/doc_pic/14_IIO/02.1_iio_buffer的核心要素与体验.tif new file mode 100644 index 0000000..3b58666 Binary files /dev/null and b/IMX6ULL/doc_pic/14_IIO/02.1_iio_buffer的核心要素与体验.tif differ diff --git a/IMX6ULL/doc_pic/14_IIO/02_iio_buffer的使用.md b/IMX6ULL/doc_pic/14_IIO/02_iio_buffer的使用.md index f3b7638..a2cbb0e 100644 --- a/IMX6ULL/doc_pic/14_IIO/02_iio_buffer的使用.md +++ b/IMX6ULL/doc_pic/14_IIO/02_iio_buffer的使用.md @@ -9,9 +9,83 @@ IIO(Industrial I/O)参考资料: -## 1. 驱动框架 +## 1. iio_buffer的核心要素与体验 + +### 1.1 问题引入 + +以DHT11为例,读取`/sys/bus/iio/devices/iio:device2/in_temp_input`时,读一次得到一个数据。如果想得到连续的数据,就要使用iio_buffer: + +* 驱动程序内部读取多次数据,把数据存在iio_buffer里 +* APP读取/dev/iio:deviceX节点,可以一次读到多个数据 + +![image-20241107103530226](pic/image-20241107103530226.png) + + + +以DHT11为例,它有2个通道:温度、湿度,那么读取到的数据格式是怎样的?有如下4种组合: + +![image-20241107115503773](pic/image-20241107115503773.png) + + + +使用iio_buffer读数据时,APP要解决这些问题: + +* 只想读温度或湿度,或者想同时读取温度和湿度,怎么设置? +* 读到的数据里,怎么分辨哪些是温度、哪些是湿度? + +驱动程序把数据存入iio_buffer时,驱动要解决这些问题: + +* 怎么向APP表示数据的格式:比如温度占据多少bit数据、湿度占据多少bit数据 + + + +### 1.2 数据如何排列 + +在驱动程序里,对于每个通道,数据的格式如下描述: + +![image-20241106185855617](pic/image-20241106185855617.png) + + + +对于APP,可以如下操作看到这些: + +![image-20241106193719202](pic/image-20241106193719202.png) + + + +### 1.3 如何使能/禁止某个通道 + +![image-20241106192827693](pic/image-20241106192827693.png) + + + +### 1.4 如何使能/禁止iio_buffer + +![image-20241107110425775](pic/image-20241107110425775.png) + + + +### 1.5 如何读取buffer + +读取设备节点即可,比如: + +```shell +hexdump /dev/iio\:device2 +``` + +注意:hexdump读到16个字节的数据才会打印出来,如果看到打印缓慢,这是正常的。 + + + +### 1.6 上机体验 + +![image-20241107110327591](pic/image-20241107110327591.png) + + + +## 2. 使用iio_buffer的DHT11驱动 + -![image-20241025100410862](pic/image-20241025100410862.png) @@ -50,6 +124,24 @@ iio_channel_start_all_cb > iio_update_buffers > __iio_update_buffers > iio_enab +#### 1.1 iio_buffer细节 + +```shell +echo 1 > /sys/bus/iio/devices/iio:device2/buffer/enable +iio_buffer_store_enable + __iio_update_buffers + iio_buffer_request_update + iio_buffer_update_bytes_per_datum + buffer->access->set_bytes_per_datum(buffer, bytes); + iio_set_bytes_per_datum_kfifo + r->bytes_per_datum = bpd; + ret = buffer->access->request_update(buffer); +``` + + + + + ### 2. 从硬件中断到APP diff --git a/IMX6ULL/doc_pic/14_IIO/pic/image-20241106185139079.png b/IMX6ULL/doc_pic/14_IIO/pic/image-20241106185139079.png new file mode 100644 index 0000000..d1498bc Binary files /dev/null and b/IMX6ULL/doc_pic/14_IIO/pic/image-20241106185139079.png differ diff --git a/IMX6ULL/doc_pic/14_IIO/pic/image-20241106185251544.png b/IMX6ULL/doc_pic/14_IIO/pic/image-20241106185251544.png new file mode 100644 index 0000000..ca9af36 Binary files /dev/null and b/IMX6ULL/doc_pic/14_IIO/pic/image-20241106185251544.png differ diff --git a/IMX6ULL/doc_pic/14_IIO/pic/image-20241106185855617.png b/IMX6ULL/doc_pic/14_IIO/pic/image-20241106185855617.png new file mode 100644 index 0000000..32640a6 Binary files /dev/null and b/IMX6ULL/doc_pic/14_IIO/pic/image-20241106185855617.png differ diff --git a/IMX6ULL/doc_pic/14_IIO/pic/image-20241106192827693.png b/IMX6ULL/doc_pic/14_IIO/pic/image-20241106192827693.png new file mode 100644 index 0000000..f239168 Binary files /dev/null and b/IMX6ULL/doc_pic/14_IIO/pic/image-20241106192827693.png differ diff --git a/IMX6ULL/doc_pic/14_IIO/pic/image-20241106193719202.png b/IMX6ULL/doc_pic/14_IIO/pic/image-20241106193719202.png new file mode 100644 index 0000000..2b1536a Binary files /dev/null and b/IMX6ULL/doc_pic/14_IIO/pic/image-20241106193719202.png differ diff --git a/IMX6ULL/doc_pic/14_IIO/pic/image-20241106194714300.png b/IMX6ULL/doc_pic/14_IIO/pic/image-20241106194714300.png new file mode 100644 index 0000000..e216921 Binary files /dev/null and b/IMX6ULL/doc_pic/14_IIO/pic/image-20241106194714300.png differ diff --git a/IMX6ULL/doc_pic/14_IIO/pic/image-20241107103530226.png b/IMX6ULL/doc_pic/14_IIO/pic/image-20241107103530226.png new file mode 100644 index 0000000..36b328a Binary files /dev/null and b/IMX6ULL/doc_pic/14_IIO/pic/image-20241107103530226.png differ diff --git a/IMX6ULL/doc_pic/14_IIO/pic/image-20241107110327591.png b/IMX6ULL/doc_pic/14_IIO/pic/image-20241107110327591.png new file mode 100644 index 0000000..79474ba Binary files /dev/null and b/IMX6ULL/doc_pic/14_IIO/pic/image-20241107110327591.png differ diff --git a/IMX6ULL/doc_pic/14_IIO/pic/image-20241107110425775.png b/IMX6ULL/doc_pic/14_IIO/pic/image-20241107110425775.png new file mode 100644 index 0000000..fa2909b Binary files /dev/null and b/IMX6ULL/doc_pic/14_IIO/pic/image-20241107110425775.png differ diff --git a/IMX6ULL/doc_pic/14_IIO/pic/image-20241107115503773.png b/IMX6ULL/doc_pic/14_IIO/pic/image-20241107115503773.png new file mode 100644 index 0000000..d708811 Binary files /dev/null and b/IMX6ULL/doc_pic/14_IIO/pic/image-20241107115503773.png differ diff --git a/IMX6ULL/source/14_iio/01_dht11_iio_buffer_software/dht11.c b/IMX6ULL/source/14_iio/01_dht11_iio_buffer_software/dht11.c new file mode 100644 index 0000000..9f5621d --- /dev/null +++ b/IMX6ULL/source/14_iio/01_dht11_iio_buffer_software/dht11.c @@ -0,0 +1,522 @@ +/* + * DHT11/DHT22 bit banging GPIO driver + * + * Copyright (c) Harald Geyer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define DRIVER_NAME "dht11" + +#define DHT11_DATA_VALID_TIME 2000000000 /* 2s in ns */ + +#define DHT11_EDGES_PREAMBLE 2 +#define DHT11_BITS_PER_READ 40 +/* + * Note that when reading the sensor actually 84 edges are detected, but + * since the last edge is not significant, we only store 83: + */ +#define DHT11_EDGES_PER_READ (2 * DHT11_BITS_PER_READ + \ + DHT11_EDGES_PREAMBLE + 1) + +/* + * Data transmission timing: + * Data bits are encoded as pulse length (high time) on the data line. + * 0-bit: 22-30uS -- typically 26uS (AM2302) + * 1-bit: 68-75uS -- typically 70uS (AM2302) + * The acutal timings also depend on the properties of the cable, with + * longer cables typically making pulses shorter. + * + * Our decoding depends on the time resolution of the system: + * timeres > 34uS ... don't know what a 1-tick pulse is + * 34uS > timeres > 30uS ... no problem (30kHz and 32kHz clocks) + * 30uS > timeres > 23uS ... don't know what a 2-tick pulse is + * timeres < 23uS ... no problem + * + * Luckily clocks in the 33-44kHz range are quite uncommon, so we can + * support most systems if the threshold for decoding a pulse as 1-bit + * is chosen carefully. If somebody really wants to support clocks around + * 40kHz, where this driver is most unreliable, there are two options. + * a) select an implementation using busy loop polling on those systems + * b) use the checksum to do some probabilistic decoding + */ +#define DHT11_START_TRANSMISSION_MIN 18000 /* us */ +#define DHT11_START_TRANSMISSION_MAX 20000 /* us */ +#define DHT11_MIN_TIMERES 34000 /* ns */ +#define DHT11_THRESHOLD 49000 /* ns */ +#define DHT11_AMBIG_LOW 23000 /* ns */ +#define DHT11_AMBIG_HIGH 30000 /* ns */ + +struct dht11 { + struct device *dev; + + int gpio; + int irq; + + struct completion completion; + /* The iio sysfs interface doesn't prevent concurrent reads: */ + struct mutex lock; + + s64 timestamp; + int temperature; + int humidity; + + /* num_edges: -1 means "no transmission in progress" */ + int num_edges; + struct {s64 ts; int value; } edges[DHT11_EDGES_PER_READ]; + struct delayed_work work; + struct iio_dev *iio; +}; + +#ifdef CONFIG_DYNAMIC_DEBUG +/* + * dht11_edges_print: show the data as actually received by the + * driver. + */ +static void dht11_edges_print(struct dht11 *dht11) +{ + int i; + + dev_dbg(dht11->dev, "%d edges detected:\n", dht11->num_edges); + for (i = 1; i < dht11->num_edges; ++i) { + dev_dbg(dht11->dev, "%d: %lld ns %s\n", i, + dht11->edges[i].ts - dht11->edges[i - 1].ts, + dht11->edges[i - 1].value ? "high" : "low"); + } +} +#endif /* CONFIG_DYNAMIC_DEBUG */ + +static unsigned char dht11_decode_byte(char *bits) +{ + unsigned char ret = 0; + int i; + + for (i = 0; i < 8; ++i) { + ret <<= 1; + if (bits[i]) + ++ret; + } + + return ret; +} + +static int dht11_decode(struct dht11 *dht11, int offset) +{ + int i, t; + char bits[DHT11_BITS_PER_READ]; + unsigned char temp_int, temp_dec, hum_int, hum_dec, checksum; + + for (i = 0; i < DHT11_BITS_PER_READ; ++i) { + t = dht11->edges[offset + 2 * i + 2].ts - + dht11->edges[offset + 2 * i + 1].ts; + if (!dht11->edges[offset + 2 * i + 1].value) { + dev_dbg(dht11->dev, + "lost synchronisation at edge %d\n", + offset + 2 * i + 1); + return -EIO; + } + bits[i] = t > DHT11_THRESHOLD; + } + + hum_int = dht11_decode_byte(bits); + hum_dec = dht11_decode_byte(&bits[8]); + temp_int = dht11_decode_byte(&bits[16]); + temp_dec = dht11_decode_byte(&bits[24]); + checksum = dht11_decode_byte(&bits[32]); + + if (((hum_int + hum_dec + temp_int + temp_dec) & 0xff) != checksum) { + dev_dbg(dht11->dev, "invalid checksum\n"); + return -EIO; + } + + dht11->timestamp = ktime_get_boot_ns(); + if (hum_int < 20) { /* DHT22 */ + dht11->temperature = (((temp_int & 0x7f) << 8) + temp_dec) * + ((temp_int & 0x80) ? -100 : 100); + dht11->humidity = ((hum_int << 8) + hum_dec) * 100; + } else { + dht11->temperature = temp_int; + dht11->humidity = hum_int; + } + + return 0; +} + +/* + * IRQ handler called on GPIO edges + */ +static irqreturn_t dht11_handle_irq(int irq, void *data) +{ + struct iio_dev *iio = data; + struct dht11 *dht11 = iio_priv(iio); + + /* TODO: Consider making the handler safe for IRQ sharing */ + if (dht11->num_edges < DHT11_EDGES_PER_READ && dht11->num_edges >= 0) { + dht11->edges[dht11->num_edges].ts = ktime_get_boot_ns(); + dht11->edges[dht11->num_edges++].value = + gpio_get_value(dht11->gpio); + + if (dht11->num_edges >= DHT11_EDGES_PER_READ) + complete(&dht11->completion); + } + + return IRQ_HANDLED; +} + +static int dht11_read_raw(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long m) +{ + struct dht11 *dht11 = iio_priv(iio_dev); + int ret, timeres, offset; + + mutex_lock(&dht11->lock); + if (dht11->timestamp + DHT11_DATA_VALID_TIME < ktime_get_boot_ns()) { + timeres = ktime_get_resolution_ns(); + dev_dbg(dht11->dev, "current timeresolution: %dns\n", timeres); + if (timeres > DHT11_MIN_TIMERES) { + dev_err(dht11->dev, "timeresolution %dns too low\n", + timeres); + /* In theory a better clock could become available + * at some point ... and there is no error code + * that really fits better. + */ + ret = -EAGAIN; + goto err; + } + if (timeres > DHT11_AMBIG_LOW && timeres < DHT11_AMBIG_HIGH) + dev_warn(dht11->dev, + "timeresolution: %dns - decoding ambiguous\n", + timeres); + + reinit_completion(&dht11->completion); + + dht11->num_edges = 0; + ret = gpio_direction_output(dht11->gpio, 0); + if (ret) + goto err; + usleep_range(DHT11_START_TRANSMISSION_MIN, + DHT11_START_TRANSMISSION_MAX); + ret = gpio_direction_input(dht11->gpio); + if (ret) + goto err; + + ret = request_irq(dht11->irq, dht11_handle_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + iio_dev->name, iio_dev); + if (ret) + goto err; + + ret = wait_for_completion_killable_timeout(&dht11->completion, + HZ); + + free_irq(dht11->irq, iio_dev); + +#ifdef CONFIG_DYNAMIC_DEBUG + dht11_edges_print(dht11); +#endif + + if (ret == 0 && dht11->num_edges < DHT11_EDGES_PER_READ - 1) { + dev_err(dht11->dev, "Only %d signal edges detected\n", + dht11->num_edges); + ret = -ETIMEDOUT; + } + if (ret < 0) + goto err; + + offset = DHT11_EDGES_PREAMBLE + + dht11->num_edges - DHT11_EDGES_PER_READ; + for (; offset >= 0; --offset) { + ret = dht11_decode(dht11, offset); + if (!ret) + break; + } + + if (ret) + goto err; + } + + ret = IIO_VAL_INT; + if (chan->type == IIO_TEMP) + *val = dht11->temperature; + else if (chan->type == IIO_HUMIDITYRELATIVE) + *val = dht11->humidity; + else + ret = -EINVAL; +err: + dht11->num_edges = -1; + mutex_unlock(&dht11->lock); + return ret; +} + +static const struct iio_info dht11_iio_info = { + .driver_module = THIS_MODULE, + .read_raw = dht11_read_raw, +}; + +static const struct iio_chan_spec dht11_chan_spec[] = { + { .type = IIO_TEMP, + .scan_index = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_type = { + .sign = 's', + .realbits = 8, + .storagebits = 8, + }, + }, + { .type = IIO_HUMIDITYRELATIVE, + .scan_index = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_type = { + .sign = 's', + .realbits = 8, + .storagebits = 8, + }, + } +}; + +static const unsigned long dht11_scan_masks[] = {0x3, 0}; + +static const struct of_device_id dht11_dt_ids[] = { + { .compatible = "dht11", }, + { } +}; +MODULE_DEVICE_TABLE(of, dht11_dt_ids); + +static int dht11_iio_buffer_preenable(struct iio_dev *indio_dev) +{ + return 0; +} + +static int dht11_iio_buffer_postenable(struct iio_dev *indio_dev) +{ + struct dht11 *dht11 = iio_priv(indio_dev); + + schedule_delayed_work(&dht11->work, HZ*5); + + return 0; +} + +static int dht11_iio_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct dht11 *dht11 = iio_priv(indio_dev); + + cancel_delayed_work_sync(&dht11->work); + + return 0; +} + +static const struct iio_buffer_setup_ops dht11_iio_buffer_setup_ops = { + .preenable = dht11_iio_buffer_preenable, + .postenable = dht11_iio_buffer_postenable, + .postdisable = dht11_iio_buffer_postdisable, +}; + +static int dht11_iio_buffer_init(struct iio_dev *indio_dev) +{ + struct iio_buffer *buffer; + + buffer = iio_kfifo_allocate(); + if (!buffer) + return -ENOMEM; + + iio_device_attach_buffer(indio_dev, buffer); + + /* Ring buffer functions - here trigger setup related */ + indio_dev->setup_ops = &dht11_iio_buffer_setup_ops; + + return 0; +} + +static void dht11_work(struct work_struct *work) +{ + struct dht11 *dht11 = container_of(work, struct dht11, work.work); + struct iio_dev *iio_dev = dht11->iio; + + int ret, timeres, offset; + uint8_t vals[2]; + + mutex_lock(&dht11->lock); + if (dht11->timestamp + DHT11_DATA_VALID_TIME < ktime_get_boot_ns()) { + timeres = ktime_get_resolution_ns(); + dev_dbg(dht11->dev, "current timeresolution: %dns\n", timeres); + if (timeres > DHT11_MIN_TIMERES) { + dev_err(dht11->dev, "timeresolution %dns too low\n", + timeres); + /* In theory a better clock could become available + * at some point ... and there is no error code + * that really fits better. + */ + ret = -EAGAIN; + goto err; + } + if (timeres > DHT11_AMBIG_LOW && timeres < DHT11_AMBIG_HIGH) + dev_warn(dht11->dev, + "timeresolution: %dns - decoding ambiguous\n", + timeres); + + reinit_completion(&dht11->completion); + + dht11->num_edges = 0; + ret = gpio_direction_output(dht11->gpio, 0); + if (ret) { + goto err; + } + usleep_range(DHT11_START_TRANSMISSION_MIN, + DHT11_START_TRANSMISSION_MAX); + ret = gpio_direction_input(dht11->gpio); + if (ret) { + goto err; + } + + ret = request_irq(dht11->irq, dht11_handle_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + iio_dev->name, iio_dev); + if (ret) { + goto err; + } + + ret = wait_for_completion_killable_timeout(&dht11->completion, + HZ); + + free_irq(dht11->irq, iio_dev); + +#ifdef CONFIG_DYNAMIC_DEBUG + dht11_edges_print(dht11); +#endif + + if (ret == 0 && dht11->num_edges < DHT11_EDGES_PER_READ - 1) { + dev_err(dht11->dev, "Only %d signal edges detected\n", + dht11->num_edges); + ret = -ETIMEDOUT; + } + if (ret < 0) { + goto err; + } + + offset = DHT11_EDGES_PREAMBLE + + dht11->num_edges - DHT11_EDGES_PER_READ; + for (; offset >= 0; --offset) { + ret = dht11_decode(dht11, offset); + if (!ret) + break; + } + + if (ret) { + goto err; + } + } + + vals[0] = dht11->temperature; + vals[1] = dht11->humidity; + iio_push_to_buffers(iio_dev, vals); +err: + dht11->num_edges = -1; + mutex_unlock(&dht11->lock); + + if (!ret) + schedule_delayed_work(&dht11->work, HZ); + else + schedule_work(&dht11->work.work); +} + +static int dht11_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct dht11 *dht11; + struct iio_dev *iio; + int ret; + + iio = devm_iio_device_alloc(dev, sizeof(*dht11)); + if (!iio) { + dev_err(dev, "Failed to allocate IIO device\n"); + return -ENOMEM; + } + + dht11 = iio_priv(iio); + dht11->dev = dev; + dht11->iio = iio; + + INIT_DELAYED_WORK(&dht11->work, dht11_work); + + ret = of_get_gpio(node, 0); + if (ret < 0) + return ret; + dht11->gpio = ret; + ret = devm_gpio_request_one(dev, dht11->gpio, GPIOF_IN, pdev->name); + if (ret) + return ret; + + dht11->irq = gpio_to_irq(dht11->gpio); + if (dht11->irq < 0) { + dev_err(dev, "GPIO %d has no interrupt\n", dht11->gpio); + return -EINVAL; + } + + dht11->timestamp = ktime_get_boot_ns() - DHT11_DATA_VALID_TIME - 1; + dht11->num_edges = -1; + + platform_set_drvdata(pdev, iio); + + init_completion(&dht11->completion); + mutex_init(&dht11->lock); + iio->name = pdev->name; + iio->dev.parent = &pdev->dev; + iio->info = &dht11_iio_info; + iio->modes = (INDIO_BUFFER_SOFTWARE | INDIO_DIRECT_MODE); + iio->channels = dht11_chan_spec; + iio->num_channels = ARRAY_SIZE(dht11_chan_spec); + iio->available_scan_masks = dht11_scan_masks; + + dht11_iio_buffer_init(iio); + + return devm_iio_device_register(dev, iio); +} + +static struct platform_driver dht11_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = dht11_dt_ids, + }, + .probe = dht11_probe, +}; + +module_platform_driver(dht11_driver); + +MODULE_AUTHOR("Harald Geyer "); +MODULE_DESCRIPTION("DHT11 humidity/temperature sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/IMX6ULL/source/14_iio/01_dht11_iio_buffer_software/dht11.dts b/IMX6ULL/source/14_iio/01_dht11_iio_buffer_software/dht11.dts new file mode 100644 index 0000000..a362841 --- /dev/null +++ b/IMX6ULL/source/14_iio/01_dht11_iio_buffer_software/dht11.dts @@ -0,0 +1,4 @@ +humidity_sensor { + compatible = "dht11"; + gpios = <&gpio4 19 GPIO_ACTIVE_HIGH>; +}; \ No newline at end of file diff --git a/README.md b/README.md index 81c0778..f34ea8c 100644 --- a/README.md +++ b/README.md @@ -901,6 +901,12 @@ git clone https://e.coding.net/weidongshan/linux/doc_and_source_for_drivers.git 01.5_通道的sysfs信息修改与体验 ``` +* 2024.11.07 发布IIO"子系统" + + ```shell + 02.1_iio_buffer的核心要素与体验 + ``` + ## 6. 联系方式