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

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

14.2 單片機(jī) I2C 尋址模式

上一節(jié)介紹的是 I2C 每一位信號的時序流程,而 I2C 通信在字節(jié)級的傳輸中,也有固定的時序要求。I2C 通信的起始信號(Start)后,首先要發(fā)送一個從機(jī)的地址,這個地址一共有7位,緊跟著的第8位是數(shù)據(jù)方向位(R/W),“0”表示接下來要發(fā)送數(shù)據(jù)(寫),‘“1”表示接下來是請求數(shù)據(jù)(讀)。

我們知道,打電話的時候,當(dāng)撥通電話,接聽方撿起電話肯定要回一個“喂”,這就是告訴撥電話的人,這邊有人了。同理,這個第九位 ACK 實際上起到的就是這樣一個作用。當(dāng)我們發(fā)送完了這7位地址和1位方向后,如果發(fā)送的這個地址確實存在,那么這個地址的器件應(yīng)該回應(yīng)一個 ACK(拉低 SDA 即輸出“0”),如果不存在,就沒“人”回應(yīng) ACK(SDA將保持高電平即“1”)。

那我們寫一個簡單的程序,訪問一下我們板子上的 EEPROM 的地址,另外再寫一個不存在的地址,看看它們是否能回一個 ACK,來了解和確認(rèn)一下這個問題。

我們板子上的 EEPROM 器件型號是 24C02,在 24C02 的數(shù)據(jù)手冊3.6節(jié)中可查到,24C02 的7位地址中,其中高4位是固定的 0b1010,而低3位的地址取決于具體電路的設(shè)計,由芯片上的 A2、A1、A0 這3個引腳的實際電平?jīng)Q定,來看一下我們的 24C02 的電路圖,它和 24C01 的原理圖完全一樣,如圖14-4所示。

http://wiki.jikexueyuan.com/project/mcu-tutorial-three/images/7.png" alt="" />

圖14-4 24C02 原理圖

從圖14-4可以看出來,我們的 A2、A1、A0 都是接的 GND,也就是說都是0,因此 24C02 的7位地址實際上是二進(jìn)制的 0b1010000,也就是 0x50。我們用 I2C 的協(xié)議來尋址 0x50,另外再尋址一個不存在的地址 0x62,尋址完畢后,把返回的 ACK 顯示到我們的 1602 液晶上,大家對比一下。

/***************************Lcd1602.c 文件程序源代碼*****************************/
#include <reg52.h>
#define LCD1602_DB P0
sbit LCD1602_RS = P1^0;
sbit LCD1602_RW = P1^1;
sbit LCD1602_E = P1^5;

/* 等待液晶準(zhǔn)備好 */
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 表示液晶正忙,重復(fù)檢測直到其等于 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ù)據(jù),dat-待寫入數(shù)據(jù)值 */
void LcdWriteDat(unsigned char dat){
    LcdWaitReady();
    LCD1602_RS = 1;
    LCD1602_RW = 0;
    LCD1602_DB = dat;
    LCD1602_E = 1;
    LCD1602_E = 0;
}
/* 設(shè)置顯示 RAM 起始地址,亦即光標(biāo)位置,(x,y)-對應(yīng)屏幕上的字符坐標(biāo) */
void LcdSetCursor(unsigned char x, unsigned char y){
    unsigned char addr;
    if (y == 0){ //由輸入的屏幕坐標(biāo)計算顯示 RAM 的地址
        addr = 0x00 + x; //第一行字符地址從 0x00 起始
    }else{
        addr = 0x40 + x; //第二行字符地址從 0x40 起始
    }
    LcdWriteCmd(addr | 0x80); //設(shè)置 RAM 地址
}
/* 在液晶上顯示字符串,(x,y)-對應(yīng)屏幕上的起始坐標(biāo),str-字符串指針 */
void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str){
    LcdSetCursor(x, y);//設(shè)置起始地址
    while (*str != '\0'){ //連續(xù)寫入字符串?dāng)?shù)據(jù),直到檢測到結(jié)束符
        LcdWriteDat(*str++);
    }
}
/* 初始化 1602 液晶 */
void InitLcd1602(){
    LcdWriteCmd(0x38); //16*2 顯示,5*7 點陣,8 位數(shù)據(jù)接口
    LcdWriteCmd(0x0C); //顯示器開,光標(biāo)關(guān)閉
    LcdWriteCmd(0x06); //文字不動,地址自動+1
    LcdWriteCmd(0x01); //清屏
}
/*****************************main.c 文件程序源代碼******************************/
#include <reg52.h>
#include <intrins.h>
#define I2CDelay() {_nop_();_nop_();_nop_();_nop_();}
sbit I2C_SCL = P3^7;
sbit I2C_SDA = P3^6;
bit I2CAddressing(unsigned char addr);
extern void InitLcd1602();
extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);

void main(){
    bit ack;
    unsigned char str[10];
    InitLcd1602(); //初始化液晶
    ack = I2CAddressing(0x50); //查詢地址為 0x50 的器件
    str[0] = '5'; //將地址和應(yīng)答值轉(zhuǎn)換為字符串
    str[1] = '0';
    str[2] = ':';
    str[3] = (unsigned char)ack + '0';
    str[4] = '\0';
    LcdShowStr(0, 0, str); //顯示到液晶上
    ack = I2CAddressing(0x62); //查詢地址為 0x62 的器件
    str[0] = '6'; //將地址和應(yīng)答值轉(zhuǎn)換為字符串
    str[1] = '2';
    str[2] = ':';
    str[3] = (unsigned char)ack + '0';
    str[4] = '\0';
    LcdShowStr(8, 0, str); //顯示到液晶上
    while (1);
}
/* 產(chǎn)生總線起始信號 */
void I2CStart(){
    I2C_SDA = 1; //首先確保 SDA、SCL 都是高電平
    I2C_SCL = 1;
    I2CDelay();
    I2C_SDA = 0; //先拉低 SDA
    I2CDelay();
    I2C_SCL = 0; //再拉低 SCL
}
/* 產(chǎn)生總線停止信號 */
void I2CStop(){
    I2C_SCL = 0; //首先確保 SDA、SCL 都是低電平
    I2C_SDA = 0;
    I2CDelay();
    I2C_SCL = 1; //先拉高 SCL
    I2CDelay();
    I2C_SDA = 1; //再拉高 SDA
    I2CDelay();
}
/* I2C 總線寫操作,dat-待寫入字節(jié),返回值-從機(jī)應(yīng)答位的值 */
bit I2CWrite(unsigned char dat){
    bit ack; //用于暫存應(yīng)答位的值
    unsigned char mask; //用于探測字節(jié)內(nèi)某一位值的掩碼變量

    for (mask=0x80; mask!=0; mask>>=1){ //從高位到低位依次進(jìn)行
        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ù)據(jù)發(fā)送完后,主機(jī)釋放 SDA,以檢測從機(jī)應(yīng)答
    I2CDelay();
    I2C_SCL = 1; //拉高 SCL
    ack = I2C_SDA; //讀取此時的 SDA 值,即為從機(jī)的應(yīng)答值
    I2CDelay();
    I2C_SCL = 0; //再拉低 SCL 完成應(yīng)答位,并保持住總線
    return ack; //返回從機(jī)應(yīng)答值
}
/* I2C 尋址函數(shù),即檢查地址為 addr 的器件是否存在,返回值-從器件應(yīng)答值 */
bit I2CAddressing(unsigned char addr){
    bit ack;
    I2CStart(); //產(chǎn)生起始位,即啟動一次總線操作
    //器件地址需左移一位,因?qū)ぶ访畹淖畹臀?    //為讀寫位,用于表示之后的操作是讀或?qū)?    ack = I2CWrite(addr<<1);
    I2CStop(); //不需進(jìn)行后續(xù)讀寫,而直接停止本次總線操作
    return ack;
}

我們把這個程序在 KST-51 開發(fā)板上運行完畢,會在液晶上邊顯示出來我們預(yù)想的結(jié)果,主機(jī)發(fā)送一個存在的從機(jī)地址,從機(jī)會回復(fù)一個應(yīng)答位,即應(yīng)答位為0;主機(jī)如果發(fā)送一個不存在的從機(jī)地址,就沒有從機(jī)應(yīng)答,即應(yīng)答位為1。

前面的章節(jié)中已經(jīng)提到利用庫函數(shù)nop()可以進(jìn)行精確延時,一個nop()的時間就是一個機(jī)器周期,這個庫函數(shù)包含在 intrins.h 這個文件中,如果要使用這個庫函數(shù),只需要在程序最開始,和包含 reg52.h 一樣,include之后,程序中就可以使用這個庫函數(shù)了。

還有一點要提一下,I2C 通信分為低速模式 100kbit/s、快速模式 400kbit/s 和高速模式 3.4Mbit/s。因為所有的 I2C 器件都支持低速,但卻未必支持另外兩種速度,所以作為通用的 I2C 程序我們選擇 100k 這個速率來實現(xiàn),也就是說實際程序產(chǎn)生的時序必須小于等于 100k 的時序參數(shù),很明顯也就是要求 SCL 的高低電平持續(xù)時間都不短于 5 us,因此我們在時序函數(shù)中通過插入 I2CDelay()這個總線延時函數(shù)(它實際上就是4個 NOP 指令,用 define 在文件開頭做了定義),加上改變 SCL 值語句本身占用的至少一個周期,來達(dá)到這個速度限制。如果以后需要提高速度,那么只需要減小這里的總線延時時間即可。

此外我們要學(xué)習(xí)一個發(fā)送數(shù)據(jù)的技巧,就是 I2C 通信時如何將一個字節(jié)的數(shù)據(jù)發(fā)送出去。大家注意函數(shù) I2CWrite 中,用的那個 for 循環(huán)的技巧。for (mask=0x80; mask!=0; mask>>=1),由于 I2C 通信是從高位開始發(fā)送數(shù)據(jù),所以我們先從最高位開始,0x80 和 dat 進(jìn)行按位與運算,從而得知 dat 第7位是0還是1,然后右移一位,也就是變成了用 0x40 和 dat 按位與運算,得到第6位是0還是1,一直到第0位結(jié)束,最終通過 if 語句,把 dat 的8位數(shù)據(jù)依次發(fā)送了出去。其它的邏輯大家對照前邊講到的理論知識,認(rèn)真研究明白就可以了。