嵌入式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
:设备节点指针,用于设备树相关操作major
、minor
:主设备号和次设备号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(0x80)表示读操作
- 调用
spi_write_then_read()
先发送寄存器地址,然后读取数据 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写操作:
- 将寄存器地址最高位清0(0x7f)表示写操作
- 将要写入的数据复制到缓冲区
- 调用
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
函数执行以下操作:
- 调用
icm20608_readdata()
读取传感器数据 - 将数据组织成数组
- 使用
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进行初始化配置:
- 先写0x80到
ICM20_PWR_MGMT_1
进行复位 - 延时50ms后写0x01启动设备
- 读取
WHO_AM_I
寄存器验证设备ID - 配置采样率分频器为0
- 设置陀螺仪量程为±2000°/s(0x18)
- 设置加速度计量程为±16g(0x18)
- 设置陀螺仪低通滤波器带宽为20Hz
- 设置加速度计低通滤波器带宽为21.2Hz
- 启用所有轴的加速度计和陀螺仪
- 关闭低功耗模式
- 关闭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
函数在设备匹配成功后被调用,执行以下初始化操作:
- 分配设备号
- 注册字符设备
- 创建设备类
- 创建设备节点
- 设置SPI模式为MODE_0
- 调用
spi_setup()
应用SPI设置 - 保存
spi_device
指针到私有数据 - 调用寄存器初始化函数
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
函数在设备移除时被调用,执行资源清理:
- 释放GPIO
- 删除字符设备
- 释放设备号
- 销毁设备节点
- 销毁设备类
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_transfer
和struct 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;
};
数据传输流程:
- 初始化
spi_message
- 添加一个或多个
spi_transfer
到消息中 - 调用
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 程序源码分析
#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 程序工作流程
- 检查命令行参数
- 打开设备文件
- 循环读取传感器数据
- 将原始数据转换为实际物理量
- 打印数据
- 延时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 编译过程
make
命令会进入内核源码目录- 通过M=参数指定模块源码目录
- 编译生成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