AT24C512讀寫例程2004/04/25無非游 這期間不便出門,閑在家里想找點樂趣,于是撿起了忘記多年的單片機編程。這幾天對單片機擴展存儲有興趣,掏了幾個AT24C512,插到已有的洞洞實驗板上,百度幾篇關于該芯片讀寫的文章后開始寫代碼,以為會輕而易舉讀寫這個芯片,結果折騰一天還沒成功。于是下載ATMEL原版PDF慢慢翻譯(是谷歌翻譯幫忙)對照讀寫時序圖,逐漸有了進展。最終在我的實驗板上看到了寫進去再讀出來的數(shù)據(jù)。興奮之余,回想當初為什么折騰呢?首先是自己能力差,其次是網(wǎng)上找到的源程序注釋很少,而且是在他的硬件系統(tǒng)里寫的,有許多與這個芯片讀寫無關的語句干擾理解。寫程序的同行都有一種共識:讀懂別人的程序比自己寫出來要難,每個人寫程序的風格不同。還是閑著沒事,于是寫這篇文章,目標是讓每一個有一點單片機基礎,無論你使用什么MCU,不管你喜歡匯編還是C都能看懂并順利的寫出自己的讀寫AT24C512芯片的代碼。我喜歡匯編所以代碼用匯編寫,讀者可以根據(jù)過程說明及每條指令后面的注釋翻譯成自己喜歡的指令語句。 一、關于這個芯片的資料網(wǎng)上很多,我只介紹其他文章較少提到容易忽略但對于入門的朋友很重要的小事。AT24C512是一個串行傳輸(I2C總線)EEPROM存儲器,每片內(nèi)部有64K字節(jié)存儲空間,每個存儲單元有自己的(16位=2字節(jié))地址,每片地址從0000H到FFFFH。這64KB存儲空間又分成512個頁,每頁128個字節(jié)。(這個芯片代號512就是這么來的。AT24Cxx是一系列類似的存儲芯片)。分頁存儲在讀取數(shù)據(jù)時不必理會,但在寫入時要理會,否則會覆蓋頁內(nèi)數(shù)據(jù)。另外與其他EEPROM不同的是寫入操作不需要先擦除扇區(qū)(頁),片內(nèi)的管理單元代用戶完成了這個動作。我還到處找擦除命令,是我少見多怪了。 二、另外與其他兩線制串行傳輸(簡化SPI)不同的是,AT24Cxx是IIC(I2C)協(xié)議,每傳輸完一個字節(jié)數(shù)據(jù)要有一個應答信號然后才能繼續(xù)。主控設備(MCU)向從設備(AT24C512)發(fā)送一字節(jié)數(shù)據(jù)后,從設備做出應答確認(拉低數(shù)據(jù)線),反之從設備向主設備發(fā)送一字節(jié)數(shù)據(jù)后,主設備要做出類似應答確認(拉低數(shù)據(jù)線),主動拉低數(shù)據(jù)線相當于輸出”0” ,這個應答確認資料上寫成 ACKNOWLEDGE,簡寫ACK。 三、芯片有8個引腳,最重要的SCL和SDA,分別連接到單片機的兩個I/O引腳上做時鐘和數(shù)據(jù),時鐘線是單向的由主控設備MCU提供,數(shù)據(jù)線是雙向的。單片機上這兩個引腳應該是普通雙向I/O,尤其SDA不能設置為其他模式,否則可能收不到應答和數(shù)據(jù)。VCC和GND不必說,但不能忽視芯片工作電壓與系統(tǒng)電壓是否匹配。WP引腳的功能是寫保護,接到VCC就只能讀不能寫,接到GND可讀可寫,如果懸空內(nèi)部接地。A0和A1可以接到VCC或GND,用來確定芯片物理地址。所以一個系統(tǒng)可以連接4片AT24C512。它們的地址分別是(二進制)00,01,10,11.如果懸空在內(nèi)部也連接到地。即使系統(tǒng)內(nèi)只有一片AT24C512,根據(jù)你的連接它的物理地址你應該清楚,因為后面的操作需要這個地址寫命令。(比如A1,A0都接地或懸空它的地址是00;如果A1接地或懸空A0接電源它的地址是01,余此類推),NO是空引腳。芯片的功耗很低,寫入數(shù)據(jù)時電流3mA,讀出時更小,空閑狀態(tài)幾個uA?梢杂脝纹瑱C的I/O引腳為芯片供電,相當于片選,所以一個系統(tǒng)可以使用若干組AT24C512來擴大容量。文章后面附個原理圖供參考。 介紹下我的實驗板結構:一個STC15W單片機(用這款MCU的原因是價格便宜,有雙列直插封裝便于洞洞板焊接);一個LED用來檢驗程序執(zhí)行情況,一片6位LCD數(shù)碼管用來顯示某些數(shù)據(jù),一個按鈕(作用是分批將讀出的數(shù)據(jù)送到LCD進行正確性驗證)。 四、AT24C512的工作頻率在5V時最高為1MHZ,2.7V時為400KHZ,就是說輸入到SCL的時鐘頻率不能超過這個數(shù)值,否則讀寫失敗,我開始沒注意到這個硬指標浪費很多時間,經(jīng)常收不到芯片應答,讀出來的數(shù)據(jù)與寫入的數(shù)據(jù)風馬牛不相及,寫入是否成功需要讀出來看看才知道,但讀寫代碼是否正確無法判斷,后來降低單片機時鐘偶爾看到讀出希望的數(shù)據(jù)才恍然大悟,是時鐘脈沖寬度不夠。結論是:VCC=5V或3.3V時,SCL脈沖寬度上下邊各不小于1us,保守點可以再寬些。 首先應該提到的是說明書上的 Device address (命令)字節(jié),如下圖 五、直譯”設備地址”,我把它叫“尋址命令”,僅一個字節(jié),前5位固定是 10100,A1,A0就是上面提到的芯片物理地址,最后一位R/W如果填1,表示對選中芯片進行讀操作,填0表示對選中芯片進行寫操作。比如要對物理地址為00的芯片進行讀操作,就發(fā)送命令:10100001,要對該芯片進行寫操作就發(fā)送命令:10100000。至于如何發(fā)送這個命令,是后面提到的發(fā)送字節(jié)數(shù)據(jù)模塊。
六、某些單片機有硬件IIC模塊,但這里介紹的是通過軟件模擬IIC通訊協(xié)議讀寫AT24C512芯片的過程,不介紹IIC協(xié)議,僅為那些拿到這個芯片但還沒順利寫出讀寫程序的朋友提供必需的程序模塊。結構化編程的思路就是先寫出程序模塊然后再根據(jù)任務用模塊寫出自己的程序代碼。操作AT24C512就是把一些數(shù)據(jù)寫到芯片里,另一個就是把芯片內(nèi)的數(shù)據(jù)讀出來。 寫數(shù)據(jù)的(時序)過程是: 1開始命令;2發(fā)送芯片尋址命令(寫);3發(fā)送2字節(jié)地址;4發(fā)送1字節(jié)數(shù)據(jù);如果繼續(xù)發(fā)轉(zhuǎn)回4;5停止命令。 讀數(shù)據(jù)的(時序)過程是:1開始命令;2發(fā)送芯片尋址命令(寫);3發(fā)送2字節(jié)地址;4開始命令;5發(fā)送芯片尋址命令(讀);6讀1字節(jié)數(shù)據(jù);繼續(xù)讀轉(zhuǎn)回6 ;7停止命令。如果從芯片當前地址讀,省略1,2,3。 七、所有操作使用下列6個(子)程序模塊: 1. 保證時鐘脈寬的延時過程: AT24C_DLY 2. 開始命令: AT24C_START 3. 停止命令: AT24C_STOP 4. 向芯片發(fā)送字節(jié)數(shù)據(jù)(包括應答ACK): AT24C_Send_Byte 5. 從芯片接收字節(jié)數(shù)據(jù): AT24C_Recv_Byte 6. 接收數(shù)據(jù)時主機應答:HOST_ACK 我用漢語言和51匯編語言描述這些過程,你根據(jù)語句后面的注釋可以把它們變成你喜歡的語言代碼。(在Keil 51平臺上允許使用分號”;”或雙斜杠”//”作為注釋) 我寫匯編程序的習慣是用一條線加上幾個十字使指令,參數(shù)及注釋上下對齊,就像小學生在格子本上寫字一樣做到整齊易讀。 1 延時子程序: AT24C_DLY: ; 保證脈寬的延時子程序AT24C_DLY ; -----+------------+------------------------+--------------- MOV R3, #5 ;給寄存器R3賦值5,MCU時鐘<=20MHZ DJNZ R3, $ ;R3減一判0,不為0轉(zhuǎn)移到本行繼續(xù)執(zhí)行 RET ;子程序返回 注:調(diào)用執(zhí)行這個子程序需要6+2+4*5+4個系統(tǒng)時鐘(相當于等量空操作NOP),修改#號后面的5,可以改變延時長短,數(shù)值大延時長。 2開始命令:在時鐘SCL高電平的條件下,數(shù)據(jù)SDA由高電平下拉到低電平。 AT24C_START: ;子程序名= AT24C_START ; -----+------------+------------------------+--------------- SETB SCL ;拉高時鐘SCL滿足前提條件 (SCL = 1) SETB SDA ;拉高數(shù)據(jù)SDA準備拉低 (SDA = 1) CALL AT24C_DLY ; 調(diào)用延時程序,保證SDA穩(wěn)定 CLR SDA ;拉低數(shù)據(jù)產(chǎn)生開始命令 (SDA = 0) RET ; 子程序返回 3 停止命令 在時鐘SCL高電平的條件下,數(shù)據(jù)SDA由高電平下拉到低電平。 AT24C_STOP: ; 子程序名= AT24C_STOP ; -----+------------+------------------------+--------------- SETB SCL ;拉高時鐘 (SCL = 1) CLR SDA ; 拉低數(shù)據(jù)(SDA = 0) CALL AT24C_DLY ; 調(diào)用延時程序,保證SDA穩(wěn)定 SETB SDA ;拉高數(shù)據(jù)產(chǎn)生停止命令 (SDA = 1) RET ; 子程序返回 4向芯片發(fā)送字節(jié)數(shù)據(jù)(在時鐘低電平期間送數(shù)據(jù)到數(shù)據(jù)線SDA) AT24C512對寫入數(shù)據(jù)的時序要求是:時鐘低電平時數(shù)據(jù)送到SDA,時鐘高電平時芯片讀取這個數(shù)據(jù),數(shù)據(jù)發(fā)送順序是高位在前低位最后,芯片每收到一個字節(jié)(8Bit)后拉低數(shù)據(jù)線做出”0”的應答確認ACK。如果主設備MCU沒有收到這個低電平應答說明前面發(fā)送的數(shù)據(jù)無效。 發(fā)送字節(jié)數(shù)據(jù)的時序是: 拉低時鐘線--送1位數(shù)據(jù)--延時--拉高時鐘線--延時-->從頭開始循環(huán)8次--發(fā)第9個時鐘--等待應答(SDA=0) 過程入口:A = 待發(fā)送數(shù)據(jù)(字節(jié));出口:無 AT24C_Send_Byte: ; 發(fā)送數(shù)據(jù)子程序 ; ------------+------------+------------------------+--------------- MOV R3, #8 ;R3作為循環(huán)次數(shù)計數(shù)器,給它賦值8 ( R3 = 8) AT24C_Send_ LOP: ; 循環(huán)開始,R3減1到0結束 (共8次) CLR SCL ; 拉低時鐘線 (SCL = 0) RLC A ;寄存器A循環(huán)左移一位,移除位給C MOV SDA, C ; 發(fā)送一位數(shù)據(jù) ( SDA = C ) CALL AT24C_DLY ; 延時,保證時鐘寬度1us SETB SCL ; 拉高時鐘線, 準備下次拉低 ( SCL = 1) CALL AT24C_DLY ; 延時,保證時鐘寬度1us DJNZ R3, AT24C_Send_ LOP ; R3減1后<>0 轉(zhuǎn)到AT24C_Send_LOP ; 發(fā)送第9個時鐘, 8Bit結束時時鐘線為高,故拉低后再生產(chǎn)一個時鐘信號 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,轉(zhuǎn)到本行執(zhí)行,(這是一條等待SDA=0的循環(huán)語句) RET ; 子程序返回 啰嗦幾句,這個過程從標號” AT24C_Send_ LOP”開始到DJNZ語句為止重復執(zhí)行8次,每次發(fā)送一位數(shù)據(jù),這樣一個字節(jié)8位數(shù)據(jù)發(fā)送結束,然后再發(fā)出一個時鐘信號,接著就是用 ( JB SDA, $ ) 循環(huán)等待SDA變?yōu)榈碗娖剑ㄐ酒l(fā)出的應答信號)往下執(zhí)行。這個應答很重要,它說明芯片已經(jīng)收到了我們發(fā)送的8位數(shù)據(jù)。 開始的時候為了檢驗發(fā)送模塊是否有效,我在DJNZ語句下面放了一條拉高數(shù)據(jù)的指令,在JB語句下面放了一條讓LED亮的指令來驗證是否收到應答,之所以拉高SDA是因為最后一位數(shù)據(jù)可能是0,那么SDA本來就是低電平,這個低電平是主控MCU拉下來的還是芯片拉下來的不得而知,所以先拉高再等待。我等待這個應答等得太久,經(jīng)歷了磨難這里發(fā)泄一下大家原諒,嘿嘿。沒有收到應答的原因很簡單,時鐘脈沖的寬度不合格?戳撕芏嗵記]人提及這件小事!當時用了多個空操作NOP,搞得代碼很長,后來用延時子程序簡單高效。 5從芯片接收字節(jié)數(shù)據(jù)(在時鐘高電平期間從數(shù)據(jù)線SDA取數(shù)據(jù)) 時序是:拉高時鐘--延時--取數(shù)據(jù)--拉低時鐘--延時-->循環(huán)8次 這個過程用到的指令與上面類似,不在浪費文字詳細注釋 入口 :無 出口: A= 讀出數(shù)據(jù)(1字節(jié)) AT24C_Recv_Byte: ; 接收數(shù)據(jù)子程序 ;------------+------------+------------------------+--------------- MOV R3, #8 ;循環(huán)8次,接收8Bit(1字節(jié)數(shù)據(jù)) AT24C_Recv_Byte_LOP: SETB SCL ; 拉高時鐘 CALL AT24C_DLY MOV C, SDA ; 取數(shù)據(jù)到C RLC A ;將收到的一位數(shù)據(jù)移入累加器A(ACC.0 = C) CLR SCL ;拉低時鐘 CALL AT24C_DLY DJNZ R3, AT24C_Recv_Byte_LOP ; R3減一判0,不為0轉(zhuǎn)移到AT24C_Recv_Byte_LOP RET ; 主機每接收1字節(jié)數(shù)據(jù)后要向從設備(芯片)發(fā)出低電平應答,但接收最后一個字節(jié)后不能應答,否則后續(xù)對從設備的操作無效,這是AT24C512(也是IIC協(xié)議)規(guī)則。所以把主機應答單獨寫成子程序。
6主機應答'0' (拉低數(shù)據(jù)--拉高時鐘--延時--拉低時鐘--拉高數(shù)據(jù)) ; -----+------------+------------------------+--------------- HOST_ACK: CLR AT24C_SDA ;拉低數(shù)據(jù) (SDA = 0) NOP SETB SCL ; 拉高時鐘(SCL = 1) CALL AT24C_DLY ; 延時 CLR SCL ;拉低時鐘(SCL = 0) SETB SDA ; 拉高數(shù)據(jù)(SDA = 1) RET ; 八、下面是一個向芯片(00)寫入n字節(jié)數(shù)據(jù)的例程。其中用到2個內(nèi)存單元定義如下 C512_AddrH DATA 30H ; 芯片內(nèi)地址高字節(jié) C512_AddrL DATA 31H ; 芯片內(nèi)地址低字節(jié) C512_BUF DATA 40H ; 讀寫出數(shù)據(jù)緩沖區(qū)(40H~7FH)共64個字節(jié) ;***************************************************** ; 說明:這個例程向物理地址為00的芯片從地址0000H開始寫入:0,1,2,……127.共128個字節(jié)數(shù)據(jù)。 ; 時序: 1開始命令--2寫芯片尋址--3寫片內(nèi)地址--4發(fā)送若干字節(jié)數(shù)據(jù)--5停止命令 ; ------------+------------+------------------------+--------------- ; 準備發(fā)送數(shù)據(jù) MOV C512_AddrH, #00H ; 地址高字節(jié) = 00H MOV C512_AddrL, #00H ; 地址低字節(jié) = 00H MOV R6, #0 ; 寫入數(shù)據(jù)初值 (R6=0) MOV R7, #128 ; 寫入數(shù)據(jù)字節(jié)數(shù)(R7=128) ; ------------+------------+------------------------+--------------- AT24C_Write_nByte: CALL AT24C_START ; 1 開始命令 MOV A, #10100000B ;發(fā)送芯片尋址(寫)命令,通知芯片00后面將向其寫入數(shù)據(jù) CALL AT24C_Send_Byte ; 2 發(fā)送上面命令字節(jié) MOV A, #00H ; 3 先發(fā)送地址高位:00H CALL AT24C_Send_Byte ; MOV A, #00H ; 再發(fā)送地址低位:00H CALL AT24C_Send_Byte AT24C_W_LOP: ; 4 循環(huán)128次 MOV A, R6 ; 把要寫入的數(shù)據(jù)送給寄存器A CALL AT24C_Send_Byte INC R6 ;R6+1 àR6 DJNZ R7, AT24C_W_LOP ; R7減一判0,不為0轉(zhuǎn)移到AT24C_W_LOP CALL AT24C_STOP ; 5 停止命令 RET ; 向AT24C512寫入數(shù)據(jù)不能跨頁,就是說上面例程如果寫入129個數(shù)據(jù),將把第129數(shù)據(jù)寫到頁的開始0000單元。如果跨頁需要修改地址后再寫。如果繼續(xù)上面的數(shù)據(jù)增1寫,從128寫到255可以用下面幾行 ;------------+------------+------------------------+--------------- MOV C512_AddrH, #00H ; 地址高字節(jié) = 00H MOV C512_AddrL, #80H ; 地址低字節(jié) = 80H (=128D) MOV R6, #128 ; 寫入數(shù)據(jù)初值 (R6=128) MOV R7, #128 ; 寫入數(shù)據(jù)字節(jié)數(shù)(R7=128) CALL AT24C_Write_nByte ; 九、現(xiàn)在芯片00的地址0000H到00FFH寫入了數(shù)據(jù)0~255,是否真的被寫入或?qū)懭胧欠裾_需要讀出來看看才能知道。下面是從芯片讀數(shù)據(jù)程序然后用你的什么顯示設備驗證一下。 ;***************************************************** ; 說明:這個例程從物理地址為00的芯片,地址0000H開始讀64字節(jié)數(shù)據(jù)。 ; 讀數(shù)據(jù)過程(時序): 1開始命令--2發(fā)送芯片尋址命令(寫)--3發(fā)送2字節(jié)地址--4開始命令--> ; --5發(fā)送芯片尋址命令(讀)--6讀1字節(jié)數(shù)據(jù)--7若繼續(xù)讀應答'0'轉(zhuǎn)6, 否則不應答--8停止命令 ;------------+------------+------------------------+--------------- ; 準備讀數(shù)據(jù) MOV C512_AddrH, #00H ; 地址高字節(jié) = 00H MOV C512_AddrL, #00H ; 地址低字節(jié) = 00H MOV R7, #63 ;讀數(shù)據(jù)字節(jié)數(shù) - 1 ! MOV R1, # C512_BUF ; 讀出數(shù)據(jù)緩沖區(qū)地址àR1 ;------------+------------+------------------------+--------------- AT24C_Read_ nByte: CALL AT24C_START ; 1開始命令 MOV A, #10100000B ; CALL AT24C_Send_Byte ; 2發(fā)送芯片尋址(寫)命令,通知芯片00后面將向其寫入數(shù)據(jù)(地址) MOV A, C512_AddrH CALL AT24C_Send_Byte ; 3 發(fā)送地址高位 MOV A, C512_AddrL CALL AT24C_Send_Byte ; 發(fā)送地址低位 CALL AT24C_START ; 4. 開始命令 MOV A, #10100001B ; 命令通知芯片00后面將從其讀數(shù)據(jù) CALL AT24C_Send_Byte ; 5. 發(fā)送讀命令 AT24C_R_LOP: ; 6 循環(huán)讀63次 CALL AT24C_Recv_Byte ; 調(diào)用讀數(shù)據(jù)過程 CALL HOST_ACK ; 主機應答, MOV @R1, A ;讀出數(shù)據(jù)送R1指向的內(nèi)存地址單元(間接尋址) INC R1 ;R1+1 àR1 DJNZ R7, AT24C_R_LOP ; R7減一判0,不為0轉(zhuǎn)移到AT24C_R_LOP CALL AT24C_Recv_Byte ; 讀最后一個字節(jié)后,不要發(fā)送應答 MOV @R1, A ; CALL AT24C_STOP ; 7. 停止命令 停止命令不是必須的,但SCL,SDA高電平使總線空閑 RET 讀數(shù)據(jù)沒有頁限制,可以從任何地址開始讀若干字節(jié)數(shù)據(jù),每收到一個字節(jié)數(shù)據(jù)需要應答,但最后一個字節(jié)不發(fā)應答,否則后續(xù)的操作芯片不理會,所以讀64字節(jié),先用63個有應答的循環(huán),最后再讀一次不應答。以上各個子程序本人驗證過,可以直接復制使用。最后提供一個用單片機I/O端口為AT24C512供電,實現(xiàn)片(組)選的陣列方案,任何時候最多有一組(4片)被選中提供電源,其它組電源被拉低到地從總線角度看相當于不存在,被選中組中只有一片在讀寫過程中,官方PDF介紹芯片最大功耗寫3mA,讀2Ma,空閑狀態(tài)個位數(shù)微安。本人用STC15W單片機實驗2組讀寫正常,因芯片數(shù)量限制,方案只是設想,更多組沒有實際驗證。本文有不妥之處歡迎指正,請回帖 |