文章目录
- 一、设备树
- 二、驱动程序
- 三、应用程序
- 四、测试
一、设备树
在pinctrl节点中添加:
/* spi驱动 icm20608 */
pinctrl_ecspi3: icm20608 {
fsl,pins = <
MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0 /* CS */ /* 只是一个普通的GPIO */
MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10b1 /* SCLK */
MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10b1 /* MISO */
MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10b1 /* MOSI */
>;
};
在最后添加:
&ecspi3 {
fsl,spi-num-chipselects = <1>; /* 当前片选数量为1 因为只接了一个设备*/
cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi3>;
status = "okay";
/* icm20608 */
icm20608: icm20608@0 { /* 连接在spi3 的 第0个通道上 */
compatible = "luatao,icm20608";
spi-max-frequency = <8000000>; /* 最大时钟频率为8MHz*/
reg = <0>;
};
二、驱动程序
icm20608.h
#ifndef ICM20608_H
#define ICM20608_H
/* 不同型号的id*/
#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
#endif
icm20608.c
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/spi/spi.h>
#include "icm20608.h"
/**
* file name:icm20608
* date: 2021-08-15 20:11
* version:1.0
* author:luatao
* describe:icm20608 device drive
*/
#define ICM20608_CNT 1 /* 设备号个数 */
#define ICM20608_NAME "icm20608" /* 设备名*/
/* 设备结构体 自定义 */
struct icm20608_dev{
dev_t devid; /*设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类*/
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
void *private_date; /* 私有数据 */
int cs_gpio; /* 片选所使用的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 icm20608; /*icm20608 设备 */
/* 从icm20608读取多个寄存器数据
@param - *dev : ap3216设备
@param - reg : 要读取的寄存器首地址
@param - *val : 读取到的数据
@param - len : 要读取的数据长度
@return :操作结果
*/
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *val, int len)
{
int ret = 0;
unsigned char txdata[len]; /* 发送数据 */
struct spi_message m; /* 发送的消息结构体 */
struct spi_transfer *t; /* 传输结构体 */
struct spi_device *client = (struct spi_device *)dev->private_date; /* 私有数据 */
gpio_set_value(dev->cs_gpio, 0); /* 片选拉低 选中icm20608 */
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); /* 申请内存 */
/* 第1次 发送要读取的寄存器地址 */
txdata[0] = reg | 0x80; /* 写数据的时候寄存器地址 bit7 要置1 */
t->tx_buf = txdata; /* 要发送的数据 */
t->len = 1; /* 1个字节 */
spi_message_init(&m); /* 初始化 spi_message */
spi_message_add_tail(t, &m); /* 将 spi_transfer 添加到spi_message */
ret = spi_sync(client, &m); /* 同步发送 */
/* 第2次 读取数据 */
txdata[0] = 0xff; /* 随便一个值, 此处无意义 */
t->rx_buf = val; /* 读取到的数据 */
t->len = len; /* 要读取的数据长度 */
spi_message_init(&m); /* 初始化 spi_message */
spi_message_add_tail(t, &m); /* 将 spi_transfer 添加到spi_message */
ret = spi_sync(client, &m); /* 同步发送 */
kfree(t); /* 释放内存 */
gpio_set_value(dev->cs_gpio, 1); /* 片选拉高 释放icm20608*/
return ret;
}
/* 从icm20608多个寄存器写入数据
@param - *dev : ap3216设备
@param - reg : 要写入的寄存器首地址
@param - *buf : 写入的数据缓冲区
@param - len : 要写入的数据长度
@return :操作结果
*/
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{
int ret = 0;
unsigned char txdata[len]; /* 发送数据缓冲区*/
struct spi_message m; /* 发送的消息结构体 */
struct spi_transfer *t; /* 传输结构体 */
struct spi_device *client = (struct spi_device *)dev->private_date; /* 私有数据 */
gpio_set_value(dev->cs_gpio, 0); /* 片选拉低 选中icm20608 */
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); /* 申请内存 */
/* 第1次 发送要读取的寄存器地址 */
txdata[0] = reg & ~0x80; /* 写数据的时候寄存器地址 bit7 要清零 */
t->tx_buf = txdata; /* 要发送的数据 */
t->len = 1; /* 1个字节 */
spi_message_init(&m); /* 初始化 spi_message */
spi_message_add_tail(t, &m); /* 将 spi_transfer 添加到spi_message */
ret = spi_sync(client, &m); /* 同步发送 */
/* 第2次 发送要写入的数据 */
t->tx_buf = buf; /* 要写入的数据 */
t->len = len; /* 写入的字节数 */
spi_message_init(&m); /* 初始化 spi_message */
spi_message_add_tail(t, &m); /* 将 spi_transfer 添加到spi_message */
ret = spi_sync(client, &m); /* 同步发送 */
kfree(t); /* 释放内存 */
gpio_set_value(dev->cs_gpio, 1); /* 片选拉高 释放icm20608*/
return ret;
}
/* 从icm20608读取指定寄存器值 读取一个寄存器
@param - *dev : ap3216设备
@param - reg : 要读取的寄存器
@return :读取到的寄存器值
*/
static unsigned char icm20608_read_reg(struct icm20608_dev *dev, u8 reg)
{
u8 data = 0;
icm20608_read_regs(dev, reg, &data, 1);
return data;
}
/* 向icm20608指定寄存器写入指定的值,写一个寄存器
@param - *dev : ap3216设备
@param - reg : 要写入的寄存器
@param - data : 要写入的值
@return :无
*/
static void icm20608_write_reg(struct icm20608_dev *dev, u8 reg, u8 data)
{
u8 buf = data;
icm20608_write_regs(dev, reg, &buf, 1); /* 调用写入多个寄存器的方法 */
}
/* 读取icm20608的数据 读取原始数据 包括三轴陀螺仪 三轴加速度计 和 内部温度
@param - *dev : ap3216设备
@return :无
*/
static void icm20608_readdata(struct icm20608_dev *dev)
{
unsigned char buf[14]; /* 读取的数据缓冲区 */
/* 读取 14 个数据 */
icm20608_read_regs(dev, ICM20_ACCEL_XOUT_H, buf, 14);
/* 处理数据 */
dev->accel_x_adc = (signed short)((buf[0] << 8) | buf[1]);
dev->accel_y_adc = (signed short)((buf[2] << 8) | buf[3]);
dev->accel_z_adc = (signed short)((buf[4] << 8) | buf[5]);
dev->temp_adc = (signed short)((buf[6] << 8) | buf[7]);
dev->gyro_x_adc = (signed short)((buf[8] << 8) | buf[9]);
dev->gyro_y_adc = (signed short)((buf[10] << 8) | buf[11]);
dev->gyro_z_adc = (signed short)((buf[12] << 8) | buf[13]);
}
/* 内部寄存器初始化函数 */
void icm20608_reginit(void)
{
u8 value = 0;
icm20608_write_reg(&icm20608, ICM20_PWR_MGMT_1, 0x80);
mdelay(50);
icm20608_write_reg(&icm20608, ICM20_PWR_MGMT_1, 0x01);
mdelay(50);
value = icm20608_read_reg(&icm20608, ICM20_WHO_AM_I);
printk("ICM20608 ID = %#X\r\n", value);
icm20608_write_reg(&icm20608, ICM20_SMPLRT_DIV, 0x00);
icm20608_write_reg(&icm20608, ICM20_GYRO_CONFIG, 0x18);
icm20608_write_reg(&icm20608, ICM20_ACCEL_CONFIG, 0x18);
icm20608_write_reg(&icm20608, ICM20_CONFIG, 0x04);
icm20608_write_reg(&icm20608, ICM20_ACCEL_CONFIG2, 0x04);
icm20608_write_reg(&icm20608, ICM20_PWR_MGMT_2, 0x00);
icm20608_write_reg(&icm20608, ICM20_LP_MODE_CFG, 0x00);
icm20608_write_reg(&icm20608, ICM20_FIFO_EN, 0x00);
}
/* 打开设备 */
static int icm20608_open(struct inode *inode, struct file *filp)
{
filp->private_data = &icm20608; /* 设置私有数据 */
return 0;
}
/* 从设备读取数据 */
static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
signed int data[7]; /* 存放读取到的数据 */
long ret = 0;
struct icm20608_dev *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;
ret = copy_to_user(buf, data, sizeof(data)); // 发送给用户空间
return 0;
}
/* 往设备写数据 */
static ssize_t icm20608_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
printk("no write operation!\r\n");
return 0;
}
/* 释放设备 */
static int icm20608_release(struct inode *inode, struct file *filp)
{
//printk("icm20608 release!\r\n");
return 0;
}
/* 设备操作函数结构体 */
static struct file_operations icm20608_fops = {
.owner = THIS_MODULE,
.open =icm20608_open,
.read =icm20608_read,
.write =icm20608_write,
.release =icm20608_release,
};
/* spi驱动的probe函数 ,当驱动与设备匹配以后此函数就会执行 */
static int icm20608_probe(struct spi_device *client)
{
int ret = 0;
printk("icm20608 driver and device has match!\r\n"); // 提示信息
/* 1. 创建设备号 */
if(icm20608.major){ // 定义了设备号
icm20608.devid = MKDEV(icm20608.major, 0 ); // 根据主设备号和次设备号合成设备号
register_chrdev_region(icm20608.devid, ICM20608_CNT, ICM20608_NAME); // 注册设备号
}else{ // 没有定义设备号 动态生成
alloc_chrdev_region(&icm20608.devid,0,ICM20608_CNT, ICM20608_NAME ); // 申请设备号
icm20608.major = MAJOR(icm20608.devid); // 获取主设备号
icm20608.minor = MINOR(icm20608.devid); // 获取次设备号
}
printk("icm20608 major = %d,minor = %d\r\n",icm20608.major, icm20608.minor); // 打印主设备号和次设备号
/* 2. 初始化 cdev */
icm20608.cdev.owner = THIS_MODULE;
cdev_init(&icm20608.cdev, &icm20608_fops); // 初始化cdev
/* 3. 添加cdev */
cdev_add(&icm20608.cdev, icm20608.devid, ICM20608_CNT ); // 向linux系统添加cdev
/* 自动创建设备节点文件 */
/* 4. 创建类 */
icm20608.class = class_create(THIS_MODULE, ICM20608_NAME); // 创建类
if(IS_ERR(icm20608.class)){
return PTR_ERR(icm20608.class);
}
/* 创建设备 */
icm20608.device = device_create(icm20608.class, NULL, icm20608.devid, NULL, ICM20608_NAME);
if(IS_ERR(icm20608.device)){
return PTR_ERR(icm20608.device);
}
/* 获取设备树中的 spi 节点*/
/* 1. 获取设备节点 */
icm20608.nd = of_find_node_by_path("/soc/aips-bus@02000000/spba-bus@02000000/ecspi@02010000"); // 通过绝对路径查找设备节点
if(icm20608.nd == NULL){
printk("ecspi3 node no find!\r\n");
return -EINVAL; /* 无效参数 不知道这个返回值是啥意思,我觉得返回一个负数就可以,这个值是23,不知道有没有处理*/
}
/* 2. 获取设备树中的gpio属性 得到cs片选所使用的gpio编号 */
icm20608.cs_gpio = of_get_named_gpio(icm20608.nd, "cs-gpio", 0); // 最后一个参数用于指定下标
if(icm20608.cs_gpio < 0 ){
printk("can't get cs-gpio\r\n");
return -EINVAL; /* 无效参数 不知道这个返回值是啥意思,我觉得返回一个负数就可以,这个值是23,不知道有没有处理*/
}
printk("cs-gpio num = %d \r\n", icm20608.cs_gpio); // 打印获取的cs-gpio属性值
/* 申请 IO */
ret = gpio_request(icm20608.cs_gpio, "cs0'");
if(ret < 0){
printk("request gpio failed!\r\n");
}
/* 3. 设置GPIO1_IO03为输出,并且输出高电平,默认关闭cs灯 */
ret = gpio_direction_output(icm20608.cs_gpio, 1);
if(ret < 0){
printk("can't set gpio!\r\n");
}
/* 初始化 spi_device*/
client->mode = SPI_MODE_0; /* MODE0, CPOL=0,CPHA=0*/
spi_setup(client);
icm20608.private_date = client; /* 私有数据为当前设备 */
/* 初始化 icm20608 内部寄存器 */
icm20608_reginit();
return 0;
}
/* spi驱动后的remove函数 */
static int icm20608_remove(struct spi_device *client)
{
/* 注销设备驱动 */
cdev_del(&icm20608.cdev); /* 删除 cdev */
unregister_chrdev_region(icm20608.devid, ICM20608_CNT ); /* 注销设备号 */
device_destroy(icm20608.class, icm20608.devid); /* 注销设备 */
class_destroy(icm20608.class); /* 注销类 */
printk("icm20608 drive unregsister ok !\r\n");
return 0;
}
/* 传统匹配方式ID列表 */
static const struct spi_device_id icm20608_id[] = {
{"luatao,icm20608", 0},
{}
};
/* 匹配列表 */
static const struct of_device_id icm20608_of_match[] = {
{.compatible = "luatao,icm20608"},
{/* Sentinel */}
};
/* spi驱动结构体 */
static struct spi_driver icm20608_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "icm20608", /* 驱动名字 用于和设备匹配 适用于没有设备树的情况*/
.of_match_table =icm20608_of_match, /* 设备树匹配列表 */
},
.probe =icm20608_probe,
.remove =icm20608_remove,
.id_table = icm20608_id, /* id配置列表 */
};
/* 驱动入口函数 */
static int __init icm20608_init(void)
{
return spi_register_driver(&icm20608_driver);
}
/* 驱动出口函数 */
static void __exit icm20608_exit(void)
{
spi_unregister_driver(&icm20608_driver);
}
/* 加载驱动入口和出口函数 */
module_init(icm20608_init);
module_exit(icm20608_exit);
/* LICENSE 和 AUTHOR 信息*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("luatao");
三、应用程序
icm20608App.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
/**
* file name:icm20608App
* date: 2021-08-15 21:21
* version:1.0
* author:luatao
* describe:icm20608测试APP
* 执行命令:./icm20608App 读取按键值
*/
/* 主程序 */
int main(int argc, char *argv[])
{
char *filename; // 可执行文件名
int fd,ret = 0; // fd: 文件句柄 ret:函数操作返回值
unsigned short databuf[7]; // 读出来的数据
signed int gyro_x_adc, gyro_y_adc, gyro_z_adc, accel_x_adc, accel_y_adc, accel_z_adc, temp_adc; // 实际的数据
float gyro_x_act, gyro_y_act, gyro_z_act, accel_x_act, accel_y_act, accel_z_act, temp_act; // 实际的数据 浮点数
/* 先判断输入的参数 */
if(argc != 2){ // 本身文件名带1个 执行文件1个
printf("parameter error!\r\n");
return -1;
}
/* 分析参数 ,提取有用的信息 */
filename = argv[1]; // 可执行文件名
/* 打开key文件 */
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_x_act = (float)(accel_x_adc) / 2048;
accel_x_act = (float)(accel_x_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);
}
sleep(1); /* 延时1s*/
}
/* 关闭文件 */
ret = close(fd);
if(ret < 0){
printf("can't close file %s \r\n", filename);
return -1;
}
return 0;
}
四、测试
加载驱动:
运行程序:
卸载驱动: