在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ 數(shù)據庫/ 14.4 單片機 EEPROM 單字節(jié)讀寫操作時序
18. RS485 通信與 Modbus 協(xié)議
17.5 A/D 差分輸入信號
15.8 C 語言復合數(shù)據類型(結構體,共用體,枚舉類型)
16.3 NEC 協(xié)議紅外遙控器
13.1 單片機通信時序解析
14.4 單片機 EEPROM 單字節(jié)讀寫操作時序
13.3 多個 .c 文件的初步認識
18.2 Modbus 通信協(xié)議介紹
15.1 BCD 碼介紹
18.3 單片機 Modbus 多機通信程序設計
18.1 單片機 RS485 通信接口、控制線、原理圖及程序實例
15. 實時時鐘 DS1302
14.7 單片機 I2C 和 EEPROM 的綜合編程
17. 模數(shù)轉換與數(shù)模轉換
16.2 紅外遙控通信原理
13.2 1602 液晶整屏移動程序
17.6 D/A 輸出
17.7 單片機信號發(fā)生器程序
16.4 溫度傳感器 DS18B20
14.6 單片機EEPROM的頁寫入
13.4 單片機計算器程序設計[詳細]
17.2 A/D(模數(shù)轉換)的主要指標
17.4 PCF8591 應用程序
17.1 A/D 和 D/A 的基本概念
17.3 PCF8591硬件接口(電路圖引腳圖)
14.3 單片機 EEPROM 簡介
13.5 單片機串口通信原理和控制程序
15.5 DS1302 寄存器介紹
15.2 單片機 SPI 通信接口
15.6 DS1302 通信時序介紹
14.5 單片機 EEPROM 多字節(jié)讀寫操作時序
16. 紅外通信與 DS18B20 溫度傳感器
14.1 單片機 I2C 時序介紹
15.3 實時時鐘芯片 DS1302 介紹
15.9 單片機電子時鐘程序設計
16.1 紅外光的基本原理
15.4 DS1302 的硬件信息
15.7 DS1302 的 BURST 模式
14.2 單片機 I2C 尋址模式
14. 單片機 I2C 總線與 EEPROM
13. 單片機 1602 液晶與串口的應用實例

14.4 單片機 EEPROM 單字節(jié)讀寫操作時序

EEPROM 寫數(shù)據流程

第一步,首先是 I2C 的起始信號,接著跟上首字節(jié),也就是我們前邊講的 I2C 的器件地址,并且在讀寫方向上選擇“寫”操作。

第二步,發(fā)送數(shù)據的存儲地址。24C02 一共256個字節(jié)的存儲空間,地址從 0x00~0xFF,我們想把數(shù)據存儲在哪個位置,此刻寫的就是哪個地址。

第三步,發(fā)送要存儲的數(shù)據第一個字節(jié)、第二個字節(jié)??注意在寫數(shù)據的過程中,EEPROM 每個字節(jié)都會回應一個“應答位0”,來告訴我們寫 EEPROM 數(shù)據成功,如果沒有回應答位,說明寫入不成功。

在寫數(shù)據的過程中,每成功寫入一個字節(jié),EEPROM 存儲空間的地址就會自動加1,當加到 0xFF 后,再寫一個字節(jié),地址會溢出又變成了 0x00。

EEPROM 讀數(shù)據流程

第一步,首先是 I2C 的起始信號,接著跟上首字節(jié),也就是我們前邊講的 I2C 的器件地址,并且在讀寫方向上選擇“寫”操作。這個地方可能有同學會詫異,我們明明是讀數(shù)據為何方向也要選“寫”呢?剛才說過了,24C02 一共有256個地址,我們選擇寫操作,是為了把所要讀的數(shù)據的存儲地址先寫進去,告訴 EEPROM 我們要讀取哪個地址的數(shù)據。這就如同我們打電話,先撥總機號碼(EEPROM 器件地址),而后還要繼續(xù)撥分機號碼(數(shù)據地址),而撥分機號碼這個動作,主機仍然是發(fā)送方,方向依然是“寫”。

第二步,發(fā)送要讀取的數(shù)據的地址,注意是地址而非存在 EEPROM 中的數(shù)據,通知EEPROM 我要哪個分機的信息。

第三步,重新發(fā)送 I2C 起始信號和器件地址,并且在方向位選擇“讀”操作。

這三步當中,每一個字節(jié)實際上都是在“寫”,所以每一個字節(jié) EEPROM 都會回應一個“應答位0”。

第四步,讀取從器件發(fā)回的數(shù)據,讀一個字節(jié),如果還想繼續(xù)讀下一個字節(jié),就發(fā)送一個“應答位 ACK(0)”,如果不想讀了,告訴 EEPROM,我不想要數(shù)據了,別再發(fā)數(shù)據了,那就發(fā)送一個“非應答位 NAK(1)”。

和寫操作規(guī)則一樣,我們每讀一個字節(jié),地址會自動加1,那如果我們想繼續(xù)往下讀,給 EEPROM 一個 ACK(0)低電平,那再繼續(xù)給 SCL 完整的時序,EEPROM 會繼續(xù)往外送數(shù)據。如果我們不想讀了,要告訴 EEPROM 不要數(shù)據了,那我們直接給一個 NAK(1)高電平即可。這個地方大家要從邏輯上理解透徹,不能簡單的靠死記硬背了,一定要理解明白。梳理一下幾個要點: A、在本例中單片機是主機,24C02 是從機; B、無論是讀是寫,SCL 始終都是由主機控制的; C、寫的時候應答信號由從機給出,表示從機是否正確接收了數(shù)據; D、讀的時候應答信號則由主機給出,表示是否繼續(xù)讀下去。

那我們下面寫一個程序,讀取 EEPROM 的 0x02 這個地址上的一個數(shù)據,不管這個數(shù)據之前是多少,我們都將讀出來的數(shù)據加1,再寫到 EEPROM 的 0x02 這個地址上。此外我們將 I2C 的程序建立一個文件,寫一個 I2C.c 程序文件,形成我們又一個程序模塊。大家也可以看出來,我們連續(xù)的這幾個程序,Lcd1602.c 文件里的程序都是一樣的,今后我們大家寫 1602 顯示程序也可以直接拿過去用,大大提高了程序移植的方便性。

/******************************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; //首先確保 SDA、SCL 都是高電平
    I2C_SCL = 1;
    I2CDelay();
    I2C_SDA = 0; //先拉低 SDA
    I2CDelay();
    I2C_SCL = 0; //再拉低 SCL
}
/* 產生總線停止信號 */
void I2CStop(){
    I2C_SCL = 0; //首先確保 SDA、SCL 都是低電平
    I2C_SDA = 0;
    I2CDelay();
    I2C_SCL = 1; //先拉高 SCL
    I2CDelay();
    I2C_SDA = 1; //再拉高 SDA
    I2CDelay();
}
/* I2C 總線寫操作,dat-待寫入字節(jié),返回值-從機應答位的值 */
bit I2CWrite(unsigned char dat){
    bit ack; //用于暫存應答位的值
    unsigned char mask; //用于探測字節(jié)內某一位值的掩碼變量

    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 位數(shù)據發(fā)送完后,主機釋放 SDA,以檢測從機應答
    I2CDelay();
    I2C_SCL = 1; //拉高 SCL
    ack = I2C_SDA; //讀取此時的 SDA 值,即為從機的應答值
    I2CDelay();
    I2C_SCL = 0; //再拉低 SCL 完成應答位,并保持住總線
    //應答值取反以符合通常的邏輯:
    //0=不存在或忙或寫入失敗,1=存在且空閑或寫入成功
    return (~ack);
}
/* I2C 總線讀操作,并發(fā)送非應答信號,返回值-讀到的字節(jié) */
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,以使從機發(fā)送出下一位
    }
    I2C_SDA = 1; //8 位數(shù)據發(fā)送完后,拉高 SDA,發(fā)送非應答信號
    I2CDelay();
    I2C_SCL = 1; //拉高 SCL
    I2CDelay();
    I2C_SCL = 0; //再拉低 SCL 完成非應答位,并保持住總線
    return dat;
}

/* I2C 總線讀操作,并發(fā)送應答信號,返回值-讀到的字節(jié) */
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,以使從機發(fā)送出下一位
    }
    I2C_SDA = 0; //8 位數(shù)據發(fā)送完后,拉低 SDA,發(fā)送應答信號
    I2CDelay();
    I2C_SCL = 1; //拉高 SCL
    I2CDelay();
    I2C_SCL = 0; //再拉低 SCL 完成應答位,并保持住總線
    return dat;
}

I2C.c 文件提供了 I2C 總線所有的底層操作函數(shù),包括起始、停止、字節(jié)寫、字節(jié)讀+應答、字節(jié)讀+非應答。

/***************************Lcd1602.c 文件程序源代碼*****************************/
#include <reg52.h>

#define LCD1602_DB P0
sbit LCD1602_RS = P1^0;
sbit LCD1602_RW = P1^1;
sbit LCD1602_E = P1^5;

/* 等待液晶準備好 */
void LcdWaitReady(){
    unsigned char sta;
    LCD1602_DB = 0xFF;
    LCD1602_RS = 0;
    LCD1602_RW = 1;
    do {
        LCD1602_E = 1;
        sta = LCD1602_DB; //讀取狀態(tài)字
        LCD1602_E = 0;
    }while (sta & 0x80); //bit7 等于 1 表示液晶正忙,重復檢測直到其等于 0 為止
}
/* 向 LCD1602 液晶寫入一字節(jié)命令,cmd-待寫入命令值 */
void LcdWriteCmd(unsigned char cmd){
    LcdWaitReady();
    LCD1602_RS = 0;
    LCD1602_RW = 0;
    LCD1602_DB = cmd;
    LCD1602_E = 1;
    LCD1602_E = 0;
}
/* 向 LCD1602 液晶寫入一字節(jié)數(shù)據,dat-待寫入數(shù)據值 */
void LcdWriteDat(unsigned char dat){
    LcdWaitReady();
    LCD1602_RS = 1;
    LCD1602_RW = 0;
    LCD1602_DB = dat;
    LCD1602_E = 1;
    LCD1602_E = 0;
}
/* 設置顯示 RAM 起始地址,亦即光標位置,(x,y)-對應屏幕上的字符坐標 */
void LcdSetCursor(unsigned char x, unsigned char y){
    unsigned char addr;

    if (y == 0){ //由輸入的屏幕坐標計算顯示 RAM 的地址
        addr = 0x00 + x; //第一行字符地址從 0x00 起始
    }else{
        addr = 0x40 + x; //第二行字符地址從 0x40 起始
    }
    LcdWriteCmd(addr | 0x80); //設置 RAM 地址
}
/* 在液晶上顯示字符串,(x,y)-對應屏幕上的起始坐標,str-字符串指針 */
void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str){
    LcdSetCursor(x, y); //設置起始地址
    while (*str != '\0'){ //連續(xù)寫入字符串數(shù)據,直到檢測到結束符
        LcdWriteDat(*str++);
    }
}
/* 初始化 1602 液晶 */
void InitLcd1602(){
    LcdWriteCmd(0x38); //16*2 顯示,5*7 點陣,8 位數(shù)據接口
    LcdWriteCmd(0x0C); //顯示器開,光標關閉
    LcdWriteCmd(0x06); //文字不動,地址自動+1
    LcdWriteCmd(0x01); //清屏
}
/*****************************main.c 文件程序源代碼******************************/
#include <reg52.h>
extern void InitLcd1602();
extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
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;
    unsigned char str[10];

    InitLcd1602(); //初始化液晶
    dat = E2ReadByte(0x02); //讀取指定地址上的一個字節(jié)
    str[0] = (dat/100) + '0'; //轉換為十進制字符串格式
    str[1] = (dat/10%10) + '0';
    str[2] = (dat%10) + '0';
    str[3] = '\0';

    LcdShowStr(0, 0, str); //顯示在液晶上
    dat++; //將其數(shù)值+1
    E2WriteByte(0x02, dat); //再寫回到對應的地址上
    while (1);
}

/* 讀取 EEPROM 中的一個字節(jié),addr-字節(jié)地址 */
unsigned char E2ReadByte(unsigned char addr){
    unsigned char dat;

    I2CStart();
    I2CWrite(0x50<<1); //尋址器件,后續(xù)為寫操作
    I2CWrite(addr); //寫入存儲地址
    I2CStart(); //發(fā)送重復啟動信號
    I2CWrite((0x50<<1)|0x01); //尋址器件,后續(xù)為讀操作
    dat = I2CReadNAK(); //讀取一個字節(jié)數(shù)據
    I2CStop();
    return dat;
}
/* 向 EEPROM 中寫入一個字節(jié),addr-字節(jié)地址 */
void E2WriteByte(unsigned char addr, unsigned char dat){
    I2CStart();
    I2CWrite(0x50<<1); //尋址器件,后續(xù)為寫操作
    I2CWrite(addr); //寫入存儲地址
    I2CWrite(dat); //寫入一個字節(jié)數(shù)據
    I2CStop();
}

這個程序,以同學們現(xiàn)在的基礎,獨立分析應該不困難了,遇到哪個語句不懂可以及時問問別人或者搜索一下,把該解決的問題理解明白。大家把這個程序復制過去后,編譯一下會發(fā)現(xiàn) Keil 軟件提示了一個警告:*** WARNING L16: UNCALLED SEGMENT, IGNORED FOR OVERLAY PROCESS,這個警告的意思是在代碼中存在沒有被調用過的變量或者函數(shù),即 I2C.c 文件中的 I2CReadACK()這個函數(shù)在本例中沒有用到。

大家仔細觀察一下這個程序,我們讀取 EEPROM 的時候,只讀了一個字節(jié)就要告訴 EEPROM 不需要再讀數(shù)據了,讀完后直接發(fā)送一個“NAK”,因此只調用了 I2CReadNAK()這個函數(shù),而并沒有調用 I2CReadACK()這個函數(shù)。我們今后很可能讀數(shù)據的時候要連續(xù)讀幾個字節(jié),因此這個函數(shù)寫在了 I2C.c 文件中,作為 I2C 功能模塊的一部分是必要的,方便我們這個文件以后移植到其他程序中使用,因此這個警告在這里就不必管它了。