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: ; 循環開始,R3減1到0結束 (共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 ; R3減1后<>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次,接收8Bit(1字節數據) AT24C_Recv_Byte_LOP: SETB SCL ; 拉高時鐘 CALL AT24C_DLY MOV C, SDA ; 取數據到C RLC A ;將收到的一位數據移入累加器A(ACC.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的地址0000H到00FFH寫入了數據0~255,是否真的被寫入或寫入是否正確需要讀出來看看才能知道。下面是從芯片讀數據程序然后用你的什么顯示設備驗證一下。 ;***************************************************** ; 說明:這個例程從物理地址為00的芯片,地址0000H開始讀64字節數據。 ; 讀數據過程(時序): 1開始命令--2發送芯片尋址命令(寫)--3發送2字節地址--4開始命令--> ; --5發送芯片尋址命令(讀)--6讀1字節數據--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組讀寫正常,因芯片數量限制,方案只是設想,更多組沒有實際驗證。本文有不妥之處歡迎指正,請回帖 |