目录

概述

1 硬件接口

2 添加ICM20608的设备树信息

2.1 使用i.MX Pins Tool v6配置SPI引脚

2.2 配置设备树

2.3 编译.dts 

2.4 在板卡中更新设备树

3 编写驱动程序

3.1 创建设备匹配表

3.2 重要函数介绍

3.2.1 初始化函数

3.2.2 写寄存器函数

3.2.3 读寄存器函数

3.2.3 完整驱动代码

3.2.3.1 驱动代码程序

3.2.3.1 驱动代码程序Makefile

4 测试驱动安装

4.1 编译驱动程序

4.2 在板卡上安装驱动

 5 验证和测试

5.1 编写测试程序

5.1.1 测试程序源代码

5.1.2 测试程序Makefile 

 5.2 运行测试代码

5.3 结论

概述

        本文详细介绍了如何在Linux platform tree框架下,编写一个基于SPI接口的驱动程序。笔者使用ICM20606芯片,应用SPI工作方式操作该芯片,实现其驱动程序。还编写了一个测试程序,并在板卡上运行测试程序。验证驱动的可靠性。

源代码地址: ICM20608linux环境下的驱动程序资源-CSDN文库

                       ICM20608linux环境下的测试程序资源-CSDN文库

1 硬件接口

     本文主要使用SPI总线驱动ICM20608芯片,如下内容会详细介绍设备树更新,驱动代码的实现等内容。由原理图可得,ICM20608与MCU的对应关系如下:

ICM20608MCU注释SDO MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 主设备输入、从设备输出线SDI MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 主设备输出、从设备输入线SCLK MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 时钟线CS MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 从设备选择线INTJTAG_MOD_SP_TX中断引脚

板卡上ICM-20608的原理图

2 添加ICM20608的设备树信息

2.1 使用i.MX Pins Tool v6配置SPI引脚

使用i.MX Pins Tool v6配置引脚,配置对应的引脚为SPI,配置参数如下:

pinctrl_ecspi3: ecspi3grp {

fsl,pins = <

MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x100b1 /* MISO*/

MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x100b1 /* MOSI*/

MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x100b1 /* CLK */

MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x100b0 /* CS */

>;

};

在.dts文件中的位置为:

2.2 配置设备树

配置设备树的目的,是将SPI的工作IO引脚和外围的设备对应起来,方便在驱动程序中匹配该参数信息。在.dts文件中添加如下代码:

&ecspi3 {

fsl,spi-num-chipselects = <1>;

cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>;

pinctrl-names = "default";

pinctrl-0 = <&pinctrl_ecspi3>;

status = "okay";

spidev: icm20608@0 {

compatible = "atk,icm20608";

spi-max-frequency = <8000000>;

reg = <0>;

};

};

spi的配置参数在.dts文件中的位置为:

2.3 编译.dts 

使用命令编译代码。然后将生成的.dtb文件发送到NFS的共享目录下,方便在板卡使用该文件

make dtbs

编译后,得到了.dtb

2.4 在板卡中更新设备树

1) 将NFS中共享的设备树文件copy到运行目录中

2)reboot板卡后,内核会重新读取.dtb文件。然后在/sys/bus/spi/devices/spi2.0目录下查看板卡device tree,使用如下命令:

cd /sys/bus/spi/devices/spi2.0/

ls -l

得到节点信息如下:

3 编写驱动程序

3.1 创建设备匹配表

其目的是和设备树中参数对应起来

static const struct of_device_id atk_dl6y2c_icm20608[] = {

{ .compatible = "atk,icm20608" },

{ },

};

static const struct spi_device_id icm20608_id[] = {

{ "xxxxyyy", (kernel_ulong_t)NULL },

{ /* END OF LIST */ }

};

/* 1. 定义platform_driver */

static struct i2c_driver icm20608_driver = {

.probe = icm20608_probe,

.remove = icm20608_remove,

.driver = {

.name = "atk_icm20608",

.of_match_table = atk_dl6y2c_icm20608,

},

.id_table = icm20608_id,

};

在.c 文件中的代码信息如下: 

3.2 重要函数介绍

3.2.1 初始化函数

在初始化函数中,通过调用spi_setup 来初始化spi接口,该函数放在prope接口中完成

3.2.2 写寄存器函数

第148行,写寄存器数据时,寄存器地址信息,bit-7必须为0

3.2.3 读寄存器函数

第111行,读寄存器数据时,必须传递一个寄存器地址信息,且bit-7必须为1

3.2.3 完整驱动代码

3.2.3.1 驱动代码程序

/***************************************************************

Copyright 2024-2029. All rights reserved.

文件名 : drv_17_icm20608.c

作者 : tangmingfei2013@126.com

版本 : V1.0

描述 : icm20608 驱动程序

其他 : 无

日志 : 初版V1.0 2024/1/30

使用方法:

1) 在.dts文件中定义节点信息

&ecspi3 {

fsl,spi-num-chipselects = <1>;

cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>;

pinctrl-names = "default";

pinctrl-0 = <&pinctrl_ecspi3>;

status = "okay";

spidev: icm20608@0 {

compatible = "atk,icm20608";

spi-max-frequency = <8000000>;

reg = <0>;

};

};

2) 在驱动匹配列表

static const struct of_device_id icm20608_of_match[] = {

{ .compatible = "atk,icm20608" },

{ } // Sentinel

};

***************************************************************/

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#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

#define DEVICE_NAME "icm20608" // dev/icm20608

/* icm20608dev设备结构体 */

struct icm20608stru_dev{

dev_t devid; /* 设备号 */

struct cdev cdev; /* cdev */

struct class *class; /* 类 */

struct device *device; /* 设备 */

int major; /* 主设备号 */

struct spi_device *spi;

};

struct icm20608stru_value{

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; /* 温度原始值 */

};

/* read or write icm20608 structure */

static struct icm20608stru_dev icm20608dev;

/*

icm20608 driver

*/

static int icm20608_read( unsigned char reg, unsigned char *buff, unsigned int len)

{

int ret = -1;

unsigned char txdata[1];

unsigned char * rxdata;

struct spi_message spi_msg;

struct spi_transfer *spi_t;

struct spi_device *spi = icm20608dev.spi;

spi_t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);

if(!spi_t)

{

return -ENOMEM;

}

rxdata = kzalloc(sizeof(char) * len, GFP_KERNEL);

if(!rxdata) {

goto ERROR1;

}

txdata[0] = reg | 0x80; // set bit-7 as 1 for read mode

spi_t->tx_buf = txdata;

spi_t->rx_buf = rxdata;

spi_t->len = len+1;

spi_message_init(&spi_msg);

spi_message_add_tail(spi_t, &spi_msg);

ret = spi_sync(spi, &spi_msg);

if(ret) {

goto ERROR2;

}

memcpy(buff , rxdata+1, len);

ERROR2:

kfree(rxdata);

ERROR1:

kfree(spi_t);

return ret;

}

static int icm20608_write( unsigned char reg, unsigned char *buff, unsigned int len)

{

int ret = -1;

unsigned char *txdata;

struct spi_message spi_msg;

struct spi_transfer *spi_t;

struct spi_device *spi = icm20608dev.spi;

spi_t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);

if(!spi_t) {

return -ENOMEM;

}

txdata = kzalloc(sizeof(char)+len, GFP_KERNEL);

if(!txdata) {

goto ERROR1;

}

*txdata = reg & ~0x80;

memcpy(txdata+1, buff, len);

spi_t->tx_buf = txdata;

spi_t->len = len+1;

spi_message_init(&spi_msg);

spi_message_add_tail(spi_t, &spi_msg);

ret = spi_sync(spi, &spi_msg);

if(ret) {

goto ERROR2;

}

ERROR2:

kfree(txdata);

ERROR1:

kfree(spi_t);

return ret;

}

static void icm20608_readdata(struct icm20608stru_value *dev)

{

unsigned char data[14] = { 0 };

icm20608_read(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]);

}

static void icm20608_reginit(void)

{

unsigned char value = 0;

unsigned char buff[1];

buff[0] = 0x80;

icm20608_write( ICM20_PWR_MGMT_1, buff , 1);

mdelay(50);

buff[0] = 0x01;

icm20608_write( ICM20_PWR_MGMT_1, buff , 1);

mdelay(50);

icm20608_read(ICM20_WHO_AM_I, &value, 1);

printk("ICM20608 ID = %#X\r\n", value);

buff[0] = 0x00;

icm20608_write( ICM20_SMPLRT_DIV, buff, 1);

buff[0] = 0x18;

icm20608_write( ICM20_GYRO_CONFIG, buff, 1);

buff[0] = 0x18;

icm20608_write( ICM20_ACCEL_CONFIG, buff, 1);

buff[0] = 0x04;

icm20608_write( ICM20_CONFIG, buff, 1);

buff[0] = 0x04;

icm20608_write( ICM20_ACCEL_CONFIG2, buff, 1);

buff[0] = 0x00;

icm20608_write( ICM20_PWR_MGMT_2, buff, 1);

buff[0] = 0x00;

icm20608_write( ICM20_LP_MODE_CFG, buff, 1);

buff[0] = 0x00;

icm20608_write( ICM20_FIFO_EN, buff, 1);

}

/*

linux driver 驱动接口:

实现对应的open/read/write等函数,填入file_operations结构体

*/

static ssize_t icm20608_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset)

{

int err;

signed int data[7];

struct icm20608stru_value stru_dev;

icm20608_readdata( &stru_dev );

data[0] = stru_dev.gyro_x_adc;

data[1] = stru_dev.gyro_y_adc;

data[2] = stru_dev.gyro_z_adc;

data[3] = stru_dev.accel_x_adc;

data[4] = stru_dev.accel_y_adc;

data[5] = stru_dev.accel_z_adc;

data[6] = stru_dev.temp_adc;

err = copy_to_user(buf, data, sizeof(data));

return err;

}

static int icm20608_drv_close(struct inode *node, struct file *file)

{

printk(" %s line %d \r\n", __FUNCTION__, __LINE__);

return 0;

}

static int icm20608_drv_open(struct inode *inode, struct file *filp)

{

icm20608_reginit();

return 0;

}

/*

定义driver的file_operations结构体

*/

static struct file_operations icm20608_fops = {

.owner = THIS_MODULE,

.read = icm20608_drv_read,

.open = icm20608_drv_open,

.release = icm20608_drv_close,

};

/* 1. 从platform_device获得GPIO

* 2. gpio=>irq

* 3. request_irq

*/

static int icm20608_probe( struct spi_device *spi )

{

printk("icm20608 driver and device was matched!\r\n");

/* initial spi port */

spi->mode = SPI_MODE_0; /*MODE0,CPOL=0,CPHA=0*/

spi_setup(spi);

icm20608dev.spi = spi;

/* register file_operations */

icm20608dev.major = register_chrdev( 0,

DEVICE_NAME, /* device name */

&icm20608_fops);

/* create the device class */

icm20608dev.class = class_create(THIS_MODULE, "icm20608_class");

if (IS_ERR(icm20608dev.class)) {

printk("%s line %d\n", __FUNCTION__, __LINE__);

unregister_chrdev( icm20608dev.major, DEVICE_NAME);

return PTR_ERR( icm20608dev.class );

}

/* create device */

device_create( icm20608dev.class, NULL,

MKDEV( icm20608dev.major, 0 ), NULL,

DEVICE_NAME); // device name

return 0;

}

static int icm20608_remove(struct spi_device *spi)

{

printk("%s line %d\n", __FUNCTION__, __LINE__);

device_destroy( icm20608dev.class, MKDEV( icm20608dev.major, 0));

class_destroy(icm20608dev.class);

unregister_chrdev(icm20608dev.major, DEVICE_NAME);

return 0;

}

static const struct of_device_id atk_dl6y2c_icm20608[] = {

{ .compatible = "atk,icm20608" },

{ },

};

static const struct spi_device_id icm20608_id[] = {

{ "xxxxyyy", (kernel_ulong_t)NULL },

{ /* END OF LIST */ }

};

/* 1. 定义platform_driver */

static struct spi_driver icm20608_driver = {

.probe = icm20608_probe,

.remove = icm20608_remove,

.driver = {

.name = "atk_icm20608",

.of_match_table = atk_dl6y2c_icm20608,

},

.id_table = icm20608_id,

};

/*

2. 入口函数: 注册driver

*/

static int __init icm20608_init(void)

{

printk("%s line %d\n",__FUNCTION__, __LINE__);

return spi_register_driver(&icm20608_driver);

}

/* 3. 出口函数:卸载driver

*/

static void __exit icm20608_exit(void)

{

printk("%s line %d\n", __FUNCTION__, __LINE__);

spi_unregister_driver(&icm20608_driver);

}

/*

4. 驱动入口和出口函数

*/

module_init(icm20608_init);

module_exit(icm20608_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("tangmingfei2013@126.com");

3.2.3.1 驱动代码程序Makefile

PWD := $(shell pwd)

KERNEL_DIR=/home/mftang/linux_workspace/study_atk_dl6y2c/kernel/atk-dl6u2c

ARCH=arm

CROSS_COMPILE=/home/ctools/gcc-linaro-4.9.4-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-

export ARCH CROSS_COMPILE

obj-m:= drv_17_icm20608.o

all:

$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules

clean:

rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions *.order *.symvers

4 测试驱动安装

4.1 编译驱动程序

在服务器上编译驱动程序,执行make命令后,会生成的.ko,复制该文件到NFS的共享目中,方便在板卡中使用

4.2 在板卡上安装驱动

在板卡上执行: 

insmod drv_17_icm20608.ko

可以看见打印如下信息,说明驱动程序已经安装到内核 

在dev目录下查看该设备驱动:

 5 验证和测试

      完成驱动程序编写后,并不能保证驱动程序是否能够正常工作,这时需要编程一个测试程序来验证其功能是否可靠。

5.1 编写测试程序

5.1.1 测试程序源代码

/***************************************************************

Copyright 2024-2029. All rights reserved.

文件名 : test_17_icm20608.c

作者 : tangmingfei2013@126.com

版本 : V1.0

描述 : 测试icm20608驱动程序

其他 : 无

日志 : 初版V1.0 2024/02/22

***************************************************************/

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#define DEV_FILE "/dev/icm20608"

struct icm20608stru_value{

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; /* 温度原始值 */

};

int main(void)

{

int count = 0;

int fd, ret;

unsigned char databuf[7];

struct icm20608stru_value stru_value;

float gyro_x_act, gyro_y_act, gyro_z_act;

float accel_x_act, accel_y_act, accel_z_act;

float temp_act;

fd = open(DEV_FILE, O_RDWR);

if (fd == -1){

printf("can not open file: %s \n", DEV_FILE);

return -1;

}

while (count < 10000 ) {

ret = read(fd, databuf, sizeof(databuf));

if(ret == 0)

{

stru_value.gyro_x_adc = databuf[0];

stru_value.gyro_y_adc = databuf[1];

stru_value.gyro_z_adc = databuf[2];

stru_value.accel_x_adc = databuf[3];

stru_value.accel_y_adc = databuf[4];

stru_value.accel_z_adc = databuf[5];

stru_value.temp_adc = databuf[6];

/* 计算实际值 */

gyro_x_act = (float)(stru_value.gyro_x_adc) / 16.4;

gyro_y_act = (float)(stru_value.gyro_y_adc) / 16.4;

gyro_z_act = (float)(stru_value.gyro_z_adc) / 16.4;

accel_x_act = (float)(stru_value.accel_x_adc) / 2048;

accel_y_act = (float)(stru_value.accel_y_adc) / 2048;

accel_z_act = (float)(stru_value.accel_z_adc) / 2048;

temp_act = ((float)(stru_value.temp_adc) - 25 ) / 326.8 + 25;

printf("\r\n %04d count value:\r\n", count);

printf("gx = %d, gy = %d, gz = %d\r\n", stru_value.gyro_x_adc,

stru_value.gyro_y_adc,

stru_value.gyro_z_adc);

printf("ax = %d, ay = %d, az = %d\r\n", stru_value.accel_x_adc,

stru_value.accel_y_adc,

stru_value.accel_z_adc);

printf("temp = %d\r\n", stru_value.temp_adc);

printf(" %04d user value: ", count);

printf("gx = %.2f°/S, gy = %.2f°/S, gz = %.2f°/S\r\n", gyro_x_act,

gyro_y_act,

gyro_z_act);

printf("ax = %.2fg, ay = %.2fg, az = %.2fg\r\n", accel_x_act,

accel_y_act,

accel_z_act);

printf("temp = %.2f°C\r\n", temp_act);

}

usleep(100000); /*100ms */

count++;

}

close(fd);

return 0;

}

5.1.2 测试程序Makefile 

CFLAGS= -Wall -O2

CC=/home/ctools/gcc-linaro-4.9.4-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc

STRIP=/home/ctools/gcc-linaro-4.9.4-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip

test_17_icm20608: test_17_icm20608.o

$(CC) $(CFLAGS) -o test_17_icm20608 test_17_icm20608.o

$(STRIP) -s test_17_icm20608

clean:

rm -f test_17_icm20608 test_17_icm20608.o

 5.2 运行测试代码

编译测试代码,将生成的可执行文件共享在NF目录中,方便在板卡中执行程序

转动板卡,观察sensor的数据变化

5.3 结论

 通过运行测试程序,App调用驱动程序,能正确的从sensor中读取数据 

推荐阅读

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。