IIC协议的通讯过程

IIC的介绍

  • I2C 通讯协议(Inter-Integrated Circuit)是由 Phiilps 公司开发的,由于它引脚少,硬件,实现简单,可扩展性强,不需要 USART、 CAN 等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。IIC属于半双工通信协议,所以在通信的时候必须等待当前数据传输结束,方可进行下一次数据的传输。根据 IIC 协议的设计,从设备不能主动发起通信,只有主设备可以发起通信并请求从设备发送数据。

IIC的物理层

  • IIC比USART,SPI等协议有着最大的优势就是他们不管有多少个从机设备,在建立通信时只需要两根总线就可以进行通讯。由于IIC采用的是同步半双工,所以不需要设置类似波特率的设置
    • SDA:数据线,用于实现双向传输数据
    • SCL:时钟线,用于数据收发的同步
  • 它的物理层有如下特点:
    1. 它是一个支持设备的总线。“总线”指多个设备共用的信号线。在一个I2C通讯总线中, 可连接多个I2C通讯设备,支持多个通讯主机及多个通讯从机。
    2. 一个I2C总线只使用两条总线线路,一条双向串行数据线(SDA) , 一条串行时钟线 (SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
    3. 每个连接到总线的设备都有一个独立的地址, 主机可以利用这个地址进行不同设备之间的访问。
    4. 总线通过上拉电阻接到电源。当I2C设备空闲时,会输出高阻态, 而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
    5. 多个主机同时使用总线时,为了防止数据冲突, 会利用仲裁方式决定由哪个设备占用总线。
    6. 具有三种传输模式:标准模式传输速率为100kbit/s ,快速模式为400kbit/s , 高速模式下可达 3.4Mbit/s,但目前大多I2C设备尚不支持高速模式。
    7. 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制。

IIC的协议层

  • 通讯的开始与停止,开始信号用于告诉从设备,主设备将要开始发送数据。在 IIC 通信中,开始信号是通过在 SDA 线上将数据从高电平切换到低电平来实现的。终止信号用于告诉从设备,主设备已经完成发送数据。在 IIC 通信中,终止信号是通过在 SDA 线上将数据从低电平切换到高电平来实现的。

  • 数据的有效性,SCL高电平时数据有效。当SCL时低电平时数据无效,此时可变更数据位下一次需要传输的数据位的准备。SDA数据线在SCL的每个时钟周期传输一位数据。传输时,SCL为高电平的时候SDA表示的数据有效,即此时的SDA为高电平时表示数据“1”, 为低电平时表示数据“0”。当SCL为低电平时,SDA的数据无效,一般在这个时候SDA进行电平切换,为下一次表示数据做好准备。

  • 应答响应,应答信号用于确认从设备已经成功接收到了主设备发送的数据。在 IIC 通信中,应答信号是由从设备在 SDA 线上拉低电平来实现的。

IIC的数据帧格式

  • 在通讯的时候由于所有从机都连接到两根总线上,所以需要发送一个地址码(7Bit或10Bit)来选择从机设备进行通讯。首先发送一个开始信号,告诉所有从机我要开始发送地址广播了。地址广播就是将我需要通讯的地址发送给总线上的每个从机设备,当对应的从机收到地址判断是和自己的地址相等,说明当前连接是与自己通讯的,从机随后返回一个应答信号,当主机接收到应答信号后通讯开始建立。如果不是自己的将忽略后面的所有数据。地址在这里相当于一个使能,只有在接收到正确的地址码,从机的使能才会开启。否则从机的使能关闭,对后面的数据不做处理。

  • 发送完地址码后紧跟着1Bit的操作指令,当此Bit为0时进行写操作,Bit为1时进行读操作。如果地址码是7Bit时,此操作指令Bit会和地址码放在一次当成一个字节一起发送出去。所以有时候读取数据需要发送两次地址码就是因为这个原因,第一次发送是写从机设备的寄存器地址,第二次是读取寄存器地址的值

  • 在每个操作完成后都需要发送一个ACK/NACK(应答与非应答)告诉主机/从机是否接收到数据。如果不需要再接收数据发送NACK即可,如果需要继续接收数据发送ACK。

  • SR标志通常在IIC协议中的数据传输阶段使用,用于指示要发送一个新的数据传输请求。在IIC通信中,当主设备需要与从设备进行数据传输时,它会先发送一个起始信号,然后发送从设备地址,以及要读取或写入的数据。如果在数据传输过程中需要与另一个从设备进行通信,则可以使用SR标志发送重复的起始信号,而不必发送停止信号。这样可以减少通信的时间和复杂性,提高IIC通信的效率。

  • 当所有数据帧都传输完毕如果不需要传输任何数据了,则将SDA拉高结束通讯发送一个停止标识符。

EEPROM的认识与介绍

存储器的认识

  • 存储器的分类:

    1. 易失性存储器是指在断电或者电源关闭的情况下会丢失数据的存储器,即当电源断电时,存储在其中的数据会立即消失。易失性存储器通常用于计算机的临时存储器,如随机存储器(RAM),它可以很快地读取和写入数据,但一旦电源关闭,存储在其中的数据就会被清除。
    2. 非易失性存储器则是在断电或电源关闭的情况下仍能保持数据的存储器。它们可以长期存储数据,如硬盘驱动器(HDD)和固态硬盘(SSD)等,这些存储器通常用于存储操作系统、应用程序和用户数据等重要信息
  • 由于一般易失性存储器存取速度快,而非易失性存储器可长期保存数据, 它们都在计算机中占据着重要角色

易失性存储器——RAM

DRAM

  • DRAM是动态随机存取存储器(Dynamic Random Access Memory)的缩写,是一种计算机内存技术。它是计算机中最常用的主存储器之一,用于存储CPU需要使用的数据和程序。DRAM是一种易失性存储器,这意味着它需要定期刷新以保持存储的数据,否则数据将会丢失。

  • DRAM的原理是通过电容器存储电荷来表示存储的数据。每个DRAM存储单元由一个电容器和一个开关构成。当电容器充电时,表示存储的数据为1,否则为0。为了读取存储的数据,DRAM需要将电荷从电容器中读取出来,这需要消耗一定的时间。

  • 由于DRAM存储单元的电容器是易失性的,因此需要定期刷新以保持数据的完整性。DRAM芯片通常会有一个控制器,用于定期刷新存储单元中的电容器。此外,为了提高访问速度,DRAM存储单元通常会按行排列,以便可以一次性读取或写入一整行数据。

  • 根据DRAM通讯方式又分同步与异步通讯。这两种通讯主要根据是否是时钟信号控制来区别。如果需要时钟信号上升沿的时候才能写入数据那么就是同步通讯,异步通信是不需要时钟线控制的。使用同步通讯的速度更快,所以更广泛使用,这种同步通讯DRAM被称为SDRAM(Synchronous Dynamic Random Access Memory)

SRAM

  • SRAM是静态随机存取器(Static Random Access Memory)的缩写。它和上面的的区别就是它不是使用电容来存储数据的,而是使用逻辑门组成的锁存器。这种电路不需要一直刷新也可以保存数据,但是掉电后数据依旧会丢失。

DDRAM

  • DDRAM(Double Data Rate Random Access Memory)是SDRAM中的一种,他与SDRAM的区别是SDRAM只有在上升沿的时候才能读写,一个时钟周期只能读写一个Bit,而DDRAM可以在上升沿和下降沿都可以读写。也就是说一个时钟周期可以读写2Bit的数据是SRAM的双倍率

  • 但其速度仍然无法满足高端计算机和服务器的需求。随着技术的不断发展,后续的DDR内存逐渐出现,包括DDR2,DDR3,DDR4和DDR5。这些内存有以下不同之处:

    1. 频率: DDR,DDR2,DDR3,DDR4和DDR5的频率逐渐提高,分别为400MHz,800MHz,1600MHz,3200MHz和4800MHz。随着频率的增加,内存的数据传输速度也随之提高。
    2. 电压: DDR,DDR2和DDR3的工作电压分别为2.5V,1.8V和1.5V,而DDR4和DDR5则分别为1.2V和1.1V。随着电压的降低,内存的功耗也相应减少。
    3. 带宽: DDR,DDR2,DDR3,DDR4和DDR5的带宽逐渐增加,分别为3.2GB/s,12.8GB/s,25.6GB/s,51.2GB/s和76.8GB/s。随着带宽的增加,内存的数据传输速度也相应提高。
    4. 密度: 随着技术的进步,DDR,DDR2,DDR3,DDR4和DDR5的内存密度逐渐提高,可以存储更多的数据和程序。

非易失性存储器——ROM

  • ROM(Read Only Memory)意思是只读存储器。由于初代的存储器都是一次性的。不具备擦除的能力,所以叫只读存储器。但由于不断地发展至今,现在的存储器都具备擦写的功能但ROM的说法一直延续至今。当代的ROM基本上都是可擦除的

MASK ROM

  • MASK ROM就是一种真正意义上的ROM只能写入一次并且不能擦除。它的原理就是在制造时,在晶圆上通过光刻计数形成芯片上固定的数据存储格式被称为mask,时定义了一个数据格式的模板。木板上的而数据模式由芯片设计人员确定的,并且不可更改。类似于电路板印刷的过程,将电路通过曝光映射在铜板上面。

  • 由于Mask ROM芯片在制造过程中的数据模式是固定的,因此它的数据内容无法被更改或擦除。由于不需要在运行时编程或擦除,因此Mask ROM在一些应用领域,如嵌入式系统中得到了广泛的应用。它的优点是可靠性高,寿命长,但生产成本高,因此仅适用于需要存储大量数据且不需要更改的应用。

PROM

  • PROM(Programmable Read-Only Memory)是一种可编程的只读存储器,他是一种通用的数字电路元件。与Mask ROM不同的是它制造后才可以写,并且只可以通过编程可以写入一次数据后不可再写只可读。

  • PROM的原理是利用电学原理来编程。PROM内部由一个阵列组成,每个阵列由一个开关和一个存储单元组成。存储单元通常是一个双稳态存储单元(flip-flop),用于存储一个比特(0或1)。开关通常由一个热电偶或一个快速脉冲所驱动,开关状态的改变可以改变存储单元中的值。

  • PROM编程时,将电压应用于特定的引脚,使它们处于特定的状态,这会烧毁内部的保护栅极,从而允许电荷在存储单元中流动,并且改变存储单元的状态。这些特定的引脚由生产商预定义,在用户购买PROM时就已经确定。一旦PROM被编程,它的数据就不能再次更改。

OTPROM
  • OTPROM是一种只读存储器,全称为One-Time Programmable Read-Only Memory,即一次性可编程只读存储器。其原理是将一次编程的信息写入存储器单元中,并且在之后无法再次进行编程或擦除,因此被称为“一次性可编程”。

  • OTPROM的存储单元由一对互补的MOS晶体管构成,其中一个MOS晶体管的栅极是与另一个晶体管的漏极相连的。这种构造被称为“互补翻转对(complementary pair)”,可用于实现非常简单的存储单元。

  • 在OTPROM的编程过程中,利用特殊的编程电压将存储单元中的电荷累积到栅极和漏极之间,形成氧化层中的隧道电离。这些电子被“捕获”在漏极区域中,这样就可以保留存储单元的状态。一旦信息被编程到OTPROM中,就无法再次更改或擦除。

  • OTPROM与EPROM(可擦除可编程只读存储器)的区别在于EPROM可以被擦除并重新编程,而OTPROM只能被编程一次。这使得OTPROM在需要固化数据或程序的应用中非常有用,例如电子设备中的BOOTLOADER程序等。

EPROM

  • EPROM是可编程只读存储器(Erasable Programmable Read-Only Memory)的缩写。它是一种存储数据的电子芯片,具有只读存储器(ROM)的特性,但它可以被编程为存储新的数据。

  • EPROM的工作原理是使用一个特殊的设备称为EPROM编程器来编程。编程器通过在EPROM的芯片上施加高电压,将EPROM的存储单元中的电荷量改变为特定的状态,从而存储新的数据。这些数据在EPROM被编程之后可以被读取,但是不能被再次修改。

  • EPROM的另一个特点是它可以被擦除。擦除可以通过将芯片曝露在紫外线下进行,这将清除芯片中存储的所有数据。擦除后,EPROM可以被重新编程以存储新的数据。

  • EPROM被广泛应用于嵌入式系统中,例如控制器、计算机系统等。在这些系统中,EPROM可以存储程序代码和数据,以便系统启动时读取。由于EPROM不需要外部电源来保持存储的数据,它是一种非常可靠的存储器。然而,EPROM需要使用专门的设备进行编程和擦除,这限制了它的灵活性和可重写性。

EEPROM

  • EEPROM是可擦写可编程只读存储器(Electrically Erasable Programmable Read-Only Memory)的缩写。它与EPROM非常相似,但是可以通过电子方式进行擦写,因此更加灵活。

  • EEPROM的工作原理是通过在芯片中施加电压来改变存储单元中的电荷量,从而存储数据。与EPROM不同的是,EEPROM的擦除是通过将电荷量逆转而不是使用紫外线进行的。EEPROM具有非常高的可重写性,因为它可以被多次擦写和编程,而无需任何外部设备。

  • EEPROM被广泛应用于存储数据和配置信息,例如BIOS设置和计算机外围设备的配置信息。由于EEPROM可以被多次擦写和编程,它非常适合需要频繁更新的应用程序。然而,EEPROM相对于其他存储器类型来说速度较慢,并且相对较昂贵。

非易失性存储器——FLASH

  • FLASH存储器又称为闪存,它也是可重复擦写的储器,部分书籍会把FLASH存储器称为FLASH ROM,但它的容量一般比EEPROM大得多, 且在擦除时,一般以多个字节为单位。如有的FLASH存储器以4096个字节为扇区,最小的擦除单位为一个扇区。根据存储单元电路的不同, FLASH存储器又分为NOR FLASH和NAND FLASH

  • NOR与NAND的共性是在数据写入前都需要有擦除操作,而擦除操作一般是以“扇区/块”为单位的。 而NOR与NAND特性的差别,主要是由于其内部“地址/数据线”是否分开导致的。


- 由于NOR的地址线和数据线分开,它可以按“字节”读写数据,符合CPU的指令译码执行要求,所以假如NOR上存储了代码指令, CPU给NOR一个地址,NOR就能向CPU返回一个数据让CPU执行,中间不需要额外的处理操作。

  • 而由于NAND的数据和地址线共用,只能按“块”来读写数据,假如NAND上存储了代码指令,CPU给NAND地址后, 它无法直接返回该地址的数据,所以不符合指令译码要求。 表 NOR_FLASH与NAND_FLASH特性对比 中的最后一项“是否支持XIP”描述的就是这种立即执行的特性(eXecute In Place)。

  • 若代码存储在NAND上,可以把它先加载到RAM存储器上,再由CPU执行。所以在功能上可以认为NOR是一种断电后数据不丢失的RAM, 但它的擦除单位与RAM有区别,且读写速度比RAM要慢得多。

  • 另外,FLASH的擦除次数都是有限的(现在普遍是10万次左右),当它的使用接近寿命的时候,可能会出现写操作失败。 由于NAND通常是整块擦写,块内有一位失效整个块就会失效,这被称为坏块,而且由于擦写过程复杂,从整体来说NOR块块更少, 寿命更长。由于可能存在坏块,所以FLASH存储器需要“探测/错误更正(EDC/ECC)”算法来确保数据的正确性。

  • 由于两种FLASH存储器特性的差异,NOR FLASH一般应用在代码存储的场合,如嵌入式控制器内部的程序存储空间。 而NAND FLASH一般应用在大数据量存储的场合,包括SD卡、U盘以及固态硬盘等,都是NAND FLASH类型的。

实战

24C02C

描述

  • 24C02C是一个2K bit串行EEPROM,工作电压范围4.5V ~ 5.5V。单个数据块由256 x 8-bit组成,具有两根串行线。

管脚描述

  • A0, A1, A2:用于设置设备的IIC地址,采用7Bit的地址,且MSB固定为1010,LSB可根据A0 ~ A2的设置决定
  • VSS:Groud
  • SDA:串行输入输出接口
  • SCL:串行时钟
  • WP:写保护输入,用于设置EEPROM的写入权限,当WP接入VCC(高电平)的时候可写,当WP接入VSS(低电平)的时候不可写。
  • VCC:供电电源,+4.5V ~ 5.5V

读写操作

字节读写

  • 建立通讯后写入需要写数据的寄存器地址,随后将一个字节的数据写入。

页读写

  • 建立通讯后写入需要写数据的寄存器地址,随后将需要写入的数据连续写入即可完成页写入

Proteus仿真搭建。

STM32实践

  • 通常情况下,使用STM32模拟I2C时序时,需要将SCL和SDA引脚设置为开漏输出。这是因为I2C总线是一个多主机系统,即多个主设备可以同时访问同一I2C总线。在这种情况下,如果一个主设备试图将I2C总线上的数据线拉低,而另一个主设备试图将数据线拉高,则会发生电平冲突。为了避免这种情况的发生,需要将SCL和SDA引脚设置为开漏输出,这意味着输出信号只能拉低,而不能拉高。

  • 在STM32上,可以通过将GPIO引脚的输出模式设置为GPIO_MODE_AF_OD来将其配置为开漏输出。需要注意的是,开漏输出时需要使用外部上拉电阻来将引脚拉高。此外,还需要在初始化时设置GPIO的速度和GPIO的上拉电阻,以确保I2C总线的时序符合标准。

  • 根据24C02C的数据手册知道,地址码MSB固定,LSB根据Proteus仿真图得知。那么设备地址当进行写操作的时候是10100000=0xA0,如果进行读操作就是10100001=0xA1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#include "stm32f10x.h"                  // Device header

#define SCK(x) (x) ? (GPIOB->BSRR = GPIO_Pin_6) : (GPIOB->BRR = GPIO_Pin_6)
#define SDA(x) (x) ? (GPIOB->BSRR = GPIO_Pin_7) : (GPIOB->BRR = GPIO_Pin_7)

#define SDA_READ GPIOB->IDR & GPIO_Pin_7

u8 LED_SEG[16]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};

void GPIOx_Init(void){
GPIO_InitTypeDef GPIO_InitStr;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);

GPIO_InitStr.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStr.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStr.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStr);

GPIO_InitStr.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStr.GPIO_Pin = GPIO_Pin_All;
GPIO_InitStr.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStr);
}



void Delay_Ms(u32 ms){
for(u32 i = 0;i<ms;i++)
for(u32 j = 0;j<1000;j++);
}


void IIC_Start(void){
SCK(1);
SDA(1);
Delay_Ms(1);
SDA(0);
Delay_Ms(1);
SCK(0);
}

void IIC_Write(u8 data){
for (u8 i=0;i<8;i++){
if (data & 0x80)
SDA(1);
else
SDA(0);
data <<= 1;
SCK(1);
Delay_Ms(1);
SCK(0);
Delay_Ms(1);
}
}

u8 IIC_Read(void){
u8 data = 0;
for(u8 i=0;i<8;i++){
data <<= 1;
SCK(1);
Delay_Ms(1);
if (SDA_READ)
data |= 1;
SCK(0);
Delay_Ms(1);

}
return data;
}

void IIC_ACK_Write(u8 ack){
if (ack)
SDA(1);
else
SDA(0);
SCK(1);
Delay_Ms(1);
SCK(0);
Delay_Ms(1);
}


u8 IIC_ACK_Read(void){
u8 ack = 0;
SCK(1);
Delay_Ms(1);
ack = SDA_READ;
SCK(0);
Delay_Ms(1);
return ack ? 1 : 0;
}



void IIC_Stop(void){
SDA(0);
Delay_Ms(1);
SCK(1);
Delay_Ms(1);
SDA(1);
}

void M24C02C_Write(u8 addr, u8 data){
IIC_Start();
IIC_Write(0xA0);
if (IIC_ACK_Read() == 0){
IIC_Write(addr);
if (IIC_ACK_Read() == 0){
IIC_Write(data);
if (IIC_ACK_Read() == 0){
IIC_Stop();
return;
}
}
}
IIC_Stop();
}



void M24C02C_Page_Write(u8 addr, u8 *data, u8 len){
IIC_Start();
IIC_Write(0xA0);
if (IIC_ACK_Read() == 0){
IIC_Write(addr);
if (IIC_ACK_Read() == 0){
for (u8 i=0;i<len;i++){
IIC_Write(*data);
if (IIC_ACK_Read())
break;
data++;
}
}
}
IIC_Stop();
}

u8 M24C02C_Read(u8 addr){
u8 data = 0;
IIC_Start();
IIC_Write(0xA0);
if (IIC_ACK_Read() == 0){
IIC_Write(addr);
if (IIC_ACK_Read() == 0){
IIC_Start();
IIC_Write(0xA1);
if (IIC_ACK_Read() == 0){
data = IIC_Read();
IIC_ACK_Write(1);
}
}
}
IIC_Stop();
return data;
}


void M24C02C_Page_Read(u8 addr, u8 *data, u8 len){
IIC_Start();
IIC_Write(0xA0);
if (IIC_ACK_Read() == 0){
IIC_Write(addr);
if (IIC_ACK_Read() == 0){
IIC_Start();
IIC_Write(0xA1);
if (IIC_ACK_Read() == 0){
for (u8 i=0;i<len;i++){
data[i] = IIC_Read();
IIC_ACK_Write(0);

}
IIC_ACK_Write(1);
}
}
}
IIC_Stop();
}

void LED_Show(u8 pos, u8 num){
GPIOA->ODR = LED_SEG[num % 16] | (~(GPIO_Pin_8 << pos)) & 0xFF00;
Delay_Ms(1);
}


int main(void){
u8 buffer[4] = {0};
u8 data[3] = {0x0B, 0x0C, 0x0D};
GPIOx_Init();
M24C02C_Write(0x00, 0x0A);
M24C02C_Page_Write(0x01, data, 3);


buffer[0] = M24C02C_Read(0x00);
buffer[1] = M24C02C_Read(0x01);
buffer[2] = M24C02C_Read(0x02);
buffer[3] = M24C02C_Read(0x03);

// M24C02C_Page_Read(0x00, buffer, 4);

while(1){
LED_Show(3, buffer[0]);
LED_Show(2, buffer[1]);
LED_Show(1, buffer[2]);
LED_Show(0, buffer[3]);

}
}

  • 将程序编译后运行,可以看到IIC调试中显示了每次读写的数据帧。
    1. 写单个数据的数据帧,S是起始信号,而A0 A 00 A 0A A是每次写的数据和ACK应答,最后面P是结束信号
    2. 写页数据,和上面一样。但是数据帧多了几个数据。这是根据上面24C02C的时序图执行多个数据的写入
    3. 随机读取数据,数据帧和上面一样。但是读取完后需要恢复一个NACK非应答信号

参考文献及内容引用

野火——STM32库开发指南24.1
野火——STM32库开发指南23.1
24C02C Datasheet
爱上半导体