Linux-SPI-ICM20608
环境
硬件环境
- 开发板型号: 100ask_imx6ull_pro 开发板
- 处理器类型:NXP IMX6ULL
- 处理器架构:恩单核 Cortex-A7
- 处理器主频:800MHZ
- 内存容量:512 MB DDR3
- 存储介质:4GB eMMC
- 本次测试的驱动:ICM20608 六轴传感器
软件环境
- 宿主机
- 宿主机操作系统:Ubuntu 18.04
- 交叉编译器:100ask 提供的工具链 arm-buildroot-linux-gnueabihf- 支持的最低内核版本:4.9.0
- 开发板
ICM20608
ICM20608 是由 InvenSense 公司生产的一款 6 轴惯性测量单元(IMU),它集成了 3 轴陀螺仪和 3 轴加速度计。这款传感器广泛应用于需要精确运动跟踪的场合,比如无人机、机器人、智能手机和可穿戴设备等。
- 陀螺仪支持 X,Y 和 Z 三轴输出,内部集成 16 位 ADC,测量范围可设置:±250,±500,±1000 和 ±2000°/s
- 加速度计支持 X,Y 和 Z 轴输出,内部集成 16 位 ADC,测量范围可设置:±2g,±4g,±8g 和 ±16g,g 表示重力加速度 1g=9.8m/s²。
- 内部包含一个 512 字节的 FIFO
- 支持快速 I2C,速度可达 400 KHz
- 支持 SPI,速度可达 8 MHz
- 寄存器 8 位,寄存器位宽 8位
如果使用 I2C 接口,则 AD0 引脚决定 I2C 设备地址的最后以为,AD0 = 0 则设备地址为 0x68,AD0 = 1 则设备地址为 0x69,我们这里用的是 SPI 接口,就不用纠结这个了。
使用 SPI 接口读写寄存器需要 16 个时钟或者更多(如果读写操作包括多个字节的话),第一个字节包含要读写的寄存器地址,寄存器地址最高位是读写标志位,如果是读的话寄存器地址最高位要为 1,如果是写的话寄存器地址最高位要为 0,剩下的 7 位才是实际的寄存器地址,寄存器地址后面跟着的就是读写的数据。
本次使用到的 ICM20608 的一些重要寄存器如下:
原理图如下:
驱动代码编写
设备描述信息
添加 ICM20608 所使用到的 IO
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| &iomuxc { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_hog_1>; imx6ul-evk { pinctrl_ecspi3: ecspi3 { fsl,pins = < MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x000010B0 MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x000010B0 MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x000010B0 MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x000010B0 >; }; }; };
|
添加 ecspi3 节点追加 ICM20608子节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| &ecspi3 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_ecspi3>; cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>; status = "okay"; spidev: icm20608@0{ compatible = "invensense,icm20608"; interrupt-parent = <&gpio1>; interrupts = <1 1>; spi-max-frequency = <8000000>; reg = <0>; }; };
|
注意事项
在修改设备树时,记得检查一下是否其他设备也用到了相同引脚。遇到了一个问题,就是驱动程序和设备树的 compatible 都相同,但是始终发现匹配不成功,没有进入到 probe 函数,驱动程序匹配主要就看 compatible 这些匹配的信息,都无误,于是查看设备树,发现原来是引脚冲突了,ecspi3 子节点和 uart2、can2 冲突,忘记删减了。
driver 驱动代码
寄存器定义和 icm20608_dev 结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| #include <linux/init.h> #include <linux/module.h> #include <linux/spi/spi.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/string.h>
#include <linux/delay.h> #include <linux/uaccess.h>
#define ICM20608_PWR_MGMT_1 0x6B #define ICM20608_PWR_MGMT_2 0x6C #define ICM20608_SMPLRT_DIV 0x19 #define ICM20608_CONFIG 0x1A #define ICM20608_GYRO_CONFIG 0x1B #define ICM20608_ACCEL_CONFIG 0x1C #define ICM20608_ACCEL_CONFIG2 0x1D #define ICM20608_LP_MODE_CFG 0x1E #define ICM20608_FIFO_EN 0x23 #define ICM20608_WHO_AM_I 0x75 #define ACCEL_XOUT_H 0x3B
#define ICM20608_NAME "icm20608"
struct icm20608_data { int16_t accel_x; int16_t accel_y; int16_t accel_z; int16_t gyro_x; int16_t gyro_y; int16_t gyro_z; int16_t temperature; };
struct icm20608_dev { dev_t devid; struct cdev cdev; struct class *class; struct device *device; struct device_node *nd; int major; void *private_data; struct icm20608_data data; };
static struct icm20608_dev icm20608_dev_t;
|
注册/注销 spi_driver 结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| static const struct of_device_id of_match_table[] = { {.compatible = "invensense,icm20608"}, {}, };
static const struct spi_device_id id_table[] = { {"invensense,icm20608"}, {}, };
static struct spi_driver icm20608_spi_driver = { .driver = { .owner = THIS_MODULE, .name = "icm20608", .of_match_table = of_match_table, }, .probe = icm20608_spi_driver_probe, .remove = icm20608_spi_driver_remove, .id_table = id_table, };
static int __init icm20608_spi_driver_init(void) { int ret; ret = spi_register_driver(&icm20608_spi_driver); printk("icm20608_spi_driver_init ok\n"); return ret; }
static void __exit icm20608_spi_driver_exit(void) { spi_unregister_driver(&icm20608_spi_driver); printk("icm20608_spi_driver_exit ok\n"); }
module_init(icm20608_spi_driver_init); module_exit(icm20608_spi_driver_exit); MODULE_LICENSE("GPL");
|
添加字符设备
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| static const struct file_operations icm20608_fops = { .owner = THIS_MODULE, .open = icm20608_open, .release = icm20608_close, .read = icm20608_read, };
static int icm20608_spi_driver_probe(struct spi_device *spi) { if(icm20608_dev_t.major) { icm20608_dev_t.devid = MKDEV(icm20608_dev_t.major, 0); register_chrdev_region(icm20608_dev_t.devid, 1, ICM20608_NAME); } else { alloc_chrdev_region(&icm20608_dev_t.devid, 0, 1, ICM20608_NAME); icm20608_dev_t.major = MAJOR(icm20608_dev_t.devid); }
cdev_init(&icm20608_dev_t.cdev, &icm20608_fops);
cdev_add(&icm20608_dev_t.cdev, icm20608_dev_t.devid, 1); icm20608_dev_t.class = class_create(THIS_MODULE, ICM20608_NAME); if (IS_ERR(icm20608_dev_t.device)) return PTR_ERR(icm20608_dev_t.device); icm20608_dev_t.device = device_create(icm20608_dev_t.class, NULL, icm20608_dev_t.devid, NULL, ICM20608_NAME); if (IS_ERR(icm20608_dev_t.device)) return PTR_ERR(icm20608_dev_t.device);
spi->mode = SPI_MODE_0; spi_setup(spi); icm20608_dev_t.private_data = spi; printk("icm20608_spi_driver_probe ok\n");
return 0; } static int icm20608_spi_driver_remove(struct spi_device *spi) { unregister_chrdev_region(icm20608_dev_t.devid, 1); cdev_del(&icm20608_dev_t.cdev); device_destroy(icm20608_dev_t.class, icm20608_dev_t.devid); class_destroy(icm20608_dev_t.class); printk("icm20608_spi_driver_remove ok\n"); return 0; }
|
收发数据函数
前面我们在设备树中将片选 IO 设置为了软中断,且 Linux 内核中的 SPI 主控制器会自动帮我们操作片选 IO,因此在收发数据函数中我们不能手动控制片选 IO 了。另外需要注意的是 SPI 接收的时候会先接收到一个字节的寄存器地址,读取数据时我们需要跳过该字节。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
|
static int icm20608_read_reg(struct icm20608_dev *dev, uint8_t reg, void *val, uint8_t len) { int ret; uint8_t tx_data[1]; uint8_t *rx_data; struct spi_transfer *t; struct spi_message msg;
struct spi_device *spi = (struct spi_device *)dev->private_data; t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); if(!t) { printk("kzalloc error\n"); kfree(t); return -ENOMEM;; } rx_data = kzalloc(sizeof(uint8_t) + len, GFP_KERNEL); if(!rx_data) { printk("kzalloc error\n"); kfree(rx_data); return -ENOMEM;; }
tx_data[0] = reg | (0x80);
t->tx_buf = tx_data; t->rx_buf = rx_data; t->len = len + 1; spi_message_init(&msg); spi_message_add_tail(t, &msg); ret = spi_sync(spi, &msg);
memcpy(val, rx_data + 1, len);
kfree(t); kfree(rx_data); return ret; }
static int icm20608_write_reg(struct icm20608_dev *dev, uint8_t reg, uint8_t *buf, uint8_t len) { int ret; uint8_t *tx_data; struct spi_transfer *t; struct spi_message msg;
struct spi_device *spi = (struct spi_device *)dev->private_data; t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); if(!t) { printk("kzalloc error\n"); kfree(t); return -ENOMEM;; } tx_data = kzalloc(sizeof(uint8_t) + len, GFP_KERNEL); if(!t) { printk("kzalloc error\n"); kfree(tx_data); return -ENOMEM;; }
*tx_data = reg & ~(0x80); memcpy(tx_data + 1, buf, len); t->tx_buf = tx_data; t->len = len + 1; spi_message_init(&msg); spi_message_add_tail(t, &msg); ret = spi_sync(spi, &msg);
kfree(t); kfree(tx_data);
return ret; }
|
open 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| static void icm20608_deinit(void) { uint8_t value;
value = 0x80; icm20608_write_reg(&icm20608_dev_t, ICM20608_PWR_MGMT_1, &value, 1); mdelay(50); value = 0x01; icm20608_write_reg(&icm20608_dev_t, ICM20608_PWR_MGMT_1, &value, 1); mdelay(50); icm20608_read_reg(&icm20608_dev_t, ICM20608_WHO_AM_I, &value, 1); printk("ICM20608 ID = %x\n", value); value = 0x00; icm20608_write_reg(&icm20608_dev_t, ICM20608_SMPLRT_DIV, &value, 1); value = 0x04; icm20608_write_reg(&icm20608_dev_t, ICM20608_CONFIG, &value, 1); value = 0x18; icm20608_write_reg(&icm20608_dev_t, ICM20608_GYRO_CONFIG, &value, 1); value = 0x18; icm20608_write_reg(&icm20608_dev_t, ICM20608_ACCEL_CONFIG, &value, 1); value = 0x04; icm20608_write_reg(&icm20608_dev_t, ICM20608_ACCEL_CONFIG2, &value, 1); value = 0x00; icm20608_write_reg(&icm20608_dev_t, ICM20608_LP_MODE_CFG, &value, 1); value = 0x00; icm20608_write_reg(&icm20608_dev_t, ICM20608_FIFO_EN, &value, 1); value = 0x00; icm20608_write_reg(&icm20608_dev_t, ICM20608_PWR_MGMT_2, &value, 1); }
static int icm20608_open (struct inode *node, struct file *file) { file->private_data = &icm20608_dev_t;
icm20608_deinit(); printk("icm20608_deinit ok\n"); return 0; }
|
read 函数
这里需要注意的就是接收到的数据是 8 位的,在合并数据时,数据移位需要提前转换成 uint16_t 类型,否则会丢失数据,最后将合并成的数据存入 kbuf,使用 copy_to_user 将数据传送给应用程序。
这里接收的数据是 ADC 模拟值不是实际值,需要根据寄存器设置的分辨率进行转换,因为涉及到浮点运算,就没有在驱动文件里面写,后面再应用程序进行运算即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| static int icm20608_read_data(struct icm20608_dev *dev) { int ret; uint8_t data[14] = {0}; ret = icm20608_read_reg(dev, ACCEL_XOUT_H, data, 14); if(ret < 0) { printk("icm20608_read_data failed\n"); return -1; } dev->data.accel_x = (((int16_t)data[0] << 8) | data[1]); dev->data.accel_y = (((int16_t)data[2] << 8) | data[3]); dev->data.accel_z = (((int16_t)data[4] << 8) | data[5]); dev->data.temperature = (((int16_t)data[6] << 8) | data[7]); dev->data.gyro_x = (((int16_t)data[8] << 8) | data[9]); dev->data.gyro_y = (((int16_t)data[10] << 8) | data[11]); dev->data.gyro_z = (((int16_t)data[12] << 8) | data[13]); return 0; }
static ssize_t icm20608_read (struct file *file, char __user *ubuf, size_t size, loff_t *loff_t) { int ret; int16_t kbuf[7]; struct icm20608_dev *dev = (struct icm20608_dev *)file->private_data; printk("icm20608_read start\n"); ret = icm20608_read_data(dev); if(ret < 0) { printk("icm20608_read error\n"); return -1; }
kbuf[0] = dev->data.accel_x; kbuf[1] = dev->data.accel_y; kbuf[2] = dev->data.accel_z; kbuf[3] = dev->data.gyro_x; kbuf[4] = dev->data.gyro_y; kbuf[5] = dev->data.gyro_z; kbuf[6] = dev->data.temperature;
ret = copy_to_user(ubuf, kbuf, sizeof(kbuf)); if(ret < 0) { printk("copy_to_user error\n"); return -1; } printk("icm20608_read ok\n"); return 0; }
|
应用程序测试
ADC 模拟值转换为实际值取决于陀螺仪和加速度计的分辨率。
由于寄存器配置时,陀螺仪和加速度计的测量量程范围不同,分辨率就不同,数据手册里面不同测量量程对应不同的分辨率如下:
ADC 模拟值转换实际值转换过程如下:16 位 ADC
实际值=215ADC模拟值测量量程的最大值
本质上读取的值在 ADC 模拟值的测量范围的位置映射到具体测量量程的位置。(ADC 模拟值没有含义需要转换成有意义的值)
FS_SEL = 3时,测量量程为 ±2000dps时,陀螺仪转换如下:
实际值=215ADC模拟值2000=16.4ADC模拟值
AFS_SEL = 3时,测量量程为 ±16g 时,陀螺仪转换如下:
实际值=215ADC模拟值16=2048ADC模拟值
温度转换成实际值:RoomTemp_Offset
是在 25°C 时的 ADC值偏移量,而 Temp_Sensitivity
是温度传感器的灵敏度,表示每单位温度变化对应的 ADC 计数变化。
因此计算方式为:
temperatureact=326.8temperatureadc−25+25
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h>
int main(int argc, char **argv) { int fd, ret; int16_t buf[7]; int16_t accel_x_adc, accel_y_adc, accel_z_adc, gyro_x_adc, gyro_y_adc, gyro_z_adc, temperature_adc; float accel_x_act, accel_y_act, accel_z_act, gyro_x_act, gyro_y_act, gyro_z_act, temperature_act;
fd = open("/dev/icm20608", O_RDWR); if(fd < 0) { printf("open error\n"); return fd; }
while(1) { ret = read(fd, buf, sizeof(buf)); if(ret == 0) { accel_x_adc= buf[0]; accel_y_adc = buf[1]; accel_z_adc = buf[2]; gyro_x_adc = buf[3]; gyro_y_adc = buf[4]; gyro_z_adc = buf[5]; temperature_adc = buf[6];
accel_x_act = (float)(accel_x_adc) / 2048; accel_y_act = (float)(accel_y_adc) / 2048; accel_z_act = (float)(accel_z_adc) / 2048; 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; temperature_act = (float)(temperature_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", temperature_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", temperature_act);
} usleep(100000); }
close(fd); return 0; }
|