找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 10304|回复: 4
打印 上一主题 下一主题
收起左侧

单片机AT24C512读写代码与原理详解

  [复制链接]
跳转到指定楼层
楼主
AT24C512读写例程2004/04/25无非游
这期间不便出门,闲在家里想找点乐趣,于是捡起了忘记多年的单片机编程。这几天对单片机扩展存储有兴趣,掏了几个AT24C512,插到已有的洞洞实验板上,百度几篇关于该芯片读写的文章后开始写代码,以为会轻而易举读写这个芯片,结果折腾一天还没成功。于是下载ATMEL原版PDF慢慢翻译(是谷歌翻译帮忙)对照读写时序图,逐渐有了进展。最终在我的实验板上看到了写进去再读出来的数据。兴奋之余,回想当初为什么折腾呢?首先是自己能力差,其次是网上找到的源程序注释很少,而且是在他的硬件系统里写的,有许多与这个芯片读写无关的语句干扰理解。写程序的同行都有一种共识:读懂别人的程序比自己写出来要难,每个人写程序的风格不同。还是闲着没事,于是写这篇文章,目标是让每一个有一点单片机基础,无论你使用什么MCU,不管你喜欢汇编还是C都能看懂并顺利的写出自己的读写AT24C512芯片的代码。我喜欢汇编所以代码用汇编写,读者可以根据过程说明及每条指令后面的注释翻译成自己喜欢的指令语句。
一、关于这个芯片的资料网上很多,我只介绍其他文章较少提到容易忽略但对于入门的朋友很重要的小事。AT24C512是一个串行传输(I2C总线)EEPROM存储器,每片内部有64K字节存储空间,每个存储单元有自己的(16位=2字节)地址,每片地址从0000H到FFFFH。这64KB存储空间又分成512个页,每页128个字节。(这个芯片代号512就是这么来的。AT24Cxx是一系列类似的存储芯片)。分页存储在读取数据时不必理会,但在写入时要理会,否则会覆盖页内数据。另外与其他EEPROM不同的是写入操作不需要先擦除扇区(页),片内的管理单元代用户完成了这个动作。我还到处找擦除命令,是我少见多怪了。
    二、另外与其他两线制串行传输(简化SPI)不同的是,AT24Cxx是IIC(I2C)协议,每传输完一个字节数据要有一个应答信号然后才能继续。主控设备(MCU)向从设备(AT24C512)发送一字节数据后,从设备做出应答确认(拉低数据线),反之从设备向主设备发送一字节数据后,主设备要做出类似应答确认(拉低数据线),主动拉低数据线相当于输出”0” ,这个应答确认资料上写成 ACKNOWLEDGE,简写ACK
    三、芯片有8个引脚,最重要的SCL和SDA,分别连接到单片机的两个I/O引脚上做时钟和数据,时钟线是单向的由主控设备MCU提供,数据线是双向的。单片机上这两个引脚应该是普通双向I/O,尤其SDA不能设置为其他模式,否则可能收不到应答和数据。VCC和GND不必说,但不能忽视芯片工作电压与系统电压是否匹配。WP引脚的功能是写保护,接到VCC就只能读不能写,接到GND可读可写,如果悬空内部接地。A0和A1可以接到VCC或GND,用来确定芯片物理地址。所以一个系统可以连接4片AT24C512。它们的地址分别是(二进制)00,01,10,11.如果悬空在内部也连接到地。即使系统内只有一片AT24C512,根据你的连接它的物理地址你应该清楚,因为后面的操作需要这个地址写命令。(比如A1,A0都接地或悬空它的地址是00;如果A1接地或悬空A0接电源它的地址是01,余此类推),NO是空引脚。芯片的功耗很低,写入数据时电流3mA,读出时更小,空闲状态几个uA。可以用单片机的I/O引脚为芯片供电,相当于片选,所以一个系统可以使用若干组AT24C512来扩大容量。文章后面附个原理图供参考。
    介绍下我的实验板结构:一个STC15W单片机(用这款MCU的原因是价格便宜,有双列直插封装便于洞洞板焊接);一个LED用来检验程序执行情况,一片6位LCD数码管用来显示某些数据,一个按钮(作用是分批将读出的数据送到LCD进行正确性验证)。
    四、AT24C512的工作频率在5V时最高为1MHZ,2.7V时为400KHZ,就是说输入到SCL的时钟频率不能超过这个数值,否则读写失败,我开始没注意到这个硬指标浪费很多时间,经常收不到芯片应答,读出来的数据与写入的数据风马牛不相及,写入是否成功需要读出来看看才知道,但读写代码是否正确无法判断,后来降低单片机时钟偶尔看到读出希望的数据才恍然大悟,是时钟脉冲宽度不够。结论是:VCC=5V或3.3V时,SCL脉冲宽度上下边各不小于1us,保守点可以再宽些。
     首先应该提到的是说明书上的 Device address (命令)字节,如下图
            五、直译”设备地址”,我把它叫“寻址命令”,仅一个字节,前5位固定是 10100,A1,A0就是上面提到的芯片物理地址,最后一位R/W如果填1,表示对选中芯片进行读操作,填0表示对选中芯片进行写操作。比如要对物理地址为00的芯片进行读操作,就发送命令:10100001,要对该芯片进行写操作就发送命令:10100000。至于如何发送这个命令,是后面提到的发送字节数据模块。
                              
六、某些单片机有硬件IIC模块,但这里介绍的是通过软件模拟IIC通讯协议读写AT24C512芯片的过程,不介绍IIC协议,仅为那些拿到这个芯片但还没顺利写出读写程序的朋友提供必需的程序模块。结构化编程的思路就是先写出程序模块然后再根据任务用模块写出自己的程序代码。操作AT24C512就是把一些数据写到芯片里,另一个就是把芯片内的数据读出来。
    写数据的(时序)过程是:
1开始命令;2发送芯片寻址命令(写);3发送2字节地址;4发送1字节数据;如果继续发转回4;5停止命令。  
    读数据的(时序)过程是:1开始命令;2发送芯片寻址命令(写);3发送2字节地址;4开始命令;5发送芯片寻址命令(读);6读1字节数据;继续读转回6 ;7停止命令。如果从芯片当前地址读,省略1,2,3。
   七、所有操作使用下列6个(子)程序模块:
   1. 保证时钟脉宽的延时过程: AT24C_DLY
   2. 开始命令: AT24C_START
   3. 停止命令: AT24C_STOP
   4. 向芯片发送字节数据(包括应答ACK): AT24C_Send_Byte
   5. 从芯片接收字节数据: AT24C_Recv_Byte
   6. 接收数据时主机应答:HOST_ACK
我用汉语言和51汇编语言描述这些过程,你根据语句后面的注释可以把它们变成你喜欢的语言代码。(在Keil 51平台上允许使用分号”;”或双斜杠”//”作为注释)
我写汇编程序的习惯是用一条线加上几个十字使指令,参数及注释上下对齐,就像小学生在格子本上写字一样做到整齐易读。
1 延时子程序
AT24C_DLY:                  ; 保证脉宽的延时子程序AT24C_DLY
; -----+------------+------------------------+---------------
         MOV         R3, #5                ;给寄存器R3赋值5,MCU时钟<=20MHZ
         DJNZ         R3, $                  ;R3减一判0,不为0转移到本行继续执行
         RET                     ;子程序返回
注:调用执行这个子程序需要6+2+4*5+4个系统时钟(相当于等量空操作NOP),修改#号后面的5,可以改变延时长短,数值大延时长。
2开始命令:在时钟SCL高电平的条件下,数据SDA由高电平下拉到低电平。
AT24C_START:                 ;子程序名= AT24C_START
; -----+------------+------------------------+---------------
         SETB         SCL            ;拉高时钟SCL满足前提条件 (SCL = 1)
         SETB         SDA           ;拉高数据SDA准备拉低 (SDA = 1)
         CALL         AT24C_DLY       ; 调用延时程序,保证SDA稳定
         CLR          SDA           ;拉低数据产生开始命令 (SDA = 0)
         RET                     ; 子程序返回
3 停止命令 在时钟SCL高电平的条件下,数据SDA由高电平下拉到低电平。
  AT24C_STOP:                            ; 子程序名= AT24C_STOP
; -----+------------+------------------------+---------------
         SETB         SCL            ;拉高时钟 (SCL = 1)
         CLR           SDA           ; 拉低数据SDA = 0
         CALL         AT24C_DLY       ; 调用延时程序,保证SDA稳定
         SETB         SDA           ;拉高数据产生停止命令 SDA = 1
         RET                            ; 子程序返回
   4向芯片发送字节数据(在时钟低电平期间送数据到数据线SDA)
AT24C512对写入数据的时序要求是:时钟低电平时数据送到SDA,时钟高电平时芯片读取这个数据,数据发送顺序是高位在前低位最后,芯片每收到一个字节(8Bit)后拉低数据线做出”0”的应答确认ACK。如果主设备MCU没有收到这个低电平应答说明前面发送的数据无效。
   发送字节数据的时序是:
   拉低时钟线--送1位数据--延时--拉高时钟线--延时-->从头开始循环8次--发第9个时钟--等待应答(SDA=0)
   过程入口:A = 待发送数据(字节);出口:无
AT24C_Send_Byte:                     ; 发送数据子程序
; ------------+------------+------------------------+---------------
         MOV         R3, #8                ;R3作为循环次数计数器,给它赋值8  ( R3 = 8)
AT24C_Send_ LOP:                     ; 循环开始,R310结束 (8)
         CLR  SCL            ; 拉低时钟线 (SCL = 0)
         RLC  A                ;寄存器A循环左移一位,移除位给C
         MOV         SDA, C               ; 发送一位数据 ( SDA = C )
         CALL         AT24C_DLY       ; 延时,保证时钟宽度1us
         SETB         SCL            ; 拉高时钟线, 准备下次拉低 ( SCL = 1)
         CALL         AT24C_DLY       ; 延时,保证时钟宽度1us
         DJNZ         R3,  AT24C_Send_ LOP   ; R31<>0 转到AT24C_Send_LOP
; 发送第9个时钟, 8Bit结束时时钟线为高,故拉低后再生产一个时钟信号
         CLR          SCL            ; 拉低时钟线 (SCL = 0)
         CALL         AT24C_DLY       ; 延时,保证时钟宽度1us
         SETB         SCL            ; 拉高时钟线 ( SCL = 1)
         CALL         AT24C_DLY       ; 延时,保证时钟宽度1us
         CLR           SCL            ; 拉低时钟线 (SCL = 0)
; 等待AT24C应答
         JB          SDA, $         ; SDA=1,转到本行执行,(这是一条等待SDA=0的循环语句)
         RET                          ; 子程序返回
     啰嗦几句,这个过程从标号” AT24C_Send_ LOP”开始到DJNZ语句为止重复执行8次,每次发送一位数据,这样一个字节8位数据发送结束,然后再发出一个时钟信号,接着就是用 ( JB  SDA, $ ) 循环等待SDA变为低电平(芯片发出的应答信号)往下执行。这个应答很重要,它说明芯片已经收到了我们发送的8位数据。
     开始的时候为了检验发送模块是否有效,我在DJNZ语句下面放了一条拉高数据的指令,在JB语句下面放了一条让LED亮的指令来验证是否收到应答,之所以拉高SDA是因为最后一位数据可能是0,那么SDA本来就是低电平,这个低电平是主控MCU拉下来的还是芯片拉下来的不得而知,所以先拉高再等待。我等待这个应答等得太久,经历了磨难这里发泄一下大家原谅,嘿嘿。没有收到应答的原因很简单,时钟脉冲的宽度不合格。看了很多帖子没人提及这件小事!当时用了多个空操作NOP,搞得代码很长,后来用延时子程序简单高效。
   5从芯片接收字节数据(在时钟高电平期间从数据线SDA取数据)
   时序是:拉高时钟--延时--取数据--拉低时钟--延时-->循环8
   这个过程用到的指令与上面类似,不在浪费文字详细注释
   入口 :无   出口: A= 读出数据(1字节)
AT24C_Recv_Byte:                    ; 接收数据子程序
;------------+------------+------------------------+---------------
         MOV         R3,  #8            ;循环8次,接收8Bit1字节数据)
AT24C_Recv_Byte_LOP:
         SETB        SCL            ; 拉高时钟
         CALL        AT24C_DLY
         MOV        C, SDA           ; 取数据到C
         RLC         A                    ;将收到的一位数据移入累加器AACC.0 = C
         CLR         SCL                 ;拉低时钟
         CALL        AT24C_DLY
         DJNZ       R3, AT24C_Recv_Byte_LOP     ; R3减一判0,不为0转移到AT24C_Recv_Byte_LOP
         RET
;
     主机每接收1字节数据后要向从设备(芯片)发出低电平应答,但接收最后一个字节后不能应答,否则后续对从设备的操作无效,这是AT24C512(也是IIC协议)规则。所以把主机应答单独写成子程序。

6主机应答'0' (拉低数据--拉高时钟--延时--拉低时钟--拉高数据)
; -----+------------+------------------------+---------------
    HOST_ACK:
        CLR  AT24C_SDA      ;拉低数据 (SDA = 0)
        NOP
        SETB         SCL       ; 拉高时钟(SCL = 1)
        CALL         AT24C_DLY       ; 延时
        CLR           SCL       ;拉低时钟(SCL = 0)
        SETB        SDA      ; 拉高数据(SDA = 1
        RET
;
、下面是一个向芯片(00)写入n字节数据的例程。其中用到2个内存单元定义如下
         C512_AddrH    DATA         30H  ; 芯片内地址高字节
         C512_AddrL     DATA         31H  ; 芯片内地址低字节
         C512_BUF        DATA         40H  ; 读写出数据缓冲区(40H~7FH)共64个字节
;*****************************************************
; 说明:这个例程向物理地址为00的芯片从地址0000H开始写入:0,1,2,……127.128个字节数据。
; 时序: 1开始命令--2写芯片寻址--3写片内地址--4发送若干字节数据--5停止命令
; ------------+------------+------------------------+---------------
; 准备发送数据
         MOV         C512_AddrH, #00H ; 地址高字节 = 00H
         MOV         C512_AddrL, #00H  ; 地址低字节 = 00H
         MOV         R6, #0                ; 写入数据初值 R6=0
         MOV         R7, #128            ; 写入数据字节数(R7=128
; ------------+------------+------------------------+---------------
AT24C_Write_nByte:
         CALL         AT24C_START  ; 1 开始命令
         MOV         A,  #10100000B     ;发送芯片寻址()命令,通知芯片00后面将向其写入数据
         CALL         AT24C_Send_Byte   ; 2 发送上面命令字节
         MOV         A, #00H             ; 3 先发送地址高位:00H
         CALL         AT24C_Send_Byte   ;
         MOV         A, #00H             ; 再发送地址低位:00H
         CALL         AT24C_Send_Byte
AT24C_W_LOP:                           ; 4 循环128
         MOV         A, R6                  ; 把要写入的数据送给寄存器A
         CALL         AT24C_Send_Byte
         INC          R6              ;R6+1 àR6
         DJNZ         R7, AT24C_W_LOP  ; R7减一判0,不为0转移到AT24C_W_LOP
         CALL         AT24C_STOP    ; 5 停止命令
         RET
;
AT24C512写入数据不能跨页,就是说上面例程如果写入129个数据,将把第129数据写到页的开始0000单元。如果跨页需要修改地址后再写。如果继续上面的数据增1写,从128写到255可以用下面几行
;------------+------------+------------------------+---------------
         MOV         C512_AddrH, #00H ; 地址高字节 = 00H
         MOV         C512_AddrL, #80H  ; 地址低字节 = 80H =128D
         MOV         R6, #128            ; 写入数据初值 R6=128
         MOV         R7, #128            ; 写入数据字节数(R7=128
         CALL         AT24C_Write_nByte
;
   、现在芯片00的地址0000H00FFH写入了数据0~255,是否真的被写入或写入是否正确需要读出来看看才能知道。下面是从芯片读数据程序然后用你的什么显示设备验证一下。
;*****************************************************
; 说明:这个例程从物理地址为00的芯片,地址0000H开始读64字节数据。
; 读数据过程(时序): 1开始命令--2发送芯片寻址命令()--3发送2字节地址--4开始命令-->
;   --5发送芯片寻址命令(读)--61字节数据--7若继续读应答'0'6, 否则不应答--8停止命令
;------------+------------+------------------------+---------------
; 准备读数据
         MOV         C512_AddrH, #00H ; 地址高字节 = 00H
         MOV         C512_AddrL, #00H  ; 地址低字节 = 00H
         MOV         R7, #63              ;读数据字节数 - 1 !!
         MOV         R1, # C512_BUF       ; 读出数据缓冲区地址àR1
;------------+------------+------------------------+---------------
AT24C_Read_ nByte:
         CALL         AT24C_START  ; 1开始命令
         MOV         A, #10100000B         ;
         CALL         AT24C_Send_Byte   ; 2发送芯片寻址()命令,通知芯片00后面将向其写入数据(地址)
         MOV         A, C512_AddrH
         CALL         AT24C_Send_Byte   ; 3 发送地址高位
         MOV         A, C512_AddrL
         CALL         AT24C_Send_Byte   ;  发送地址低位
         CALL         AT24C_START  ; 4. 开始命令
         MOV         A, #10100001B         ; 命令通知芯片00后面将从其读数据
         CALL         AT24C_Send_Byte   ; 5. 发送读命令
AT24C_R_LOP:                            ; 6 循环读63
         CALL         AT24C_Recv_Byte   ; 调用读数据过程
         CALL         HOST_ACK        ; 主机应答,
         MOV         @R1,  A          ;读出数据送R1指向的内存地址单元(间接寻址)
         INC           R1              ;R1+1 àR1
         DJNZ         R7, AT24C_R_LOP   ; R7减一判0,不为0转移到AT24C_R_LOP
         CALL         AT24C_Recv_Byte   ; 读最后一个字节后,不要发送应答
         MOV         @R1,  A          ;
         CALL         AT24C_STOP    ; 7. 停止命令  停止命令不是必须的,SCL,SDA高电平使总线空闲
         RET
读数据没有页限制,可以从任何地址开始读若干字节数据,每收到一个字节数据需要应答,但最后一个字节不发应答,否则后续的操作芯片不理会,所以读64字节,先用63个有应答的循环,最后再读一次不应答。以上各个子程序本人验证过,可以直接复制使用。最后提供一个用单片机I/O端口为AT24C512供电,实现片(组)选的阵列方案,任何时候最多有一组(4片)被选中提供电源,其它组电源被拉低到地从总线角度看相当于不存在,被选中组中只有一片在读写过程中,官方PDF介绍芯片最大功耗写3mA,读2Ma,空闲状态个位数微安。本人用STC15W单片机实验2组读写正常,因芯片数量限制,方案只是设想,更多组没有实际验证。本文有不妥之处欢迎指正,请回帖

AT24C512阵列.jpg (221.19 KB, 下载次数: 109)

AT24C512阵列.jpg

评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的黑币奖励!

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏9 分享淘帖 顶1 踩
回复

使用道具 举报

沙发
ID:275111 发表于 2022-2-21 17:29 | 只看该作者
最后这个IO口阵列供电让我开了眼界。这个电流够用吗?不过这个很NB的样子。
回复

使用道具 举报

板凳
ID:830196 发表于 2022-4-20 20:59 | 只看该作者
tianqi911 发表于 2022-2-21 17:29
最后这个IO口阵列供电让我开了眼界。这个电流够用吗?不过这个很NB的样子。

可以使用小功率场效应管控制电源引脚,比如说SL2301,SL2302
回复

使用道具 举报

地板
ID:1989 发表于 2023-12-11 08:07 | 只看该作者
汇编呀,看不懂了。
回复

使用道具 举报

5#
ID:1101997 发表于 2023-12-12 09:24 | 只看该作者
这种设计还是别用在项目里面了,玩玩就行了,数据线连接了那么多芯片,考虑过什么是扇出系数了吗?
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表