I2C_i2c官网

2022-09-20 09:13:12 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

1) I2C结构

I2C 总线在物理连接上比较简单,分别由 SDA(串行数据线)和 SCL(串行时钟线)两条总线及上拉电阻组成。通信的原理是通过控制 SCL 和 SDA 的时序,使其满足 I2C 的总线协议从而进行数据的传输。I2C 总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址(可以从 I2C 器件数据手册得知),主从设备之间就是通过这个地址来确定与哪个器件进行通信。本次实验我们把 FPGA 作为主设备,把挂载在总线上的其他设备(ADV7513)作为从设备。I2C 总线数据传输速率在标准模式下可达 100kbit/s,快速模式下可达400kbit/s,高速模式下可达 3.4Mbit/s。 I2C 总线上的主设备与从设备之间以字节(8 位)为单位进行双向的数据传输。

2) I2C时序

通过查阅ADV7513的数据手册,可以大概的了解一下IIC的整体时序图时序。

IIC整体时序图注意点如下:

① 空闲状态,SDA和SCL都保持为高

② I2C协议起始位:SCL为高电平时,SDA出现下降沿,产生一个起始位

③ I2C结束位:SCL为高电平时,SDA出现上升沿,产生一个停止位

④ I2C在数据传输过程中SCL与SDA的变化关系:

当 I2C 主机(后面简称主机)向 I2C 从机(后面简称从机) 写入数据时,SDA 上的每一位数据在 SCL 的高电平期间被写入从机中。 从主机角度来看, 需要在 SCL 低电平期间改变要写入的数据。而当主机读取从机中数据时,从机在SCL 低电平期间将数据输出到 SDA 总线上,在 SCL 的高电平期间保持数据稳定,从主机角度来看, 需要在 SCL 的高电平期间将 SDA 线上的数据读取并存储。

每当一个字节的数据或命令传输完成时,数据接收方都会向发送方响应一位应答位。在响应应答位时,数据发出方将 SDA 总线设置为三态输入,由于 I2C 总线上都有上拉电阻,因此此时总线默认为高电平,若数据接收方正确接收到数据,则数据接收方将 SDA 总线拉低,以示正确应答。

例如当主机向从机写入数据或命令时,每个字节都需要从机产生应答信号以告诉主机此次的数据或命令是否成功被写入。所以,当主机将一字节的数据或命令传出后,会将 SDA 信号设置为三态输入,等待从机应答(等待 SDA 被从机拉低为低电平),若从机正确应答,表明当前数据或命令传输成功,可以结束或开始下一个数据或命令的传输,否则表明数据或命令写入失败,主机就可以决定是否放弃写入或者重新发起写入。

值得注意的是,当主机从设备中读出数据时,当读出最后一个数据的时候,要向总线上发送一个NACK停止本次读取。总的来说,就是谁是数据的接收方,谁就要给出响应。

3) IIC写时序

上图是一张IIC写时序图,从图中我们可以看到,想要对I2C设备进行写操作,需要经历如下步骤。

首先产生一个起始位,然后向总线上给出要访问的I2C设备的7位设备ID和一位读写操作位(0为写操作,1为读操作),等到从机给总线上反馈一个低有效信号ACK时证明总线上存在这个设备,然后才能进行读写操作。

在接收到ACK后,主机向总线上写入要访问的寄存器地址,如存在要访问的寄存器地址,从机会在总线上反馈一个ACK信号,此时,主机可以向从机写入数据。

如主机向从机单次写数据,在写完8位数据后,接收到响应ACK后,将产生一个停止位,结束本次写操作。若是进行连续写入多字节数据,每当写完一字节数据,都会等待ACK才能进行下一字节的写操作。写入全部字节后将产生一个停止位,结束本次写操作。

写操作要注意的几点就是:IIC设备地址,IIC寄存器地址以及将要填充的字节数据。

4) IIC读时序

IIC读操作分为两个步骤,进行I2C读操作时,首先要进行写操作,然后才能进行读操作。

首先需要向I2C总线上给出要访问的设备的地址,若总线上存在这个设备,将会给出一个响应ACK信号,然后在给出将要访问的寄存器,接收到ACK信号后产生停止位。

经历上一个步骤后,才能开始发起读操作。读设备中寄存器内的值时,首先也要产生一个起始位,然后给出设备地址和读操作命令,接收到ACK信号后,给出要访问的寄存器地址,接收到ACK信号后,从机将会把寄存器中的地址输出到I2C总线上。

若是只读一字节数据,在一字节数据接收完成后,将会产生一个NACK信号,主机接收到NACK信号后将终止本次读操作;若是进行多字节的读操作,当还未接收到最后一字节数据时,每当接收完一字节的数据,将会接收到一个ACK信号,主机在接收到ACK信号后继续接收从机发出的数据。当主机接收到从机的NACK信号后,产生停止位,结束本次操作。

3) 功能设计

驱动模块的功能框图如下,上游模块给出读写请求和读写地址,在使能start信号后即可以开始进行数据的传输。用户可以将数据写入到I2C从设备中也可以从I2C从设备中读出数据,传输错误时将给出一个错误信号err_flag。传输完成后根据读写请求回应一个读写完成信号。

状态跳转图如下:

代码设计:

代码语言:javascript复制
/*
* 提示:该行代码过长,系统自动注释不进行高亮。一键复制会移除系统注释 
* 1 /*============================================  2 #  3 # Author: Wcc - 1530604142@qq.com  4 #  5 # QQ : 1530604142  6 #  7 # Last modified: 2019-11-08 18:00  8 #  9 # Filename: i2c_driver.v  10 #  11 # Description: I2C驱动模块,暂时只能完成对设备地址为1字节的设备进行访问  12 # 并且暂时还没有错误重传机制,将在后面完善  13 ============================================*/  14 `timescale 1ns / 1ps  15 module i2c_driver(  16 input wire clk ,//系统时钟  17 input wire rst ,//系统复位  18 input wire wr_req ,//写请求信号  19 input wire rd_req ,//读请求信号  20 input wire start ,//一次读写开始信号  21 input wire [7:0] dev_addr,//设备地址  22 input wire [7:0] mem_addr,//寄存器地址  23 input wire [7:0] wr_data ,//写入寄存器的数据  24  25 output reg [7:0] rd_data ,//从寄存器读出的数据  26 output reg rd_done ,//一次读操作结束  27 output reg wr_done ,//一次写操作结束  28 output reg err_flag,//发生错误信号  29 output reg scl ,//i2c时钟  30 inout wire sda //i2c数据总线   31  );  32  33 //==================================================  34 //parameter define  35 //==================================================  36 parameter IDLE = 10'b00_0000_0001;//空闲状态  37 parameter WR_START= 10'b00_0000_0010;//写起始  38 parameter WR_DEV = 10'b00_0000_0100;//确认设备地址  39 parameter WR_MEM = 10'b00_0000_1000;//确认寄存器地址  40 parameter WR_DATA = 10'b00_0001_0000;//写数据  41 parameter RD_START= 10'b00_0010_0000;//读开始  42 parameter RD_DEV = 10'b00_0100_0000;//设备地址读操作  43 parameter RD_DATA = 10'b00_1000_0000;//读数据  44 parameter STOP = 10'b01_0000_0000;//停止  45 parameter ERROR = 10'b10_0000_0000;//错误  46  47  48 parameter SYS_CYCLE = 20;//系统时钟50M  49 parameter IIC_CYCLE = 5000;//IIC 工作频率200K  50 parameter MAX = (IIC_CYCLE/SYS_CYCLE) -1;//驱动时钟的计数最大值  51 parameter T_HIGH = 2000 ;//I2C时钟高电平  52 parameter T_LOW = 3000 ;//I2C时钟低电平  53  54 parameter FLAG0 = ((T_HIGH/SYS_CYCLE)>>1) - 1;//SCL高电平中点  55 parameter FLAG1 = (T_HIGH/SYS_CYCLE) - 1;//SCL下降沿  56 parameter FLAG2 = (T_HIGH/SYS_CYCLE)   ((T_LOW/SYS_CYCLE)>>1) -1;//SCL低电平中点  57 parameter FLAG3 = (T_HIGH/SYS_CYCLE)   (T_LOW/SYS_CYCLE) - 1;//SCL上升沿  58 //==================================================  59 //internal signals  60 //==================================================  61 reg [2:0] cnt_freq ;//计数drive_flag 产生I2C时钟  62 wire add_cnt_freq;  63 wire end_cnt_freq;  64  65  66 reg [5:0] cnt_flag ;//计数当前状态有多少个drive_flag  67 wire add_cnt_flag;  68 wire end_cnt_flag;  69 reg [5:0] x ;//可变计数器的最大值  70  71 reg [9:0] cnt ;//用来产生驱动信号drive_flag  72 wire add_cnt ;  73 wire end_cnt ;  74  75 reg drive_flag ;//用于驱动本模块工作的信号  76 reg [8:0] state ;//state register  77 reg work_flag ;//work flag  78 reg wr_en ;//三态数据线写使能  79 reg [7:0] data_shift ;//移位寄存器  80 reg ack_flag ;//响应信号  81  82 reg wr_sda ;  83 wire rec_sda ;  84  85 //三态端口声明  86 assign sda = wr_en?wr_sda:1'bz;//当主机向数据向数据总线上写数据时,wr_en=1,给出用户数据,当接收数据时,置为高阻  87 assign rec_sda = sda;  88 //--------------------state machine define--------------------  89 always @(posedge clk)begin  90 if(rst == 1'b1)begin  91 state <= IDLE;  92 end  93 else begin  94 case(state)  95 IDLE:begin  96 if(start==1'b1 && (wr_req==1'b1 || rd_req==1'b1))  97 state <= WR_START;//接收到开始信号,进入起始状态  98 else  99 state <= IDLE; 100 end 101 102 WR_START:begin 103 if(cnt_flag=='d6 && drive_flag) 104 state <= WR_DEV;//起始状态结束,进入确认设备地址状态 105 else 106 state <= WR_START; 107 end 108 109 WR_DEV:begin 110 if(cnt_flag=='d35 && drive_flag && ack_flag==1'b1)//发送完设备地址,并且正确接收到响应 111 state <= WR_MEM;//确认地址状态结束,进入确认寄存器地址状态 112 else if(cnt_flag=='d35 && drive_flag && ack_flag==1'b0)//发送完设备地址,并且没有接收到响应 113 state <= ERROR;//确认地址状态结束,进入确认寄存器地址状态 114 else 115 state <= WR_DEV; 116 end 117 118 WR_MEM:begin 119 if(cnt_flag=='d35 && drive_flag && ack_flag==1'b1)begin//已经给出写入的寄存器,并且正确接收到响应 120 if(wr_req==1'b1) 121 state <= WR_DATA;//确认寄存器地址状态结束,进入写数据状态 122 else if(wr_req==1'b0 && rd_req==1'b1) 123 state <= RD_START;//确认寄存器地址状态结束,进入写开始状态 124 end 125 else if(cnt_flag=='d35 && drive_flag && ack_flag==1'b0)//发送完设备地址,并且没有接收到响应 126 state <= ERROR;//确认地址状态结束,进入确认寄存器地址状态 127 else 128 state <= WR_MEM; 129 end 130 131 WR_DATA:begin 132 if(cnt_flag=='d35 && drive_flag && ack_flag==1'b1)//已经给出写入数据并正确接收到响应 133 state <= STOP;//数据写入完成进入停止状态 134 else if(cnt_flag=='d35 && drive_flag && ack_flag==1'b0)//发送完设备地址,并且没有接收到响应 135 state <= ERROR;//确认地址状态结束,进入确认寄存器地址状态 136 else 137 state <= WR_DATA; 138 end 139 140 RD_START:begin 141 if(cnt_flag=='d3 && drive_flag && rd_req) 142 state <= RD_DEV;//进入设备地址读操作 143 else 144 state <= RD_START; 145 end 146 147 RD_DEV:begin 148 if(cnt_flag=='d35 && drive_flag && ack_flag==1'b1) 149 state <= RD_DATA;//进入读数据状态 150 else if(cnt_flag=='d35 && drive_flag && ack_flag==1'b0)//发送完设备地址,并且没有接收到响应 151 state <= ERROR;//确认地址状态结束,进入确认寄存器地址状态 152 else 153 state <= RD_DEV; 154 end 155 156 RD_DATA:begin 157 if(cnt_flag=='d35 && drive_flag && ack_flag==1'b1) 158 state <= STOP; 159 else if(cnt_flag=='d35 && drive_flag && ack_flag==1'b0)//发送完设备地址,并且没有接收到响应 160 state <= ERROR;//确认地址状态结束,进入确认寄存器地址状态 161 else 162 state <= RD_DATA; 163 end 164 165 STOP:begin 166 if(cnt_flag=='d3 && drive_flag) 167 state <= IDLE; 168 else 169 state <= STOP; 170 end 171 172 ERROR:begin 173 state <= IDLE; 174 end 175 176 default:begin 177 state <= IDLE; 178 end 179 endcase 180 end 181 end 182 183 //-------------------work_flag--------------------- 184 always @(posedge clk)begin 185 if(rst == 1'b1)begin 186 work_flag <= 1'b0; 187 end 188 else if(state==WR_START)begin//接收到开始信号 189 work_flag <= 1'b1; 190 end 191 else if(wr_done==1'b1 || rd_done==1'b1)begin//一次读写完成 192 work_flag <= 1'b0; 193 end 194 end 195 196 //--------------------cnt-------------------- 197 always @(posedge clk)begin 198 if(rst==1'b1)begin 199 cnt <= 0; 200 end 201 else if(add_cnt)begin 202 if(end_cnt) 203 cnt <= 0; 204 else 205 cnt <= cnt   1'b1; 206 end 207 else begin 208 cnt <= 'd0; 209 end 210 end 211 212 assign add_cnt = work_flag;//处于工作状态时一直计数  213 assign end_cnt = add_cnt && cnt== MAX;//计数到最大值清零 214 215 //--------------------drive_flag-------------------- 216 always @(posedge clk)begin 217 if(rst == 1'b1)begin 218 drive_flag <= 1'b0; 219 end 220 else if(cnt==FLAG0 || cnt==FLAG1 || cnt==FLAG2 || cnt==FLAG3)begin//产生一个驱动信号 221 drive_flag <= 1'b1; 222 end 223 else begin 224 drive_flag <= 1'b0; 225 end 226 end 227 228 //--------------------cnt_freq-------------------- 229 //对驱动信号进行计数,以此来产生I2C时钟 230 always @(posedge clk)begin 231 if(rst==1'b1)begin 232 cnt_freq <= 0; 233 end 234 else if(work_flag == 1'b0)begin 235 cnt_freq <= 'd0; 236 end 237 else if(add_cnt_freq)begin 238 if(end_cnt_freq) 239 cnt_freq <= 0; 240 else 241 cnt_freq <= cnt_freq   1'b1; 242 end 243 else begin 244 cnt_freq <= cnt_freq; 245 end 246 end 247 248 assign add_cnt_freq = drive_flag; 249 assign end_cnt_freq = add_cnt_freq && cnt_freq== 4-1; 250 251 //--------------------scl-------------------- 252 always @(posedge clk)begin 253 if(rst == 1'b1)begin 254 scl <= 1'b1; 255 end 256 else if(work_flag==1'b1)begin 257 if(cnt_freq=='d1 && drive_flag &&state==STOP)begin 258 scl <= 1'b1; 259 end 260 else if(cnt_freq=='d1 && drive_flag && state!= STOP)begin 261 scl <= 1'b0; 262 end 263 else if(cnt_freq=='d3 && drive_flag)begin 264 scl <= 1'b1; 265 end 266 end 267 else begin 268 scl <= 1'b1; 269 end 270 end 271 272 //--------------------cnt_flag-------------------- 273 //计数当前状态下有多少个drive_flag 274 always @(posedge clk)begin 275 if(rst==1'b1)begin 276 cnt_flag <= 0; 277 end 278 else if(work_flag==1'b0)begin 279 cnt_flag <= 'd0; 280 end 281 else if(add_cnt_flag)begin 282 if(end_cnt_flag) 283 cnt_flag <= 0; 284 else 285 cnt_flag <= cnt_flag   1'b1; 286 end 287 else begin 288 cnt_flag <= cnt_flag; 289 end 290 end 291 292 assign add_cnt_flag = drive_flag; 293 assign end_cnt_flag = add_cnt_flag && cnt_flag== x ; 294 295 //--------------------x-------------------- 296 //x为不同状态下,计数器的计数最大值 297 always @(*)begin 298 case(state) 299 IDLE: x=0; 300 WR_START: x= 7 - 1; 301 WR_DEV,WR_MEM,WR_DATA,RD_DEV,RD_DATA: x=36 - 1; 302 RD_START: x= 4 - 1; 303 STOP: x = 4 - 1; 304 default: x = 0; 305 endcase 306 end 307 308 //------------------wr_en---------------------- 309 always @(posedge clk)begin 310 if(rst == 1'b1)begin 311 wr_en <= 1'b0; 312 end 313 else if(state==WR_START || state==RD_START || state==STOP)begin 314 wr_en <= 1'b1;//在写开始,读开始,和结束状态,由用户操纵数据总线,产生开始或者停止信号,所以写数据使能有效 315 end 316 else if(state==WR_DEV || state==WR_MEM ||state==WR_DATA || state==RD_DEV)begin 317 if(cnt_flag < 'd32)begin//给出用户数据时,写数据使能有效 318 wr_en <= 1'b1; 319 end 320 else begin 321 wr_en <= 1'b0;//等待从设备响应时,写数据使能无效 322 end 323 end 324 else if(state==RD_DATA)begin 325 if(cnt_flag < 'd32)begin 326 wr_en <= 1'b0;//接收数据状态,此时由从机发送数据给主机,写数据使能无效 327 end 328 else begin 329 wr_en <= 1'b1;//接收数据完成,主机需要对从机做出应答,写数据使能有效 330 end 331 end 332 else begin 333 wr_en <= 1'b0; 334 end 335 end 336 337 //--------------------data_shift-------------------- 338 always @(posedge clk)begin 339 if(rst == 1'b1)begin 340 data_shift <= 'd0; 341 end 342 else begin 343 case(state) 344 IDLE:begin 345 data_shift <= 'd0;//空闲状态,让移位寄存器保持为0 346 end 347 348 WR_START:begin 349 data_shift <= {dev_addr[7:1],1'b0};//写开始状态,给出设备写指令 350 end 351 352 WR_DEV:begin 353 if(end_cnt_flag && ack_flag==1'b1) 354 data_shift <= mem_addr;//确认设备状态结束时,给出寄存器地址 355 else if(cnt_flag<'d32 && cnt_flag[1:0]==2'd3 && drive_flag) 356 data_shift <= {data_shift[6:0],1'b0}; 357 end 358 359 WR_MEM:begin 360 if(end_cnt_flag && ack_flag==1'b1 && wr_req==1'b1)//即将进入写数据状态 361 data_shift <= wr_data;//确认寄存器地址状态结束后给出要写入的数据 362 else if(cnt_flag<'d32 && cnt_flag[1:0]==2'd3 && drive_flag) 363 data_shift <= {data_shift[6:0],1'b0}; 364 end 365 366 WR_DATA:begin 367 if(cnt_flag<'d32 && cnt_flag[1:0]==2'd3 && drive_flag) 368 data_shift <= {data_shift[6:0],1'b0};//将数据写入到寄存器中 369 else 370 data_shift <= data_shift; 371 end 372 373 RD_START:begin 374 data_shift <= {dev_addr[7:1],1'b1};//读开始时,将读命令填充入移位寄存器 375 end 376 377 378 RD_DEV:begin 379 if(end_cnt_flag && ack_flag==1'b1) 380 data_shift <= 'd0; 381 else if(cnt_flag<'d32 && cnt_flag[1:0]==2'd3 && drive_flag) 382 data_shift <= {data_shift[6:0],1'b0}; 383 end 384 385 RD_DATA:begin 386 if(cnt_flag<'d32 && cnt_flag[1:0]==2'd1 && drive_flag) 387 data_shift <= {data_shift[6:0],rec_sda};//将从寄存器中读出的数据填充入移位寄存器 388 else 389 data_shift <= data_shift; 390 end 391 392 default:begin 393 data_shift <= data_shift; 394 end 395 endcase 396 end 397 end 398 399 400 401 //--------------------wr_sda-------------------- 402 always @(posedge clk)begin 403 if(rst == 1'b1)begin 404 wr_sda = 1'b1; 405 end 406 else begin 407 case(state) 408 WR_START:begin 409 if(cnt_flag=='d4 && drive_flag) 410 wr_sda <= 1'b0;//产生起始位 411 else 412 wr_sda <= wr_sda; 413 end 414 415 WR_DEV,WR_MEM,WR_DATA,RD_DEV:begin 416 wr_sda <= data_shift[7];//将数据发送至数据总线上 417 end 418 419 RD_START:begin 420 if(cnt_flag=='d0) 421 wr_sda <= 1'b1;//产生读起始位 422 else if(cnt_flag=='d1 && drive_flag) 423 wr_sda <= 1'b0; 424 end 425 426 RD_DATA:begin 427 if(cnt_flag>='d32) 428 wr_sda <= 1'b1;//产生NACK 429 else 430 wr_sda <= wr_sda; 431 end 432 433 STOP:begin 434 if(cnt_flag=='d0 && wr_en) 435 wr_sda <= 1'b0; 436 else if(cnt_flag=='d1 && drive_flag) 437 wr_sda <= 1'b1; 438 end 439 440 default:wr_sda <= 1'b1; 441 endcase 442 end 443 end 444 445 //--------------------wr_done,rd_done-------------------- 446 always @(posedge clk)begin 447 if(rst == 1'b1)begin 448 wr_done <= 1'b0; 449 rd_done <= 1'b0; 450 end 451 else if(state==STOP && end_cnt_flag)begin//即将结束本次读写操作的时候,产生完成信号 452 if(wr_req==1'b1) 453 wr_done <= 1'b1; 454 else if(wr_req==1'b0 && rd_req==1'b1) 455 rd_done <= 1'b1; 456 end 457 else begin 458 wr_done <= 1'b0; 459 rd_done <= 1'b0; 460 end 461 end 462 463 //--------------------ack_flag-------------------- 464 //是否接收到ACK或者产生NACK 465 always @(posedge clk)begin 466 if(rst == 1'b1)begin 467 ack_flag <= 1'b0; 468 end 469 else begin 470 case(state) 471 WR_DEV:begin 472 if(cnt_flag>='d32 && cnt_flag[1:0]=='d1 && drive_flag && sda==1'b0) 473 ack_flag <= 1'b1;//写完设备地址,并且接收到响应 474 else if(end_cnt_flag) 475 ack_flag <= 1'b0; 476 end 477 478 WR_MEM:begin 479 if(cnt_flag>='d32 && cnt_flag[1:0]=='d1 && drive_flag && sda==1'b0) 480 ack_flag <= 1'b1;//写完寄存器地址,接收到响应 481 else if(end_cnt_flag) 482 ack_flag <= 1'b0; 483 end 484 485 WR_DATA:begin 486 if(cnt_flag>='d32 && cnt_flag[1:0]=='d1 && drive_flag && sda==1'b0) 487 ack_flag <= 1'b1;//写完数据,并且接收到响应 488 else if(end_cnt_flag) 489 ack_flag <= 1'b0; 490 end 491 492 RD_DEV:begin 493 if(cnt_flag>='d32 && cnt_flag[1:0]=='d1 && drive_flag && sda==1'b0) 494 ack_flag <= 1'b1;//读指令发送完毕,接收到响应 495 else if(end_cnt_flag) 496 ack_flag <= 1'b0; 497 end 498 499 RD_DATA:begin 500 if(cnt_flag>='d32 && cnt_flag[1:0]=='d1 && drive_flag && sda==1'b1) 501 ack_flag <= 1'b1;//数据全部读完,主机给出NACK 502 else if(end_cnt_flag) 503 ack_flag <= 1'b0; 504 end 505 506 default: ack_flag <= 1'b0; 507 endcase 508 end 509 end 510 511 //--------------------rd_data-------------------- 512 always @(posedge clk)begin 513 if(rst == 1'b1)begin 514 rd_data <= 1'b0; 515 end 516 else if(rd_done)begin//即将结束本次读写操作的时候,产生完成信号 517 rd_data <= data_shift; 518 end 519 else begin 520 rd_data <= rd_data; 521 end 522 end 523 524 always @(posedge clk)begin 525 if(rst == 1'b1)begin 526 err_flag <= 1'b0; 527 end 528 else if(state==ERROR)begin//即将结束本次读写操作的时候,产生完成信号 529 err_flag <= 1'b1; 530 end 531 else begin 532 err_flag <= 1'b0; 533 end 534 end 535 536 endmodule
*/

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/167875.html原文链接:https://javaforall.cn

0 人点赞