正点原子imx6ull-mini-Linux驱动之Regmap API 实验
我们在前面学习 I2C 和 SPI 驱动的时候,针对 I2C 和 SPI 设备寄存器的操作都是通过相关 的 API 函数进行操作的。这样 Linux 内核中就会充斥着大量的重复、冗余代码,但是这些本质 上都是对寄存器的操作,所以为了方便内核开发人员统一访问 I2C/SPI 设备的时候,为此引入 了 Regmap 子系统,本章我们就来学习一下如何使用 RegmapAPI 函数来读写 I2C/SPI 设备寄存 器
1:Regmap API 简介
1.1:什么是 Regmap
Linux 下大部分设备的驱动开发都是操作其内部寄存器,比如 I2C/SPI 设备的本质都是一样 的,通过 I2C/SPI 接口读写芯片内部寄存器。芯片内部寄存器也是同样的道理,比如 I.MX6ULL 的 PWM、定时器等外设初始化,最终都是要落到寄存器的设置上。 Linux 下使用 i2c_transfer 来读写 I2C 设备中的寄存器,SPI 接口的话使用 spi_write/spi_read 等。I2C/SPI 芯片又非常的多,因此 Linux 内核里面就会充斥了大量的 i2c_transfer 这类的冗余 代码,再者,代码的复用性也会降低。比如 icm20608 这个芯片既支持 I2C 接口,也支持 SPI 接 口。假设我们在产品设计阶段一开始将 icm20608 设计为 SPI 接口,但是后面发现 SPI 接口不够 用,或者 SOC 的引脚不够用,我们需要将 icm20608 改为 I2C 接口。这个时候 icm20608 的驱动 就要大改,我们需要将 SPI 接口函数换为 I2C 的,工作量比较大。 基于代码复用的原则,Linux 内核引入了 regmap 模型,regmap 将寄存器访问的共同逻辑抽 象出来,驱动开发人员不需要再去纠结使用 SPI 或者 I2C 接口 API 函数,统一使用 regmapAPI 函数。这样的好处就是统一使用 regmap,降低了代码冗余,提高了驱动的可以移植性。regmap 模型的重点在于: 通过 regmap 模型提供的统一接口函数来访问器件的寄存器,SOC 内部的寄存器也可以使 用 regmap 接口函数来访问。 regmap 是 Linux 内核为了减少慢速 I/O 在驱动上的冗余开销,提供了一种通用的接口来操 作硬件寄存器。另外,regmap 在驱动和硬件之间添加了 cache,降低了低速 I/O 的操作次数,提 高了访问效率,缺点是实时性会降低。 什么情况下会使用 regmap:
①、硬件寄存器操作,比如选用通过 I2C/SPI 接口来读写设备的内部寄存器,或者需要读 写 SOC 内部的硬件寄存器。
②、提高代码复用性和驱动一致性,简化驱动开发过程。
③、减少底层 I/O 操作次数,提高访问效率。 本章教程我们就来重点学习一下如何将《第六十二章 Linux SPI 驱动实验》中编写的 SPI 接口的 icm20608 驱动改为使用 regmap API。
1.2:Regmap 驱动框架
1.2.1:regmap 框架结构
regmap 驱动框架如下图所示:
regmap 框架分为三层:
①、底层物理总线:regmap 就是对不同的物理总线进行封装,目前 regmap 支持的物理总 线有 i2c、i3c、spi、mmio、sccb、sdw、slimbus、irq、spmi 和 w1。
②、regmap 核心层,用于实现 regmap,我们不用关心具体实现。
③、regmapAPI 抽象层,regmap 向驱动编写人员提供的 API 接口,驱动编写人员使用这些 API 接口来操作具体的芯片设备,也是驱动编写人员重点要掌握的。
2、regmap 结构体 Linux 内 核 将 regmap 框 架 抽 象 为 regmap 结 构 体 , 这 个 结 构 体 定 义 在 文 件 drivers/base/regmap/internal.h 中,结构体内容如下(有缩减):
51 struct regmap {
52 union {
53 struct mutex mutex;
54 struct {
55 spinlock_t spinlock;
56 unsigned long spinlock_flags;
57 };
58 };
59 regmap_lock lock;
60 regmap_unlock unlock;
61 void *lock_arg; /* This is passed to lock/unlock functions */
62
63 struct device *dev; /* Device we do I/O on */
64 void *work_buf; /* Scratch buffer used to format I/O */
65 struct regmap_format format; /* Buffer format */
66 const struct regmap_bus *bus;
67 void *bus_context;
68 const char *name;
69
70 bool async;
71 spinlock_t async_lock;
72 wait_queue_head_t async_waitq;
73 struct list_head async_list;
74 struct list_head async_free;
75 int async_ret;
......
89 unsigned int max_register;
90 bool (*writeable_reg)(struct device *dev, unsigned int reg);
91 bool (*readable_reg)(struct device *dev, unsigned int reg);
92 bool (*volatile_reg)(struct device *dev, unsigned int reg);
93 bool (*precious_reg)(struct device *dev, unsigned int reg);
94 const struct regmap_access_table *wr_table;
95 const struct regmap_access_table *rd_table;
96 const struct regmap_access_table *volatile_table;
97 const struct regmap_access_table *precious_table;
98
99 int (*reg_read)(void *context, unsigned int reg,
unsigned int *val);
100 int (*reg_write)(void *context, unsigned int reg,unsigned int val);
......
147 struct rb_root range_tree;
148 void *selector_work_buf; /* Scratch buffer used for selector */
149 };
要使用 regmap,肯定要先给驱动分配一个具体的 regmap 结构体实例,一会讲解如何分配 regmap 实例。大家可以看到示例代码 74.1.2.1 中第 90~100 行有很多的函数以及 table,这些需 要驱动编写人员根据实际情况选择性的初始化,regmap 的初始化通过结构体 regmap_config 来 完成。
1.2.2:regmap_config 结构体
顾名思义,regmap_config 结构体就是用来初始化 regmap 的,这个结构体也定义在 include/linux/regmap.h 文件中,结构体内容如下:
186 struct regmap_config {
187 const char *name;
188
189 int reg_bits;
190 int reg_stride;
191 int pad_bits;
192 int val_bits;
193
194 bool (*writeable_reg)(struct device *dev, unsigned int reg);
195 bool (*readable_reg)(struct device *dev, unsigned int reg);
196 bool (*volatile_reg)(struct device *dev, unsigned int reg);
197 bool (*precious_reg)(struct device *dev, unsigned int reg);
198 regmap_lock lock;
199 regmap_unlock unlock;
200 void *lock_arg;
201
202 int (*reg_read)(void *context, unsigned int reg, unsigned int
*val);
203 int (*reg_write)(void *context, unsigned int reg, unsigned int
val);
204
205 bool fast_io;
206
207 unsigned int max_register;
208 const struct regmap_access_table *wr_table;
209 const struct regmap_access_table *rd_table;
210 const struct regmap_access_table *volatile_table;
211 const struct regmap_access_table *precious_table;
212 const struct reg_default *reg_defaults;
213 unsigned int num_reg_defaults;
214 enum regcache_type cache_type;
215 const void *reg_defaults_raw;
216 unsigned int num_reg_defaults_raw;
217
218 u8 read_flag_mask;
219 u8 write_flag_mask;
220
221 bool use_single_rw;
222 bool can_multi_write;
223
224 enum regmap_endian reg_format_endian;
225 enum regmap_endian val_format_endian;
226
227 const struct regmap_range_cfg *ranges;
228 unsigned int num_ranges;
229 };
Linux 内核里面已经对 regmap_config 各个成员变量进行了详细的讲解,这里我们只看一些 比较重要的:
第 187 行 name:名字。
第 189 行 reg_bits:寄存器地址位数,必填字段。
第 190 行 reg_stride:寄存器地址步长。
第 191 行 pad_bits:寄存器和值之间的填充位数。
第 192 行 val_bits:寄存器值位数,必填字段。
第 194 行 writeable_reg:可选的可写回调函数,寄存器可写的话此回调函数就会被调用, 并返回 true。
第 195 行 readable_reg:可选的可读回调函数,寄存器可读的话此回调函数就会被调用,并 返回 true。
第 196 行 volatile_reg:可选的回调函数,当寄存器值不能缓存的时候此回调函数就会被调 用,并返回 true。
第 197 行 precious_reg:当寄存器值不能被读出来的时候此回调函数会被调用,比如很多中 断状态寄存器读清零,读这些寄存器就可以清除中断标志位,但是并没有读出这些寄存器内部 的值。
第 202 行 reg_read:可选的读操作回调函数,所有读寄存器的操作此回调函数就会执行。
第 203 行 reg_write:可选的写操作回调函数,所有写寄存器的操作此回调函数就会执行。
第 205 行 fast_io:快速 I/O,使用 spinlock 替代 mutex 来提升锁性能。
第 207 行 max_register:有效的最大寄存器地址,可选。
第 208 行 wr_table:可写的地址范围,为 regmap_access_table 结构体类型。后面的 rd_table、 volatile_table、precious_table、wr_noinc_table 和 rd_noinc_table 同理。
第 212 行 reg_defaults:寄存器模式值,为 reg_default 结构体类型,此结构体有两个成员变 量:reg 和 def,reg 是寄存器地址,def 是默认值。
第 216 行 num_reg_defaults:默认寄存器表中的元素个数。
第 218 行 read_flag_mask:读标志掩码。
第 219 行 write_flag_mask:写标志掩码。 关于 regmap_config 结构体成员变量就介绍这些,其他没有介绍的自行查阅 Linux 内核中的 相关描述。
1.3:Regmap 操作函数
1.3.1:Regmap 申请与初始化
前面说了,regmap 支持多种物理总线,比如 I2C 和 SPI,我们需要根据所使用的接口来选 择合适的 regmap 初始化函数。Linux 内核提供了针对不同接口的 regmap 初始化函数,SPI 接口 初始化函数为 regmap_init_spi,函数原型如下:
struct regmap * regmap_init_spi(struct spi_device *spi,const struct regmap_config *config)
函数参数和返回值含义如下:
spi:需要使用 regmap 的 spi_device。
config:regmap_config 结构体,需要程序编写人员初始化一个 regmap_config 实例,然后将 其地址赋值给此参数。
返回值:申请到的并进过初始化的 regmap
I2C 接口的 regmap 初始化函数为 regmap_init_i2c,函数原型如下:
struct regmap * regmap_init_i2c(struct i2c_client *i2c,const struct regmap_config *config)
函数参数和返回值含义如下:
i2c:需要使用 regmap 的 i2c_client。
config:regmap_config 结构体,需要程序编写人员初始化一个 regmap_config 实例,然后将 其地址赋值给此参数。
返回值:申请到的并进过初始化的 regmap。 还有很多其他物理接口对应的 regmap 初始化函数,这里就不介绍了,大家直接查阅 Linux 内核即可,基本和 SPI/I2C 的初始化函数相同 在退出驱动的时候需要释放掉申请到的 regmap,不管是什么接口,全部使用 regmap_exit 这 个函数来释放 regmap,函数原型如下:
void regmap_exit(struct regmap *map)
函数参数和返回值含义如下:
map:需要释放的 regmap
返回值:无。
我们一般会在 probe 函数中初始化 regmap_config,然后申请并初始化 regmap。
1.3.2:regmap 设备访问 API 函数
不管是 I2C 还是 SPI 等接口,还是 SOC 内部的寄存器,对于寄存器的操作就两种:读和 写。regmap 提供了最核心的两个读写操作:regmap_read 和 regmap_write。这两个函数分别用来 读/写寄存器,regmap_read 函数原型如下:
int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val)
函数参数和返回值含义如下:
map:要操作的 regmap。
reg:要读的寄存器。
val:读到的寄存器值。
返回值:0,读取成功;其他值,读取失败。 regmap_write 函数原型如下:
int regmap_write(struct regmap *map, unsigned int reg, unsigned int val)
函数参数和返回值含义如下:
map:要操作的 regmap。
reg:要写的寄存器。
val:要写的寄存器值。
返回值:0,写成功;其他值,写失败。
在 regmap_read 和 regmap_write 的基础上还衍生出了其他一些 regmap 的 API 函数,首先是 regmap_update_bits 函数,看名字就知道,此函数用来修改寄存器指定的 bit,函数原型如下:
int regmap_update_bits (struct regmap *map,
unsigned int reg,unsigned int mask,
unsigned int val,)
函数参数和返回值含义如下: map:要操作的 regmap。 reg:要操作的寄存器。 mask:掩码,需要更新的位必须在掩码中设置为 1。 val:需要更新的位值。 返回值:0,写成功;其他值,写失败。 比如要将寄存器的 bit1 和 bit2 置 1,那么 mask 应该设置为 0X00000011,此时 val 的 bit1 和 bit2 应该设置为 1,也就是 0Xxxxxxx11。如果要清除寄存器的 bit4 和 bit7,那么 mask 应该 设置为 0X10010000,val 的 bit4 和 bit7 设置为 0,也就是 0X0xx0xxxx。 接下来看一下 regmap_bulk_read 函数,此函数用于读取多个寄存器的值,函数原型如下:
int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val,size_t val_count)
函数参数和返回值含义如下:
map:要操作的 regmap。
reg:要读取的第一个寄存器。
val:读取到的数据缓冲区。
val_count:要读取的寄存器数量。
返回值:0,写成功;其他值,读失败。 另外也有多个寄存器写函数 regmap_bulk_write,函数原型如下:
int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val,size_t val_count)
函数参数和返回值含义如下:
map:要操作的 regmap。
reg:要写的第一个寄存器。
val:要写的寄存器数据缓冲区。
val_count:要写的寄存器数量。
返回值:0,写成功;其他值,读失败。
关于 regmap 常用到 API 函数就讲解到这里,还有很多其他功能的 API 函数,大家自行查 阅 Linux 内核即可,内核里面对每个 API 函数都有详细的讲解。
1.4:regmap_config 掩码设置
结构体 regmap_config 里面有三个关于掩码的成员变量:read_flag_mask 和 write_flag_mask, 这二个掩码非常重要,本节我们来学习一下如何使用这三个掩码。我们在学习 icm20608 的时候 讲过了,icm20608 支持 i2c 和 spi 接口,但是当使用 spi 接口的时候,读取 icm20608 寄存器的 时候地址最高位必须置 1,写内部寄存器的是时候地址最高位要设置为 0。因此这里就涉及到对 寄存器地址最高位的操作,在《第六十二章 SPI 驱动实验》中我们在使用 SPI 接口函数读取 icm20608 内部寄存器的时候手动将寄存器地址的最高位置 1,代码如下所示:
1 static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg,
void *buf, int len)
2 {
3
......
21 txdata[0] = reg | 0x80; /* 写数据的时候首寄存器地址 bit7 要置 1 */
22 t->tx_buf = txdata; /* 要发送的数据 */
23 t->rx_buf = rxdata; /* 要读取的数据 */
24 t->len = len+1; /* t->len=发送的长度+读取的长度 */
25 spi_message_init(&m); /* 初始化 spi_message */
26 spi_message_add_tail(t, &m);
27 ret = spi_sync(spi, &m); /* 同步发送 */
......
39 return ret;
40 }
示例代码 74.1.4.1 就是标准的 SPI 驱动,其中第 21 行将寄存器的地址 bit7 置 1,表示这是 一个读操作。 当我们使用 regmap 的时候就不需要手动将寄存器地址的 bit7 置 1,在初始化 regmap_config 的时候直接将 read_flag_mask 设置为 0X80 即可,这样通过 regmap 读取 SPI 内部寄存器的时候 就会将寄存器地址与 read_flag_mask 进行或运算,结果就是将 bit7 置 1,但是整个过程不需要 我们来操作,全部由 regmap 框架来完成的。 同理 write_flag_mask 用法也一样的,只是 write_flag_mask 用于写寄存器操作。 打开 regmap-spi.c 文件,这个文件就是 regmap 的 spi 总线文件,找到如下所示内容:
105 static struct regmap_bus regmap_spi = {
106 .write = regmap_spi_write,
107 .gather_write = regmap_spi_gather_write,
108 .async_write = regmap_spi_async_write,
109 .async_alloc = regmap_spi_async_alloc,
110 .read = regmap_spi_read,
111 .read_flag_mask = 0x80,
112 .reg_format_endian_default = REGMAP_ENDIAN_BIG,
113 .val_format_endian_default = REGMAP_ENDIAN_BIG,
114 };
......
125 struct regmap *regmap_init_spi(struct spi_device *spi,
126 const struct regmap_config *config)
127 {
128 return regmap_init(&spi->dev, ®map_spi, &spi->dev, config);
129 }
第 105~114 行初始化了一个 regmap_bus 实例:regmap_spi,我们重点看一下第 111 行中 read_flag_mask 默认为 0X80。注意,这里是将 regmap_bus 的 read_flag_mask 成员变量设置为 0X80。regmap_bus 结构体大家自行查看一下,这里就不讲了。
第 125~129 行为 regmap_init_spi 函数,前面说了要想在 spi 总线中使用 regmap 框架,首先 要使用 regmap_init_spi 函数用于并申请一个 SPI 总线的 regmap。
从第 128 行可以看出 regmap_init_spi 函数只是对 regmap_init 的简单封装,因此最终完成 regmap 申请并初始化的是 regmap_init 函数。在 regmap_init 函数中找到如下所示内容:
598 if (config->read_flag_mask || config->write_flag_mask) {
599 map->read_flag_mask = config->read_flag_mask;
600 map->write_flag_mask = config->write_flag_mask;
601 } else if (bus) {
602 map->read_flag_mask = bus->read_flag_mask;
603 }
第 598~601 行就是用 regmap_config 中的读写掩码来初始化 regmap_bus 中的掩码。由于 regmap_spi 默认将 read_flag_mask 设置为 0X80,当你所使用的 SPI 设备不需要读掩码,在初始 化 regmap_config 的时候一定要将 read_flag_mask 设置为 0X00。 regmap 框架就讲解到这里,接下来学习如何将《第六十二章 Linux SPI 驱动实验》中编写 的 icm20608 驱动改为 regmap 框架。
2:实验程序编写
本实验不需要修改设备树,直接使用《第六十二章 Linux SPI 驱动实验》中的 ICM20608 设 备树。注意!第六十二章以前版本教程里面没有使用 Linux 内核自带的片选信号,新版本教程 (V1.6 及以后版本以后)改为了使用内部片选信号。因此如果你的 ICM20608 设备树节点按照以 前教程编写的,在这里请将其参考新版教程修改。 本实验对 应的例程路径为: 开发 板光盘 -> 01、程序源 码 ->02、 Linux 驱动例 程 ->26_regmap->spi。
2.1:修改设备结构体,添加 regmap 和 regmap_config
regmap 框架的核心就是 regmap 和 regmap_config 结构体,我们一般都是在自定义的设备结 构体里面添加这两个类型的成员变量,所以我们首先在 icm20608_dev 结构体里面添加 regmap 和 regmap_config,修改完成以后的 icm20608_dev 结构体内容如下:
1 struct icm20608_dev {
2 struct spi_device *spi; /* spi 设备 */
3 dev_t devid; /* 设备号 */
4 struct cdev cdev; /* cdev */
5 struct class *class; /* 类 */
6 struct device *device; /* 设备 */
7 struct device_node *nd; /* 设备节点 */
8 signed int gyro_x_adc; /* 陀螺仪 X 轴原始值 */
9 signed int gyro_y_adc; /* 陀螺仪 Y 轴原始值 */
10 signed int gyro_z_adc; /* 陀螺仪 Z 轴原始值 */
11 signed int accel_x_adc; /* 加速度计 X 轴原始值 */
12 signed int accel_y_adc; /* 加速度计 Y 轴原始值 */
13 signed int accel_z_adc; /* 加速度计 Z 轴原始值 */
14 signed int temp_adc; /* 温度原始值 */
15 struct regmap *regmap;
16 struct regmap_config regmap_config;
17 };
第 15 行,regmap 指针变量,regmap 我们需要使用 regmap_init_spi 函数来申请和初始化, 所以这里是指针类型。 第 16 行,regmap_config 结构体成员变量,从来配置 regmap。
2.2:初始化 regmap
一般在 probe 函数中初始化 regmap,本章节就是 icm20608_probe 函数,初始化内容如下:
1 static int icm20608_probe(struct spi_device *spi)
2 {
3 int ret;
4 struct icm20608_dev *icm20608dev;
5
6 /* 分配 icm20608dev 对象的空间 */
7 icm20608dev = devm_kzalloc(&spi->dev, sizeof(*icm20608dev),
GFP_KERNEL);
8 if(!icm20608dev)
9 return -ENOMEM;
10
11 /* 初始化 regmap_config 设置 */
12 icm20608dev->regmap_config.reg_bits = 8; /* 寄存器长度 8bit */
13 icm20608dev->regmap_config.val_bits = 8; /* 值长度 8bit */
14 icm20608dev->regmap_config.read_flag_mask = 0x80; /* 读掩码 */
15
16 /* 初始化 IIC 接口的 regmap */
17 icm20608dev->regmap = regmap_init_spi(spi,
&icm20608dev->regmap_config);
18 if (IS_ERR(icm20608dev->regmap)) {
19 return PTR_ERR(icm20608dev->regmap);
20 }
21
22 /* 注册字符设备驱动 */
23 /* 1、创建设备号 */
24 ret = alloc_chrdev_region(&icm20608dev->devid, 0, ICM20608_CNT,ICM20608_NAME);
25 if(ret < 0) {
26 pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n",
ICM20608_NAME, ret);
27 goto del_regmap;
28 }
......
61
62 return 0;
63 destroy_class:
64 device_destroy(icm20608dev->class, icm20608dev->devid);
65 del_cdev:
66 cdev_del(&icm20608dev->cdev);
67 del_unregister:
68 unregister_chrdev_region(icm20608dev->devid, ICM20608_CNT);
69 del_regmap:
70 regmap_exit(icm20608dev->regmap);
71 return -EIO;
72 }
第 11~14 行,regmap_config 的初始化,icm20608 的寄存器地址地址长度为 8bit,寄存器值 也是 8bit,因此 reg_bits 和 val_bits 都设置为 8。由于 icm20608 通过 SPI 接口读取的时候地址寄 存器最高位要设置为 1,因此 read_flag_mask 设置为 0X80。
第 17 行,通过 regmap_init_spi 函数来申请并初始化 SPI 总线的 regmap。
第 70 行,如果要删除 regmap 就使用 regmap_exit 函数。 同理,在 remove 函数中就要删除 probe 里面申请的 regmap,icm20608_remove 函数内容如 下:
1 static int icm20608_remove(struct spi_device *spi)
2 {
3 struct icm20608_dev *icm20608dev = spi_get_drvdata(spi);
4
......
12 /* 4、注销类 */
13 class_destroy(icm20608dev->class);
14 /* 5、删除 regmap */
15 regmap_exit(icm20608dev->regmap);
16 return 0;
17 }
第 17 行,卸载驱动的时候使用 regmap_exit 删除掉 probe 函数中申请的 regmap。
2.3:读写设备内部寄存器
regmap 已经设置好了,接下来就是使用 regmap API 函数来读写 icm20608 内部寄存器了。 以前我们使用 spi 驱动框架编写读写函数,现在直接使用 regmap_read、regmap_write 的函数即 可,修改后的 icm20608 内部寄存器读写函数如下:
1 /*
2 * @description : 读取 icm20608 指定寄存器值,读取一个寄存器
3 * @param – dev : icm20608 设备
4 * @param – reg : 要读取的寄存器
5 * @return : 读取到的寄存器值
6 */
7 static unsigned char icm20608_read_onereg(struct icm20608_dev *dev,
u8 reg)
8 {
9 u8 ret;
10 unsigned int data;
11
12 ret = regmap_read(dev->regmap, reg, &data);
13 return (u8)data;
14 }
15
16 /*
17 * @description : 向 icm20608 指定寄存器写入指定的值,写一个寄存器
18 * @param – dev : icm20608 设备
19 * @param – reg : 要写的寄存器
20 * @param – data : 要写入的值
21 * @return : 无
22 */
23
24 static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg,
u8 value)
25 {
26 regmap_write(dev->regmap, reg, value);
27 }
28
29 /*
30 * @description : 读取 ICM20608 的数据,读取原始数据,包括三轴陀螺仪、
31 * : 三轴加速度计和内部温度。
32 * @param - dev : ICM20608 设备
33 * @return : 无。
34 */
35 void icm20608_readdata(struct icm20608_dev *dev)
36 {
37 u8 ret;
38 unsigned char data[14];
39
40 ret = regmap_bulk_read(dev->regmap, ICM20_ACCEL_XOUT_H, data,
14);
41
42 dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]);
43 dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]);
44 dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]);
45 dev->temp_adc = (signed short)((data[6] << 8) | data[7]);
46 dev->gyro_x_adc = (signed short)((data[8] << 8) | data[9]);
47 dev->gyro_y_adc = (signed short)((data[10] << 8) | data[11]);
48 dev->gyro_z_adc = (signed short)((data[12] << 8) | data[13]);
49 }
第 7~14 行,icm20608_read_onereg 函数用于读取 icm20608 内部单个寄存器,这里直接使 用 regmap_read 函数来完成寄存器读取操作。
第 24~27 行,icm20608_write_onereg 函数用于向 icm20608 指定寄存器写入数据,这里也 直接使用 regmap_write 函数来完成写操作。
第 35~49 行,icm20608_readdata 函数用于读取 icm20608 内部陀螺仪、加速度计和温度计 的数据,从 ICM20_ACCEL_XOUT_H 寄存器开始,连续读取 14 个寄存器。这里直接使用 regmap_bulk_read 函数来显示多个寄存器的读取。 对比《第四十五章 Linux SPI 驱动实验》中的 icm20608 驱动,采用 regmap API 以后驱动 程序精简了很多。具体涉及到 SPI 总线的部分全部由 regmap 来处理了,驱动编写人员不用管, 极大的方便了我们的驱动编写。而且驱动的可以执行提高了很多,即使将来更换为 IIC 接口, 也只需要更改很少的一部分即可。
3:运行测试
测试 APP 直接第四十五章编写的 icm20608App.c 即可。测试方法也和四十五章一样,输入 如下命令:
depmod //第一次加载驱动的时候需要运行此命令
modprobe icm20608.ko //加载驱动模块
./icm20608App /dev/icm20608 //app 读取内部数据
如果 regmap API 工作正常,那么就会正确的初始化 icm20608,并且读出传感器数据,结果 和四十五章一样,如图 74.3.1 所示:
IIC 总线的 regmap 框架基本和 SPI 一样,只是需要使用 regmap_init_i2c 来申请并初始化对 应的 regmap,同样都是使用 regmap_read 和 regmap_write 来读写 I2C 设备内部寄存器。这里我 们也已经将《第六十一章 Linux I2C 驱动实验》中的 ap3216c 驱动改为了 regmap API 接口的, 相应的驱动程序已经放到了开发板光盘中,路径为:本实验对应的例程路径为:开发板光盘-> 01、程序源码->02、Linux 驱动例程->26_regmap->iic。大家自行查阅,这里就不详细详解了。
相关文章:

正点原子imx6ull-mini-Linux驱动之Regmap API 实验
我们在前面学习 I2C 和 SPI 驱动的时候,针对 I2C 和 SPI 设备寄存器的操作都是通过相关 的 API 函数进行操作的。这样 Linux 内核中就会充斥着大量的重复、冗余代码,但是这些本质 上都是对寄存器的操作,所以为了方便内核开发人员统一访问 I2C…...
postgresql 双重排序后 重复项 标识次序
postgresql 双重排序后 重复项 标识次序 在PostgreSQL中,如果你想要在双重排序后标识重复项的次序,可以使用窗口函数(window functions)。一个常见的方法是使用ROW_NUMBER()窗口函数,它会为每个分组内的行分配一个唯一…...

线程池ThreadPoolExecutor使用
文章目录 一、基础-Java中线程创建的方式1.1、继承Thread类创建线程1.2、实现Runnable接口创建线程1.3、实现Calable接口创建线程1.4、使用线程池创建线程二、概念-线程池基本概念2.1、并发和井行的主要区别2.1.1、处理任务不同2.1.2、存在不同2.1.3、CPU资源不同2.2、什么是线…...

Codeforces Round 963 (Div. 2)
A题:Question Marks 题目: Tim正在做一个由 4n 个问题组成的测试,每个问题都有 4 个选项:“A”、“B”、“C”和“D”。对于每个选项,有 n 个正确答案对应于该选项,这意味着有 n 个问题的答案为“A”。 n…...
Mysql函数学习笔记
MySQL 字符串函数 ASCII(s) 返回字符串 s 的第一个字符的 ASCII 码。 //返回 CustomerName 字段第一个字母的 ASCII 码 SELECT ASCII(CustomerName) AS NumCodeOfFirstChar FROM Customers;CHAR_LENGTH(s)-返回字符串 s 的字符数 //返回字符串 RUNOOB 的字符数 SELECT CHAR…...

【Linux基础】Linux基本指令(一)
目录 前言1, ls指令2,pwd指令三,cd指令3.1 当前目录与上级目录3.2 绝对路径和相对路径 四,创建一个普通文件或目录4.1 touch指令4.2 mkdir指令 五,删除目录或文件5.1 rmdir指令5.2 rm 指令 前言 从本章开始࿰…...

全球视野:航空蓄电池的国际标准与技术创新
航空蓄电池是一种专门为满足航空工业独特要求而设计的高性能储能设备。由于航空环境的特殊性,如高海拔、极端温度变化、频繁的充放电需求、以及对于设备重量和体积的严格限制,航空蓄电池需要具备一系列高级特性以确保飞机在各种飞行条件下能够安全有效地…...
11-初识python的函数——定义和调用
1 函数简介 function input()、print()、range()、len()都是python的内置函数,可以直接使用的 函数:可以用来保存代码,在需要的时候对这些语句进行重复调用 优点: 1. 遇到重复功能的时候,直接调用即可,…...

Windows安装Swoft框架
实现方式: 安装虚拟机,在虚拟机里用宝塔搭建环境后安装Swoft, 然后用Phpstorm SSH方式开发,用Apipost调用 websocket服务。 1、安装虚拟机,下载和安装参见 : https://blog.csdn.net/2401_84297265/article…...

阅读台灯什么品牌好?一文带你了解热门阅读台灯推荐
阅读台灯最终都绕不开护眼这个话题。护眼灯作为保护视力的辅助工具,以有效护眼的价值深受大众青睐。学生长时间用眼,普通台灯的伤害大,而阅读台灯的出现,通过其先进的技术和设计,能为学生提供了一个既舒适又健康的照明…...

1、.Net UI框架:Xamarin Forms - .Net宣传系列文章
Xamarin.Forms是一个跨平台移动应用开发框架,它允许开发者使用C#和.NET进行一次编码,然后在iOS、Android、macOS和Windows等多个平台上运行。Xamarin.Forms是Xamarin的一部分,而Xamarin是微软的.NET跨平台开发工具集,它提供了一套…...

Tomcat 最大连接数实现原理
spring boot 内置tomcat设置连接数 max-connections: 5 server:port: 9898servlet:context-path: /testtomcat:connection-timeout: 5000max-connections: 5accept-count: 5 ##初始化连接数量connectionLimitLatch protected LimitLatch initializeConnectionLatch() {if (ma…...

大数据应用【大数据导论】
各位大佬好 ,这里是阿川的博客,祝您变得更强 个人主页:在线OJ的阿川 大佬的支持和鼓励,将是我成长路上最大的动力 阿川水平有限,如有错误,欢迎大佬指正 目录 大数据在许多领域应用互联网领域应用生物医学…...

IP地址申请SSL证书实现https访问
为IP地址申请SSL证书以实现HTTPS访问,可以确保通过网络传输的数据得到加密保护。下面是为IP地址申请并安装SSL证书一般的步骤: 1 访问CA 打开JoySSL官网,注册一个账号用于申请证书,注册时会有选填项注册码,填写后可获…...

未授权访问漏洞上(漏洞复现合集)
目录 一:Redis未授权访问漏洞 * 步骤一:进入vulhub目录使用以下命令启动靶机... 步骤二:在Kali上安装redis程序进行服务的链接 步骤三:可以直接连接执行命令且不需要认证说明存在未授权访问漏洞...下载以下攻击项目... 步骤四:使用工具执行以下命令获取目标的命…...

多久没有清理你的电脑磁盘了?轻松解锁免费轻量磁盘清理工具
随着我们日常使用电脑的时间越来越长,磁盘上积累的无用文件和垃圾数据也越来越多。这些文件不仅占用宝贵的存储空间,还可能拖慢电脑的运行速度。 那么,你多久没有清理过你的电脑磁盘了呢? 今天,我将为大家推荐几款免…...

高精度加法c++
题目描述 计算ab的值,a,b皆为不超过240位的正整数。 输入 两个正整数,每行一个 输出 一个数,代表两个整数的和 样例输入 111111111111111111111111111111111111 222222222222222222222222222222222222 样例输出 3333333333333333333…...

SQL布尔盲注
目录 1 布尔盲注 2布尔盲注流程 2.1输入id进行测试 2.2判断注入类型 2.3爆数据库名 2.4爆表名 2.5爆字段名 2.6查询数据 1 布尔盲注 布尔盲注就是在SQL注入过程中,SQL语句执行后,查询到的数据不能回显到前端页面,如果正确执行了构造的…...

OpenGL实现3D游戏编程【连载3】——3D空间模型光照初步
1、本节实现的内容 上一节课,我们建立了简单的坐标系,同时也显示了一个正方体,但正方体的颜色为纯红色,好像一个平面物体一样,我们这节课就可以加一些光照,并创建更多的模型,使这些物体变得更加…...

Python 进行反射和元编程
反射和元编程是Python中两种强大且高级的编程技术。反射允许程序在运行时检查和修改自身结构和行为,而元编程则是编写可以操作其他代码的代码,通常通过使用元类、装饰器等技术来实现。 1. 反射 反射是指程序在运行时检查和操作自身结构的能力。Python通…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...
【JavaSE】多线程基础学习笔记
多线程基础 -线程相关概念 程序(Program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存…...
MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用
文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...