Heng30的博客
搜索 分类 关于 订阅

Linux驱动使用SPI协议获取设备信息

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");

Makefile编译脚本

#!/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