2025-03-11
SPI是嵌入式开发中常用的一个通信协议,下面的例子给大家演示如何使用IIO子系统来和SPI设备进行通信,并获取设备的信息。
raspberry pi3b
Linux raspberrypi 6.1.21-v8+ #1642 SMP PREEMPT Mon Apr 3 17:24:16 BST 2023 aarch64 GNU/Linux
// testoverlay.dts
/dts-v1/;
/plugin/;
/ {
compatible = "raspberrypi,3-model-bbrcm,bcm2837";
// 禁用默认的spi0设备设置
fragment@0 {
target = <&spidev0>;
__overlay__ {
status = "disabled";
};
};
fragment@1 {
target = <&spi0>;
__overlay__ {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
my_adc: my_adc@0 {
compatible = "brightlight,myadc";
reg = <0x0>;
spi-max-frequency = <4000>;
spi-bits-per-word = <8>;
status = "okay";
};
};
};
};
// simple.c
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#define CMD_GET_STATE 0x11
#define CMD_SET_STATE 0x22
#define CMD_GET_ADC_VAL 0x55
struct my_adc {
struct spi_device *client;
};
static struct of_device_id my_driver_ids[] = {
{
.compatible = "brightlight,myadc",
},
{},
};
MODULE_DEVICE_TABLE(of, my_driver_ids);
static struct spi_device_id my_adc[] = {
{"my_adc", 0},
{},
};
MODULE_DEVICE_TABLE(spi, my_adc);
static int my_adc_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val,
int *val2, long mask) {
int ret = 0;
struct my_adc *adc = iio_priv(indio_dev);
if (mask == IIO_CHAN_INFO_RAW) {
ret = spi_w8r8(adc->client, CMD_GET_ADC_VAL);
if (ret < 0) {
pr_err("spi_w8r8 adc error\n");
return ret;
}
*val = ret;
} else {
return -EINVAL;
}
return IIO_VAL_INT;
}
static const struct iio_chan_spec my_adc_channels[] = {
{
.type = IIO_VOLTAGE,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
},
};
static const struct iio_info my_adc_info = {
.read_raw = my_adc_read_raw,
};
static int _dt_probe(struct spi_device *client) {
struct iio_dev *indio_dev = NULL;
struct my_adc *adc = NULL;
unsigned char buf[2] = {CMD_SET_STATE, 0x1};
int ret = 0;
pr_info("simple_dt_probe\n");
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(struct iio_dev));
if (!indio_dev) {
pr_err("devm_iio_device_alloc error");
return -ENOMEM;
}
adc = iio_priv(indio_dev);
adc->client = client;
indio_dev->name = "myadc";
indio_dev->info = &my_adc_info;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = my_adc_channels;
indio_dev->num_channels = ARRAY_SIZE(my_adc_channels);
ret = spi_setup(client);
if (ret < 0) {
pr_err("spi_setup error");
return ret;
}
ret = spi_write(adc->client, buf, 2);
if (ret < 0) {
pr_err("spi_write error");
return ret;
}
spi_set_drvdata(client, indio_dev);
ret = devm_iio_device_register(&client->dev, indio_dev);
if (ret < 0) {
pr_err("devm_iio_device_register error");
return ret;
}
return 0;
}
static void _dt_remove(struct spi_device *client) {
struct iio_dev *indio_dev = NULL;
struct my_adc *adc = NULL;
unsigned char buf[2] = {CMD_SET_STATE, 0x0};
pr_info("simple_dt_remove\n");
indio_dev = spi_get_drvdata(client);
adc = iio_priv(indio_dev);
spi_write(adc->client, buf, 2);
}
static struct spi_driver my_dirver = {
.probe = _dt_probe,
.remove = _dt_remove,
.id_table = my_adc,
.driver =
{
.name = "my_adc",
.of_match_table = my_driver_ids,
},
};
// 这里会调用init和exit函数
module_spi_driver(my_dirver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("heng30");
MODULE_VERSION("v0.0.1");
MODULE_DESCRIPTION("A simple kernel module");
#!/bin/sh
top-dir = $(shell pwd)
kernel-version = $(shell uname -r)
kernel-dir ?= /lib/modules/$(kernel-version)/build
obj-m += simple.o
all: dt driver
driver:
make -C $(kernel-dir) modules M=$(top-dir)
dt:
dtc -@ -I dts -O dtb -o testoverlay.dtbo testoverlay.dts
clean:
rm -f *.o *.ko *.mod *.mod.c *.order *.symvers *.dtbo
make -C $(kernel-dir) clean m=$(top-dir)
编译程序:make
安装设备树节点:sudo dtoverlay testoverlay.dtbo
查看设备文件:ls /dev/spidev0.1
查看设备树节点信息:ls /proc/device-tree/soc/spi@*
如果没有/sys/bus/iio
目录,执行sudo modprobe industrialio
安装驱动
安装驱动:insmod simple.ko
查看iio
节点信息:ls /sys/bus/iio/devices/
读取数据:cat /sys/bus/iio/devices/iio:device0/in_voltage_raw
移除驱动:rmmod simple.ko
移除设备树节点:sudo dtoverlay -r testoverlay