RK3568 I2C底层驱动详解
前提须知:I2C协议不懂的话就去看之前的内容吧,这个文章需要读者一定的基础。
RK3568 I2C 简介
RK3568 支持 6 个独立 I2C: I2C0、I2C1、I2C2、I2C3、I2C4、I2C5。I2C 控制器支持以下特性:
① 兼容 i2c 总线 ② AMBA APB 从接口 ③ 支持 I2C 总线主模式 ④ 软件可编程时钟频率和传输速率高达 400Kbit/sec ⑤ 支持 7 位和 10 位寻址模式 ⑥ 中断或轮询驱动的多字节数据传输 ⑦ 时钟拉伸和等待状态生成 ⑧ 过滤掉 SCL 和 SDA 上的故障
我们需要驱动AP3216C这个芯片,利用IIC,AP3216C 常被用于手机、平板、导航设备等,其内置的接近传感器可以用于检测是否有物体接近,比如手机上用来检测耳朵是否接触听筒,如果检测到的话就表示正在打电话,手机就会关闭手机屏幕以省电。也可以使用环境光传感器检测光照强度,可以实现自动背光亮度调节。
AP3216 的设备地址为 0X1E,同几乎所有的 I2C 从器件一样,AP3216C 内部也有一些寄存 器,通过这些寄存器我们可以配置 AP3216C 的工作模式,并且读取相应的数据。
寄存器地址
寄存器地址 | 位 | 功能描述 |
---|---|---|
0x00 | 2:0 | 系统模式: |
- 000:掉电模式 (默认) | ||
- 001:使能 ALS | ||
- 010:使能 PS+IR | ||
- 011:使能 ALS+PS+IR | ||
- 100:软复位 | ||
- 101:ALS 单次模式 | ||
- 110:PS+IR 单次模式 | ||
- 111:ALS+PS+IR 单次模式 | ||
0x0A | 7 | IR 低位数据:0 表示 IR&PS 数据有效,1 表示无效 |
1:0 | IR 最低 2 位数据 | |
0x0B | 7:0 | IR 高 8 位数据 |
0x0C | 7:0 | ALS 低 8 位数据 |
0x0D | 7:0 | ALS 高 8 位数据 |
0x0E | 7 | PS 低位数据:0 表示远离,1 表示接近 |
6 | 0:IR&PS 数据有效,1:IR&PS 数据无效 | |
3:0 | PS 最低 4 位数据 | |
0x0F | 7 | PS 高位数据:0 表示远离,1 表示接近 |
6 | 0:IR&PS 数据有效,1:IR&PS 数据无效 | |
5:0 | PS 最低 6 位数据 |
0X00 这个寄存器是模式控制寄存器,用来设置 AP3216C 的工作模式, 一般开始先将其设置为 0X04,也就是先软件复位一次 AP3216C。接下来根据实际使用情况选 择合适的工作模式,比如设置为 0X03,也就是开启 ALS+PS+IR。0X0A~0X0F 这 6 个寄存器就 是数据寄存器,保存着 ALS、PS 和 IR 这三个传感器获取到的数据值。如果同时打开 ALS、PS 和 IR 的读取间隔最少要 112.5ms,因为 AP3216C 完成一次转换需要 112.5ms。
Linux I2C 总线框架简介
使用裸机的方式编写一个 I2C 器件的驱动程序,我们一般需要实现两部分:
①、I2C 主机驱动。
②、I2C 设备驱动。
I2C 主机驱动也就是 SoC 的 I2C 控制器对应的驱动程序,I2C 设备驱动其实就是挂在 I2C 总线下的具体设备对应的驱动程序,例如 eeprom、触摸屏 IC、传感器 IC 等;对于主机驱 动来说,一旦编写完成就不需要再做修改,其他的 I2C 设备直接调用主机驱动提供的 API 函 数完成读写操作即可。这个正好符合 Linux 的驱动分离与分层的思想,因此 Linux 内核也将 I2C 驱动分为两部分。
Linux 内核开发者为了让驱动开发工程师在内核中方便的添加自己的 I2C 设备驱动程序, 方便大家更容易的在 linux 下驱动自己的 I2C 接口硬件,进而引入了 I2C 总线框架,我们一般 也叫作 I2C 子系统,Linux 下 I2C 子系统总体框架如下所示:
1、I2C 核心(I2C-core) I2C
核心提供了 I2C 总线驱动(适配器)和设备驱动的注册、注销方法,I2C 通信方法 (algorithm)与具体硬件无关的代码,以及探测设备地址的上层代码等;
2、I2C 总线驱动(I2C adapter)
I2C 总线驱动是 I2C 适配器的软件实现,提供 I2C 适配器与从设备间完成数据通信的能力。 I2C 总线驱动由 i2c_adapter 和 i2c_algorithm 来描述。I2C 适配器是 SoC 中内置 i2c 控制器的软 件抽象,可以理解为他所代表的是一个 I2C 主机;
3、I2C 设备驱动(I2C client driver)
包括两部分:设备的注册和驱动的注册。 I2C 子系统帮助内核统一管理 I2C 设备,让驱动开发工程师在内核中可以更加容易地添加 自己的 I2C 设备驱动程序。
I2C 总线驱动如下所示:
首先来看一下 I2C 总线,在讲 platform 的时候就说过,platform 是虚拟出来的一条总线, 目的是为了实现总线、设备、驱动框架。对于 I2C 而言,不需要虚拟出一条总线,直接使用 I2C 总线即可。I2C 总线驱动重点是 I2C 适配器(也就是 SoC 的 I2C 接口控制器)驱动,这里要用到 两个重要的数据结构:i2c_adapter 和 i2c_algorithm,I2C 子系统将 SoC 的 I2C 适配器(控制器)抽 象成一个 i2c_adapter 结构体,i2c_adapter 结构体定义在 include/linux/i2c.h 文件中,结构体内容 如下:
672 struct i2c_adapter {
673 struct module *owner;
674 unsigned int class; /* classes to allow probing for */
675 const struct i2c_algorithm *algo; /* the algorithm to access the
bus */
676 void *algo_data;
677
678 /* data fields that are valid for all devices */
679 const struct i2c_lock_operations *lock_ops;
680 struct rt_mutex bus_lock;
681 struct rt_mutex mux_lock;
682
683 int timeout; /* in jiffies */
684 int retries;
685 struct device dev; /* the adapter device */
686
687 int nr;
688 char name[48];
689 struct completion dev_released;
690
691 struct mutex userspace_clients_lock;
692 struct list_head userspace_clients;
693
694 struct i2c_bus_recovery_info *bus_recovery_info;
695 const struct i2c_adapter_quirks *quirks;
696
697 struct irq_domain *host_notify_domain;
698 };
字段 | 类型 | 描述 |
---|---|---|
owner | struct module * | 指向拥有此 I2C 适配器的内核模块,通常用于 模块引用计数,防止模块被卸载。 |
class | unsigned int | 适配器的类别,用于自动探测某些特定类型的设备(如 SMBus、标准 I2C)。 |
algo | const struct i2c_algorithm * | 指向 I2C 适配器算法(i2c_algorithm 结构体),定义了 I2C 读写操作、时序等方法。 |
algo_data | void * | 用于存储 algo 相关的私有数据(如设备的 I/O 映射地址)。 |
lock_ops | const struct i2c_lock_operations * | 定义锁操作(互斥锁等),用于 同步 I2C 访问,避免多个进程并发访问 I2C 适配器时发生冲突。 |
bus_lock | struct rt_mutex | I2C 适配器总线锁,防止多个进程同时访问 I2C 适配器。 |
mux_lock | struct rt_mutex | I2C 多路复用(MUX)锁,在 I2C 适配器支持 多路复用 时使用。 |
timeout | int | I2C 操作超时时间(以 jiffies 计数,Linux 内核时间单位)。 |
retries | int | I2C 传输失败时的重试次数。 |
dev | struct device | 适配器对应的设备结构体,用于在 sysfs 下创建设备节点,使用户态可以通过 /sys/class/i2c-adapter/ 访问 I2C 适配器。 |
nr | int | I2C 适配器编号(如 /dev/i2c-0 ,/dev/i2c-1 )。 |
name | char[48] | I2C 适配器的名称(如 "i2c-0" , "i2c-1" )。 |
dev_released | struct completion | 设备释放的同步机制,确保适配器被正确释放。 |
userspace_clients_lock | struct mutex | 用于保护 userspace_clients 访问的锁,防止并发操作 userspace I2C 设备列表。 |
userspace_clients | struct list_head | 用户空间 I2C 设备列表,存储已打开的 I2C 设备(如 /dev/i2c-* 被访问时)。 |
bus_recovery_info | struct i2c_bus_recovery_info * | I2C 总线恢复机制,用于在 I2C 设备死锁或异常时尝试恢复总线(例如 GPIO 模拟 SCL 时钟信号)。 |
quirks | const struct i2c_adapter_quirks * | I2C 适配器的特殊行为(如最大传输字节数、是否支持 repeated start)。 |
host_notify_domain | struct irq_domain * | 用于支持 I2C 从设备发送 Host Notify 中断的 IRQ 域(Host Notify 允许 I2C 从设备通知主机)。 |
526 struct i2c_algorithm {
527 /*
528 * If an adapter algorithm can't do I2C-level access, set
529 * master_xfer to NULL. If an adapter algorithm can do SMBus
530 * access, set smbus_xfer. If set to NULL, the SMBus protocol is
531 * simulated using common I2C messages.
532 *
533 * master_xfer should return the number of messages successfully
534 * processed, or a negative value on error
535 */
536 int (*master_xfer)(struct i2c_adapter *adap,
struct i2c_msg *msgs,
537 int num);
538 int (*master_xfer_atomic)(struct i2c_adapter *adap,
539 struct i2c_msg *msgs, int num);
540 int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr,
541 unsigned short flags, char read_write,
542 u8 command, int size, union i2c_smbus_data *data);
543 int (*smbus_xfer_atomic)(struct i2c_adapter *adap, u16 addr,
544 unsigned short flags, char read_write,
545 u8 command, int size, union i2c_smbus_data *data);
546
547 /* To determine what the adapter supports */
548 u32 (*functionality)(struct i2c_adapter *adap);
549
550 #if IS_ENABLED(CONFIG_I2C_SLAVE)
551 int (*reg_slave)(struct i2c_client *client);
552 int (*unreg_slave)(struct i2c_client *client);
553 #endif
554 };
519 struct i2c_algorithm {
520 /* If an adapter algorithm can't do I2C-level access, set
master_xfer
521 to NULL. If an adapter algorithm can do SMBus access, set
522 smbus_xfer. If set to NULL, the SMBus protocol is simulated
523 using common I2C messages */
524 /* master_xfer should return the number of messages successfully
525 processed, or a negative value on error */
526 int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg
*msgs,
527 int num);
528 int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
529 unsigned short flags, char read_write,
530 u8 command, int size, union i2c_smbus_data *data);
531
532 /* To determine what the adapter supports */
533 u32 (*functionality) (struct i2c_adapter *);
534
535 #if IS_ENABLED(CONFIG_I2C_SLAVE)
536 int (*reg_slave)(struct i2c_client *client);
537 int (*unreg_slave)(struct i2c_client *client);
538 #endif
539 };
字段 | 类型 | 描述 |
---|---|---|
master_xfer | int (*)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) | I2C 主模式传输函数,用于 I2C 读写多个消息。 |
master_xfer_atomic | int (*)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) | 原子模式的 I2C 传输,适用于中断上下文。 |
smbus_xfer | int (*)(struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data) | SMBus 传输函数(SMBus 是 I2C 的子集)。如果 NULL ,内核会使用 master_xfer 模拟 SMBus 传输。 |
smbus_xfer_atomic | int (*)(struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data) | SMBus 原子传输,适用于中断上下文。 |
functionality | u32 (*)(struct i2c_adapter *adap) | 返回 I2C 适配器支持的功能,例如是否支持 10-bit 地址、SMBus 协议等。 |
reg_slave | int (*)(struct i2c_client *client) | 注册 I2C 从设备(仅适用于支持 I2C 从模式的控制器)。 |
unreg_slave | int (*)(struct i2c_client *client) | 取消注册 I2C 从设备。 |
I2C 设备驱动重点关注两个数据结构:i2c_client 和 i2c_driver,根据总线、设备和驱动模型, I2C 总线上一小节已经讲了。还剩下设备和驱动,i2c_client 用于描述 I2C 总线下的设备, i2c_driver 则用于描述 I2C 总线下的设备驱动,类似于 platform 总线下的 platform_device 和 platform_driver。
i2c_driver 的注册示例代码如下:
1 /* i2c 驱动的 probe 函数 */
2 static int xxx_probe(struct i2c_client *client,
const struct i2c_device_id *id)
3 {
4 /* 函数具体程序 */
5 return 0;
6 }
7
8 /* i2c 驱动的 remove 函数 */
9 static int ap3216c_remove(struct i2c_client *client)
10 {
11 /* 函数具体程序 */
12 return 0;
13 }
14
15 /* 传统匹配方式 ID 列表 */
16 static const struct i2c_device_id xxx_id[] = {
17 {"xxx", 0},
18 {}
19 };
20
21 /* 设备树匹配列表 */
22 static const struct of_device_id xxx_of_match[] = {
23 { .compatible = "xxx" },
24 { /* Sentinel */ }
25 };
26
27 /* i2c 驱动结构体 */
28 static struct i2c_driver xxx_driver = {
29 .probe = xxx_probe,
30 .remove = xxx_remove,
31 .driver = {
32 .owner = THIS_MODULE,
33 .name = "xxx",
34 .of_match_table = xxx_of_match,
35 },
36 .id_table = xxx_id,
37 };
38
39 /* 驱动入口函数 */
40 static int __init xxx_init(void)
41 {
42 int ret = 0;
43
44 ret = i2c_add_driver(&xxx_driver);
45 return ret;
46 }
47
48 /* 驱动出口函数 */
49 static void __exit xxx_exit(void)
50 {
51 i2c_del_driver(&xxx_driver);
52 }
53
54 module_init(xxx_init);
55 module_exit(xxx_exit);
i2c_device_id,无设备树的时候匹配 ID 表。
of_device_id,设备树所使用的匹配表。
i2c_driver,当 I2C 设备和 I2C 驱动匹配成功以后 probe 函数就会执行,这些 和 platform 驱动一样,probe 函数里面基本就是标准的字符设备驱动那一套了。
I2C 设备和驱动匹配过程
I2C 设备和驱动的匹配过程是由 I2C 子系统核心层来完成的,drivers/i2c/i2c-core-base.c 就 是 I2C 的核心部分,I2C 核心提供了一些与具体硬件无关的 API 函数,比如前面讲过的:
i2c_adapter 注册/注销函数
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
void i2c_del_adapter(struct i2c_adapter * adap)
i2c_driver 注册/注销函数
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
int i2c_add_driver (struct i2c_driver *driver)
void i2c_del_driver(struct i2c_driver *driver)
设备和驱动的匹配过程也是由核心层完成的,I2C 总线的数据结构为 i2c_bus_type,定义在 drivers/i2c/i2c-core-base.c 文件,i2c_bus_type 内容如下:
505 struct bus_type i2c_bus_type = {
506 .name = "i2c",
507 .match = i2c_device_match,
508 .probe = i2c_device_probe,
509 .remove = i2c_device_remove,
510 .shutdown = i2c_device_shutdown,
511 };
.match 就是 I2C 总线的设备和驱动匹配函数,在这里就是 i2c_device_match 这个函数,此函数内容如下
103 static int i2c_device_match(struct device *dev,
struct device_driver *drv)
104 {
105 struct i2c_client *client = i2c_verify_client(dev);
106 struct i2c_driver *driver;
107
108
109 /* Attempt an OF style match */
110 if (i2c_of_match_device(drv->of_match_table, client))
111 return 1;
112
113 /* Then ACPI style match */
114 if (acpi_driver_match_device(dev, drv))
115 return 1;
116
117 driver = to_i2c_driver(drv);
118
119 /* Finally an I2C match */
120 if (i2c_match_id(driver->id_table, client))
121 return 1;
122
123 return 0;
124 }
还是这套函数逻辑,来对比match dts。
RK3568 I2C 适配器驱动分析如下。
重点分为 I2C 适配器驱动和 I2C 设备驱动, 其中 I2C 适配器驱动就是 SoC 的 I2C 控制器驱动。I2C 设备驱动是需要用户根据不同的 I2C 从 设备去编写,而 I2C 适配器驱动一般都是 SoC 厂商去编写的,比如 RK 就已经提供了 RK3568 的 I2C 适配器驱动程序。在内核源码 arch/arm64/boot/dts/rockchip/rk3568.dtsi 设备树文件中找到 RK3568 的 I2C 控制器节点,节点内容如下所示:
2900 i2c1: i2c@fe5a0000 {
2901 compatible = "rockchip,rk3399-i2c";
2902 reg = <0x0 0xfe5a0000 0x0 0x1000>;
2903 clocks = <&cru CLK_I2C1>, <&cru PCLK_I2C1>;
2904 clock-names = "i2c", "pclk";
2905 interrupts = <GIC_SPI 47 IRQ_TYPE_LEVEL_HIGH>;
2906 pinctrl-names = "default";
2907 pinctrl-0 = <&i2c1_xfer>;
2908 #address-cells = <1>;
2909 #size-cells = <0>;
2910 status = "disabled";
2911 };
重点关注 i2c1 节点的 compatible 属性值,因为通过 compatible 属性值可以在 Linux 源码里 面找到对应的驱动文件。这里 i2c1 节点的 compatible 属性值“rockchip,rk3399-i2c”,在 Linux 源码中搜索这个字符串即可找到对应的驱动文件。RK3568 的 I2C 适配器驱动驱动文件为 drivers/i2c/busses/i2c-rk3x.c。
具体源码分析就去上述的pwd下观看吧。
I2C 设备数据收发处理流程
I2C 设备驱动首先要做的就是初始化 i2c_driver 并向 Linux 内核注册。当设备和驱动匹配以后 i2c_driver 里面的 probe 函数就会执行,probe 函数里面所做的就 是字符设备驱动那一套了。一般需要在 probe 函数里面初始化 I2C 设备,要初始化 I2C 设备就 必须能够对 I2C 设备寄存器进行读写操作,这里就要用到 i2c_transfer 函数了。i2c_transfer 函数 最终会调用 I2C 适配器中 i2c_algorithm 里面的 master_xfer 函数,对于 RK3568 而言就是 rk3x_i2c_xfer 这个函数。i2c_transfer 函数原型如下:
1 /* 设备结构体 */
2 struct xxx_dev {
3 ......
4 void *private_data; /* 私有数据,一般会设置为 i2c_client */
5 };
6
7 /*
8 * @description : 读取 I2C 设备多个寄存器数据
9 * @param – dev : I2C 设备
10 * @param – reg : 要读取的寄存器首地址
11 * @param – val : 读取到的数据
12 * @param – len : 要读取的数据长度
13 * @return : 操作结果
14 */
15 static int xxx_read_regs(struct xxx_dev *dev, u8 reg, void *val,
int len)
16 {
17 int ret;
18 struct i2c_msg msg[2];
19 struct i2c_client *client = (struct i2c_client *)
dev->private_data;
20
21 /* msg[0],第一条写消息,发送要读取的寄存器首地址 */
22 msg[0].addr = client->addr; /* I2C 器件地址 */
23 msg[0].flags = 0; /* 标记为发送数据 */
24 msg[0].buf = ® /* 读取的首地址 */
25 msg[0].len = 1; /* reg 长度 */
26
27 /* msg[1],第二条读消息,读取寄存器数据 */
28 msg[1].addr = client->addr; /* I2C 器件地址 */
29 msg[1].flags = I2C_M_RD; /* 标记为读取数据 */
30 msg[1].buf = val; /* 读取数据缓冲区 */
31 msg[1].len = len; /* 要读取的数据长度 */
32
33 ret = i2c_transfer(client->adapter, msg, 2);
34 if(ret == 2) {
35 ret = 0;
36 } else {
37 ret = -EREMOTEIO;
38 }
39 return ret;
40 }
41
42 /*
43 * @description : 向 I2C 设备多个寄存器写入数据
44 * @param – dev : 要写入的设备结构体
45 * @param – reg : 要写入的寄存器首地址
46 * @param – val : 要写入的数据缓冲区
47 * @param – len : 要写入的数据长度
48 * @return : 操作结果
49 */
50 static s32 xxx_write_regs(struct xxx_dev *dev, u8 reg, u8 *buf,
u8 len)
51 {
52 u8 b[256];
53 struct i2c_msg msg;
54 struct i2c_client *client = (struct i2c_client *)
dev->private_data;
55
56 b[0] = reg; /* 寄存器首地址 */
57 memcpy(&b[1],buf,len); /* 将要发送的数据拷贝到数组 b 里面 */
58
59 msg.addr = client->addr; /* I2C 器件地址 */
60 msg.flags = 0; /* 标记为写数据 */
61
62 msg.buf = b; /* 要发送的数据缓冲区 */
63 msg.len = len + 1; /* 要发送的数据长度 */
64
65 return i2c_transfer(client->adapter, &msg, 1);
66 }
第2~5行,设备结构体,在设备结构体里面添加一个执行void的指针成员变量private_data, 此成员变量用于保存设备的私有数据。在 I2C 设备驱动中我们一般将其指向 I2C 设备对应的 i2c_client。 第 15~40 行,xxx_read_regs 函数用于读取 I2C 设备多个寄存器数据。第 18 行定义了一个 i2c_msg 数组,2 个数组元素,因为 I2C 读取数据的时候要先发送要读取的寄存器地址,然后再 读取数据,所以需要准备两个 i2c_msg。一个用于发送寄存器地址,一个用于读取寄存器值。对 于 msg[0],将 flags 设置为 0,表示写数据。msg[0]的 addr 是 I2C 设备的器件地址,msg[0]的 buf 成员变量就是要读取的寄存器地址。对于 msg[1],将 flags 设置为 I2C_M_RD,表示读取数据。 msg[1]的 buf 成员变量用于保存读取到的数据,len 成员变量就是要读取的数据长度。调用 i2c_transfer 函数完成 I2C 数据读操作。
第 50~66 行,xxx_write_regs 函数用于向 I2C 设备多个寄存器写数据,I2C 写操作要比读操 作简单一点,因此一个 i2c_msg 即可。数组 b 用于存放寄存器首地址和要发送的数据,第 59 行 设置 msg 的 addr 为 I2C 器件地址。第 60 行设置 msg 的 flags 为 0,也就是写数据。第 62 行设 置要发送的数据,也就是数组 b。第 63 行设置 msg 的 len 为 len+1,因为要加上一个字节的寄 存器地址。最后通过 i2c_transfer 函数完成向 I2C 设备的写操作。
635 ap3216c_ls@1e { 636 compatible = "ls_ap321xx"; 637 reg = <0x1e>; 638 type = <SENSOR_TYPE_LIGHT>; 639 //irq-gpio = <&gpio4 RK_PD2 IRQ_TYPE_EDGE_FALLING>; 640 irq_enable = <0>; 641 poll_delay_ms = <20>; 642 layout = <1>; 643 status = "disabled"; 644 }; 645 646 ap3216c_ps@1e { 647 compatible = "ps_ap321xx"; 648 reg = <0x1e>; 649 type = <SENSOR_TYPE_PROXIMITY>; 650 //irq-gpio = <&gpio4 RK_PD2 IRQ_TYPE_EDGE_FALLING>; 651 irq_enable = <0>; 652 poll_delay_ms = <20>; 653 layout = <1>; 654 status = "disabled"; 655 };
接着我们在 rk3568-atk-evb1-ddr4-v10.dtsi 文件,通过节点内容追加的方式,向 i2c5 节点中 添加“ap3216c@1e”子节点,节点如下所示:
1 &i2c5 {
2 ap3216c@1e {
3 compatible = "alientek,ap3216c";
4 reg = <0x1e>;
5 };
6 };
第 2 行,ap3216c 子节点,@后面的“1e”是 ap3216c 的器件地址。 第 3 行,设置 compatible 值为“alientek,ap3216c”。 第 4 行,reg 属性也是设置 ap3216c 器件地址的,因此 reg 设置为 0x1e。 设备树修改完成以后使用“/build.sh kernel”重新编译一下,然后重新烧写 boot.img 启动 Linux 内核。/sys/bus/i2c/devices 目录下存放着所有 I2C 设备,如果设备树修改正确的话,会在 /sys/bus/i2c/devices 目录下看到一个名为“5-001e”的子目录
图中的“5-001e”就是 ap3216c 的设备目录,“1e”就是 ap3216c 器件地址。进入 5-001e 目录,可以看到“name”文件,name 文件保存着此设备名字,在这里就是“ap3216c”, 如图 28.6.1.2 所示:
AP3216C 驱动编写
#ifndef AP3216C_H
#define AP3216C_H
#define AP3216C_ADDR 0X1E /* AP3216C 器件地址 */
/* AP3316C 寄存器 */
#define AP3216C_SYSTEMCONG 0x00 /* 配置寄存器 */
#define AP3216C_INTSTATUS 0X01 /* 中断状态寄存器 */
#define AP3216C_INTCLEAR 0X02 /* 中断清除寄存器 */
#define AP3216C_IRDATALOW 0x0A /* IR 数据低字节 */
#define AP3216C_IRDATAHIGH 0x0B /* IR 数据高字节 */
#define AP3216C_ALSDATALOW 0x0C /* ALS 数据低字节 */
#define AP3216C_ALSDATAHIGH 0X0D /* ALS 数据高字节 */
#define AP3216C_PSDATALOW 0X0E /* PS 数据低字节 */
#define AP3216C_PSDATAHIGH 0X0F /* PS 数据高字节 */
#endif
11 #include <linux/types.h>
12 #include <linux/kernel.h>
13 #include <linux/delay.h>
14 #include <linux/ide.h>
15 #include <linux/init.h>
16 #include <linux/module.h>
17 #include <linux/errno.h>
18 #include <linux/gpio.h>
19 #include <linux/cdev.h>
20 #include <linux/device.h>
21 #include <linux/of_gpio.h>
22 #include <linux/semaphore.h>
23 #include <linux/timer.h>
24 #include <linux/i2c.h>
25 //#include <asm/mach/map.h>
26 #include <asm/uaccess.h>
27 #include <asm/io.h>
28 #include "ap3216creg.h"
29
30 #define AP3216C_CNT 1
31 #define AP3216C_NAME "ap3216c"
32
33 struct ap3216c_dev {
34 struct i2c_client *client; /* i2c 设备 */
35 dev_t devid; /* 设备号 */
36 struct cdev cdev; /* cdev */
37 struct class *class; /* 类 */
38 struct device *device; /* 设备 */
39 struct device_node *nd; /* 设备节点 */
40 unsigned short ir, als, ps; /* 三个光传感器数据 */
41 };
42
43 /*
44 * @description : 从 ap3216c 读取多个寄存器数据
45 * @param – dev : ap3216c 设备
46 * @param – reg : 要读取的寄存器首地址
47 * @param – val : 读取到的数据
48 * @param – len : 要读取的数据长度
49 * @return : 操作结果
50 */
51 static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg,
void *val, int len)
52 {
53 int ret;
54 struct i2c_msg msg[2];
55 struct i2c_client *client = (struct i2c_client *)dev->client;
57 /* msg[0]为发送要读取的首地址 */
58 msg[0].addr = client->addr; /* ap3216c 地址 */
59 msg[0].flags = 0; /* 标记为发送数据 */
60 msg[0].buf = ® /* 读取的首地址 */
61 msg[0].len = 1; /* reg 长度 */
62
63 /* msg[1]读取数据 */
64 msg[1].addr = client->addr; /* ap3216c 地址 */
65 msg[1].flags = I2C_M_RD; /* 标记为读取数据 */
66 msg[1].buf = val; /* 读取数据缓冲区 */
67 msg[1].len = len; /* 要读取的数据长度 */
68
69 ret = i2c_transfer(client->adapter, msg, 2);
70 if(ret == 2) {
71 ret = 0;
72 } else {
73 printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
74 ret = -EREMOTEIO;
75 }
76 return ret;
77 }
78
79 /*
80 * @description: 向 ap3216c 多个寄存器写入数据
81 * @param - dev: ap3216c 设备
82 * @param - reg: 要写入的寄存器首地址
83 * @param - val: 要写入的数据缓冲区
84 * @param - len: 要写入的数据长度
85 * @return : 操作结果
86 */
87 static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg,
u8 *buf, u8 len)
88 {
89 u8 b[256];
90 struct i2c_msg msg;
91 struct i2c_client *client = (struct i2c_client *)dev->client;
92
93 b[0] = reg; /* 寄存器首地址 */
94 memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组 b 里面 */
95
96 msg.addr = client->addr; /* ap3216c 地址 */
97 msg.flags = 0; /* 标记为写数据 */
99 msg.buf = b; /* 要写入的数据缓冲区 */
100 msg.len = len + 1; /* 要写入的数据长度 */
101
102 return i2c_transfer(client->adapter, &msg, 1);
103 }
104
105 /*
106 * @description: 读取 ap3216c 指定寄存器值,读取一个寄存器
107 * @param - dev: ap3216c 设备
108 * @param - reg: 要读取的寄存器
109 * @return : 读取到的寄存器值
110 */
111 static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev,
u8 reg)
112 {
113 u8 data = 0;
114
115 ap3216c_read_regs(dev, reg, &data, 1);
116 return data;
117 }
118
119 /*
120 * @description: 向 ap3216c 指定寄存器写入指定的值,写一个寄存器
121 * @param - dev: ap3216c 设备
122 * @param - reg: 要写的寄存器
123 * @param - data: 要写入的值
124 * @return : 无
125 */
126 static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg,
u8 data)
127 {
128 u8 buf = 0;
129 buf = data;
130 ap3216c_write_regs(dev, reg, &buf, 1);
131 }
132
133 /*
134 * @description : 读取 AP3216C 的数据,包括 ALS,PS 和 IR, 注意!如果同时
135 * :打开 ALS,IR+PS 两次数据读取的时间间隔要大于 112.5ms
136 * @param – ir : ir 数据
137 * @param - ps : ps 数据
138 * @param - ps : als 数据
139 * @return : 无。
140 */
141 void ap3216c_readdata(struct ap3216c_dev *dev)
142 {
143 unsigned char i =0;
144 unsigned char buf[6];
145
146 /* 循环读取所有传感器数据 */
147 for(i = 0; i < 6; i++) {
148 buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);
149 }
150
151 if(buf[0] & 0X80) /* IR_OF 位为 1,则数据无效 */
152 dev->ir = 0;
153 else /* 读取 IR 传感器的数据 */
154 dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);
155
156 dev->als = ((unsigned short)buf[3] << 8) | buf[2];
157
158 if(buf[4] & 0x40) /* IR_OF 位为 1,则数据无效 */
159 dev->ps = 0;
160 else /* 读取 PS 传感器的数据 */
161 dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] &
0X0F);
162 }
163
164 /*
165 * @description : 打开设备
166 * @param – inode : 传递给驱动的 inode
167 * @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
168 * 一般在 open 的时候将 private_data 指向设备结构体。
169 * @return : 0 成功;其他 失败
170 */
171 static int ap3216c_open(struct inode *inode, struct file *filp)
172 {
173 /* 从 file 结构体获取 cdev 指针,再根据 cdev 获取 ap3216c_dev 首地址 */
174 struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;
175 struct ap3216c_dev *ap3216cdev = container_of(cdev,
struct ap3216c_dev, cdev);
176
177 /* 初始化 AP3216C */
178 ap3216c_write_reg(ap3216cdev, AP3216C_SYSTEMCONG, 0x04);
179 mdelay(50);
180 ap3216c_write_reg(ap3216cdev, AP3216C_SYSTEMCONG, 0X03);
181 return 0;
182 }
183
184 /*
185 * @description : 从设备读取数据
186 * @param - filp : 要打开的设备文件(文件描述符)
187 * @param - buf : 返回给用户空间的数据缓冲区
188 * @param - cnt : 要读取的数据长度
189 * @param - offt : 相对于文件首地址的偏移
190 * @return : 读取的字节数,如果为负值,表示读取失败
191 */
192 static ssize_t ap3216c_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *off)
193 {
194 short data[3];
195 long err = 0;
196
197 struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;
198 struct ap3216c_dev *dev = container_of(cdev, struct ap3216c_dev,
cdev);
199
200 ap3216c_readdata(dev);
201
202 data[0] = dev->ir;
203 data[1] = dev->als;
204 data[2] = dev->ps;
205 err = copy_to_user(buf, data, sizeof(data));
206 return 0;
207 }
208
209 /*
210 * @description : 关闭/释放设备
211 * @param - filp : 要关闭的设备文件(文件描述符)
212 * @return : 0 成功;其他 失败
213 */
214 static int ap3216c_release(struct inode *inode, struct file *filp)
215 {
216 return 0;
217 }
218
219 /* AP3216C 操作函数 */
220 static const struct file_operations ap3216c_ops = {
221 .owner = THIS_MODULE,
222 .open = ap3216c_open,
223 .read = ap3216c_read,
224 .release = ap3216c_release,
225 };
226
227 /*
228 * @description : i2c 驱动的 probe 函数,当驱动与
229 * 设备匹配以后此函数就会执行
230 * @param – client : i2c 设备
231 * @param - id : i2c 设备 ID
232 * @return : 0,成功;其他负值,失败
233 */
234 static int ap3216c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
235 {
236 int ret;
237 struct ap3216c_dev *ap3216cdev;
238
239
240 ap3216cdev = devm_kzalloc(&client->dev, sizeof(*ap3216cdev),
GFP_KERNEL);
241 if(!ap3216cdev)
242 return -ENOMEM;
243
244 /* 注册字符设备驱动 */
245 /* 1、创建设备号 */
246 ret = alloc_chrdev_region(&ap3216cdev->devid, 0, AP3216C_CNT,
AP3216C_NAME);
247 if(ret < 0) {
248 pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n",
AP3216C_NAME, ret);
249 return -ENOMEM;
250 }
251
252 /* 2、初始化 cdev */
253 ap3216cdev->cdev.owner = THIS_MODULE;
254 cdev_init(&ap3216cdev->cdev, &ap3216c_ops);
255
256 /* 3、添加一个 cdev */
257 ret = cdev_add(&ap3216cdev->cdev, ap3216cdev->devid,
AP3216C_CNT);
258 if(ret < 0) {
259 goto del_unregister;
260 }
261
262 /* 4、创建类 */
263 ap3216cdev->class = class_create(THIS_MODULE, AP3216C_NAME);
264 if (IS_ERR(ap3216cdev->class)) {
265 goto del_cdev;
266 }
267
268 /* 5、创建设备 */
269 ap3216cdev->device = device_create(ap3216cdev->class, NULL,
ap3216cdev->devid, NULL, AP3216C_NAME);
270 if (IS_ERR(ap3216cdev->device)) {
271 goto destroy_class;
272 }
273 ap3216cdev->client = client;
274 /* 保存 ap3216cdev 结构体 */
275 i2c_set_clientdata(client,ap3216cdev);
276
277 return 0;
278 destroy_class:
279 device_destroy(ap3216cdev->class, ap3216cdev->devid);
280 del_cdev:
281 cdev_del(&ap3216cdev->cdev);
282 del_unregister:
283 unregister_chrdev_region(ap3216cdev->devid, AP3216C_CNT);
284 return -EIO;
285 }
286
287 /*
288 * @description : i2c 驱动的 remove 函数,移除 i2c 驱动的时候此函数会执行
289 * @param - client : i2c 设备
290 * @return : 0,成功;其他负值,失败
291 */
292 static int ap3216c_remove(struct i2c_client *client)
293 {
294 struct ap3216c_dev *ap3216cdev = i2c_get_clientdata(client);
295 /* 注销字符设备驱动 */
296 /* 1、删除 cdev */
297 cdev_del(&ap3216cdev->cdev);
298 /* 2、注销设备号 */
299 unregister_chrdev_region(ap3216cdev->devid, AP3216C_CNT);
300 /* 3、注销设备 */
301 device_destroy(ap3216cdev->class, ap3216cdev->devid);
302 /* 4、注销类 */
303 class_destroy(ap3216cdev->class);
304 return 0;
305 }
306
307 /* 传统匹配方式 ID 列表 */
308 static const struct i2c_device_id ap3216c_id[] = {
309 {"alientek,ap3216c", 0},
310 {}
311 };
312
313 /* 设备树匹配列表 */
314 static const struct of_device_id ap3216c_of_match[] = {
315 { .compatible = "alientek,ap3216c" },
316 { /* Sentinel */ }
317 };
318
319 /* i2c 驱动结构体 */
320 static struct i2c_driver ap3216c_driver = {
321 .probe = ap3216c_probe,
322 .remove = ap3216c_remove,
323 .driver = {
324 .owner = THIS_MODULE,
325 .name = "ap3216c",
326 .of_match_table = ap3216c_of_match,
327 },
328 .id_table = ap3216c_id,
329 };
330
331 /*
332 * @description : 驱动入口函数
333 * @param : 无
334 * @return : 无
335 */
336 static int __init ap3216c_init(void)
337 {
338 int ret = 0;
339
340 ret = i2c_add_driver(&ap3216c_driver);
341 return ret;
342 }
343
344 /*
345 * @description : 驱动出口函数
346 * @param : 无
347 * @return : 无
348 */
349 static void __exit ap3216c_exit(void)
350 {
351 i2c_del_driver(&ap3216c_driver);
352 }
353
354 /* module_i2c_driver(ap3216c_driver) */
355
356 module_init(ap3216c_init);
357 module_exit(ap3216c_exit);
358 MODULE_LICENSE("GPL");
359 MODULE_AUTHOR("ALIENTEK");
360 MODULE_INFO(intree, "Y");
12 #include "stdio.h"
13 #include "unistd.h"
14 #include "sys/types.h"
15 #include "sys/stat.h"
16 #include "sys/ioctl.h"
17 #include "fcntl.h"
18 #include "stdlib.h"
19 #include "string.h"
20 #include <poll.h>
21 #include <sys/select.h>
22 #include <sys/time.h>
23 #include <signal.h>
24 #include <fcntl.h>
25 /*
26 * @description : main 主程序
27 * @param - argc : argv 数组元素个数
28 * @param - argv : 具体参数
29 * @return : 0 成功;其他 失败
30 */
31 int main(int argc, char *argv[])
32 {
33 int fd;
34 char *filename;
35 unsigned short databuf[3];
36 unsigned short ir, als, ps;
37 int ret = 0;
38
39 if (argc != 2) {
40 printf("Error Usage!\r\n");
41 return -1;
42 }
43
44 filename = argv[1];
45 fd = open(filename, O_RDWR);
46 if(fd < 0) {
47 printf("can't open file %s\r\n", filename);
48 return -1;
49 }
50
51 while (1) {
52 ret = read(fd, databuf, sizeof(databuf));
53 if(ret == 0) { /* 数据读取成功 */
54 ir = databuf[0]; /* ir 传感器数据 */
55 als = databuf[1]; /* als 传感器数据 */
56 ps = databuf[2]; /* ps 传感器数据 */
57 printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);
58 }
59 usleep(200000); /*100ms */
60 }
61 close(fd); /* 关闭文件 */
62 return 0;
63 }
1 KERNELDIR := /home/lcm/rk3568_linux_sdk/kernel
......
4 obj-m := ap3216c.o
......
11 clean:
12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
/opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-gcc ap3216cApp.c -o ap3216cApp
adb push ap3216c.ko ap3216cApp /lib/modules/4.19.232
depmod
modprobe ap3216c
./ap3216cApp /dev/ap3216c
相关文章:
RK3568 I2C底层驱动详解
前提须知:I2C协议不懂的话就去看之前的内容吧,这个文章需要读者一定的基础。 RK3568 I2C 简介 RK3568 支持 6 个独立 I2C: I2C0、I2C1、I2C2、I2C3、I2C4、I2C5。I2C 控制器支持以下特性: ① 兼容 i2c 总线 ② AMBA APB 从接口 ③ 支持 I2C 总线主模式…...
【大语言模型_8】vllm启动的模型通过fastapi封装增加api-key验证
背景: vllm推理框架启动模型不具备api-key验证。需借助fastapi可以实现该功能 代码实现: rom fastapi import FastAPI, Header, HTTPException, Request,Response import httpx import logging# 创建 FastAPI 应用 app FastAPI() logging.basicConfig(…...
hadoop-HDFS操作
1. 使用的是hadoop的用户登录到系统,那么 cd ~ 是跳转到/home/hadoop下。 2. 在操作hdfs时,需要在hadoop用户下的/usr/local/hadoop,此时是在根目录下。 cd /usr/local/hadoop或者cd / cd usr/local/hadoop 3. 回到Linux的操作目录 我们把…...
Mysql 安装教程和Workbench的安装教程以及workbench的菜单栏汉化
Mysql 安装教程和Workbench的安装教程 详细请参考我的文件 Mysql 安装教程和Workbench的安装教程 或者下载我的资源Mysql 安装教程和Workbench的安装教程 汉化菜单 英文版菜单文件:下载链接 汉化版菜单文件:下载链接 默认情况下,安…...
失物招领|校园失物招领系统|基于Springboot的校园失物招领系统设计与实现(源码+数据库+文档)
校园失物招领系统目录 目录 基于Springboot的校园失物招领系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、 管理员功能实现 (1) 失物招领管理 (2) 寻物启事管理 (3) 公告管理 (4) 公告类型管理 2、用户功能实现 (1) 失物招领 (2) 寻物启事 (3) 公告 …...
一条不太简单的TEX学习之路
目录 rule raisebox \includegraphics newenviro 、\vspace \stretch \setlength 解释: 总结: 、\linespread newcommand \par 小四 \small simple 、mutiput画网格 解释: 图案解释: xetex pdelatex etc index 报…...
如何为AI开发选择合适的服务器?
选择适合的服务器可以为您的AI项目带来更高的效率,确保最佳性能、可扩展性和可靠性,从而实现无缝的开发与部署。 选择适合的AI开发服务器可能并不容易。您需要一台能够处理大量计算和大型数据集的服务器,同时它还需要符合您的预算并易于管理…...
doris:审计日志
Doris 提供了对于数据库操作的审计能力,可以记录用户对数据库的登陆、查询、修改操作。在 Doris 中,可以直接通过内置系统表查询审计日志,也可以直接查看 Doris 的审计日志文件。 开启审计日志 通过全局变量 enable_audit_plugin 可以随时…...
CSS中的transition与渐变
目录 一、CSS transition 1. 核心属性 简写语法 2. 子属性详解 2.1 transition-property 2.2 transition-duration 2.3 transition-timing-function 2.4 transition-delay 3. 使用场景示例 3.1 悬停效果(Hover) 3.2 展开/收起动画 3.3 动态移…...
AI + 医疗 Qwq大模型离线本地应用
通义千问Qwq-32b-FP16可用于社区医院、乡镇卫生院、诊所等小型医疗机构,替代专业合理用药系统,作为药品知识库,实现以下功能: 药品信息智能查询:检索药品的详细说明书、适应症、禁忌症、不良反应及药物相互作用等关键信…...
大数据环境搭建
目录 一:虚拟机:VirtualBox 二:Shell工具:MobaXterm 三:安装脚本 四:JDK和Hadoop 4.1:安装 4.2:启动 4.3:Hadoop可视化访问 4.4:关机 一:虚拟机:VirtualBox Virt…...
七天免登录 为什么不能用seesion,客户端的http请求自动携带cookei的机制(比较重要)涉及HTTP规范
如果是七天免登录,和session肯定没关系,因为session不能持久化,主要是客户端一旦关闭,seesion就失效了/// 所以必须是能持久化的,这就清晰了,要莫在的服务器保存,要摸在客户端设置 cook机制 1. 使用Cookie实现七天免登录 前端(登…...
从PGC到AIGC:海螺AI多模态内容生成系统的技术革命
一、内容生产的范式迁移:从PGC到AIGC的进化之路 在数字内容生产的历史长河中,人类经历了三次重大范式转变:专业生成内容(PGC)的工业化生产、用户生成内容(UGC)的全民创作浪潮,以及当…...
常考计算机操作系统面试习题(三上)
目录 1. 为何要引入与设备的无关性?如何实现设备的独立性? 2. 页面置换先进先出算法 3. 页面置换先进先出算法,4个页框 4. 进程优先级调度算法 5. 短作业优先调度策略 6. 平均内存访问时间计算 7. 页式存储和段式存储的物理地址计算 …...
数据结构之双向链表-初始化链表-头插法-遍历链表-获取尾部结点-尾插法-指定位置插入-删除节点-释放链表——完整代码
数据结构之双向链表-初始化链表-头插法-遍历链表-获取尾部结点-尾插法-指定位置插入-删除节点-释放链表——完整代码 #include <stdio.h> #include <stdlib.h>typedef int ElemType;typedef struct node{ElemType data;struct node *next, *prev; }Node;//初化链表…...
一键部署 GPU Kind 集群,体验 vLLM 极速推理
随着 Kubernetes 在大模型训练和推理领域的广泛应用,越来越多的开发者需要在本地环境中搭建支持 GPU 的 Kubernetes 集群,以便进行测试和开发。大家都知道,本地搭建 Kubernetes 集群通常可以使用 Kind(Kubernetes IN Docker&#…...
C/C++蓝桥杯算法真题打卡(Day6)
一、P8615 [蓝桥杯 2014 国 C] 拼接平方数 - 洛谷 方法一:算法代码(字符串分割法) #include<bits/stdc.h> // 包含标准库中的所有头文件,方便编程 using namespace std; // 使用标准命名空间,避免每次调用…...
【C++】入门
1.命名空间 1.1 namespace的价值 在C/C中,变量,函数和后面要学到的类都是大量存在的,这些变量,函数和类的名称将存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,…...
CUDA 学习(2)——CUDA 介绍
GeForce 256 是英伟达 1999 年开发的第一个 GPU,最初用作显示器上渲染高端图形,只用于像素计算。 在早期,OpenGL 和 DirectX 等图形 API 是与 GPU 唯一的交互方式。后来,人们意识到 GPU 除了用于渲染图形图像外,还可以…...
构建自定义MCP天气服务器:集成Claude for Desktop与实时天气数据
构建自定义MCP天气服务器:集成Claude for Desktop与实时天气数据 概述 本文将指导开发者构建一个MCP(Model Control Protocol)天气服务器,通过暴露get-alerts和get-forecast工具,为Claude for Desktop等客户端提供实时天气数据支持。该方案解决了传统LLM无法直接获取天气…...
[Lc_2 二叉树dfs] 布尔二叉树的值 | 根节点到叶节点数字之和 | 二叉树剪枝
目录 1.计算布尔二叉树的值 题解 2.求根节点到叶节点数字之和 3. 二叉树剪枝 题解 1.计算布尔二叉树的值 链接:2331. 计算布尔二叉树的值 给你一棵 完整二叉树 的根,这棵树有以下特征: 叶子节点 要么值为 0 要么值为 1 ,其…...
搜广推校招面经五十六
字节推荐算法 一、Attention的复杂度是多少? 见【搜广推校招面经三十八】 二、如何对普适性强的物品(如新华字典)设计指标进行降权 2.1. 问题背景 普适性强的物品(如新华字典)在推荐系统或搜索排序中可能频繁出现…...
ZYNQ的cache原理与一致性操作
在Xilinx Zynq SoC中,Cache管理是确保处理器与外部设备(如FPGA逻辑、DMA控制器)之间数据一致性的关键。Zynq的ARM Cortex-A9处理器包含L1 Cache(指令/数据)和L2 Cache,其刷新(Flush/Invalidate&…...
安装React开发者工具
我们在说组件之前,需要先安装一下React官方推出的开发者工具,首先我们分享在线安装方式 首先打开谷歌网上应用商店(针对谷歌浏览器),在输入框内搜索react,安装如下插件: 注意安装提供方为Facebook的插件,这…...
多层感知机
多层感知机(Multilayer Perceptron,简称 MLP)是一种基于前馈神经网络(Feedforward Neural Network)的深度学习模型,由多个神经元层组成,每一层与前一层全连接。它包括至少一个隐藏层(…...
2025年渗透测试面试题总结- PingCAP安全工程师(题目+回答)
网络安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 PingCAP安全工程师 一、SQL注入判断数据库类型技术分析 1. 常规判断方法 2. 盲注场景下的判断 3. 补…...
CAD模型导入Geant4
CADMesh是一个开源项目,专门用于将STL格式的CAD模型导入Geant4。以下是使用CADMesh操作STL模型的步骤: 准备工作 下载CADMesh开源代码:可以从GitHub或Gitee下载CADMesh的开源代码。 将CAD模型转换为STL格式:在CAD软件中创建几何…...
DeepSORT 目标追踪算法详解
DeepSORT(Deep Simple Online and Realtime Tracking)是 多目标追踪(MOT) 领域的经典算法,通过结合目标检测、运动预测和外观特征匹配,实现了高效、稳定的实时追踪。其核心思想是通过 检测驱动追踪…...
mne溯源后的数据初步处理方法
文章目录 导入库 Yeo2011_7Networks_N1000绘制一些圆球来代表区域大小和强度 单网络绘制和扩展的方式AI补充一下背景知识📚 **背景与研究来源**🧠 **7 个功能网络的定义**📂 **标签数据获取**🔍 **标签文件内容解析**🛠…...
基于STM32进行FFT滤波并计算插值DA输出
文章目录 一、前言背景二、项目构思1. 确定FFT点数、采样率、采样点数2. 双缓存设计 三、代码实现1. STM32CubeMX配置和HAL库初始化2. 核心代码 四、效果展示和后话五、项目联想与扩展1. 倍频2. 降频3. 插值3.1 线性插值3.2 样条插值 一、前言背景 STM32 对 AD 采样信号进行快…...
【用 Trace读源码】PlanAgent 执行流程
前提条件 在 Trae 中打开 OpenManus 工程,使用 build 模式,模型选择 claude-sonnet-3.7 提示词 分析 agent/planning.py 中 main 方法及相关类的执行流程,以流程图的方式展示PlanningAgent 执行流程图 以下流程图展示了 PlanningAgent 类…...
AI代码编辑器:Cursor和Trae
Cursor 定义:Cursor 是一款基于AI的代码编辑器,它继承了VS Code的核心功能,并在此基础上增加了深度AI支持。它支持代码生成、优化、重构以及调试等功能,提供直观的Diff视图和自动补全功能,是一款功能强大的编程工具。…...
LSM-Tree(Log-Structured Merge-Tree)详解
1. 什么是 LSM-Tree? LSM-Tree(Log-Structured Merge-Tree)是一种 针对写优化的存储结构,广泛用于 NoSQL 数据库(如 LevelDB、RocksDB、HBase、Cassandra)等系统。 它的核心思想是: 写入时只追加写(Append-Only),将数据先写入内存缓冲区(MemTable)。内存数据满后…...
介绍一个测试boostrap表格插件的好网站!
最近在开发一个物业管理系统。用到bootstrap的表格插件bootstrap table,官方地址: https://bootstrap-table.com/ 因为是英文界面,对国人不是很友好。后来发现了IT小书童网站 IT小书童 - 为程序员提供优质教程和文档 网站: IT…...
虚拟路由与单页应用(SPA):详解
在单页应用(SPA,Single Page Application)中,虚拟路由(也称为前端路由)是一种关键的技术,用于管理页面导航和状态变化,而无需重新加载整个页面。为了帮助你更好地理解这一概念&#…...
基于树莓派3B+的人脸识别实践:Python与C联合开发
基于树莓派3B的人脸识别实践:Python与C联合开发 引言 树莓派因其小巧的体积和丰富的扩展性,成为嵌入式开发的理想平台。本文将分享如何通过Python与C语言联合开发,在树莓派3B上实现从硬件控制、摄像头拍照到百度API人脸比对的完整流程。项目…...
尝试使用Tauri2+Django+React项目(2)
前言 尝试使用tauri2DjangoReact的项目-CSDN博客https://blog.csdn.net/qq_63401240/article/details/146403103在前面笔者不知道怎么做,搞了半天 笔者看到官网,原来可以使用二进制文件,好好好 嵌入外部二进制文件 | Taurihttps://v2.taur…...
Qt桌面客户端跨平台开发实例
在Windows平台上,桌面客户端软件通常使用C/C语言和Qt跨平台开发框架进行开发。因此,大部分代码可以运行于不同平台环境,但是程序运行依赖的三方库以及代码中一些平台相关的头文件和接口需要进行平台兼容。本文以windows桌面端应用迁移到Linux…...
c++进阶之------红黑树
一、概念 红黑树(Red-Black Tree)是一种自平衡二叉查找树,它在计算机科学的许多领域中都有广泛应用,比如Java中的TreeMap和C中的set/map等数据结构的底层实现。红黑树通过在每个节点上增加一个颜色属性(红色或黑色&am…...
政安晨【超级AI工作流】—— 使用Dify通过工作流对接ComfyUI实现多工作流协同
政安晨的个人主页:政安晨 欢迎 👍点赞✍评论⭐收藏 希望政安晨的博客能够对您有所裨益,如有不足之处,欢迎在评论区提出指正! 目录 一、准备工作 Dify跑起来 ollama局域网化配置 Dify配置并验证 启动ComfyUI 二、…...
javaweb开发以及部署
先说一个阿里云学生无门槛免费领一年2核4g服务器的方法: 阿里云服务器学生无门槛免费领一年2核4g_阿里云学生认证免费服务器-CSDN博客 Java Web开发是使用Java编程语言开发Web应用程序的过程,通常涵盖了使用Java EE(Java Enterprise Edition…...
树莓派5介绍与系统安装
简介 Raspberry Pi 5采用运行频率为2.4GHz的64位四核Arm Cortex-A76处理器,与Raspberry Pi 4相比, CPU性能提高了2至3倍。此外,它还配备了一个800MHz的VideoCore VII GPU,可以提供大幅度的图形 性能提升,通过HDMI实现…...
菜鸟之路Day25一一前端工程化(二)
菜鸟之路Day25一一前端工程化(二) 作者:blue 时间:2025.3.19 文章目录 菜鸟之路Day25一一前端工程化(二)1.概述2.Element快速入门3.综合案例一.布局二.组件三.Axios异步加载数据1. 生命周期钩子概述2. mo…...
vue如何获取 sessionStorage的值,获取token
// 使用Axios发送请求并处理下载 import axios from axios;const handleDownload () > {const params {warehouseId: selectedWarehouseId.value};const apiUrl /api/materials/wmMatCheck/export-wmMatCheckDetail;axios.get(apiUrl, {params,responseType: blob, // 接…...
图解AUTOSAR_CP_DiagnosticLogAndTrace
AUTOSAR 诊断日志和跟踪(DLT)模块详解 AUTOSAR 经典平台中的诊断和调试关键组件 目录 1. 概述2. DLT模块架构 2.1 模块位置2.2 内部组件2.3 接口定义 3. DLT操作流程 3.1 初始化流程3.2 日志和跟踪消息处理3.3 控制命令处理 4. 数据结构与配置模型 4.1 配置类4.2 消息格式4.3 …...
微调实战 - 使用 Unsloth 微调 QwQ 32B 4bit (单卡4090)
本文参考视频教程:赋范课堂 – 只需20G显存,QwQ-32B高效微调实战!4大微调工具精讲!知识灌注问答风格微调,DeepSeek R1类推理模型微调Cot数据集创建实战打造定制大模型! https://www.bilibili.com/video/BV1…...
金仓KESV8R6任务调度
基本概念 • 程序(program) 程序对象描述调度器要运行的内容。 • 调度计划(schedule) 调度计划对象指定作业何时运行以及运行多少次。调度计划可以被多个作业共享。 • 作业(job) 作业就是用户定义的…...
Maven常见问题汇总
Maven刷新,本地仓库无法更新 现象 This failure was cached in the local repository and resolution is not reattempted until the update interval of aliyunmaven has elapsed or updates are forced原因 因为上一次尝试下载,发现对应的仓库没有这个maven配置…...
颠覆者的困局:解构周鸿祎商业哲学中的“永恒战争”
引言:被误解的破坏者 在北京海淀区知春路银谷大厦的某间会议室里,周鸿祎用马克笔在白板上画出一个巨大的爆炸图案——这是2010年360与腾讯开战前夜的战术推演场景。这个充满硝烟味的瞬间,恰是《颠覆者》精神内核的完美隐喻:在中国…...
基于ChatGPT、GIS与Python机器学习的地质灾害风险评估、易发性分析、信息化建库及灾后重建高级实践
第一章、ChatGPT、DeepSeek大语言模型提示词与地质灾害基础及平台介绍【基础实践篇】 1、什么是大模型? 大模型(Large Language Model, LLM)是一种基于深度学习技术的大规模自然语言处理模型。 代表性大模型:GPT-4、BERT、T5、Ch…...