linux下i2c开发与框架源码分析

ops/2024/11/25 10:06:14/

目录

1 概述

2 I2c子系统框架

3 I2C的使用流程

3.1 在驱动里使用

3.2 在应用层使用

3.3 I2ctool的使用

4 为硬件i2c注册一个适配器

5 i2c子系统源码流程分析

5.1 i2c device与driver绑定过程

5.1.1 Driver的注册与处理

5.1.2 Client device的生成

5.2 I2c的发送与接收

5.3 i2c总线设备生成过程

6 i2c作为slave的使用


1 概述

        本文主要描述了i2c的使用以及分析了大部分i2c子系统源码和实现原理,本文以读者对i2c硬件原理已掌握为基础来描述,需要读者理解基础的i2c通信过程。

2 I2c子系统框架

        从分层角度上看,i2c子系统大致分为设备驱动层client、i2c核心层和i2c适配器层,如下图大致描述了整个i2c应用和内核框架的关系逻辑,从上到下,用户可通过底层提供的总线设备或者外设设备来访问挂载在总线上的i2c设备。

        i2c子系统向驱动层提供了i2c client,每一个i2c设备将被实现成一个client,设备驱动在拿到i2c_client后,即可通过该对象来读写i2c数据访问i2c设备。I2c核心层向下也提供了i2c适配器层,每一个硬件i2c都被实现成一个i2c adapter,主要负责向i2c核心层提供硬件操作接口

3 I2C的使用流程

        本节讲i2c在驱动层如何被调用使用,没有特殊说明,均认为i2c作为master,后面章节将介绍i2c作为slave的用法。

3.1 在驱动里使用

        在i2c的驱动应用中,比较常见的是先在设备树里的i2c节点挂在硬件上挂在到该i2c总线的i2c设备,例如一个sensor的节点:

&i2c2 {status = "okay";
...ov5695: ov5695@36 {compatible = "ovti,ov5695";reg = <0x36>;avdd-supply = <&vcc2v8_dvp>;clocks = <&cru SCLK_CIF_OUT>;clock-names = "xvclk";dvdd-supply = <&vcc1v5_dvp>;dovdd-supply = <&vcc1v8_dvp>;pinctrl-names = "default";pinctrl-0 = <&cif_clkout_m0 &mipi_pdn>;reset-gpios = <&gpio2 RK_PB6 GPIO_ACTIVE_LOW>;...};
};

        然后在sensor的驱动里,调用i2c_register_driver将自己定义好的struct i2c_driver传入该接口,i2c总线将会调用我们自定义好的probe函数,让设备驱动程序加载起来,有时也会用i2c注册宏module_i2c_driver来做。

static struct i2c_driver ov5695_i2c_driver = {.driver = {.name = "ov5695",.pm = &ov5695_pm_ops,.of_match_table = of_match_ptr(ov5695_of_match),},.probe      = ov5695_probe,.remove     = ov5695_remove,
};
module_i2c_driver(ov5695_i2c_driver);
----------------------或者-----------------------
static int mpu6050_driver_init(void)
{i2c_add_driver(&mpu6050_driver);return 0;
}
static void mpu6050_driver_exit(void)
{i2c_del_driver(&mpu6050_driver);
}
module_init(mpu6050_driver_init);
module_exit(mpu6050_driver_exit);

        之后将进入probe函数,传入的i2c_client结构体,用于i2c数据收发

static int ov5695_probe(struct i2c_client *client)
{
}
static int ov5695_read_reg(struct i2c_client *client, u16 reg, unsigned int len, u32 *val)
{struct i2c_msg msgs[2];...int ret;if (len > 4)return -EINVAL;data_be_p = (u8 *)&data_be;/* Write register address */msgs[0].addr = client->addr;msgs[0].flags = 0;msgs[0].len = 2;msgs[0].buf = (u8 *)&reg_addr_be;/* Read data from register */msgs[1].addr = client->addr;msgs[1].flags = I2C_M_RD;msgs[1].len = len;msgs[1].buf = &data_be_p[4 - len];ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));...return 0;
}

3.2 在应用层使用

        I2c子系统同时提供了每个i2c总线的设备,应用可以直接打开i2c总线设备,传入从机地址来进行通信

int fd = open("/dev/i2c-0", O_RDWR);
int i2c_write(uint8_t slave, uint8_t reg, uint8_t * data, int len)
{unsigned char buf[1024];struct i2c_rdwr_ioctl_data i2c_data;struct i2c_msg i2c_msg;i2c_data.nmsgs = 1;i2c_data.msgs = &i2c_msg ;ioctl(_fd, I2C_TIMEOUT, 1);ioctl(_fd, I2C_RETRIES, 2);memset(buf, 0, 1024);buf[0] = reg;memcpy(&buf[1], buf, len);i2c_data.msgs[0].addr = slave;i2c_data.msgs[0].flags = 0; i2c_data.msgs[0].buf = &buf[0];i2c_data.msgs[0].len = len+1;int ret = ioctl(fd, I2C_RDWR, (unsigned long)&i2c_data);...return 0;
}
int i2c_read(int8_t slave, int8_t reg, uint8_t * data, int len)unsigned char buf[2];struct i2c_rdwr_ioctl_data i2c_data;struct i2c_msg i2c_msg[2] ;i2c_data.nmsgs = 2;i2c_data.msgs = &i2c_msg[0] ;ioctl(_fd, I2C_TIMEOUT, 1);ioctl(_fd, I2C_RETRIES, 2);buf[0] = reg ;i2c_data.msgs[0].addr = slave;i2c_data.msgs[0].flags = 0;     i2c_data.msgs[0].buf = &buf[0];i2c_data.msgs[0].len = 1;i2c_data.msgs[1].addr = slave;i2c_data.msgs[1].flags = 1;    i2c_data.msgs[1].buf = data;i2c_data.msgs[1].len = len;int ret = ioctl(_fd, I2C_RDWR, (unsigned long)&i2c_data);....
}
close(fd);

3.3 I2ctool的使用

        有时在调试时,会使用i2ctool命令行进行测试,使用这些命令行接口,需要先把该库打包进行文件系统,或者移植。

        i2cdetect:用于扫描 i2c 总线上的设备,并显示地址。
        i2cset:设置i2c设备某个寄存器的值。
        i2cget:读取i2c设备某个寄存器的值。
        i2cdump:读取某个i2c设备所有寄存器的值。
        i2ctransfer:一次性读写多个字节。

注:参考https://blog.csdn.net/yyz_1987/article/details/131953108

        驱动中常用接口:

        //发送接收消息

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
static inline int i2c_master_recv(const struct i2c_client *client,char *buf, int count)
static inline int i2c_master_send(const struct i2c_client *client,const char *buf, int count)

        //注册与注销:

#define i2c_add_driver(driver) \i2c_register_driver(THIS_MODULE, driver)
int i2c_register_driver(struct module *owner, struct i2c_driver *driver) 
void i2c_del_driver(struct i2c_driver *driver)

        另外i2c还提供了SMBus设备相关接口,不在此文讨论范围。

4 为硬件i2c注册一个适配器

        如何注册一个i2c adapter

        以rockchip为例子,分析rockchip注册一个adapter源码,实现在drivers/i2c/busses/i2c-rk3x.c

设备树对i2c2的定义如下:

i2c2: i2c@ff1a0000 {compatible = "rockchip,px30-i2c", "rockchip,rk3399-i2c";reg = <0x0 0xff1a0000 0x0 0x1000>;clocks = <&cru SCLK_I2C2>, <&cru PCLK_I2C2>;clock-names = "i2c", "pclk";interrupts = <GIC_SPI 9 IRQ_TYPE_LEVEL_HIGH>;pinctrl-names = "default";pinctrl-0 = <&i2c2_xfer>;#address-cells = <1>;#size-cells = <0>;status = "disabled";};

        将和驱动匹配

static struct platform_driver rk3x_i2c_driver = {.probe   = rk3x_i2c_probe,.remove_new = rk3x_i2c_remove,.driver  = {.name  = "rk3x-i2c",.of_match_table = rk3x_i2c_match,.pm = &rk3x_i2c_pm_ops,},
};
module_platform_driver(rk3x_i2c_driver);

        注册过程:

rk3x_i2c_probe
struct rk3x_i2c *i2c;
//adapter结构体的初始化,其中rk3x_i2c_algorithm是硬件接口,后面分析strscpy(i2c->adap.name, "rk3x-i2c", sizeof(i2c->adap.name));i2c->adap.owner = THIS_MODULE;i2c->adap.algo = &rk3x_i2c_algorithm;i2c->adap.retries = 3;i2c->adap.dev.of_node = np;i2c->adap.algo_data = i2c;
i2c->adap.dev.parent = &pdev->dev;
//后续大部分是获取i2c设备节点的信息,包括基地址、中断申请、时钟获取和配置等
i2c->regs = devm_platform_ioremap_resource(pdev, 0);
ret = devm_request_irq(&pdev->dev, irq, rk3x_i2c_irq,0, dev_name(&pdev->dev), i2c);
ret = clk_prepare(i2c->clk);
//最后调用i2c子系统api,注册一个adapter
ret = i2c_add_adapter(&i2c->adap);
------------------------------------------------------------------------------
注册到adapter的硬件回调如下,这些将提供给i2c子系统操作硬件的接口,主要是发送与接收数据
static const struct i2c_algorithm rk3x_i2c_algorithm = {.master_xfer        = rk3x_i2c_xfer,//发送与接收.master_xfer_atomic = rk3x_i2c_xfer_polling,//原子发送接收.functionality      = rk3x_i2c_func,//当前i2c 适配器支持哪些特性
};

        以上便是一个i2c adapter的注册过程,比较简单,需要注意的是,在设备树定义了多个i2c节点是,这个driver将被调用多次,即将申请多个adapter,这里一个硬件i2c就申请一个adapter。

i2c0: i2c@ff180000 {compatible = "rockchip,px30-i2c", "rockchip,rk3399-i2c";...};i2c1: i2c@ff190000 {compatible = "rockchip,px30-i2c", "rockchip,rk3399-i2c";....};i2c2: i2c@ff1a0000 {compatible = "rockchip,px30-i2c", "rockchip,rk3399-i2c";...};i2c3: i2c@ff1b0000 {compatible = "rockchip,px30-i2c", "rockchip,rk3399-i2c";....};

        其他都是I2C硬件接口配置过程,不做详细分析。

5 i2c子系统源码流程分析

        I2c子系统源码实现在Linux内核的drivers/i2c下,这里有芯片产商文件,也有i2c子系统核心文件:

        drivers/i2c/i2c-boardinfo.c//i2c ip信息整理接口

        drivers/i2c/i2c-core-acpi.c//acpi设备接口

        drivers/i2c/i2c-core-base.c//核心文件

        drivers/i2c/i2c-core-of.c//设备树解析相关文件

        drivers/i2c/i2c-core-slave.c//i2c从机接口

        drivers/i2c/i2c-core-smbus.c/smbus设备相关接口

        drivers/i2c/i2c-dev.c//i2c总线设备相关文件

5.1 i2c device与driver绑定过程

        I2c子系统如何生成client device与client驱动匹配后调用client的probe函数的流程

5.1.1 Driver的注册与处理

        以sensor节点为例子,在设备树中,有如下设备树i2c定义,i2c节点相关信息定义还有定义在i2c2下的ov5695节点。

 i2c2: i2c@ff1a0000 {compatible = "rockchip,px30-i2c", "rockchip,rk3399-i2c";reg = <0x0 0xff1a0000 0x0 0x1000>;clocks = <&cru SCLK_I2C2>, <&cru PCLK_I2C2>;clock-names = "i2c", "pclk";interrupts = <GIC_SPI 9 IRQ_TYPE_LEVEL_HIGH>;pinctrl-names = "default";pinctrl-0 = <&i2c2_xfer>;#address-cells = <1>;#size-cells = <0>;status = "disabled";};
&i2c2 {...ov5695: ov5695@36 {compatible = "ovti,ov5695";reg = <0x36>;avdd-supply = <&vcc2v8_dvp>;clocks = <&cru SCLK_CIF_OUT>;clock-names = "xvclk";dvdd-supply = <&vcc1v5_dvp>;dovdd-supply = <&vcc1v8_dvp>;pinctrl-names = "default";pinctrl-0 = <&cif_clkout_m0 &mipi_pdn>;reset-gpios = <&gpio2 RK_PB6 GPIO_ACTIVE_LOW>;...};
};

        在Linux初始化的时候,这里会被Linux解析成两个device,且i2c2这个device下挂着ov5695这个device,ov5695的driver定义在drivers/media/i2c/ov5695.c,看驱动注册部分:

static struct i2c_driver ov5695_i2c_driver = {.driver = {.name = "ov5695",.pm = &ov5695_pm_ops,.of_match_table = of_match_ptr(ov5695_of_match),},.probe      = ov5695_probe,.remove     = ov5695_remove,
};
module_i2c_driver(ov5695_i2c_driver);
MODULE_DESCRIPTION("OmniVision ov5695 sensor driver");
MODULE_LICENSE("GPL v2");

        定义了一个name为ov5695 的sensor driver,通过module_i2c_driver 注册成一个driver,module_i2c_driver 声明

clude/linux/i2c.h
#define module_i2c_driver(__i2c_driver) \module_driver(__i2c_driver, i2c_add_driver, \i2c_del_driver)
#define i2c_add_driver(driver) \i2c_register_driver(THIS_MODULE, driver)include/linux/device/driver.h
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
整理如上宏定义,最后调用方式
static int __init ov9282_driver_init(void) 
{ return i2c_register_driver(THIS_MODULE, &(ov9282_driver)); 
} 
module_init(ov9282_driver_init);
static void __exit ov9282_driver_exit(void) 
{ i2c_del_driver(&(ov9282_driver)); 
}
module_exit(ov9282_driver_exit);

        即最后调用i2c_register_driver 来注册,看这个函数如何注册一个driver的:

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)(drivers/i2c/i2c-core-base.c)driver->driver.owner = owner;driver->driver.bus = &i2c_bus_type;//选择总线,该总线在i2c子系统初始化时加载INIT_LIST_HEAD(&driver->clients);
res = driver_register(&driver->driver);//将该driver注册到i2c_bus_type这个总线上。i2c_for_each_dev(driver, __process_new_driver);//检查是否已有对应device,有则扫描该i2c设备是否在线。

        I2c子系统对driver的注册比较简单,整体来说就是将driver注册到名为i2c_bus_type 的总线上,这条总线是在i2c子系统初始化的时候注册的:

struct bus_type i2c_bus_type = {.name = "i2c",.match = i2c_device_match,.probe = i2c_device_probe,.remove = i2c_device_remove,.shutdown = i2c_device_shutdown,
};
static int __init i2c_init(void) {(drivers/i2c/i2c-core-base.c)retval = bus_register(&i2c_bus_type);//注册一条platform总线
...
retval = i2c_add_driver(&dummy_driver);//添加一个dummy driver,没有做任操作,用来匹配adapter注册时生成的device
}
postcore_initcall(i2c_init);
module_exit(i2c_exit);

一旦总线匹配到device和对应driver,probe将被调用:

static int i2c_device_probe(struct device *dev) {(drivers/i2c/i2c-core-base.c)
struct i2c_client *client = i2c_verify_client(dev);//拿到dev绑定的clientif (driver->probe)status = driver->probe(client);//调用driver的probe,并传入client,这里将调用ov5695里probe函数elsestatus = -EINVAL;
}

        上面分析了driver是如何注册以及如何被调用,client如何传进driver的probe函数,接下来分析i2c子系统是如何生成对应的device,以匹配bus上的driver调用driver的probe函数的。

5.1.2 Client device的生成

        来看此时i2c子系统是如何解析这个层级关系,以及client如何生成。I2c子系统在添加一个adapter的时候,就会轮询该i2c节点下所有的节点,逐一生成client和device的。

int i2c_add_adapter(struct i2c_adapter *adapter)(drivers/i2c/i2c-core-base.c)struct device *dev = &adapter->dev;//如下主要寻找i2c adapter记录在i2c_adapter_idr的序号,如果用的是设备节点的方式,则进如下if分支if (dev->of_node) {id = of_alias_get_id(dev->of_node, "i2c");if (id >= 0) {adapter->nr = id;return __i2c_add_numbered_adapter(adapter);}}
//如果用不是设备树方式或者第一次创建,将用随机分配id的方式进行id = idr_alloc(&i2c_adapter_idr, adapter, __i2c_first_dynamic_bus_num, 0,GFP_KERNEL);return i2c_register_adapter(adapter);
=>__i2c_add_numbered_adapter(这里分析的是使用设备树方式)
记录adapter到i2c_adapter_idr后,调用i2c_register_adapter
==>i2c_register_adapterdev_set_name(&adap->dev, "i2c-%d", adap->nr);//生成一个i2c-x的device,匹配bus注册是dummy driveradap->dev.bus = &i2c_bus_type;adap->dev.type = &i2c_adapter_type;res = device_register(&adap->dev);//注册到i2c_bus_type总线。
===>of_i2c_register_devices(drivers/i2c/i2c-core-of.c)
bus = of_node_get(adap->dev.of_node);//找到parent节点,即i2c2这个节点for_each_available_child_of_node(bus, node) {//轮询i2c2节点下的所有子节点if (of_node_test_and_set_flag(node, OF_POPULATED))//该子节点是否被其他驱动使用了continue;client = of_i2c_register_device(adap, node);//注册一个client device}of_node_put(bus);
====>of_i2c_register_device(drivers/i2c/i2c-core-of.c)ret = of_i2c_get_board_info(&adap->dev, node, &info);//获取节点的数据client = i2c_new_client_device(adap, &info);//生成client device
=====>of_i2c_get_board_info
//找出node这个子节点,compatible标签里的值,并去掉该值‘,’前面的值,取后面的值,在这里取到了ovti,ov5695,只取,	//后面的值,最后type的值是ov5695if (of_alias_from_compatible(node, info->type, sizeof(info->type)) < 0) {dev_err(dev, "of_i2c: modalias failure on %pOF\n", node);return -EINVAL;
}
ret = of_property_read_u32(node, "reg", &addr);//读取i2c设备的从机地址=====>i2c_new_client_device(drivers/i2c/i2c-core-base.c)struct i2c_client *client;client = kzalloc(sizeof *client, GFP_KERNEL);//申请一个i2c_client
//后续有很多client数据填充
client->adapter = adap;
...
i2c_check_addr_validity(client->addr, client->flags);//检查地址有效性,必须是7bit地址i2c_check_addr_busy(adap, i2c_encode_flags_to_addr(client));//检查i2c设备是否在线
//如下是重点,配置dev的属性client->dev.parent = &client->adapter->dev;client->dev.bus = &i2c_bus_type;//该dev要挂载的总线,跟之前driver挂在在同一个总线上client->dev.type = &i2c_client_type;//将ov5695节点赋值到dev下,同时和driver的compitable	一致,所以这里将和上面注册driver匹配
client->dev.of_node = of_node_get(info->of_node);client->dev.fwnode = info->fwnode;
i2c_dev_set_name(adap, client, info);//设置device的名称status = device_register(&client->dev);//注册到总线,此时driver的probe函数将被调用。

        如上分析了client device的生成以及如何和driver匹配,使得driver驱动被加载。

5.2 I2c的发送与接收

        I2c的接收发送接口,最终都调用到adapter提供的硬件通信接口

>i2c_master_send(include/linux/i2c.h)
=>i2c_transfer_buffer_flags(drivers/i2c/i2c-core-base.c)
组装msg
struct i2c_msg msg = {.addr = client->addr,.flags = flags | (client->flags & I2C_M_TEN),.len = count,.buf = buf,};
==>i2c_transfer
申请总线锁
===>__i2c_transfer
分原子操作和非原子操作接口,取决于硬件adapter是否实现了原子操作
adap->algo->master_xfer_atomic
adap->algo->master_xfer adapter的硬件接口
--------------------------------------------------------
>i2c_transfer
=>__i2c_transfer
adap->algo->master_xfer adapter的硬件接口

5.3 i2c总线设备生成过程

        总线设备的生成,主要实现在drivers/i2c/i2c-dev.c这个文件里。


static int __init i2c_dev_init(void) {...res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");//申请设备号范围i2c_dev_class = class_create("i2c-dev");//创建class...
//如果有设备注册到总线i2c_bus_type,i2cdev_notifier会立即被回调(回调下面分析)res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);...
//这里实际上跟上一句话目的一样,如果此时已经有设备注册,则轮询所有已注册设备,绑定adapter创建总线设备。i2c_for_each_dev(NULL, i2c_dev_attach_adapter);...
}
module_init(i2c_dev_init);

        回调过程:

i2c_dev_attach_adapter / i2cdev_notifier_call->i2cdev_attach_adapter
=>i2cdev_attach_adapter
i2c_dev = get_free_i2c_dev(adap);//获取未注册过的i2c dev
cdev_init(&i2c_dev->cdev, &i2cdev_fops);//初始化操作集相关
res = dev_set_name(&i2c_dev->dev, "i2c-%d", adap->nr);// /dev下的设备名称,nr是适配器序号,即设备树i2cxres = cdev_device_add(&i2c_dev->cdev, &i2c_dev->dev);//生成该设备
其中操作集的定义如下:
static const struct file_operations i2cdev_fops = {.owner = THIS_MODULE,.llseek = no_llseek,.read = i2cdev_read,.write = i2cdev_write,.unlocked_ioctl = i2cdev_ioctl,.compat_ioctl = compat_i2cdev_ioctl,.open = i2cdev_open,.release = i2cdev_release,
};
操作集都将调用i2c子系统的发送接收接口,最终调用adapter的硬件接口
---------------
i2cdev_read
=>i2c_master_recv
-------------
i2cdev_write
=>i2c_master_send
-------------
i2cdev_ioctl
=>i2cdev_ioctl_rdwr
==>i2c_transfer

6 i2c作为slave的使用

        I2c子系统对salve的实现是在drivers/i2c/i2c-core-slave.c,提供了三个api。

        注册一个slave

int i2c_slave_register(struct i2c_client *client, i2c_slave_cb_t slave_cb)

        释放

int i2c_slave_unregister(struct i2c_client *client)

        有数据请求时,将被调用,由控制器驱动实现,最终调用用户在i2c_slave_register注册的slave_cb回调。

int i2c_slave_event(struct i2c_client *client,enum i2c_slave_event event, u8 *val)

检查当前设备树配置的设备是否是从机设备

bool i2c_detect_slave_mode(struct device *dev)

http://www.ppmy.cn/ops/136534.html

相关文章

【C++】类(三):类的其它特性

7.3 类的其它特性 本节将继续介绍之前章节当中 Sales_data 没有体现出来的类的特性&#xff0c;包括&#xff1a;类型成员、类的成员的类内初始值、可变数据成员、内联成员函数、从成员函数返回*this、如何定义并使用类类型及友元类等。 7.3.1 类成员再探 这部分定义了一对相…

机器学习入门-Scikit-learn

目录 一.Sklearn基本介绍 二.以鸢尾花数据集为例&#xff0c;理解基础运用 1.导入包 2.加载数据集 3.数据预处理 4.数据集拆分 5.模型训练 6.模型评估 7.模型保存和加载 三.碎碎念 一.Sklearn基本介绍 scikit-learn是一个开源的Python机器学习库&#xff0c;提供了大…

无Linux管理员权限,照样可以安装CUDA

以下演示内容使用CUDA版本为 CUDA11.7 1、官网 官网:CUDA官网下载地址 这里会列出所有的CUDA版本,选择需要的版本即可。 2、查看系统信息 这里分享三个命令,可以查看Linux系统的配置信息,方便下一步下载合适的CUDA版本。 可以根据这些命令输出的系统配置信息选择相应的…

阿里云IIS虚拟主机部署ssl证书

宝塔配置SSL证书用起来是很方便的&#xff0c;只需要在站点里就可以配置好&#xff0c;但是云虚拟主机在管理的时候是没有这个权限的&#xff0c;只提供了简单的域名管理等信息。 此处记录下阿里云&#xff08;原万网&#xff09;的IIS虚拟主机如何配置部署SSL证书。 进入虚拟…

IntelliJ IDEA常用快捷键

文章目录 环境快捷键外观编辑移动光标提示查找Live Templates列操作调试运行 环境 Ubuntu 24.04.1IntelliJ IDEA 2024.1.6 快捷键 外观 Alt 1&#xff1a;打开/关闭“项目”窗口&#xff08;即左边的导航窗口&#xff09; Alt 4&#xff1a;打开/关闭“运行”窗口 Alt …

HarmonyOS 应用中复杂业务场景下的接口设计

文章目录 前言设计理念与原则模块化设计动态可扩展性支持多种查询模式统一响应格式实现复杂业务接口示例后端接口代码&#xff08;Node.js Express&#xff09;前端调用代码&#xff08;ArkTS&#xff09;前端界面&#xff08;ArkUI&#xff09; 代码详解后端代码详解前端代码…

数据结构:链表进阶

链表进阶 1. ArrayList的缺陷2. 链表2.1 链表的概念及结构2.2 链表的实现 3.链表面试题4.LinkedList的使用5.1 什么是LinkedList4.2 LinkedList的使用 5. ArrayList和LinkedList的区别 1. ArrayList的缺陷 通过源码知道&#xff0c;ArrayList底层使用数组来存储元素&#xff1…

Python3 Flask 应用中使用阿里短信发送

代码大部分都是官网提供的&#xff0c;稍做了一点修改。 需要申请 access_key_id 和 access_key_secret 很简单这里就不絮叨了&#xff0c;如果你不会私信我&#xff0c;我教你。 安装命令 pip install alibabacloud_dysmsapi201705253.1.0 # -*- coding: utf-8 -*- from…