当前位置: 首页 > news >正文

嵌入式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的设备树配置如下:

&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的引脚复用配置如下:

pinctrl_ecspi3: ecspi3grp {fsl,pins = <MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20        0x10b0MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK    0x10b1MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI        0x10b1MX6UL_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 头文件包含

#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的所有寄存器地址:

#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 驱动数据结构

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 多字节读取函数
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 多字节写入函数
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 单字节读写函数
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 数据读取函数

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函数
static int icm20608_open(struct inode *inode, struct file *filp)
{filp->private_data = &icm20608dev;return 0;
}

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

5.6.2 read函数
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函数
static int icm20608_release(struct inode *inode, struct file *filp)
{return 0;
}

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

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

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

5.7 寄存器初始化函数

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函数

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函数

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 设备匹配表

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驱动结构体

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 模块初始化和退出函数

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结构体实现:

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(时钟相位)决定:

模式CPOLCPHA时钟空闲状态数据采样边沿
000低电平上升沿
101低电平下降沿
210高电平下降沿
311高电平上升沿

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

7. 用户空间测试程序

7.1 程序源码分析

#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分析

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_moduleskernel_modules:$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) modulesclean:$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) clean

8.1 变量说明

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

8.2 编译过程

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

8.3 编译命令

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 编译驱动模块

make

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

9.2 加载驱动模块

insmod icm20608.ko

9.3 查看设备节点

ls /dev/icm20608

9.4 运行测试程序

./icm20608APP /dev/icm20608

9.5 卸载驱动模块

rmmod icm20608

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

http://www.dtcms.com/a/357999.html

相关文章:

  • 期刊 | 《电讯技术》期刊2025年投稿指南总结
  • 硬件SPI读写W25Q64
  • 北京交通大学:LLM的agent推理框架综述
  • 算法复习笔记: 双指针_二分查找篇
  • 详解推测性采样加速推理的算法逻辑
  • Kafka 消费模型
  • 本地运行的检索PDF文件中出现关键字的python程序
  • 2025年如何批量下载雪球帖子和文章导出pdf?
  • 2025 批量下载雪球和东方财富帖子和文章导出excel和pdf
  • kind集群应用
  • 《云原生架构从崩溃失控到稳定自愈的实践方案》
  • 「鸿蒙系统的编程基础」——探索鸿蒙开发
  • 第12章:推荐算法与实践
  • 科技感网页计时器.html
  • 设计模式:抽象工厂模式(Abstract Factory Pattern)
  • 在word以及latex中引用zotero中的参考文献
  • 单例模式的mock类注入单元测试与友元类解决方案
  • 云存储(参考自腾讯云计算工程师认证)
  • Twitter舆情裂变链:指纹云手机跨账号协同机制提升互动率200%
  • 使用电脑操作Android11手机,连接步骤
  • 【序列晋升】21 Spring Cloud Gateway 云原生网关演进之路
  • DVWA靶场通关笔记-CSRF(Impossible级别)
  • 【90页PPT】新能源汽车数字化转型SAP解决方案(附下载方式)
  • 汽车加气站操作工证考试的复习重点是什么?
  • 【自然语言处理与大模型】多机多卡分布式微调训练的有哪些方式
  • C++ constexpr:编译时计算的高效秘籍
  • 复现论文块体不锈钢上的光栅耦合表面等离子体共振
  • 10.2 工程学中的矩阵
  • hadoop安欣医院挂号看诊管理系统(代码+数据库+LW)
  • 使用 Ansible 和 Azure Pipelines 增强您的 DevOps