Linux驱动学习—I2C总线

1、应用层实现I2C通信

1.1 I2C简介

I2C是很常见的一种总线协议,I2C是NXP公司设计的,I2C使用两条线在主控制器和从机之间进行数据通信。一条是SCL(串行时钟线),另外一条是SDA(串行数据线),因为I2C这两条数据线是开漏输出的,所以需要接上拉电阻,总线空闲的时候SCL和SDA处于高电平。I2C总线标准模式下速度可以达到100Kb/s,快速模式下可以达到400kb/s。如果大家玩过51单片机,肯定对模拟I2C时序这个操作并不陌生,但是在Linux上,还需要我们继续来模拟I2C的时序吗,答案是不需要的,cpu会自带I2C控制器,有了这个I2C控制器后,就不需要模拟时序了,只需要关心怎么把数据写到寄存器和怎么从寄存器读数据即可,具体的时序都是由I2C控制器来帮我们自动完成。

1.2 如何查看板子系统上有几个i2c?

Linux把I2C控制器抽象成一个i2c_adapter,我们只要来分配这个i2c_adapter,就可以得到一个I2C控制器。我们可以先来体验一下,在Linux上操作I2C是多模任意,先来看一下系统里面都有哪些I2C的节点,在开发板串口输入:

ls /dev/i2c-*

查看i2c节点:

Linux有一个非常重要的概念是一切皆文件,那么我们能不能在应用层通过open这些节点来操作I2C来跟外设I2C通信的芯片进行数据交互呢?当然是可以的,我们来以前看一下,这里我们以7寸RGB屏幕上的触摸芯片FT5X06为例。

通过原理图来确定FT5X06使用的是哪个I2C,通过下面的截图我们可以得到在开发板上,触摸芯片FT5X06使用的是I2C2,对应的节点是dev下面的i2c-1。那么跟触摸芯片FT5X06进行通信,是不是操作dev下的i2c-1这个节点就可以了?

1.3 数据包的结构体是i2c_rdwr_ioctl_data及i2c_msg

怎么在应用层操作I2C呢,应用层操作I2C是以数据包进行交流的,所有我们在应用层就要进行封包的操作。数据包对应的结构体是i2c_rdwr_ioctl_data,这个结构体在include\uapi\linux\i2c-dev.h下面,定义如下:

struct i2c_rdwr_ioctl_data {struct i2c_msg __user *msgs;    /* pointers to i2c_msgs 要发送的数据包的指针*/__u32 nmsgs;            /* number of i2c_msgs发送数据包的个数 */
};

再来看一下i2c_msg结构体的定义,这个结构体是定义在include\uapi\linux\i2c.h下面,定义如下:

struct i2c_msg {__u16 addr; /* slave addres 从机地址*/__u16 flags ;/*读写标志位,为1表示为读,反之为0,则为写*/
#define I2C_M_RD        0x0001  /* read data, from slave to master *//* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN       0x0010  /* this is a ten bit chip address */
#define I2C_M_RECV_LEN      0x0400  /* length will be first received byte */
#define I2C_M_NO_RD_ACK     0x0800  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK    0x1000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR  0x2000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART       0x4000  /* if I2C_FUNC_NOSTART */
#define I2C_M_STOP      0x8000  /* if I2C_FUNC_PROTOCOL_MANGLING */__u16 len;      /* msg length 为buf的大小,单位是字节*/__u8 *buf;      /* pointer to msg data  当flags为1是,buf是要接受的数据,当flags为0,就是要发送的数据*/
};

1.4 应用程序编写

那么怎么设计程序呢,首先要看一下触摸芯片的数据手册:

了解相关的寄存器后,就可以开始写程序了

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/inpuit.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#incldue <sys/ioctl.h>
​
int fd;
​
int i2c_read_data(unsigned int slave_addr, unsigned char reg_addr)
{unsigned char data;struct i2c_rdwr_ioctl_data i2c_read_lcd;struct i2c_msg msg[2] = {[0] = {//第一个数据包先写要操作的寄存器的地址.addr = slave_addr,.flags = 0,.buf = &reg_addr,.len = sizeof(reg_addr)},[1] = {//第二个数据包再读这个寄存器的数据.addr = slave_addr,.flags = 1,.buf = &data,.len = sizeof(data)},};i2c_read_lcd.msgs = msg;i2c_read_lcd.nmsgs = 2;ret = ioctl(fd, I2C_RDWR, &i2c_read_lcd);if (ret < 0) {perror("ioctl errror is:");return ret;}return data;
}
​
int main(int argc, char *argv[])
{int TD_STATUS;fd = open("/dev/i2c-1", O_RDWR);if (fd < 0) {perror("open error");return fd;}while (1) {TD_STATUS = i2c_read_data(0x38,0x02);printf("TD_STATUS value is %d\n", TD_STATUS);sleep(1);}return 0;
}

编译并在开发板上运行这个app:

2、I2C总线实现clien设备

2.1 Linux I2C驱动框架简介

Linux中的I2C也是按照平台总线模型设计的,既然也是按照平台总线模型设计的,是不是也分为一个device和一个driver呢?但是I2C这里的device不叫device,也叫client。platform是虚拟出来的一条总线,目的是未来实现总线、设备、驱动框架。对于I2C而言,不需要虚拟出一条总线,直接使用I2C总线即可。

同样,这里先从非设备树开始,先看一下再没有设备树之前怎么实现的I2C的device部分,也就是client部分。然后再学习有了设备树之后,我们的client是怎么编写的,按照Linux的发展路径来学习。

在没有使用设备树之前,我们使用的是i2c_board_info这个结构体来描述一个I2C设备的, i2c_board_info 这个结构体如下,在include/linux/i2c.h:

struct i2c_board_info {char        type[I2C_NAME_SIZE];unsigned short  flags;unsigned short  addr;void        *platform_data;struct dev_archdata *archdata;struct device_node *of_node;struct fwnode_handle *fwnode;int     irq;
};

在上面的这个结构体中,type和addr这两个成员变量是必须要设置的,一个是I2C设备的名字,这个名字就是用来进行匹配用的,一个是I2C设备的器件地址。也可以使用宏,在include/linux/i2c.h::

#define I2C_BOARD_INFO(dev_type, dev_addr) \.type = dev_type, .addr = (dev_addr)

可以看出,I2C_BOARD_INFO宏其实就是设置i2c_board_info的 type 和 addr这两个成员变量。

2.2 I2C核心提供的具体硬件无关的API函数

I2C设备和驱动的匹配过程是由I2C核心来完成的,在Linux源码的drivers/i2c/i2c-core.c就是I2C的核心部分,I2C核心提供了一些与具体硬件无关的函数,如下:

2.2.1 i2c_get_adapter函数

作用:获得一个I2C适配器。

struct i2c_adapter *i2c_get_adapter(int nr);
参数:
nr:要获得的哪个I2C适配器的编号。
返回值:失败返回NULL。
2.2.2 i2c_put_adapter函数

作用:释放I2C适配器。

void i2c_put_adapter(struct i2c_adapter *adap);
参数:
adap:要释放I2C适配器。
返回值:失败返回NULL。
2.2.3 i2c_new_device函数

作用:把I2C适配器和I2C器件关联起来。

struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info);
参数:
adap:I2C适配器。
info:i2c_board_info的指针。
返回值:失败返回NULL。
2.2.4 i2c_unregister_device函数

作用:注销一个client。

void i2c_unregister_device(struct i2c_client *client);
client:i2c_client的指针。

2.3 如果使用设备树要怎么描述硬件信息呢?

在使用设备树以后,就不用这么复杂了,使用给设备树的时候只要在对应的I2C节点下创建相应设备的节点即可,比如我想添加一个触摸芯片FT5X06的设备,我就可以在对应的I2C的节点下这样写:

注意:这里使用的是10.1寸的触摸芯片gt911,4.3寸触摸芯片是tsc2007.其他都是ft5426。

查看对应设备树节点:

注意:我们使用的是I2C2,上图设备树节点看是1-0038,为啥是1呢,因为平台上的是I2C2,是从1开始计数的,而这里是从0开始计数的。

2.4 不用设备树的方法怎么描述硬件信息

下面演示不用设备树的方法进行实验,

<1> 首先先要去掉设备树上的节点:

<2> 再make menuconfig把相关i2c驱动注释掉:

注释掉之后编译内核源码,并烧写至系统,会发现看不到设备节点了

现在运行编译脚本就不会把这个设备驱动编译进去了

<3> 最后编写实现client驱动源码,并加载到系统上,会发现可以重新看到有对应的设备节点了
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
​
//分配一个I2C适配器指针
struct i2c_adapter *i2c_ada;
​
//分配一个i2c_client指针
struct i2c_client *i2c_client;
​
//支持的I2C的设备列表
struct ic_board_info ft5x06_info[] = {//每一项都代表一个I2C设备,这句话的意思就是说这个设备的名字代表ft5x06_test,器件地址是0x38{I2C_BOARD_INFO("ft5x06_test", 0x38)},
};
​
static int ft5x06_client_init(void)
{//调用 i2c_get_adapter ,获得一个I2C总线,因为ft5x06是挂载动力I2C2上,所以这个参数就是1,所以这句代码的意思就是把这个触摸触摸芯片挂载到i2c2上.i2c_ada = i2c_get_adapter(1);//把I2C适配器和I2C器件关联起来 i2c_new_device(i2c_ada, ft5x06_info);//释放I2C控制器i2c_put_adapter(i2c_ada);printk("This is ft5x06_client_init\n");return0;
}
​
static void ft5x06_client_exit(void)
{i2c_unsigned_device(i2c_client);printk("This is ft5x06_client_exit\n");
}
​
module_init(ft5x06_client_init);
module_exit(ft5x06_client_exit);
MODULE_LICENSE("GPL");

挂载驱动,可以看到有对应的设备节点了:

3、I2C总线实现driver驱动

上面实现了client部分,然后我们再来看driver部分。不管是使用设备树还是非设备树,driver部分就比较复杂了。和注册一个杂项设备或者是字符设备的套路一样,也是要先顶一个i2c_driver的结构体,然后再对他进行初始化,下面先看一下这个结构体的定义,如下图所示:

struct i2c_driver {unsigned int class;
​/* Notifies the driver that a new bus has appeared. You should avoid* using this, it will be removed in a near future.*/int (*attach_adapter)(struct i2c_adapter *) __deprecated;
​/* Standard driver model interfaces */int (*probe)(struct i2c_client *, const struct i2c_device_id *);int (*remove)(struct i2c_client *);
​/* driver model interfaces that don't relate to enumeration  */void (*shutdown)(struct i2c_client *);
​/* Alert callback, for example for the SMBus alert protocol.* The format and meaning of the data value depends on the protocol.* For the SMBus alert protocol, there is a single bit of data passed* as the alert response's low bit ("event flag").* For the SMBus Host Notify protocol, the data corresponds to the* 16-bit payload data reported by the slave device acting as master.*/void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,unsigned int data);
​/* a ioctl like command that can be used to perform specific functions* with the device.*/int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
​struct device_driver driver;const struct i2c_device_id *id_table;
​/* Device detection callback for automatic device creation */int (*detect)(struct i2c_client *, struct i2c_board_info *);const unsigned short *address_list;struct list_head clients;
};

初始化完成以后就是把i2c_driver注册进内核,注册进内核我们使用的是i2c_add_driver。

<1>i2c_add_driver函数宏

作用:注册一个i2c驱动。

#define i2c_add_driver(driver) \i2c_register_driver(THIS_MODULE, driver)
参数:
driver: struct i2c_driver的指针。
返回值:失败返回负值。
<2>i2c_del_driver函数

作用:删除一个i2c驱动。

extern void i2c_del_driver(struct i2c_driver *);
参数:driver: struct i2c_driver的指针。
返回值:失败返回负值。
<3>驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
​
static const struct i2c_device_id ft5x06_id_ts[] = {{"xxx",0},
};
​
static const struct of_device_id ft5x06_id[]  = {{.compatible = "edt,edt-ft5306", 0},{.compatible = "edt,edt-ft5x06", 0},{.compatible = "edt,edt-ft5406", 0},
};
​
int ft5x06_probe(struct i2c_client *i2c_client, const struct i2c_device_id *id)
{printk("This is ft5x06_probe\n");//注册一个杂项设备 或 注册一个字符设备return 0;
}
​
int ft5x06_remove(struct i2c_client *i2c_client)
{return 0;
}
static struct i2c_driver ft5x06_driver = {.driver = {.owner = YHIS_MODULE,.name = "ft5x06_test",.of_match_table = ft5x06_id,},.probe = ft5x06_probe,.remove = ft5x06_remove,.id_table = ft5x06_pid_ts
};
​
static int ft5x06_driver_init(void)
{int ret;ret = i2c_add_driver(&ft5x06_driver);if (ret < 0) {printk("i2c_add_driver is error\n");return ret;}printk("This is ft5x06_driver_init\n");return 0;
}
​
static void ft5x06_driver_exit(void)
{i2c_del_driver(&ft5x06_driver);printk("This is ft5x06_driver_exit\n");
}
module_init(ft5x06_driver_init);
module_exit(ft5x06_driver_exit);
MODULE_LICENSE("GPL");

makefile:

编译驱动,加载驱动前需要取消设备树相关内容注释并烧写到板子上:

4、驱动程序实现I2C通信

在上面程序的基础上修改:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
​
static const struct i2c_device_id ft5x06_id_ts[] = {{"xxx",0},
};
​
static const struct of_device_id ft5x06_id[]  = {{.compatible = "edt,edt-ft5306", 0},{.compatible = "edt,edt-ft5x06", 0},{.compatible = "edt,edt-ft5406", 0},
};
​
static struct i2c_client *ft5x06_client;
static int ft5x06_read_reg(u8 reg_addr);
static void ft5x06_write_reg(u8 reg_addr, u8 data, u8 len);
​
static void ft5x06_read_reg(u8 reg_addr)
{u8 data;struct i2c_msg msgs[] {//第一个数据包,写[0] = {.addr  = ft5x06_client->addr,.flags = 0,.len = sizeof(reg_addr),.buf = &reg_addr,},//第二个数据包,读[1] = {.addr  = ft5x06_client->addr,.flags = 0,.len =  sizeof(data),.buf = &data,},};i2c_transfer(ft5x06_client->adapter, msgs, 2);return data;
}
​
static void ft5x06_write_reg(u8 reg_addr, u8 data, u8 len)
{u8 buff[256];struct i2c_msg msgs[] = {[0] = {.addr  = ft5x06_client->addr,.flags = 0,.len = len+1,.buf = buff,}};buff[0] = reg_addr;memcpy(&buff[1], &data, len);i2c_transfer(ft5x06_client->adapter, msgs, 1);
}
​
int ft5x06_probe(struct i2c_client *i2c_client, const struct i2c_device_id *id)
{printk("This is ft5x06_probe\n");ft5x06_client = client;//因为我们再别的函数里面用到这个client,所以我们要把他复制出来。//往地址为0x80的寄存器里面写入0x4bft5x06_write_reg(0x80, 0x4b, 1);//读取寄存器地址为0x80的数据ret = ft5x06_read_reg(0x80);printk("ret is %#x\n",ret);return 0;
}
​
int ft5x06_remove(struct i2c_client *i2c_client)
{return 0;
}
static struct i2c_driver ft5x06_driver = {.driver = {.owner = YHIS_MODULE,.name = "ft5x06_test",.of_match_table = ft5x06_id,},.probe = ft5x06_probe,.remove = ft5x06_remove,.id_table = ft5x06_pid_ts
};
​
static int ft5x06_driver_init(void)
{int ret;ret = i2c_add_driver(&ft5x06_driver);if (ret < 0) {printk("i2c_add_driver is error\n");return ret;}printk("This is ft5x06_driver_init\n");return 0;
}
​
static void ft5x06_driver_exit(void)
{i2c_del_driver(&ft5x06_driver);printk("This is ft5x06_driver_exit\n");
}
module_init(ft5x06_driver_init);
module_exit(ft5x06_driver_exit);
MODULE_LICENSE("GPL");

编译加载驱动:

改成0x02:

重新编译加载驱动:这个是没有放手指头的

放一个手指头在屏幕上,重新加载驱动:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/620302.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Windows Server 2019配置多用户远程桌面登录服务器

正文共&#xff1a;1234 字 21 图&#xff0c;预估阅读时间&#xff1a;2 分钟 很久之前&#xff0c;在介绍显卡直通的时候我们简单介绍过RDP&#xff08;Remote Desktop Protocol&#xff0c;远程桌面协议&#xff09;&#xff08;前人栽树&#xff1a;失败的服务器显卡操作&a…

第 3 场 蓝桥杯小白入门赛 解题报告 | 珂学家 | 单调队列优化的DP + 三指针滑窗

前言 整体评价 T5, T6有点意思&#xff0c;这场小白入门场&#xff0c;好像没真正意义上的签到&#xff0c;整体感觉是这样。 A. 召唤神坤 思路: 前后缀拆解 #include <iostream> #include <algorithm> #include <vector> using namespace std;int main()…

Android平台RTMP推送|轻量级RTSP服务|GB28181设备接入模块之实时快照保存JPG还是PNG?

JPG还是PNG&#xff1f; JPG和PNG是两种常见的图片文件格式&#xff0c;在压缩方式、图像质量、透明效果和可编辑性等方面存在显著差异。 压缩方式&#xff1a;JPG是一种有损压缩格式&#xff0c;通过丢弃图像数据来减小文件大小&#xff0c;因此可能会损失一些图像细节和质量…

【AIGC】IP-Adapter:文本兼容图像提示适配器,用于文本到图像扩散模型

前言 IPAdapter能够通过图像给Stable Diffusion模型以内容提示&#xff0c;让其生成参考该图像画风&#xff0c;可以免去Lora的训练&#xff0c;达到参考画风人物的生成效果。 摘要 通过文本提示词生成的图像&#xff0c;往往需要设置复杂的提示词&#xff0c;通常设计提示词变…

系列十一、Spring Security登录接口兼容JSON格式登录

一、Spring Security登录接口兼容JSON格式登录 1.1、概述 前后端分离中&#xff0c;前端和后端的数据交互通常是JSON格式&#xff0c;而Spring Security的登录接口默认支持的是form-data或者x-www-form-urlencoded的&#xff0c;如下所示&#xff1a; 那么如何让Spring Securi…

Open3D 反算点云缩放系数(21)

Open3D 反算点云缩放系数(21) 一、算法介绍二、算法实现1.方法12.方法2(通用)一、算法介绍 上一章按照指定的系数,对点云进行了等比例缩放,这里输入缩放后的两块点云,反算二者之间的缩放系数。 二、算法实现 已知使用的俩点云是1/2的缩放关系,用于验证计算结果是否…

【数据结构】串,数组,广义表 | 笔记整理 | C/C++实现

文章目录 前言一、串1.1、串的定义1.2、案例引入1.3、串的类型定义和存储结构1.4、串的模式匹配算法1.4.1、BF算法1.4.2、KMP算法 二、数组2.1、数组的定义2.2、数组的抽象数据类型定义2.3、数组的顺序存储2.4、特殊矩阵的压缩存储 三、广义表四、病毒案例 前言 参考视频&…

【C++】wxWidgets库实现窗体程序

一、安装wxWidgets库 在Debian系统上使用wxWidgets库来创建一个基本的窗体程序&#xff0c;首先需要确保已经安装了wxWidgets相关的库和开发工具。下面是安装wxWidgets的步骤&#xff1a; 打开终端&#xff0c;使用下述命令安装wxWidgets库及其开发文件&#xff1a; sudo ap…

MySQL之导入、导出远程备份

一、Navicat工具导入、导出 1.1 导入 第一步&#xff1a; 右键&#xff0c;点击运行SQL文件 第二步&#xff1a; 选择要运行的SQL&#xff0c;点击开始 第三步&#xff1a; 关闭即可 1.2 导出 第一步&#xff1a; 右键选择&#xff0c;导出向导 第二步&#xff1a; 选择SQL脚…

1.3MATLAB变量及其操作

变量 变量是内存单元的一个抽象&#xff0c;在MATLAB中&#xff0c;变量以字母开头&#xff0c;后接数字下划线构成&#xff0c;MATLAB中变量名最多占据 63 个字符。变量区分大小写标准函数及命令一般使用小写字母 赋值语句 变量 表达式(;)表达式(;)总结&#xff1a;加分号&…

C++ 实现游戏(例如MC)键位显示

效果&#xff1a; 是不是有那味儿了&#xff1f; 显示AWSD&#xff0c;空格&#xff0c;Shift和左右键的按键情况以及左右键的CPS。 彩虹色轮廓&#xff0c;黑白填充。具有任务栏图标&#xff0c;可以随时关闭字体是Minecraft AE Pixel&#xff0c;如果你没有装&#xff08;大…

使用numpy处理图片——灰阶影像

大纲 载入图像灰阶处理lightnessaverageluminosity 灰阶&#xff08;Gray scale&#xff09;影像是每个像素只有一个采样颜色的图像。 载入图像 import numpy as np import PIL.Image as Imageimg Image.open(lena.png) data np.array(img)灰阶处理 我们有三种方法来生成这…

Linux中常使用的命令之ls、cd、pwd、mkdir、rmdir

ls: 列出目录 cd&#xff1a;切换目录 pwd&#xff1a;显示目前的目录 mkdir&#xff1a;创建一个新的目录 -m &#xff1a;配置文件的权限-p &#xff1a;帮助你直接将所需要的目录(包含上一级目录)递归创建起来&#xff01; rmdir&#xff1a;删除一个空的目录 注意这…

基于springboot时间管理系统源码和论文

在Internet高速发展的今天&#xff0c;我们生活的各个领域都涉及到计算机的应用&#xff0c;其中包括时间管理系统的网络应用&#xff0c;在外国时间管理系统已经是很普遍的方式&#xff0c;不过国内的管理系统可能还处于起步阶段。时间管理系统具有时间管理功能的选择。时间管…

Unity Shader 属性的定义

Unity Shader 属性的定义 什么是材质球 人的衣服 什么是shader 决定材质跟灯光的作用 Property 若是把shader看作class&#xff0c;那么Property就可以看成成员变量 属性定义的通用格式 Properites{ Property[Property…] } ep:定义一个int&#xff1a; name("dis…

ODBC 在指定的DSN中,驱动程序和应用程序之间的体系结构不匹配

常规办法就是64位或32位匹配&#xff0c;如果解决不了&#xff0c;往下看。 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓解决方案↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 解压AccessDatabaseEngine_X64.exe&#xf…

为什么选择Go语言编写网络应用程序

关注公众号【爱发白日梦的后端】分享技术干货、读书笔记、开源项目、实战经验、高效开发工具等&#xff0c;您的关注将是我的更新动力&#xff01; 作为一名后端开发者&#xff0c;你一定对选择合适的编程语言来编写网络应用程序非常重视。在众多的编程语言中&#xff0c;Go语言…

我在代码随想录|写代码Day7之454.四数相加II ,​ 383. 赎金信​,​ 15. 三数之和​

454.四数相加II 题目 解题思路 四个数字相加的和为0,我们要选俩数组,让他们的笛卡尔积储存在哈希表中,然后我们要找的是这俩数和的相反数,然后就是将后面俩数组相加在后面的数组和中找相反数. 383. 赎金信 解题思路 题目意思是让在字符串1中找到字母组成字符串2所以找字符串1…

【无标题】关于异常处理容易犯的错

一般项目是方法打上 try…catch…捕获所有异常记录日志&#xff0c;有些会使用 AOP 来进行类似的“统一异常处理”。 其实&#xff0c;这种处理异常的方式非常不可取。那么今天&#xff0c;我就和你分享下不可取的原因、与异常处理相关的坑和最佳实践。 捕获和处理异常容易犯…

(南京观海微电子)——色温介绍

色温是表示光线中包含颜色成分的一个计量单位。从理论上说&#xff0c;黑体温度指绝对黑体从绝对零度&#xff08;&#xff0d;273℃&#xff09;开始加温后所呈现的颜色。黑体在受热后&#xff0c;逐渐由黑变红&#xff0c;转黄&#xff0c;发白&#xff0c;最后发出蓝色光。当…