Linux学习第44天:Linux 多点电容触摸屏实验(二):难忘记第一次牵你手的温存

Linux版本号4.1.15   芯片I.MX6ULL                                    大叔学Linux    品人间百味  思文短情长 


        本章的思维导图内容如下:

二、硬件原理图分析

三、实验程序编写

1、修改设备树

1)、添加FT5426所使用的IO

        一个复位 IO、一个中断 IO、 I2C2 的 SCL 和 SDA。

        检查有没有被占用。

2)、添加FT5426节点

 &i2c2 {
2 clock_frequency = <100000>;
3 pinctrl-names = "default";
4 pinctrl-0 = <&pinctrl_i2c2>;
5 status = "okay";
6 7
/****************************/
8 /* 省略掉其他的设备节点 */
9 /****************************/
10
11 /* zuozhongkai FT5406/FT5426 */
12 ft5426: ft5426@38 {
13 compatible = "edt,edt-ft5426";
14 reg = <0x38>;
15 pinctrl-names = "default";
16 pinctrl-0 = <&pinctrl_tsc
17 &pinctrl_tsc_reset >;
18 interrupt-parent = <&gpio1>;
19 interrupts = <9 0>;
20 reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;
21 interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
22 };
23 };

2、编写多点电容触摸驱动

1 #include <linux/module.h>
2 #include <linux/ratelimit.h>
......
15 #include <linux/i2c.h>
16 /***************************************************************
17 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
18 文件名 : ft5x06.c
19 作者 : 左忠凯
20 版本 : V1.0
21 描述 : FT5X06,包括 FT5206、 FT5426 等触摸屏驱动程序
22 其他 : 无
23 论坛 : www.openedv.com
24 日志 : 初版 V1.0 2019/12/23 左忠凯创建个
25 ***************************************************************/
26
27 #define MAX_SUPPORT_POINTS 5 /* 5 点触摸 */
28 #define TOUCH_EVENT_DOWN 0x00 /* 按下 */
29 #define TOUCH_EVENT_UP 0x01 /* 抬起 */
30 #define TOUCH_EVENT_ON 0x02 /* 接触 */
31 #define TOUCH_EVENT_RESERVED 0x03 /* 保留 */
32
33 /* FT5X06 寄存器相关宏定义 */
34 #define FT5X06_TD_STATUS_REG 0X02 /* 状态寄存器地址 */
35 #define FT5x06_DEVICE_MODE_REG 0X00 /* 模式寄存器 */
36 #define FT5426_IDG_MODE_REG 0XA4 /* 中断模式 */
37 #define FT5X06_READLEN 29 /* 要读取的寄存器个数 */
38
39 struct ft5x06_dev {
40 struct device_node *nd; /* 设备节点 */
41 int irq_pin,reset_pin; /* 中断和复位 IO */
42 int irqnum; /* 中断号 */
43 void *private_data; /* 私有数据 */
44 struct input_dev *input; /* input 结构体 */
45 struct i2c_client *client; /* I2C 客户端 */
46 };
47
48 static struct ft5x06_dev ft5x06;
49
50 /*
51 * @description : 复位 FT5X06
52 * @param - client : 要操作的 i2c
53 * @param – multidev : 自定义的 multitouch 设备
54 * @return : 0,成功;其他负值,失败
55 */
56 static int ft5x06_ts_reset(struct i2c_client *client,
struct ft5x06_dev *dev)
57 {
58 int ret = 0;
59
60 if (gpio_is_valid(dev->reset_pin)) { /* 检查 IO 是否有效 */
61 /* 申请复位 IO,并且默认输出低电平 */
62 ret = devm_gpio_request_one(&client->dev,
63 dev->reset_pin, GPIOF_OUT_INIT_LOW,
64 "edt-ft5x06 reset");
65 if (ret) {
66 return ret;
67 }
68 msleep(5);
69 gpio_set_value(dev->reset_pin, 1); /* 输出高电平,停止复位 */
70 msleep(300);
71 }
72 return 0;
73 }
74
75 /*
76 * @description : 从 FT5X06 读取多个寄存器数据
77 * @param – dev : ft5x06 设备
78 * @param – reg : 要读取的寄存器首地址
79 * @param – val : 读取到的数据
80 * @param – len : 要读取的数据长度
81 * @return : 操作结果
82 */
83 static int ft5x06_read_regs(struct ft5x06_dev *dev, u8 reg,
void *val, int len)
84 {
85 int ret;
86 struct i2c_msg msg[2];
87 struct i2c_client *client = (struct i2c_client *)dev->client;
......
101 ret = i2c_transfer(client->adapter, msg, 2);
102 if(ret == 2) {
103 ret = 0;
104 } else {
105 ret = -EREMOTEIO;
106 }
107 return ret;
108 }
109
110 /*
111 * @description : 向 ft5x06 多个寄存器写入数据
112 * @param – dev : ft5x06 设备
113 * @param – reg : 要写入的寄存器首地址
114 * @param – val : 要写入的数据缓冲区
115 * @param – len : 要写入的数据长度
116 * @return : 操作结果
117 */
118 static s32 ft5x06_write_regs(struct ft5x06_dev *dev, u8 reg,
u8 *buf, u8 len)
119 {
120 u8 b[256];
121 struct i2c_msg msg;
122 struct i2c_client *client = (struct i2c_client *)dev->client;
123
124 b[0] = reg; /* 寄存器首地址 */
125 memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组 b 里面 */
126
127 msg.addr = client->addr; /* ft5x06 地址 */
128 msg.flags = 0; /* 标记为写数据 */
129
130 msg.buf = b; /* 要写入的数据缓冲区 */
131 msg.len = len + 1; /* 要写入的数据长度 */
132
133 return i2c_transfer(client->adapter, &msg, 1);
134 }
135
136 /*
137 * @description : 向 ft5x06 指定寄存器写入指定的值,写一个寄存器
138 * @param – dev : ft5x06 设备
139 * @param – reg : 要写的寄存器
140 * @param – data : 要写入的值
141 * @return : 无
142 */
143 static void ft5x06_write_reg(struct ft5x06_dev *dev, u8 reg,
u8 data)
144 {
145 u8 buf = 0;
146 buf = data;
147 ft5x06_write_regs(dev, reg, &buf, 1);
148 }
149
150 /*
151 * @description : FT5X06 中断服务函数
152 * @param - irq : 中断号
153 * @param - dev_id : 设备结构。
154 * @return : 中断执行结果
155 */
156 static irqreturn_t ft5x06_handler(int irq, void *dev_id)
157 {
158 struct ft5x06_dev *multidata = dev_id;
159
160 u8 rdbuf[29];
161 int i, type, x, y, id;
162 int offset, tplen;
163 int ret;
164 bool down;
165
166 offset = 1; /* 偏移 1,也就是 0X02+1=0x03,从 0X03 开始是触摸值 */
167 tplen = 6; /* 一个触摸点有 6 个寄存器来保存触摸值 */
168
169 memset(rdbuf, 0, sizeof(rdbuf)); /* 清除 */
170
171 /* 读取 FT5X06 触摸点坐标从 0X02 寄存器开始,连续读取 29 个寄存器 */
172 ret = ft5x06_read_regs(multidata, FT5X06_TD_STATUS_REG,
rdbuf, FT5X06_READLEN);
173 if (ret) {
174 goto fail;
175 }
176
177 /* 上报每一个触摸点坐标 */
178 for (i = 0; i < MAX_SUPPORT_POINTS; i++) {
179 u8 *buf = &rdbuf[i * tplen + offset];
180
181 /* 以第一个触摸点为例,寄存器 TOUCH1_XH(地址 0X03),各位描述如下:
182 * bit7:6 Event flag 0:按下 1:释放 2:接触 3:没有事件
183 * bit5:4 保留
184 * bit3:0 X 轴触摸点的 11~8 位。
185 */
186 type = buf[0] >> 6; /* 获取触摸类型 */
187 if (type == TOUCH_EVENT_RESERVED)
188 continue;
189
190 /* 我们所使用的触摸屏和 FT5X06 是反过来的 */
191 x = ((buf[2] << 8) | buf[3]) & 0x0fff;
192 y = ((buf[0] << 8) | buf[1]) & 0x0fff;
193
194 /* 以第一个触摸点为例,寄存器 TOUCH1_YH(地址 0X05),各位描述如下:
195 * bit7:4 Touch ID 触摸 ID,表示是哪个触摸点
196 * bit3:0 Y 轴触摸点的 11~8 位。
197 */
198 id = (buf[2] >> 4) & 0x0f;
199 down = type != TOUCH_EVENT_UP;
200
201 input_mt_slot(multidata->input, id);
202 input_mt_report_slot_state(multidata->input, MT_TOOL_FINGER,
down);
203
204 if (!down)
205 continue;
206
207 input_report_abs(multidata->input, ABS_MT_POSITION_X, x);
208 input_report_abs(multidata->input, ABS_MT_POSITION_Y, y);
209 }
210
211 input_mt_report_pointer_emulation(multidata->input, true);
212 input_sync(multidata->input);
213
214 fail:
215 return IRQ_HANDLED;
216
217 }
218
219 /*
220 * @description : FT5x06 中断初始化
221 * @param - client : 要操作的 i2c
222 * @param – multidev : 自定义的 multitouch 设备
223 * @return : 0,成功;其他负值,失败
224 */
225 static int ft5x06_ts_irq(struct i2c_client *client,
struct ft5x06_dev *dev)
226 {
227 int ret = 0;
228
229 /* 1,申请中断 GPIO */
230 if (gpio_is_valid(dev->irq_pin)) {
231 ret = devm_gpio_request_one(&client->dev, dev->irq_pin,
232 GPIOF_IN, "edt-ft5x06 irq");
233 if (ret) {
234 dev_err(&client->dev,
235 "Failed to request GPIO %d, error %d\n",
236 dev->irq_pin, ret);
237 return ret;
238 }
239 }
240
241 /* 2,申请中断,client->irq 就是 IO 中断, */
242 ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
243 ft5x06_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
244 client->name, &ft5x06);
245 if (ret) {
246 dev_err(&client->dev, "Unable to request
touchscreen IRQ.\n");
247 return ret;
248 }
249
250 return 0;
251 }
252
253 /*
254 * @description : i2c 驱动的 probe 函数,当驱动与
255 * 设备匹配以后此函数就会执行
256 * @param - client : i2c 设备
257 * @param - id : i2c 设备 ID
258 * @return : 0,成功;其他负值,失败
259 */
260 static int ft5x06_ts_probe(struct i2c_client *client,
const struct i2c_device_id *id)
261 {
262 int ret = 0;
263
264 ft5x06.client = client;
265
266 /* 1,获取设备树中的中断和复位引脚 */
267 ft5x06.irq_pin = of_get_named_gpio(client->dev.of_node,
"interrupt-gpios", 0);
268 ft5x06.reset_pin = of_get_named_gpio(client->dev.of_node,
"reset-gpios", 0);
269
270 /* 2,复位 FT5x06 */
271 ret = ft5x06_ts_reset(client, &ft5x06);
272 if(ret < 0) {
273 goto fail;
274 }
275
276 /* 3,初始化中断 */
277 ret = ft5x06_ts_irq(client, &ft5x06);
278 if(ret < 0) {
279 goto fail;
280 }
281
282 /* 4,初始化 FT5X06 */
283 ft5x06_write_reg(&ft5x06, FT5x06_DEVICE_MODE_REG, 0);
284 ft5x06_write_reg(&ft5x06, FT5426_IDG_MODE_REG, 1);
285
286 /* 5, input 设备注册 */
287 ft5x06.input = devm_input_allocate_device(&client->dev);
288 if (!ft5x06.input) {
289 ret = -ENOMEM;
290 goto fail;
291 }
292 ft5x06.input->name = client->name;
293 ft5x06.input->id.bustype = BUS_I2C;
294 ft5x06.input->dev.parent = &client->dev;
295
296 __set_bit(EV_KEY, ft5x06.input->evbit);
297 __set_bit(EV_ABS, ft5x06.input->evbit);
298 __set_bit(BTN_TOUCH, ft5x06.input->keybit);
299
300 input_set_abs_params(ft5x06.input, ABS_X, 0, 1024, 0, 0);
301 input_set_abs_params(ft5x06.input, ABS_Y, 0, 600, 0, 0);
302 input_set_abs_params(ft5x06.input, ABS_MT_POSITION_X,
0, 1024, 0, 0);
303 input_set_abs_params(ft5x06.input, ABS_MT_POSITION_Y,
0, 600, 0, 0);
304 ret = input_mt_init_slots(ft5x06.input, MAX_SUPPORT_POINTS, 0);
305 if (ret) {
306 goto fail;
307 }
308
309 ret = input_register_device(ft5x06.input);
310 if (ret)
311 goto fail;
312
313 return 0;
314
315 fail:
316 return ret;
317 }
318
319 /*
320 * @description : i2c 驱动的 remove 函数,移除 i2c 驱动的时候此函数会执行
321 * @param – client : i2c 设备
322 * @return : 0,成功;其他负值,失败
323 */
324 static int ft5x06_ts_remove(struct i2c_client *client)
325 {
326 /* 释放 input_dev */
327 input_unregister_device(ft5x06.input);
328 return 0;
329 }
330
331
332 /*
333 * 传统驱动匹配表
334 */
335 static const struct i2c_device_id ft5x06_ts_id[] = {
336 { "edt-ft5206", 0, },
337 { "edt-ft5426", 0, },
338 { /* sentinel */ }
339 };
340
341 /*
342 * 设备树匹配表
343 */
344 static const struct of_device_id ft5x06_of_match[] = {
345 { .compatible = "edt,edt-ft5206", },
346 { .compatible = "edt,edt-ft5426", },
347 { /* sentinel */ }
348 };
349
350 /* i2c 驱动结构体 */
351 static struct i2c_driver ft5x06_ts_driver = {
352 .driver = {
353 .owner = THIS_MODULE,
354 .name = "edt_ft5x06",
355 .of_match_table = of_match_ptr(ft5x06_of_match),
356 },
357 .id_table = ft5x06_ts_id,
358 .probe = ft5x06_ts_probe,
359 .remove = ft5x06_ts_remove,
360 };
361
362 /*
363 * @description : 驱动入口函数
364 * @param : 无
365 * @return : 无
366 */
367 static int __init ft5x06_init(void)
368 {
369 int ret = 0;
370
371 ret = i2c_add_driver(&ft5x06_ts_driver);
372
373 return ret;
374 }
375
376 /*
377 * @description : 驱动出口函数
378 * @param : 无
379 * @return : 无
380 */
381 static void __exit ft5x06_exit(void)
382 {
383 i2c_del_driver(&ft5x06_ts_driver);
384 }
385
386 module_init(ft5x06_init);
387 module_exit(ft5x06_exit);
388 MODULE_LICENSE("GPL");
389 MODULE_AUTHOR("zuozhongkai");

        代码挺多的,就不贴了。

四、运行测试

1、编译驱动程序

1 KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imxrel_imx_4.1.15_2.1.0_ga_alientek
......
4 obj-m := ft5x06.o
......
11 clean:
12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

2、运行测试

depmod //第一次加载驱动的时候需要运行此命令
modprobe ft5x06.ko //加载驱动模块

        生成eventx触摸文件,触摸屏幕,会看见文件上报信息内容变化。

3、将驱动添加到内核中

1)、将驱动文件放到合适的位置

2)、修改对应的Makefile

        最后一行,添加一行:

obj-y += ft5x06.o

        修改完成以后重新编译 linux 内核,然后用新的 zImage 启动开发板。

hexdump /dev/input/event1 //查看触摸屏原始数据上报信息

        查看上报数据内容,检验触屏驱动正确性。

以下内容将在下节更新:

五、tslib移植与使用

1、tslib移植

1)、获取tslib源码

2)、修改tslib源码所属用户

3)、ubuntu工具安装

4)、编译tslib

5)、配置tslib

6)、tslib测试

六、使用内核自带的驱动

1、修改edt-ft5x06.c

2、使能内核自带的FT5X06驱动

3、修改设备树

七、4.3.寸屏触摸驱动实验

1、修改设备树pinctrl_tsc节点内容

2、在设备树的i2c2节点下添加tgt9147子节点

3、修改设备树的lcdif节点

4、编译GT9147驱动文件

八、总结


本文为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,不得用于商业用途。

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

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

相关文章

合并区间问题

以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中的所有区间 。 示例 1&#xff1a; 输入&#xff1a;intervals [[1,…

React函数组件渲染两次

渲染两次是因为react默认开启了严格模式 React.StrictMode标签作用&#xff1a; 1、识别不安全的生命周期 2、关于使用过时字符串 ref API 的警告 3、关于使用废弃的 findDOMNode 方法的警告 4、检测意外的副作用 5、检测过时的 context API 注释掉React.StrictMode即为关闭严…

Kotlin学习——hello kotlin 函数function 变量 类 + 泛型 + 继承

Kotlin 是一门现代但已成熟的编程语言&#xff0c;旨在让开发人员更幸福快乐。 它简洁、安全、可与 Java 及其他语言互操作&#xff0c;并提供了多种方式在多个平台间复用代码&#xff0c;以实现高效编程。 https://play.kotlinlang.org/byExample/01_introduction/02_Functio…

01背包与完全背包学习总结

背包问题分类见下图 参考学习点击&#xff1a;代码随想录01背包讲解 01背包问题&#xff1a; 核心思路&#xff1a; 1、先遍历物品个数&#xff0c;再遍历背包容量。因为容量最先是最大的&#xff0c;往背包里放物品&#xff0c;所以背包容量在慢慢减少&#xff0c;但背包容量…

vue3+elementPlus登录向后端服务器发起数据请求Ajax

后端的url登录接口 先修改main.js文件 // 导入Ajax 前后端数据传输 import axios from "axios"; const app createApp(App) //vue3.0使用app.config.globalProperties.$http app.config.globalProperties.$http axios app.mount(#app); login.vue 页面显示部分…

UE5 操作WebSocket

插件&#xff1a;https://www.unrealengine.com/marketplace/zh-CN/product/websocket-client 参考&#xff1a;http://dascad.net/html/websocket/bp_index.html 1. 安装Plugings 2.测试websocket服务器 http://www.websocket-test.com/ 3.连接服务器 如果在Level BP里使用&a…

VMware 16 Pro 安装以及下载

1、下载地址&#xff1a; https://www.aliyundrive.com/s/nj3PSD4TN9G 2、安装文件 右击打开 下一步 密钥&#xff1a;ZF3R0-FHED2-M80TY-8QYGC-NPKYF 到此&#xff0c;安装完毕

YOLOv8 加持 MobileNetv3,目标检测新篇章

🗝️YOLOv8实战宝典--星级指南:从入门到精通,您不可错过的技巧   -- 聚焦于YOLO的 最新版本, 对颈部网络改进、添加局部注意力、增加检测头部,实测涨点 💡 深入浅出YOLOv8:我的专业笔记与技术总结   -- YOLOv8轻松上手, 适用技术小白,文章代码齐全,仅需 …

【Node.js】大前端技能最通俗易懂的讲解 快速入门必看

目录 1、概述前端工具VSCode安装 2、NodeJS的安装 3、NodeJS了解和快速入门 4、NodeJS实现HttpServer服务 5、NodeJS实现操作MySQL数据库 Node.js是一个基于Chrome V8引擎的JavaScript运行环境&#xff0c;它允许开发者在服务器端执行Node.js是一个基于Chrome V8引擎的Ja…

【西行纪年番】孙悟空对战阴界王,素衣奄奄一息,巨灵拳霸气一击

Hello,小伙伴们&#xff0c;我是拾荒君。 《西行纪年番》第20集已更新。为了救回素衣&#xff0c;孙悟空想尽办法&#xff0c;最后他拜托沙悟净帮忙&#xff0c;终于成功把自己传送到阴界。原来&#xff0c;素衣的魂魄被阴界王藏在了他制造的人偶之中。沙悟净提醒孙悟空必须在…

【Computer Vision Foundation】全球计算机视觉基金会论文网

计算机视觉基金会&#xff08;Computer Vision Foundation&#xff0c;简称CVF&#xff09;是一个致力于推动计算机视觉领域研究和发展的组织。以下是关于计算机视觉基金会的一些基本信息&#xff1a; 成立目的&#xff1a; CVF成立的目的是促进计算机视觉领域的学术研究、技术…

【追求卓越09】算法--散列表(哈希表)

引导 通过前面几个章节的学习&#xff08;二分查找&#xff0c;跳表&#xff09;&#xff0c;我们发现想要快速查找某一个元素&#xff0c;首先需要将所有元素进行排序&#xff0c;再利用二分法思想进行查找&#xff0c;复杂度是O(logn)。有没有更快的查找方式呢&#xff1f; 本…

2023年【上海市安全员C证】考试及上海市安全员C证找解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年上海市安全员C证考试为正在备考上海市安全员C证操作证的学员准备的理论考试专题&#xff0c;每个月更新的上海市安全员C证找解析祝您顺利通过上海市安全员C证考试。 1、【多选题】2017年9月颁发的《中共上海市委…

el-tree 与table表格联动

html部分 <div class"org-left"><el-input v-model"filterText" placeholder"" size"default" /><el-tree ref"treeRef" class"filter-tree" :data"treeData" :props"defaultProp…

基于区域划分的GaN HEMT 准物理大信号模型

GaN HEMT器件的大信号等效电路模型分为经验基模型和物理基模型。经验基模型具有较高精度但参数提取困难&#xff0c;特别在GaN HEMT器件工艺不稳定的情况下不易应用。相比之下&#xff0c;物理基模型从器件工作机理出发&#xff0c;参数提取相对方便&#xff0c;且更容易更新和…

【DevOps】Git 图文详解(五):远程仓库

Git 图文详解&#xff08;五&#xff09;&#xff1a;远程仓库 1.远程用户登录1.1 &#x1f511; 远程用户登录&#xff1a;HTTS1.2 &#x1f511; 远程用户登录&#xff1a;SSH 2.远程仓库指令 &#x1f525;3.推送 push / 拉取 pull4.fetch 与 pull 有什么不同 &#xff1f; …

【C++】vector的介绍与使用

&#x1f9d1;‍&#x1f393;个人主页&#xff1a;简 料 &#x1f3c6;所属专栏&#xff1a;C &#x1f3c6;个人社区&#xff1a;越努力越幸运社区 &#x1f3c6;简 介&#xff1a;简料简料&#xff0c;简单有料~在校大学生一枚&#xff0c;专注C/C/GO的干货分…

数据库实验二 数据库表的数据插入、修改、删除操作

数据库实验二 数据库表的数据插入、修改、删除操作 一、实验目的二、设计性实验三、观察与思考 一、实验目的 1&#xff0e;掌握MySQL数据库表的数据插入、修改、删除操作SQL语法格式 2&#xff0e;掌握数据表的数据的录入、增加和删除的方法 二、设计性实验 某超市的食品管…

报错注入 [极客大挑战 2019]HardSQL1

打开题目 输入1或者1"&#xff0c;页面均回显NO,Wrong username password&#xff01;&#xff01;&#xff01; 那我们输入1 试试万能密码 1 or 11 # 输入1 and 12 # 输入1 union select 1,2,3 # 输入1 ununionion seselectlect 1,2,3 # 输入1 # 输入1# 页面依旧回…

redis运维(十六) 有序集合

一 有序集合 把握一点&#xff1a; 各种redis 命令都提供各种语言对应的API 接口,后续API是关键 ① 概念 1、sorted set --> 有序集合2、redis有序集合也是集合类型的一部分&#xff0c;所以它保留了集合中元素不能重复的特性3、但是不同的是,有序集合给每个元素多设置…