51單片機之IIC通信原理及軟件仿真


關於IIC我覺這個博客里面說的已經夠清楚了

如下圖所示的寫操作的時序圖:

 

其實像這種通信協議的要求是很精確的,一點點不對都可能導致在實際工程中無法讀取數據。我就是被一個應答位耽誤了好久,還好最后被我發現了。雖然程序不長,但是每一句話都是值得我們認真學習的,下面是我自己結合網上還有書中的程序綜合的,親測可用。最后用keil的邏輯分析儀測試了iic端口輸出的波形。

iic.h

#ifndef __IIC_H
#define    __IIC_H

#include <reg52.h>
#include <intrins.h>

sbit SCL = P2^1;
sbit SDA = P2^0;

void iic_init(void);
void iic_start(void);
void iic_stop(void);
void iic_dalay_us(void);//iic延時專用
void iic_send_ack(bit _ack);//讀取數據之后發送應答位
void iic_wait_ack(void);//寫入數據之后,等待從機應答信號
void iic_write_byte(unsigned char _byte); //寫一個字節
unsigned char iic_read_byte(void);//讀取一個字節
void iic_write_addr_byte(unsigned char _addr,unsigned char _byte);//任意地址寫一個字節
unsigned char iic_read_addr_byte(unsigned char _addr); //任意地址讀一個字節



#endif

iic.c

//可以用keil自帶的邏輯分析儀查看iic端口的輸出波形,通過對波形的分析,判斷iic協議有沒有工作。
#include "iic.h"
#include "mpu6050.h"


//---------5us,實際測試是8us--------
void iic_dalay_us(void)
{
    unsigned char i;

        _nop_();//nop是1個指令周期 = 1機器周期 = 12時鍾周期 = 12*1/f,對於12M晶振,1us
        i = 1;
        while(--i);        //執行一次while循環2us
}


//-------起始信號,時鍾高,數據高變低---------------
void iic_start(void)
{
    SDA = 1;
    iic_dalay_us();
    SCL = 1;
    iic_dalay_us();
    SDA = 0;
    iic_dalay_us();
    SCL = 0;       //鉗住I2C總線,准備發送或接收數據
}

//--------停止信號-------
void iic_stop(void)
{
    SDA = 0;
    iic_dalay_us();
    SCL = 0;
    iic_dalay_us();
    SCL = 1;
    iic_dalay_us();
    SDA = 1;
    iic_dalay_us();
}

//-------------
void iic_init(void)
{
    SCL = 1;
    iic_dalay_us();
    SDA = 1;    
    iic_dalay_us();
}


//----讀取數據完成后,發送應答位,
//----0->ack,應答,告訴從機我要繼續讀取下一個字節,從機收到這個信號后繼續發送數據
//----1->not ack,不應答,告訴從機我不在繼續接受數據,從機停止發送數據
void iic_send_ack(bit _ack)
{
    SDA = _ack;//接收完成后,拉高SDA,發送非應答信號
    SCL = 1;
    iic_dalay_us();
    SCL = 0; //拉低,完成應答位
    iic_dalay_us();
}


//-------------主機發送完成,等待應答----------------
//主機發送完一個字節的數據之后會把SDA拉高,等待從機的信號,如果從機不應答,SDA將一直拉高,此時while函數等待250us,
//如果在SCL高電平期間,SDA被從設備拉低表示從設備應答,此時while跳過,應答位結束,
void iic_wait_ack(void)
{
    unsigned char i = 0;

    SCL = 1;
    iic_dalay_us();
    while((SDA==1)&&(i<250))i++;//應答時,SDA為0表示從機成功接受到數據,
    SCL = 0;   //拉低,完成應答位
}

//------------------------
//寫一個字節,從高位往低位寫
//主機給從機發送數據,從機是在時鍾的下降沿采集SDA的數據
void iic_write_byte(unsigned char _byte) 
{
    unsigned char i;
    
    for(i=0;i<8;i++)
    {
        SDA = _byte&0X80;     //先寫高位
        iic_dalay_us();
        SCL = 1;
        iic_dalay_us();
        SCL = 0;          //下降沿采集數據
        iic_dalay_us();
        _byte = _byte<<1;    
    }

    SDA = 1;//發送完畢后,釋放數據線,檢測從機應答
    iic_wait_ack();//等待應答
}


//----------------------------------
//任意地址寫一個字節,因為我使用的時候從設備只有一個,所以不需要寫入從設備地址,
//如果有多個從設備的話,可以增加一個參數,寫入從設備地址
void iic_write_addr_byte(unsigned char _addr,unsigned char _byte)
{
    iic_start();
    iic_write_byte(SlaveAddress); //此處寫入從機地址,我這里只有一個,沒有在函數里面體現
    iic_write_byte(_addr);
    iic_write_byte(_byte);    
    iic_stop();
}

//------------------------
//讀字節,也是先讀取的是高位,需要左移
//主機從從機讀取數據,本質上是從機檢測主機發出的時鍾信號有8個上升沿
unsigned char iic_read_byte(void)
{
    unsigned char i = 0;
    unsigned char read_byte = 0;

    SDA = 1;      //先確保主機釋放SDA
    iic_dalay_us();

    for(i = 0;i<8;i++)
    {
        SCL = 1;
        read_byte = (read_byte<<1)|SDA ;
        iic_dalay_us();
        SCL = 0;    //拉低,采集數據
        iic_dalay_us();
    }

    return read_byte;
}

//------------------
//在某一地址內讀取數據
unsigned char iic_read_addr_byte(unsigned char _addr)
{
    unsigned char read_data;

    iic_start();
    iic_write_byte(SlaveAddress);//發送設備地址+寫信號
    iic_write_byte(_addr);    //發送存儲器單元
    iic_start();            //必須從新啟動IIC
    iic_write_byte(SlaveAddress + 1);//發送設備地址+讀信號
    read_data = iic_read_byte();//讀出數據
    iic_send_ack(1);//發送非應答信號
    /*
    原來問題出在這里!!,如果后面跟的是iic_stop的話,就發送非應答,告訴從機不要在發數據了,注意這里沒有連續讀取
    不然的話,從機收到0的應答位之后會一直發送數據,但是這時主機又沒有接受,導致數據只能第一次發送成功,后面都收不到數據。
    */
    iic_stop();

    return read_data;
}

 

keil仿真波形:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM