i.MX6ULL驱动开发 | 14 - 基于 Linux SPI 驱动框架读取ICM-20608传感器
Posted Mculover666
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了i.MX6ULL驱动开发 | 14 - 基于 Linux SPI 驱动框架读取ICM-20608传感器相关的知识,希望对你有一定的参考价值。
本系列文章驱动源码仓库,欢迎Star~
https://github.com/Mculover666/linux_driver_study。
一、ICM20608
1. 简介
InvenSense 的 ICM-20608 是一款 6 轴运动跟踪器件(MEMS传感器),也是 MPU-6500 的后续产品,集成了3轴加速度计和3轴陀螺仪。
相比以前的 6 轴器件,Invensense 的 ICM 20608 具有更低的功耗和噪声并采用更薄的封装。 该器件为陀螺仪提供了一种占空比工作模式,相比以前的 6 轴器件能将陀螺仪的功耗降低一半或一半以上(具体视 ODR 而定)。 此外,该器件的噪声比以前的器件降低约 20%,封装薄约 17%。
2. 功能使用
3. Alpha开发板原理图
ICM-20608传感器使用SPI3,引脚连接情况如下:
传感器引脚 | iMX6ULL引脚 |
---|---|
CS | ECSPI3_SS0 |
SCLK | ECSPI3_SCLK |
SDI | ECSPI3_MOSI |
SDO | ECSPI3_MISO |
二、添加设备树节点
1. 设置SPI3引脚
首先设置 SPI3 引脚的复用功能和电气属性,在 iomuxc 节点中添加:
pinctrl_spi3: spi3grp
fsl,pins = <
MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0
MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10b1
MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10b1
MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10b1
>;
;
其中UART2_TX_DATA这个引脚设置为普通IO是为了手动控制SPI片选信号。
检查这四个引脚有没有复用,以 MX6UL_PAD_UART2_TX_DATA 引脚为例,已经被uart2使用:
所以这里我们要找到 pinctrl_uart2
的引用,将uart2节点先屏蔽:
同样的方法,解决其它引脚 MX6UL_PAD_UART2_RTS_B 和 MX6UL_PAD_UART2_CTS_B 冲突问题。
这两个引脚作为FLEXCAN2的引脚使用,将该节点先注释了:
2. 添加ICM-20608设备节点
添加对ecspi3节点的补充描述:
&ecspi3
fsl,spi-num-chipselects = <1>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_spi3>;
status = "okay";
mems_spi: icm20608@0
compatible = "atk,icm20608";
spi-max-frequency = <8000000>;
reg = <0>;
cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>;
;
;
重新编译设备树,使用新的设备树启动,查看设备树是否有新添加的节点:
三、编写ICM-20608设备驱动
1. 先写个模块
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
static int __init icm20608_module_init(void)
return 0;
static void __exit icm20608_module_exit(void)
module_init(icm20608_module_init)
module_exit(icm20608_module_exit)
MODULE_AUTHOR("Mculover666");
MODULE_LICENSE("GPL");
写个Makefile编译一下:
KERNEL_DIR = /home/mculover666/imx6ull/kernel/linux-imx6ull
obj-m = icm20608.o
build: kernel_module
kernel_module:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
2. 再搭个spi驱动框架
包含头文件:
#include <linux/spi/spi.h>
完成 spi 设备驱动基本框架:
static int icm20608_probe(struct spi_device *spi)
return 0;
static int icm20608_remove(struct spi_device *spi)
return 0;
/* 设备树匹配 */
static const struct of_device_id icm20608_of_match[] =
.compatible = "atk,icm20608" ,
,
;
/* 传统id方式匹配 */
static const struct spi_device_id icm20608_id[] =
"atk,icm20608", 0 ,
,
;
/**
*@brief spi驱动结构体
*/
static struct spi_driver icm20608_driver =
.probe = icm20608_probe,
.remove = icm20608_remove,
.driver =
.owner = THIS_MODULE,
.name = "icm20608",
.of_match_table = icm20608_of_match,
,
.id_table = icm20608_id;
;
static int __init icm20608_module_init(void)
int ret;
/* 注册spi_driver */
ret = spi_register_driver(&icm20608_driver);
if (ret < 0)
printk("spi_register_driver fail!\\n");
return -1;
return 0;
static void __exit icm20608_module_exit(void)
/* 注销spi_driver */
spi_unregister_driver(&icm20608_driver);
3. 再写字符设备驱动框架
引入头文件:
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
添加字符设备驱动框架相关代码:
struct icm20608_dev
dev_t dev;
struct cdev *cdev;
struct class *class;
struct device *device;
;
static struct icm20608_dev icm20608;
static int icm20608_open(struct inode *node, struct file *fp)
return 0;
static int icm20608_read(struct file *fp, char __user *buf, size_t size, loff_t *off)
return 0;
static int icm20608_write(struct file *fp, const char __user *buf, size_t size, loff_t *off)
return 0;
static int icm20608_release(struct inode *node, struct file *fp)
return 0;
static struct file_operations icm20608_fops =
.owner = THIS_MODULE,
.open = icm20608_open,
.read = icm20608_read,
.write = icm20608_write,
.release = icm20608_release,
;
static int icm20608_probe(struct spi_device *spi)
int ret;
// 申请设备号
ret = alloc_chrdev_region(&icm20608.dev, 0, 1, "icm20608");
if (ret != 0)
printk("alloc_chrdev_region fail!");
return -1;
// 创建cdev
icm20608.cdev = cdev_alloc();
if (!icm20608.cdev)
printk("cdev_alloc fail!");
return -1;
icm20608.cdev->owner = THIS_MODULE;
icm20608.cdev->ops = &icm20608_fops;
// 注册cdev
cdev_add(icm20608.cdev, icm20608.dev, 1);
// 创建设备类
icm20608.class = class_create(THIS_MODULE, "icm20608");
if (!icm20608.class)
printk("class_create fail!");
return -1;
// 创建设备节点
icm20608.device = device_create(icm20608.class, NULL, icm20608.dev, NULL, "icm20608");
if (IS_ERR(icm20608.device))
printk("device_create led_dts_device0 fail!");
return -1;
return 0;
static int icm20608_remove(struct spi_device *spi)
// 将设备从内核删除
cdev_del(icm20608.cdev);
// 释放设备号
unregister_chrdev_region(icm20608.dev, 1);
// 删除设备节点
device_destroy(icm20608.class, icm20608.dev);
// 删除设备类
class_destroy(icm20608.class);
return 0;
4. 获取设备树信息
因为本文中我们使用gpio作为片选引脚,手动控制,所以要从设备树中获取gpio引脚信息和spi的一些设置信息,获取信息的代码放到probe函数中。
(1)获取spi节点信息
全局变量中添加node成员:
// 获取设备树中spi节点信息
icm20608.node = of_find_compatible_node(NULL, NULL, "atk,icm20608");
if (!icm20608.node)
printk("icm20608 node find fail!");
return -1;
(2)进一步获取片选引脚gpio信息
全局变量中添加cs_gpio成员:
包含头文件:
#include <linux/of_gpio.h>
#include <linux/gpio.h>
获取gpio引脚信息:
// 进一步获取gpio片选引脚信息
icm20608.cs_gpio = of_get_named_gpio(icm20608.node, "cs-gpio", 0);
if (icm20608.cs_gpio < 0)
printk("cs-gpio propname in icm20608 node find fail!");
return -1;
// 设置gpio引脚方向并默认输出高电平
ret = gpio_direction_output(icm20608.cs_gpio, 1);
if (ret < 0)
printk("gpio_direction_output fail!");
return -1;
5. 封装spi操作代码
(1)spi_device成员
模块全局数据中,添加 spi_device 成员:
在probe函数中,将probe函数传入的spi_device值,存到模块全局数据的spi_devices成员中:
// 存储spi_device值
spi->mode = SPI_MODE_0;
spi_setup(spi);
icm20608.spi = spi;
(2)spi发送然后读取函数封装
static int icm20608_send_then_recv(struct icm20608_dev *dev, uint8_t *send_buf, ssize_t send_len, uint8_t *recv_buf, ssize_t recv_len)
int ret;
struct spi_device *spi;
struct spi_message m;
struct spi_transfer *t;
if (!dev)
return -1;
spi = dev->spi;
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
if (!t)
printk("spi_transfer kzalloc fail!\\n");
return -1;
/* 使能片选 */
gpio_set_value(dev->cs_gpio, 0);
/* 发送数据 */
if (send_buf && send_len != 0)
t->tx_buf = send_buf;
t->len = send_len;
spi_message_init(&m);
spi_message_add_tail(t, &m);
ret = spi_sync(spi, &m);
if (ret < 0)
printk("spi_sync fail!\\n");
goto exit;
/* 接收数据 */
if (recv_buf && recv_len != 0)
t->rx_buf = recv_buf;
t->len = send_len;
spi_message_init(&m);
spi_message_add_tail(t, &m);
ret = spi_sync(spi, &m);
if (ret < 0)
printk("spi_sync fail!\\n");
goto exit;
ret = 0;
/* 禁止片选 */
exit:
gpio_set_value(dev->cs_gpio, 1);
return ret;
(3)icm20608寄存器读写
icm20608写寄存器函数:
static int icm20608_write_reg(struct icm20608_dev *dev, uint8_t reg, uint8_t dat)
int ret;
uint8_t send_buf[2];
send_buf[0] = reg & (~0x80); // MSB is W(0)
send_buf[1] = dat;
ret = icm20608_send_then_recv(dev, send_buf, 2, NULL, 0);
return ret < 0 ? -1 : 0;
icm20608读寄存器函数:
static int icm20608_read_reg(struct icm20608_dev *dev, uint8_t reg, uint8_t *dat)
int ret;
uint8_t send_buf;
send_buf = reg | 0x80; // MSB is R(1)
ret = icm20608_send_then_recv(dev, &send_buf, 1, dat, 1);
return ret < 0 ? -1 : 0;
6. icm20608板级操作函数
6.1. icm20608初始化函数
需要使用mdelay函数,引入头文件:
#include <linux/delay.h>
操作 PWOER MANAGEMENT 1 寄存器,进行软复位,然后选择时钟:
static int icm20608_board_soft_reset(void)
// reset the internal registers and restore the default settings.
icm20608_write_reg(&icm20608, 0x6B, 0x80);
mdelay(50);
// auto select the best available clock source.
icm20608_write_reg(&icm20608, 0x6B, 0x01);
mdelay(50);
return 0;
读取芯片ID以检测是否通信正常:
static int icm20608_board_read_id(uint8_t *id)
int ret;
ret = icm20608_read_reg(&icm20608, 0x75, id);
return ret < 0 ? -1 : 0;
设置芯片内部一些配置:
static int icm20608_board_config(void)
icm20608_write_reg(&icm20608, 0x19, 0x00); // SMPLRT_DIV
icm20608_write_reg(&icm20608, 0x1A, 0x04); // CONFIG
icm20608_write_reg(&icm20608, 0x1B, 0x18); // GYRO_CONFIG
icm20608_write_reg(&icm20608, 0x1C, 0x18); // ACCEL_CONFIG
icm20608_write_reg(&icm20608, 0x1D, 0x04); // ACCEL_CONFIG2
icm20608_write_reg(&icm20608, 0x1E, 0x00); // LP_MODE_CFG
icm20608_write_reg(&icm20608, 0x23, 0x00); // FIFO_EN
icm20608_write_reg(&icm20608, 0x6C, 0x00); // PWR_MGMT_2
return 0;
整合为Icm20608初始化函数:
static int icm20608_board_init(void)
uint8_t id;
if (icm20608_board_soft_reset() < 0)
printk("icm20608_board_soft_reset fail\\n!");
return -1;
if (icm20608_board_read_id(&id) < 0)
printk("icm20608_board_read_id fail\\n!");
return -1;
if (icm20608_board_config() < 0)
printk("icm20608_board_config fail\\n!");
return -1;
printk("icm20608 id: 0x%x\\n", id);
return 0;
编译,加载,检查是否可以正常读取到芯片id:
6.2. icm20608读取数据函数
首先抽象出icm20608数据结构:
struct icm20608_row_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;
;
添加到模块的全局变量中:
i.MX6ULL驱动开发 | 08 -基于pinctrl子系统和gpio子系统点亮LED
i.MX6ULL驱动开发 | 09 -基于Linux自带的LED驱动点亮LED
i.MX6ULL驱动开发 | 34 - 基于SPI框架驱动spi lcd(st7789)
i.MX6ULL驱动开发 | 03-基于字符设备驱动框架点亮LED