嵌入式Linux驱动开发:ICM20608六轴传感器SPI驱动

嵌入式Linux驱动开发:ICM20608六轴传感器SPI驱动

1. 项目概述

本笔记详细记录了基于i.MX6ULL开发板的ICM20608六轴传感器(3轴陀螺仪+3轴加速度计)的SPI驱动开发全过程。项目包含完整的Linux内核模块驱动程序和用户空间测试应用程序,实现了对传感器数据的采集、处理和输出。驱动采用设备树方式配置硬件资源,符合现代Linux驱动开发规范。

2. 硬件平台介绍

2.1 i.MX6ULL处理器

NXP i.MX6ULL是一款基于ARM Cortex-A7架构的低功耗应用处理器,主要特性包括:

  • 单核ARM Cortex-A7,主频可达900MHz
  • 集成多种外设接口:SPI、I2C、UART、USB、Ethernet等
  • 支持Linux、Android等操作系统
  • 广泛应用于工业控制、智能家居、物联网等领域

2.2 ICM20608传感器

ICM20608是InvenSense公司生产的六轴运动传感器,集成了3轴陀螺仪和3轴加速度计,主要特性:

  • 陀螺仪量程:±250, ±500, ±1000, ±2000 °/s
  • 加速度计量程:±2g, ±4g, ±8g, ±16g
  • 内置温度传感器
  • 支持SPI和I2C两种通信接口
  • 内置11位ADC,数据输出速率可达8kHz
  • 工作电压:1.71V-3.6V

3. 软件架构设计

本驱动采用分层架构设计,主要包括以下几个层次:

复制代码
+---------------------+
|  用户空间应用程序   |
+---------------------+
|  字符设备驱动层     |
+---------------------+
|  SPI核心层          |
+---------------------+
|  SPI控制器驱动      |
+---------------------+
|  硬件物理层         |
+---------------------+

4. 设备树配置分析

4.1 设备树基础概念

设备树(Device Tree)是一种描述硬件配置的数据结构,它将硬件信息从内核代码中分离出来,使得内核可以支持多种硬件平台而无需重新编译。设备树文件以.dts为扩展名,编译后生成.dtb文件供内核使用。

4.2 ICM20608设备节点配置

imx6ull-alientek-emmc.dts文件中,ICM20608的设备树配置如下:

dts 复制代码
&ecspi3 {
    fsl,spi-num-chipselects = <1>;
    cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_ecspi3>;
    status = "okay";

    spidev0: icm20608@0 {
        reg = <0>;
        compatible = "alientek,icm20608";
        spi-max-frequency = <8000000>;
    };
};
4.2.1 节点解析
  • &ecspi3:引用SPI3控制器节点
  • fsl,spi-num-chipselects = <1>:指定SPI控制器支持1个片选信号
  • cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>:配置片选GPIO,使用GPIO1_20,低电平有效
  • pinctrl-0 = <&pinctrl_ecspi3>:引用引脚控制配置
  • status = "okay":启用该设备
  • icm20608@0:子节点,表示连接在SPI总线上的ICM20608设备
    • reg = <0>:设备地址,SPI设备使用片选索引
    • compatible = "alientek,icm20608":兼容性字符串,用于匹配驱动程序
    • spi-max-frequency = <8000000>:SPI通信最大频率8MHz

4.3 引脚复用配置

&iomuxc节点中,SPI3的引脚复用配置如下:

dts 复制代码
pinctrl_ecspi3: ecspi3grp {
    fsl,pins = <
        MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20        0x10b0
        MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK    0x10b1
        MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI        0x10b1
        MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO        0x10b1
    >;
};
  • GPIO1_IO20:配置为SPI_CS片选信号
  • UART2_RX_DATA:复用为ECSPI3_SCLK(时钟信号)
  • UART2_CTS_B:复用为ECSPI3_MOSI(主出从入)
  • UART2_RTS_B:复用为ECSPI3_MISO(主入从出)

5. 驱动程序详解

5.1 头文件包含

c 复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/string.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/input.h>
#include <linux/spi/spi.h>
#include <linux/delay.h>
5.1.1 关键头文件说明
  • <linux/module.h>:模块相关定义,如module_init、module_exit等
  • <linux/kernel.h>:内核常用宏和函数
  • <linux/fs.h>:文件系统相关定义,如file_operations结构体
  • <linux/cdev.h>:字符设备相关定义
  • <linux/device.h>:设备模型相关定义
  • <linux/of.h>:设备树相关API
  • <linux/spi/spi.h>:SPI子系统核心头文件
  • <linux/delay.h>:延时函数

5.2 寄存器定义

icm20608reg.h头文件中定义了ICM20608的所有寄存器地址:

c 复制代码
#define ICM20608G_ID 0XAF /* ID值 */
#define ICM20608D_ID 0XAE /* ID值 */

#define ICM20_SELF_TEST_X_GYRO 0x00
#define ICM20_SELF_TEST_Y_GYRO 0x01
#define ICM20_SELF_TEST_Z_GYRO 0x02
#define ICM20_SELF_TEST_X_ACCEL 0x0D
#define ICM20_SELF_TEST_Y_ACCEL 0x0E
#define ICM20_SELF_TEST_Z_ACCEL 0x0F

#define ICM20_XG_OFFS_USRH 0x13
#define ICM20_XG_OFFS_USRL 0x14
#define ICM20_YG_OFFS_USRH 0x15
#define ICM20_YG_OFFS_USRL 0x16
#define ICM20_ZG_OFFS_USRH 0x17
#define ICM20_ZG_OFFS_USRL 0x18

#define ICM20_SMPLRT_DIV 0x19
#define ICM20_CONFIG 0x1A
#define ICM20_GYRO_CONFIG 0x1B
#define ICM20_ACCEL_CONFIG 0x1C
#define ICM20_ACCEL_CONFIG2 0x1D
#define ICM20_LP_MODE_CFG 0x1E
#define ICM20_ACCEL_WOM_THR 0x1F
#define ICM20_FIFO_EN 0x23
#define ICM20_FSYNC_INT 0x36
#define ICM20_INT_PIN_CFG 0x37
#define ICM20_INT_ENABLE 0x38
#define ICM20_INT_STATUS 0x3A

#define ICM20_ACCEL_XOUT_H 0x3B
#define ICM20_ACCEL_XOUT_L 0x3C
#define ICM20_ACCEL_YOUT_H 0x3D
#define ICM20_ACCEL_YOUT_L 0x3E
#define ICM20_ACCEL_ZOUT_H 0x3F
#define ICM20_ACCEL_ZOUT_L 0x40

#define ICM20_TEMP_OUT_H 0x41
#define ICM20_TEMP_OUT_L 0x42

#define ICM20_GYRO_XOUT_H 0x43
#define ICM20_GYRO_XOUT_L 0x44
#define ICM20_GYRO_YOUT_H 0x45
#define ICM20_GYRO_YOUT_L 0x46
#define ICM20_GYRO_ZOUT_H 0x47
#define ICM20_GYRO_ZOUT_L 0x48

#define ICM20_SIGNAL_PATH_RESET 0x68
#define ICM20_ACCEL_INTEL_CTRL 0x69
#define ICM20_USER_CTRL 0x6A
#define ICM20_PWR_MGMT_1 0x6B
#define ICM20_PWR_MGMT_2 0x6C
#define ICM20_FIFO_COUNTH 0x72
#define ICM20_FIFO_COUNTL 0x73
#define ICM20_FIFO_R_W 0x74
#define ICM20_WHO_AM_I 0x75

#define ICM20_XA_OFFSET_H 0x77
#define ICM20_XA_OFFSET_L 0x78
#define ICM20_YA_OFFSET_H 0x7A
#define ICM20_YA_OFFSET_L 0x7B
#define ICM20_ZA_OFFSET_H 0x7D
#define ICM20_ZA_OFFSET_L 0x7E

5.3 驱动数据结构

c 复制代码
struct icm20608_dev
{
    dev_t devid;            /* 设备号 	 */
    struct cdev cdev;       /* cdev 	*/
    struct class *class;    /* 类 		*/
    struct device *device;  /* 设备 	 */
    struct device_node *nd; /* 设备节点 */
    int major;              /* 主设备号 */
    int minor;
    void *private_data; /* 私有数据 */
    int cs_gpio;
    signed int gyro_x_adc;  /* 陀螺仪X轴原始值 	 */
    signed int gyro_y_adc;  /* 陀螺仪Y轴原始值		*/
    signed int gyro_z_adc;  /* 陀螺仪Z轴原始值 		*/
    signed int accel_x_adc; /* 加速度计X轴原始值 	*/
    signed int accel_y_adc; /* 加速度计Y轴原始值	*/
    signed int accel_z_adc; /* 加速度计Z轴原始值 	*/
    signed int temp_adc;    /* 温度原始值 			*/
};
static struct icm20608_dev icm20608dev;
5.3.1 结构体成员说明
  • devid:设备号,由主设备号和次设备号组成
  • cdev:字符设备结构体,用于注册字符设备
  • class:设备类,用于在/sys/class/下创建设备类
  • device:设备结构体,用于在/dev/下创建设备节点
  • nd:设备节点指针,用于设备树相关操作
  • majorminor:主设备号和次设备号
  • private_data:私有数据,这里用于存储spi_device指针
  • cs_gpio:片选GPIO编号
  • 各种ADC原始值:存储从传感器读取的原始数据

5.4 SPI读写函数

5.4.1 多字节读取函数
c 复制代码
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{
    int ret = 0;
    struct spi_device *spi = dev->private_data;
    u8 data = reg | 0x80;
    spi_write_then_read(spi, &data, 1, buf, 1);
    return ret;
}

该函数使用spi_write_then_read()函数实现SPI读操作:

  1. 将寄存器地址最高位置1(0x80)表示读操作
  2. 调用spi_write_then_read()先发送寄存器地址,然后读取数据
  3. spi_write_then_read()是SPI核心层提供的便捷函数,自动处理片选信号
5.4.2 多字节写入函数
c 复制代码
static int icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, int len)
{
    int ret = 0;
    u8 data[len + 1];
    struct spi_device *spi = dev->private_data;
    data[0] = reg & 0x7f;
    memcpy(&data[1], buf, len);
    ret = spi_write(spi, data, len + 1);
    return ret;
}

该函数实现SPI写操作:

  1. 将寄存器地址最高位清0(0x7f)表示写操作
  2. 将要写入的数据复制到缓冲区
  3. 调用spi_write()函数发送数据
5.4.3 单字节读写函数
c 复制代码
static u8 icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{
    u8 buf = 0;
    icm20608_read_regs(dev, reg, &buf, 1);
    return buf;
}

static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{
    u8 data = value;
    icm20608_write_regs(dev, reg, &data, 1);
}

单字节读写函数基于多字节读写函数实现,用于读写单个寄存器。

5.5 数据读取函数

c 复制代码
void icm20608_readdata(struct icm20608_dev *dev)
{
    unsigned char data[14] = {0};
    icm20608_read_regs(dev, ICM20_ACCEL_XOUT_H, data, 14);

    dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]);
    dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]);
    dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]);
    dev->temp_adc = (signed short)((data[6] << 8) | data[7]);
    dev->gyro_x_adc = (signed short)((data[8] << 8) | data[9]);
    dev->gyro_y_adc = (signed short)((data[10] << 8) | data[11]);
    dev->gyro_z_adc = (signed short)((data[12] << 8) | data[13]);
}

该函数读取14个连续的寄存器数据:

  • ICM20_ACCEL_XOUT_H(0x3B)开始,连续读取14个字节
  • 数据格式:AXH, AXL, AYH, AYL, AZH, AZL, TH, TL, GXH, GXL, GYH, GYL, GZH, GZL
  • 将高低字节组合成16位有符号整数

5.6 字符设备操作函数

5.6.1 open函数
c 复制代码
static int icm20608_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &icm20608dev;
    return 0;
}

open函数将设备结构体指针保存到file->private_data中,供其他函数使用。

5.6.2 read函数
c 复制代码
static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
    signed int data[7];
    long err = 0;
    struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data;

    icm20608_readdata(dev);
    data[0] = dev->gyro_x_adc;
    data[1] = dev->gyro_y_adc;
    data[2] = dev->gyro_z_adc;
    data[3] = dev->accel_x_adc;
    data[4] = dev->accel_y_adc;
    data[5] = dev->accel_z_adc;
    data[6] = dev->temp_adc;
    err = copy_to_user(buf, data, sizeof(data));
    return 0;
}

read函数执行以下操作:

  1. 调用icm20608_readdata()读取传感器数据
  2. 将数据组织成数组
  3. 使用copy_to_user()将数据复制到用户空间
5.6.3 release函数
c 复制代码
static int icm20608_release(struct inode *inode, struct file *filp)
{
    return 0;
}

release函数为空实现,因为不需要特殊处理。

5.6.4 file_operations结构体
c 复制代码
static const struct file_operations icm20608_ops = {
    .owner = THIS_MODULE,
    .open = icm20608_open,
    .read = icm20608_read,
    .release = icm20608_release,
};

定义了字符设备的操作函数集合。

5.7 寄存器初始化函数

c 复制代码
void icm20608_reginit(struct icm20608_dev *dev)
{
    u8 value = 0;
    icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x80);
    mdelay(50);
    icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x01);
    mdelay(50);

    value = icm20608_read_onereg(dev, ICM20_WHO_AM_I);
    printk("Driver: icm20608 ID = %#X\r\n", value);

    value = icm20608_read_onereg(dev, ICM20_PWR_MGMT_1);
    printk("Driver: ICM20_PWR_MGMT_1 = %#X\r\n", value);

    icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00);    /* 输出速率是内部采样率					*/
    icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18);   /* 陀螺仪±2000dps量程 				*/
    icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18);  /* 加速度计±16G量程 					*/
    icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04);        /* 陀螺仪低通滤波BW=20Hz 				*/
    icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04); /* 加速度计低通滤波BW=21.2Hz 			*/
    icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_2, 0x00);    /* 打开加速度计和陀螺仪所有轴 				*/
    icm20608_write_onereg(&icm20608dev, ICM20_LP_MODE_CFG, 0x00);   /* 关闭低功耗 						*/
    icm20608_write_onereg(&icm20608dev, ICM20_FIFO_EN, 0x00);       /* 关闭FIFO						*/
}

该函数对ICM20608进行初始化配置:

  1. 先写0x80到ICM20_PWR_MGMT_1进行复位
  2. 延时50ms后写0x01启动设备
  3. 读取WHO_AM_I寄存器验证设备ID
  4. 配置采样率分频器为0
  5. 设置陀螺仪量程为±2000°/s(0x18)
  6. 设置加速度计量程为±16g(0x18)
  7. 设置陀螺仪低通滤波器带宽为20Hz
  8. 设置加速度计低通滤波器带宽为21.2Hz
  9. 启用所有轴的加速度计和陀螺仪
  10. 关闭低功耗模式
  11. 关闭FIFO功能

5.8 probe函数

c 复制代码
static int icm20608_probe(struct spi_device *spi)
{
    int ret = 0;
    printk("Driver: icm20608_probe\r\n");
    /* 1、构建设备号 */
    if (icm20608dev.major)
    {
        icm20608dev.devid = MKDEV(icm20608dev.major, 0);
        register_chrdev_region(icm20608dev.devid, icm20608_CNT, icm20608_NAME);
    }
    else
    {
        alloc_chrdev_region(&icm20608dev.devid, 0, icm20608_CNT, icm20608_NAME);
        icm20608dev.major = MAJOR(icm20608dev.devid);
        icm20608dev.minor = MINOR(icm20608dev.devid);
    }
    printk("Driver: Major: %d, Minor: %d\r\n", icm20608dev.major, icm20608dev.minor);

    /* 2、注册设备 */
    cdev_init(&icm20608dev.cdev, &icm20608_ops);
    ret = cdev_add(&icm20608dev.cdev, icm20608dev.devid, icm20608_CNT);
    if (ret)
    {
        printk("Driver: cdev_add\r\n");
    }

    /* 3、创建类 */
    icm20608dev.class = class_create(THIS_MODULE, icm20608_NAME);
    if (IS_ERR(icm20608dev.class))
    {
        printk("Driver: icm20608dev.class\r\n");
        return PTR_ERR(icm20608dev.class);
    }

    /* 4、创建设备 */
    icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, icm20608_NAME);
    if (IS_ERR(icm20608dev.device))
    {
        printk("Driver: icm20608dev.device\r\n");
        return PTR_ERR(icm20608dev.device);
    }

    spi->mode = SPI_MODE_0;
    spi_setup(spi);

    icm20608dev.private_data = spi;

    icm20608_reginit(&icm20608dev);
    return 0;
}

probe函数在设备匹配成功后被调用,执行以下初始化操作:

  1. 分配设备号
  2. 注册字符设备
  3. 创建设备类
  4. 创建设备节点
  5. 设置SPI模式为MODE_0
  6. 调用spi_setup()应用SPI设置
  7. 保存spi_device指针到私有数据
  8. 调用寄存器初始化函数

5.9 remove函数

c 复制代码
static int icm20608_remove(struct spi_device *spi)
{
    gpio_free(icm20608dev.cs_gpio);
    cdev_del(&icm20608dev.cdev);
    unregister_chrdev_region(icm20608dev.devid, icm20608_CNT);
    device_destroy(icm20608dev.class, icm20608dev.devid);
    class_destroy(icm20608dev.class);
    return 0;
}

remove函数在设备移除时被调用,执行资源清理:

  1. 释放GPIO
  2. 删除字符设备
  3. 释放设备号
  4. 销毁设备节点
  5. 销毁设备类

5.10 设备匹配表

c 复制代码
static struct of_device_id icm20608_id[] = {
    {
        .compatible = "alientek,icm20608",
    },
    {/* sentinel */}
};

static struct spi_device_id icm20608_spi_id[] = {
    {"alientek,icm20608", 0},
    {}};

定义了设备匹配表,内核通过compatible字符串匹配设备树节点和驱动程序。

5.11 SPI驱动结构体

c 复制代码
struct spi_driver icm20608_driver = {
    .probe = icm20608_probe,
    .remove = icm20608_remove,
    .driver = {
        .name = "icm20608",
        .owner = THIS_MODULE,
        .of_match_table = of_match_ptr(icm20608_id),
    },
    .id_table = icm20608_spi_id,
};

定义了SPI驱动结构体,包含了probe、remove函数指针和驱动信息。

5.12 模块初始化和退出函数

c 复制代码
static int __init icm20608_init(void)
{
    int ret = 0;
    ret = spi_register_driver(&icm20608_driver);
    if (ret != 0)
    {
        ret = -EINVAL;
        printk("Kernel: fail spi_register_driver\r\n");
    }
    return 0;
}

static void __exit icm20608_exit(void)
{
    spi_unregister_driver(&icm20608_driver);
}

module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("alientek");
  • module_init():指定模块初始化函数
  • module_exit():指定模块退出函数
  • MODULE_LICENSE():声明模块许可证
  • MODULE_AUTHOR():声明模块作者

6. SPI子系统架构分析

6.1 SPI子系统层次结构

Linux SPI子系统采用分层架构设计:

复制代码
+---------------------+
|  用户空间应用程序   |
+---------------------+
|  SPI设备驱动        |
+---------------------+
|  SPI核心层          |
+---------------------+
|  SPI控制器驱动      |
+---------------------+
|  硬件物理层         |
+---------------------+
6.1.1 SPI核心层

SPI核心层(spi.c)提供统一的API接口,管理SPI设备和控制器的注册、注销,以及数据传输的调度。主要数据结构包括:

  • struct spi_master:SPI控制器抽象
  • struct spi_device:SPI设备抽象
  • struct spi_driver:SPI驱动抽象
6.1.2 SPI控制器驱动

SPI控制器驱动负责实现具体的SPI控制器硬件操作,如i.MX6ULL的ECSPI控制器驱动。主要工作包括:

  • 初始化SPI控制器硬件
  • 实现数据传输函数
  • 处理中断(如果有)
6.1.3 SPI设备驱动

SPI设备驱动是针对具体SPI设备的驱动程序,如本例中的ICM20608驱动。主要工作包括:

  • 实现probe和remove函数
  • 实现设备特定的操作函数
  • 通过SPI核心层API与硬件通信

6.2 SPI数据传输机制

SPI数据传输通过struct spi_transferstruct spi_message结构体实现:

c 复制代码
struct spi_transfer {
    const void *tx_buf;
    void *rx_buf;
    unsigned len;
    dma_addr_t tx_dma;
    dma_addr_t rx_dma;
    struct sg_table tx_sg;
    struct sg_table rx_sg;
    unsigned cs_change:1;
    unsigned tx_nbits:3;
    unsigned rx_nbits:3;
    u8 bits_per_word;
    u16 delay_usecs;
    u32 speed_hz;
    struct list_head transfer_list;
};

struct spi_message {
    struct list_head transfers;
    struct spi_device *spi;
    unsigned is_dma_mapped:1;
    void (*complete)(void *context);
    void *context;
    unsigned actual_length;
    int status;
    struct list_head list;
};

数据传输流程:

  1. 初始化spi_message
  2. 添加一个或多个spi_transfer到消息中
  3. 调用spi_sync()spi_async()发送消息

6.3 SPI模式说明

SPI有四种工作模式,由CPOL(时钟极性)和CPHA(时钟相位)决定:

模式 CPOL CPHA 时钟空闲状态 数据采样边沿
0 0 0 低电平 上升沿
1 0 1 低电平 下降沿
2 1 0 高电平 下降沿
3 1 1 高电平 上升沿

本驱动中设置为SPI_MODE_0(CPOL=0, CPHA=0),即时钟空闲为低电平,数据在上升沿采样。

7. 用户空间测试程序

7.1 程序源码分析

c 复制代码
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
    int fd;
    char *filename;
    signed int databuf[7];
    unsigned char data[14];
    signed int gyro_x_adc, gyro_y_adc, gyro_z_adc;
    signed int accel_x_adc, accel_y_adc, accel_z_adc;
    signed int temp_adc;

    float gyro_x_act, gyro_y_act, gyro_z_act;
    float accel_x_act, accel_y_act, accel_z_act;
    float temp_act;

    int ret = 0;

    if (argc != 2)
    {
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];
    fd = open(filename, O_RDWR);
    if (fd < 0)
    {
        printf("can't open file %s\r\n", filename);
        return -1;
    }

    while (1)
    {
        ret = read(fd, databuf, sizeof(databuf));
        if (ret == 0)
        {
            gyro_x_adc = databuf[0];
            gyro_y_adc = databuf[1];
            gyro_z_adc = databuf[2];
            accel_x_adc = databuf[3];
            accel_y_adc = databuf[4];
            accel_z_adc = databuf[5];
            temp_adc = databuf[6];

            /* 计算实际值 */
            gyro_x_act = (float)(gyro_x_adc) / 16.4;
            gyro_y_act = (float)(gyro_y_adc) / 16.4;
            gyro_z_act = (float)(gyro_z_adc) / 16.4;
            accel_x_act = (float)(accel_x_adc) / 2048;
            accel_y_act = (float)(accel_y_adc) / 2048;
            accel_z_act = (float)(accel_z_adc) / 2048;
            temp_act = ((float)(temp_adc)-25) / 326.8 + 25;

            printf("\r\n原始值:\r\n");
            printf("gx = %d, gy = %d, gz = %d\r\n", gyro_x_adc, gyro_y_adc, gyro_z_adc);
            printf("ax = %d, ay = %d, az = %d\r\n", accel_x_adc, accel_y_adc, accel_z_adc);
            printf("temp = %d\r\n", temp_adc);
            printf("实际值:");
            printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/S\r\n", gyro_x_act, gyro_y_act, gyro_z_act);
            printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\r\n", accel_x_act, accel_y_act, accel_z_act);
            printf("act temp = %.2f°C\r\n", temp_act);
        }
        usleep(100000); /*100ms */
    }
    close(fd);
    return 0;
}

7.2 程序工作流程

  1. 检查命令行参数
  2. 打开设备文件
  3. 循环读取传感器数据
  4. 将原始数据转换为实际物理量
  5. 打印数据
  6. 延时100ms后继续循环

7.3 数据转换公式

7.3.1 陀螺仪数据转换

陀螺仪量程设置为±2000°/s,16位ADC,因此:

  • 满量程范围:-32768 ~ 32767
  • 灵敏度:2000 / 32768 ≈ 16.4 LSB/(°/s)

转换公式:

复制代码
实际角速度 = 原始值 / 16.4
7.3.2 加速度计数据转换

加速度计量程设置为±16g,16位ADC,因此:

  • 满量程范围:-32768 ~ 32767
  • 灵敏度:16 / 32768 = 2048 LSB/g

转换公式:

复制代码
实际加速度 = 原始值 / 2048
7.3.3 温度数据转换

根据ICM20608数据手册,温度转换公式为:

复制代码
实际温度 = ((原始值 - 25) / 326.8) + 25

其中:

  • 25:室温校准值
  • 326.8:温度传感器灵敏度,单位LSB/°C

8. Makefile分析

makefile 复制代码
KERNERDIR := /home/ubuntu2004/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENTDIR := $(shell pwd)

obj-m := icm20608.o
build : kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) modules

clean:
	$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) clean

8.1 变量说明

  • KERNERDIR:内核源码目录路径
  • CURRENTDIR:当前目录路径
  • obj-m:指定生成的模块文件名

8.2 编译过程

  1. make命令会进入内核源码目录
  2. 通过M=参数指定模块源码目录
  3. 编译生成icm20608.ko模块文件

8.3 编译命令

bash 复制代码
make -C /home/ubuntu2004/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga M=/path/to/module modules

9. 驱动编译与测试

9.1 编译驱动模块

bash 复制代码
make

执行make命令后,会生成icm20608.ko文件。

9.2 加载驱动模块

bash 复制代码
insmod icm20608.ko

9.3 查看设备节点

bash 复制代码
ls /dev/icm20608

9.4 运行测试程序

bash 复制代码
./icm20608APP /dev/icm20608

9.5 卸载驱动模块

bash 复制代码
rmmod icm20608

源码仓库位置:https://gitee.com/dream-cometrue/linux_driver_imx6ull

相关推荐
Yana.nice13 小时前
openssl将证书从p7b转换为crt格式
java·linux
AI逐月13 小时前
tmux 常用命令总结:从入门到稳定使用的一篇实战博客
linux·服务器·ssh·php
小白跃升坊13 小时前
基于1Panel的AI运维
linux·运维·人工智能·ai大模型·教学·ai agent
跃渊Yuey14 小时前
【Linux】线程同步与互斥
linux·笔记
杨江14 小时前
seafile docker安装说明
运维
舰长11514 小时前
linux 实现文件共享的实现方式比较
linux·服务器·网络
好好沉淀14 小时前
Docker开发笔记(详解)
运维·docker·容器
zmjjdank1ng14 小时前
Linux 输出重定向
linux·运维
路由侠内网穿透.14 小时前
本地部署智能家居集成解决方案 ESPHome 并实现外部访问( Linux 版本)
linux·运维·服务器·网络协议·智能家居
树℡独14 小时前
ns-3仿真之应用层(三)
运维·服务器·ns3