亚洲春色中文字幕久久久-三上亚,一吻二脱三床四吻胸,国产真实伦对白视频全集,在线毛片观看,精品成品入口黄网,国产毛aⅴ片久久久,亚洲AV色香蕉一区二区三区老师,萧皇后A级艳片,色情日本视频更新,99久久亚洲精品日本无码

 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 381|回復: 0
打印 上一主題 下一主題
收起左側

第12章 I2C總線與EEPROM12.3 12.4

[復制鏈接]
跳轉到指定樓層
樓主
12.3 EEPROM的學習
在實際應用中,保存在單片機RAM中的數據,掉電后就丟失了,使用code關鍵字保存在單片機的FLASH中的數據,又不能隨意改變,也就是不能用它來記錄變化的數值。但是在某些場合,又確實需要記錄下某些數據,而它們還時常需要改變或更新,掉電之后數據還不能丟失,比如家用電表度數,電視機里邊的頻道記憶,一般都是使用EEPROM來保存數據,特點就是掉電后不丟失。Kingst51開發板上使用的這個器件是24C02,是一個容量大小是2Kbits,也就是256個字節的EEPROM。一般情況下,EEPROM擁有30萬到100萬次的壽命,也就是它可以反復寫入30-100萬次,而讀取次數是無限的。
24C02是一個基于I2C通信協議的器件,但要分清楚,I2C是一個通信協議,它擁有嚴密的通信時序邏輯要求,而EEPROM是一個器件,只是這個器件采用了I2C協議的接口與單片機相連而已,二者并沒有必然的聯系,EEPROM可以用其它接口,I2C也可以用在其它很多器件上。
12.3.1 EEPROM單字節讀寫操作時序
1EEPROM寫數據流程
1)首先是I2C的起始信號,接著跟上首字節,也就是I2C的器件地址,并且在讀寫方向上選擇“寫”操作。
2發送數據的存儲地址。24C02一共256個字節的存儲空間,地址從0x000xFF,想把數據存儲在哪個位置,此刻寫的就是哪個地址。
3發送要存儲的數據第一個字節、第二個字節……注意在寫數據的過程中,EEPROM每個字節都會回應一個“應答位0”,來通知用戶EEPROM數據成功,如果沒有回應答位,說明寫入不成功。
在寫數據的過程中,每成功寫入一個字節,EEPROM存儲空間的地址就會自動加1,當加到0xFF后,再寫一個字節,地址會溢出又變成了0x00
2EEPROM讀數據流程
1)首先是I2C的起始信號,接著跟上首字節,也就是I2C的器件地址,并且在讀寫方向上選擇“寫”操作。明明是讀數據為何方向也要選“寫”呢?24C02一共有256個地址,選擇寫操作,是為了把所要讀的數據的存儲地址先寫進去,告訴EEPROM要讀取哪個地址的數據。這就如同打電話,先撥總機號碼(EEPROM器件地址),而后還要繼續撥分機號碼(數據地址),而撥分機號碼這個動作,主機仍然是發送方,方向依然是“寫”。
2發送要讀取的數據的地址,注意是地址而非存在EEPROM中的數據,通知EEPROM要哪個分機的信息。
3)重新發送I2C起始信號和器件地址,并且在方向位選擇“讀”操作。
3步當中,每一個字節實際上都是在“寫”,所以每一個字節EEPROM都會回應一個“應答位0”。
4)讀取從器件發回的數據,讀一個字節后,如果還想繼續讀下一個字節,就發送一個“應答位ACK(0)”,如果不想讀了,通知EEPROM不想要數據了,那就發送一個“非應答位NAK(1)”。
和寫操作規則一樣,每讀一個字節地址會自動加1如果想繼續往下讀,給EEPROM一個ACK(0)低電平,再繼續給SCL完整的時序,EEPROM會繼續往外送數據。如果不想讀了,直接給一個NAK(1)高電平。
梳理一下幾個要點:
1、在本例中單片機是主機,24C02是從機;
2、無論是讀是寫,SCL始終都是由主機控制的;
3、寫的時候應答信號由從機給出,表示從機是否正確接收了數據;
4、讀的時候應答信號則由主機給出,表示是否繼續讀下去。
下面寫一個程序,讀取EEPROM0x02這個地址上的一個數據,不管這個數據之前是多少都將讀出來的數據加1,再寫到EEPROM0x02這個地址上。此外將I2C的程序建立一個文件,寫一個I2C.c程序文件,形成又一個程序模塊。
/*****************************I2C.c文件程序源代碼*****************************/
#include <reg52.h>
#include <intrins.h>
#define I2CDelay()  {_nop_();_nop_();_nop_();_nop_();}
sbit I2C_SCL = P3^7;
sbit I2C_SDA = P3^6;
/* 產生總線起始信號 */
void I2CStart()
{
    I2C_SDA = 1; //首先確保SDASCL都是高電平
    I2C_SCL = 1;
    I2CDelay();
    I2C_SDA = 0; //先拉低SDA
    I2CDelay();
    I2C_SCL = 0; //再拉低SCL
}
/* 產生總線停止信號 */
void I2CStop()
{
    I2C_SCL = 0; //首先確保SDASCL都是低電平
    I2C_SDA = 0;
    I2CDelay();
    I2C_SCL = 1; //先拉高SCL
    I2CDelay();
    I2C_SDA = 1; //再拉高SDA
    I2CDelay();
}
/* I2C總線寫操作,dat-待寫入字節,返回值-從機應答位的值 */
bit I2CWrite(unsigned char dat)
{
    bit ack;  //用于暫存應答位的值
    unsigned char mask;  //用于探測字節內某一位值的掩碼變量
    for (mask=0x80; mask!=0; mask>>=1) //從高位到低位依次進行
    {
        if ((mask&dat) == 0)  //該位的值輸出到SDA
            I2C_SDA = 0;
        else
            I2C_SDA = 1;
        I2CDelay();
        I2C_SCL = 1;          //拉高SCL
        I2CDelay();
        I2C_SCL = 0;          //再拉低SCL,完成一個位周期
    }
    I2C_SDA = 1;   //8位數據發送完后,主機釋放SDA,以檢測從機應答
    I2CDelay();
    I2C_SCL = 1;   //拉高SCL
    ack = I2C_SDA; //讀取此時的SDA值,即為從機的應答值
    I2CDelay();
    I2C_SCL = 0;   //再拉低SCL完成應答位,并保持住總線
    return (~ack); //應答值取反以符合通常的邏輯:
                   //0=不存在或忙或寫入失敗,1=存在且空閑或寫入成功
}
/* I2C總線讀操作,并發送非應答信號,返回值-讀到的字節 */
unsigned char I2CReadNAK()
{
    unsigned char mask;
    unsigned char dat;
    I2C_SDA = 1;  //首先確保主機釋放SDA
    for (mask=0x80; mask!=0; mask>>=1) //從高位到低位依次進行
    {
        I2CDelay();
        I2C_SCL = 1;      //拉高SCL
        if(I2C_SDA == 0)  //讀取SDA的值
            dat &= ~mask; //0時,dat中對應位清零
        else
            dat |= mask;  //1時,dat中對應位置1
        I2CDelay();
        I2C_SCL = 0;      //再拉低SCL,以使從機發送出下一位
    }
    I2C_SDA = 1;   //8位數據發送完后,拉高SDA,發送非應答信號
    I2CDelay();
    I2C_SCL = 1;   //拉高SCL
    I2CDelay();
    I2C_SCL = 0;   //再拉低SCL完成非應答位,并保持住總線
    return dat;
}
/* I2C總線讀操作,并發送應答信號,返回值-讀到的字節 */
unsigned char I2CReadACK()
{
    unsigned char mask;
    unsigned char dat;
    I2C_SDA = 1;  //首先確保主機釋放SDA
    for (mask=0x80; mask!=0; mask>>=1) //從高位到低位依次進行
    {
        I2CDelay();
        I2C_SCL = 1;      //拉高SCL
        if(I2C_SDA == 0)  //讀取SDA的值
            dat &= ~mask; //0時,dat中對應位清零
        else
            dat |= mask;  //1時,dat中對應位置1
        I2CDelay();
        I2C_SCL = 0;      //再拉低SCL,以使從機發送出下一位
    }
    I2C_SDA = 0;   //8位數據發送完后,拉低SDA,發送應答信號
    I2CDelay();
    I2C_SCL = 1;   //拉高SCL
    I2CDelay();
    I2C_SCL = 0;   //再拉低SCL完成應答位,并保持住總線
    return dat;
}
/****************************main.c文件程序源代碼*****************************/
#include <reg52.h>
extern void I2CStart();
extern void I2CStop();
extern unsigned char I2CReadNAK();
extern bit I2CWrite(unsigned char dat);
unsigned char E2ReadByte(unsigned char addr);
void E2WriteByte(unsigned char addr, unsigned char dat);
void main()
{
    unsigned char dat;
    dat = E2ReadByte(0x02);    //讀取指定地址上的一個字節
    dat++;                     //將其數值+1
    E2WriteByte(0x02, dat);    //再寫回到對應的地址上
   
    while (1);
}
/* 讀取EEPROM中的一個字節,addr-字節地址 */
unsigned char E2ReadByte(unsigned char addr)
{
    unsigned char dat;
   
    I2CStart();
    I2CWrite(0x50<<1); //尋址器件,后續為寫操作
    I2CWrite(addr);    //寫入存儲地址
    I2CStart();        //發送重復啟動信號
    I2CWrite((0x50<<1)|0x01); //尋址器件,后續為讀操作
    dat = I2CReadNAK();       //讀取一個字節數據
    I2CStop();
   
    return dat;
}
/* EEPROM中寫入一個字節,addr-字節地址 */
void E2WriteByte(unsigned char addr, unsigned char dat)
{
    I2CStart();
    I2CWrite(0x50<<1); //尋址器件,后續為寫操作
    I2CWrite(addr);    //寫入存儲地址
    I2CWrite(dat);     //寫入一個字節數據
    I2CStop();
}
I2C.c文件提供了I2C總線底層函數,包括起始、停止、字節寫、字節讀+應答、字節讀+非應答。這個程序復編譯會發現Keil軟件提示一個警告:*** WARNING L16: UNCALLED SEGMENT, IGNORED FOR OVERLAY PROCESS,這個警告的意思是在代碼中存在沒有被調用過的變量或者函數,I2C.c文件中的I2CReadACK()這個函數在本例中沒有用到。
讀取EEPROM的時候,由于只讀了一個字節就要告訴EEPROM不需要再讀數據了,讀完后直接發送一個“NAK”,因此只調用了I2CReadNAK()這個函數,而并沒有調用I2CReadACK()這個函數。今后很可能讀數據的時候要連續讀幾個字節,因此這個函數寫在了I2C.c文件中,作為I2C功能模塊的一部分是必要的,方便這個文件以后移植到其他程序中使用,因此這個警告在這里就不必管它了。
將這個程序中,I2C的讀寫EEPROM操作用邏輯分析儀抓出來,并且用I2C-EEPROM協議解析出來,如圖12-7所示。
                                                                                                                                                            
12-7  I2C-EEPROM解析結果圖
從圖12-7能看出,第一個字節是器件地址0x50+ACK,第二個字節是數據地址0x02+ACK,第三個字節是器件地址0x50+ACK,第四個是讀取到了0x04+NAK數據,第五個字節是器件地址0x50+ACK,第6個字節是數據地址0x02+ACK,第七個字節是寫入數據0x05+ACK
12.3.2 EEPROM多字節讀寫操作時序
讀取EEPROM的時候很簡單,EEPROM根據主機的時序,直接就把數據送出來了,但是寫EEPROM卻沒有這么簡單了。EEPROM發送數據后,先保存在了EEPROM的緩存,EEPROM必須要把緩存中的數據搬移到“非易失”的區域,才能達到掉電不丟失的效果。而往非易失區域寫需要一定的時間,每種器件不完全一樣,ATMEL公司的24C02的這個寫入時間最高不超過5ms。在往非易失區域寫的過程,EEPROM是不會再響應訪問的,不僅接收不到數據,即使用I2C標準的尋址模式去尋址,EEPROM都不會應答,就如同這個總線上沒有這個器件一樣。數據寫入非易失區域完畢后,EEPROM再次恢復正常。
12.2節程序中寫數據的代碼,程序上有讀取應答ACK,但是讀取完畢后沒有做任何處理。這是因為一次只寫一個字節的數據進去,等到下次重新再寫的時候,時間肯定遠遠超過了5ms,但是如果是連續寫入幾個字節的時候,就必須得考慮到應答位的問題了。寫入一個字節后,再寫入下一個字節之前,必須要等待EEPROM再次響應才可以
先從EEPROM0x90這個地址連續讀出4個字節,然后把這4個數據分別加1,加2,加3, 加4后重新寫入到這四個地址中去。I2C.c文件和之前是完全一樣的,因此只把main.c文件給發出來
/****************************I2C.c文件程序源代碼******************************/
(此處省略,可參考之前章節的代碼)
/****************************main.c文件程序源代碼*****************************/
#include <reg52.h>
extern void I2CStart();
extern void I2CStop();
extern unsigned char I2CReadACK();
extern unsigned char I2CReadNAK();
extern bit I2CWrite(unsigned char dat);
void E2Read(unsigned char *buf, unsigned char addr, unsigned char len);
void E2Write(unsigned char *buf, unsigned char addr, unsigned char len);
void main()
{
    unsigned char i;
    unsigned char buf[5];
    E2Read(buf, 0x90, sizeof(buf));   //E2中讀取一段數據
    for (i=0; i<sizeof(buf); i++)     //數據依次+1,+2,+3...
    {
        buf = buf + 1 + i;
    }
    E2Write(buf, 0x90, sizeof(buf));  //再寫回到E2
   
    while(1);
}
/* E2讀取函數,buf-數據接收指針,addr-E2中的起始地址,len-讀取長度 */
void E2Read(unsigned char *buf, unsigned char addr, unsigned char len)
{
    do {                       //用尋址操作查詢當前是否可進行讀寫操作
        I2CStart();
        if (I2CWrite(0x50<<1)) //應答則跳出循環,非應答則進行下一次查詢
        {
            break;
        }
        I2CStop();
    } while(1);
    I2CWrite(addr);            //寫入起始地址
    I2CStart();                //發送重復啟動信號
    I2CWrite((0x50<<1)|0x01);  //尋址器件,后續為讀操作
    while (len > 1)            //連續讀取len-1個字節
    {
        *buf++ = I2CReadACK(); //最后字節之前為讀取操作+應答
        len--;
    }
    *buf = I2CReadNAK();       //最后一個字節為讀取操作+非應答
    I2CStop();
}
/* E2寫入函數,buf-源數據指針,addr-E2中的起始地址,len-寫入長度 */
void E2Write(unsigned char *buf, unsigned char addr, unsigned char len)
{
    while (len--)
    {
        do {                       //用尋址操作查詢當前是否可進行讀寫操作
            I2CStart();
            if (I2CWrite(0x50<<1)) //應答則跳出循環,非應答則進行下一次查詢
            {
                break;
            }
            I2CStop();
        } while(1);
        I2CWrite(addr++);  //寫入起始地址
        I2CWrite(*buf++);  //寫入一個字節數據
        I2CStop();         //結束寫操作,以等待寫入完成
    }
}
函數E2Read:讀數據前,要查詢當前是否允許讀寫操作,EEPROM正常響應才表示允許。讀最后一個字節之前的,全部給ACK,而讀完最后一個字節,要給出一個NAK
函數E2Write:寫操作前,要查詢當前EEPROM是否響應,正常響應后才可以寫數據。
I2C多字節讀寫EEPROM的時序部分用邏輯分析儀抓取,由于此次的讀寫數據量特別大,因此用邏輯分析儀抓取后,直接將解析后的數據導出到excel表格中,如圖12-8所示。

12-8 連續讀寫解析后數據示意圖
從圖12-8表格看出,第一行為讀到的4個字節的數據,下面只有紅框內為寫入EEPROM的數據,而紅框外的為檢測0x50是否響應。由于EEPROM正在將前次寫入的數據搬移到非易失區,因此一直檢測一直等待到EEPROM響應才能再次往里邊寫數據。
12.3.3 EEPROM的頁寫入
在向EEPROM連續寫入多個字節的數據時,如果每寫一個字節都要等待幾ms的話,整體上的寫入效率就太低了。因此EEPROM的廠商就想了一個辦法,把EEPROM分頁管理。24C0124C02這兩個型號是8個字節一個頁,而24C0424C0824C1616個字節一頁。Kingst51開發板上用的型號是24C02,一共是256個字節,8個字節一頁,一共有32頁。
分配好頁之后,同一個頁內連續寫入幾個字節后再發送停止位,EEPROM檢測到停止位后,就會一次性把這一頁的數據寫到非易失區域,不需要寫一個字節檢測一次了,并且頁寫入的時間也不會超過5ms。如果寫入的數據跨頁了,寫完了一頁之后,要發送一個停止位,然后等待并且檢測EEPROM的空閑模式,一直等到把上一頁數據完全寫到非易失區域后,再進行下一頁的寫入,這樣就可以在很大程度上提高數據的寫入效率,程序如下。
/****************************I2C.c文件程序源代碼******************************/
(此處省略,可參考之前章節的代碼)
/***************************eeprom.c文件程序源代碼****************************/
#include <reg52.h>
extern void I2CStart();
extern void I2CStop();
extern unsigned char I2CReadACK();
extern unsigned char I2CReadNAK();
extern bit I2CWrite(unsigned char dat);
/* E2讀取函數,buf-數據接收指針,addr-E2中的起始地址,len-讀取長度 */
void E2Read(unsigned char *buf, unsigned char addr, unsigned char len)
{
    do {                       //用尋址操作查詢當前是否可進行讀寫操作
        I2CStart();
        if (I2CWrite(0x50<<1)) //應答則跳出循環,非應答則進行下一次查詢
        {
            break;
        }
        I2CStop();
    } while(1);
    I2CWrite(addr);            //寫入起始地址
    I2CStart();                //發送重復啟動信號
    I2CWrite((0x50<<1)|0x01);  //尋址器件,后續為讀操作
    while (len > 1)            //連續讀取len-1個字節
    {
        *buf++ = I2CReadACK(); //最后字節之前為讀取操作+應答
        len--;
    }
    *buf = I2CReadNAK();       //最后一個字節為讀取操作+非應答
    I2CStop();
}
/* E2寫入函數,buf-源數據指針,addr-E2中的起始地址,len-寫入長度 */
void E2Write(unsigned char *buf, unsigned char addr, unsigned char len)
{
    while (len > 0)
    {
        //等待上次寫入操作完成
        do {                       //用尋址操作查詢當前是否可進行讀寫操作
            I2CStart();
            if (I2CWrite(0x50<<1)) //應答則跳出循環,非應答則進行下一次查詢
            {
                break;
            }
            I2CStop();
        } while(1);
        //按頁寫模式連續寫入字節
        I2CWrite(addr);           //寫入起始地址
        while (len > 0)
        {
            I2CWrite(*buf++);     //寫入一個字節數據
            len--;                //待寫入長度計數遞減
            addr++;               //E2地址遞增
            if ((addr&0x07) == 0) //檢查地址是否到達頁邊界,24C02每頁8字節,
            {                     //所以檢測低3位是否為零即可
                break;            //到達頁邊界時,跳出循環,結束本次寫操作
            }
        }
        I2CStop();
    }
}
遵循模塊化的原則,把EEPROM的讀寫函數單獨寫成一個eeprom.c文件。其中E2Read函數和上一節是一樣的,因為讀操作與分頁無關。重點是E2Write函數,在寫入數據的時候,要計算下一個要寫的數據的地址是否是一個頁的起始地址,如果是的話,則必須跳出循環,等待EEPROM把當前這一頁寫入到非易失區域后,再進行后續頁的寫入。
/****************************main.c文件程序源代碼*****************************/
#include <reg52.h>
extern void E2Read(unsigned char *buf, unsigned char addr, unsigned char len);
extern void E2Write(unsigned char *buf, unsigned char addr, unsigned char len);
void main()
{
    unsigned char i;
    unsigned char buf[5];
    E2Read(buf, 0x8E, sizeof(buf));   //E2中讀取一段數據
    for (i=0; i<sizeof(buf); i++)     //數據依次+1,+2,+3...
    {
        buf = buf + 1 + i;
    }
    E2Write(buf, 0x8E, sizeof(buf));  //再寫回到E2
   
    while(1);
}
同樣數量的多字節寫入時間和頁寫入的時間到底差別多大呢?現在把兩次寫入時間用邏輯分析儀給抓了出來,并且用時間標簽A1A2標注了開始位置和結束位置,如圖12-9和圖12-10所示,右側顯示的|A1-A2|就是最終寫入5個字節所耗費的時間。多字節一個一個寫入,每次寫入后都需要再次通信檢測EEPROM是否在“忙”,因此耗費了大量的時間,同樣的寫入5個字節的數據,一個一個寫入用了8.4ms左右的時間,而使用頁寫入,并且還跨頁操作,只用了3.5ms左右的時間。

12-9  多字節寫入時間

12-10  跨頁寫入時間
12.4練習題
1、徹底理解I2C的通信時序。
2、能夠獨立完成EEPROM任意地址的單字節讀寫、多字節的跨頁連續寫入讀出。
3、將前邊學的交通燈進行改進,使用EEPROM保存紅燈和綠燈倒計時的時間,并且可以通過UART改變紅燈和綠燈倒計時時間。

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏 分享淘帖 頂 踩
回復

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規則

小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術交流QQ群281945664

Powered by 單片機教程網

快速回復 返回頂部 返回列表