最近在学习汇编完成常用芯片的控制,并预留一些接口。24C02是我购买的开发板上的芯片,故先从他入手。(更新中)
24C02数据手册理解
推荐的数据手册
24C02的开发商很多,数据手册的设计参差不齐,而我实在是不知道开发板上这个是哪个公司生产的,故浏览了一些厂商的数据手册,总的来说性能差异不太大。
故选择一个我认为将时序说的比较清楚的数据手册推荐给大家。
24C02数据手册-CSDN免积分下载
传输状态
- 开始传输状态
SCL高,SDA下降沿。(时序4)
- 停止传输状态
SCL高,SDA上升沿。(时序4)
- 数据传输状态
SCL高,SDA保持。(时序3)
- 主机等待响应
先将SDA拉高,再置SCL高,等待SDA被从机拉低。(时序6)
我观察到很多例程都对这个过程进行了包容处理,我认为应该认真对待,具体的会从程序中体现。
总结:
①所有时间均为ns级,即51单片机无需延时。
②SCL可以理解为高电平敏感。
SCL高电平、SDA保持至SCL置低,则为传输数据;
SCL高电平、SDA变化则为控制信号,开始或停止。
③控制不能是连续的,所以设计了从机的响应机制,保证控制流的正确执行。
主要是因为若向ROM写入数据,在传输完成后,ROM会进入写周期,此时无法响应总线控制,可从没有响应拉低电平的信号中得到是否处于该状态。
字节定义
- 器件地址+读写控制
D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|
1 | 0 | 1 | 0 | A2 | A1 | A0 | R/ W ‾ \overline W W |
(1) 对于不同型号对D3-D1的定义不同,24C02是芯片地址,用于拓展。ROM接收该字节后,会与自己引脚数据比较,以确认自己是否为从机。
(2) D0是控制位,1表示读,0表示写。这个字节决定了往后的数据流控制的逻辑。
- 数据地址
数据地址根据型号的不同表现也不相同。
具体来说对于24C02其一页有8字节,故数据地址中低三位表示字节地址,前五位表示页地址。其他型号以此类推。
D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|
AY4 | AY3 | AY2 | AY1 | AY0 | AZ2 | AZ1 | AZ0 |
- 数据
常规的8位数据。
总结:
①串行总线传输的数据总是先发送高位MSB再发送地位LSB。
②字节流以1字节对象+1位响应组成的9位帧组成。而对象无非是上述三者。为便于描述记ROM的地址设置为0x000,流依次称为控制读(0xA1)、控制写(0xA0)、地址流和数据流。
D8-D1 | D0 |
---|---|
字节对象D7-D0 | 响应ACK |
芯片支持数据流
- 单地址写字节
开始+控制写+地址流+数据流+停止 - 本页连续写字节
开始+控制写+地址流+数据流×n+停止
这种方法虽然对连续写入没有限制,但仅在输入地址的那一页循环。故写入过多会覆盖先前数据。 - 当前地址读字节
开始+控制读+数据流+停止
这个实际上是对ROM片内地址计数器的应用,计数器会记录上次访问的地址+1后的地址。也就是说通过该方法,可以分为独立过程读入连续的数据。当然仅限当前页循环读取。 - 单地址读字节
开始+控制写+地址流+开始+控制读+数据流+停止 - 指定地址连续读字节
该数据流是唯一一个需要主机应答ACK的流。需要主机应答的流用&标记。两种读作为起始都可以。
(1)开始+控制读+数据流&+数据流&×n+数据流+停止
(2)开始+控制写+地址流+开始+控制读+数据流&+数据流&×n+数据流+停止 - 询问状态
这是我看到的例程都省略的一个很重要的流。还是会在程序中体现。
开始+(控制写+等待应答)×n直到接收到应答+下一操作。
注:这里的下一操作我尚未实验,我目前选择给一个停止后重新发送地址流。这是合理的,因为只要本次询问得到了ACK意味着此时ROM已结束写周期,故后面的ACK必定会响应,故给出一个等待响应的状态就行,连判断有没有拉低都不需要。
软件抽象数据流
- 写入字节流
即无论是器件地址还是数据地址还是数据,其抽象模型就是字节流 - 读取数据流
读取数据是ROM唯一的读取操作。 - 等待ROM空闲
即询问状态的流,以决定是否进行下面的操作。
程序源码及接口
- 起始条件
/*
FUNC_NAME:EEPROM_IIC_START
INPUT:NONE
OUTPUT:NONE
*/
EEPROM_IIC_START:
SETB SDA
SETB SCL
CLR SDA
CLR SCL
RET
- 停止条件
/*
FUNC_NAME:EEPROM_IIC_STOP
INPUT:NONE
OUTPUT:NONE
*/
EEPROM_IIC_STOP:
CLR SDA
SETB SCL
SETB SDA
RET
- 询问状态
/*
FUNC_NAME:EEPROM_IIC_ASK
INPUT:R0<DEVICE_ADDR>
OUTPUT:NONE
*/
EEPROM_IIC_ASK:
LCALL EEPROM_IIC_START
MOV A,R0
PUSH FOR_I
MOV FOR_I,#8
LOOP_EEPROM_IIC_ASK_I:
RLC A
MOV SDA,C
SETB SCL
CLR SCL
DJNZ FOR_I,LOOP_EEPROM_IIC_ASK_I
SETB SDA
SETB SCL
POP FOR_I
MOV C,SDA
JC EEPROM_IIC_ASK
CLR SCL
LCALL EEPROM_IIC_STOP
RET
- 发送字节
/*
FUNC_NAME:EEPROM_IIC_SEND_DATA
INPUT:R0<DATA>
OUTPUT:NONE
*/
EEPROM_IIC_SEND_DATA:
MOV A,R0
PUSH FOR_I
MOV FOR_I,#8
LOOP_EEPROM_IIC_SEND_DATA_I:
RLC A
MOV SDA,C
SETB SCL
CLR SCL
DJNZ FOR_I,LOOP_EEPROM_IIC_SEND_DATA_I
POP FOR_I
SETB SCL
MOV C,SDA
CLR SCL
RET
- 读取字节
/*
FUNC_NAME:EEPROM_IIC_READ_DATA
INPUT:NONE
OUTPUT:ACC
*/
EEPROM_IIC_READ_DATA:
SETB SDA
CLR A
PUSH FOR_I
MOV FOR_I,#8
LOOP_EEPROM_IIC_READ_DATA_I:
SETB SCL
MOV C,SDA
RLC A
CLR SCL
DJNZ FOR_I,LOOP_EEPROM_IIC_READ_DATA_I
POP FOR_I
RET
只需要将具体的数据送入寄存器,然后组合调用这些抽象函数,便可以实现具体的操作了。