codeblocks 51單片機學習(三)總線協議

  • 引言:

這次總結包括很多的模塊,這些模塊無一例外都是需要編寫底層時序的,都是需要與各種時序線打交道的。所以要理解起來就會比較難,剛開始我只是對着模板程序照抄,看一眼模板程序,寫一下自己的,雖然它說的什麼東西還是能夠理解的,但是一旦脫離了,就什麼都寫不出來。
越是這樣的情況越是需要一些時間自己去把內容花時間消化,如果僅僅是利用這些底層時序,在應用層做一下文章,那是很簡單的,你甚至一晚上就能將這些模塊全部掌握,但是我覺得在學習階段更是應該深入理解,特別是自學這些東西的時候,更應該在這些東西上深入思考一下。

  • 一、I2C協議與EEPROM
    之前學的一個模塊叫做EEPROM,是一個外部存儲器,能夠存放一些數據,特點是即使斷電也不會丟失,可以看到其中有一個詞ROM,這其實就是一個只讀存儲器,只能在斷電時將數據寫入,並且芯片中帶有寫保護,需要接觸這個保護才能進行數據的寫入。主機與外部存儲器EEPROM之間的交互是通過I2C協議的,我在實驗中做的就是就是將一個字符串存儲到EEPROM裏再從EEPROM中讀取這些數據。
    在這裏插入圖片描述
    I2C協議,通過兩條線:SCL和SDA線進行數據的傳輸,SCL(Serial Clock)是串行時鐘線,SDA(Serial Data Address)串行數據/地址線,這裏我先按照我的理解去分析這兩條線的作用。首先I2C和SPI一樣都是通過傳輸線的上升沿和下降沿來進行數據的傳輸的,上升沿就是數據線由低電平提升爲高電平的過程,下降沿同理,這個過程可以讀或寫數據。就拿我今天學習的24C02來說,寫入數據時,將SCL由低電平提升到高電平產生一個上升沿,就能將一個位的數據讀入,如果SCL這段上升沿是在SDA高電平時完成的,那麼將會讀入一個1,如果是在SDA低電平完成的,那麼將會讀入一個0,而讀取時則是通過SCL的下降沿完成的。具體的操作就是用一個循環,將八位數據一位一位賦值給SDA,然後每次產生一個上升沿,這樣就將這個八位數據讀入了,爲什麼是八位數據呢,是這個外部存儲器24C02有兩個模式,一個是8位傳輸,一個是12位傳輸,爲了方便這裏使用的是8位傳輸模式。
    以上只是數據的傳輸機制,怎樣完成讀寫操作呢,通過查閱數據手冊,我才真正理解了。首先數據手冊有一句話,大概意思是主器件發送起始命令和從器件地址後,從器件會產生一個應答,這時候可以將數據發送過來,發送完後從器件再次產生一個應答,主器件接收到了之後就停止,這時候主器件進行內部數據的擦寫,也就完成了數據寫入的操作。而數據讀取,我記得也是差不多的,一位一位地讀取用一個變量去保存它,最後得到地這個變量的值就是讀出的結果。
    起始命令和終止命令在數據手冊上都有圖片示意,很好理解,就是在SCL處於高電平時,SDA完成下降沿就是開始,SDA完成上升沿就是結束。發送從器件的地址需要去數據手冊上找,寫入器件的地址16進制是0xa1,讀取器件是0xa2。雖說數據手冊說在發送地址後,要讓從器件產生應答並讓主器件接收到應答後,才能發送數據,但操作上我們就只需要在發送地址後,再發送數據就可以了,大概是因爲已經有某個程序寫在芯片中所以我們不需要做這些操作吧。
/*這是項目中實現24C02功能的函數文件*/
#include "common.h"

#define AT24C02_WRITE_ADDR		0xa0  		// 24C02寫的從地址
#define AT24C02_READ_ADDR		0xa1	 	// 24C02讀的從地址

static void I2C_start(void);
static void I2C_stop(void);
static void delay5us(void);
static void I2C_send_byte(u8 dat);
static u8 I2C_read_byte(void);
/*起始信號,維持SCL高電平,SDA拉高在拉低*/
static void I2C_start(void)
{
    SCL=1;
    delay5us();
    SDA=1;
    delay5us();
    SDA=0;
    delay5us();
    SCL=0;//後續寫入數據需要SCL拉高再拉低,所以這裏需要將SCL變爲低電平
    delay5us();
}
/*中止信號,維持SCL高電平,SDA拉低再拉高*/
static void I2C_stop(void)
{
     SDA=0;
    delay5us();
    SCL=1;
    delay5us();
    SDA=1;
    delay5us();
}

/*將主機的數據發送寫入EEPROM*/
static void I2C_send_byte(u8 dat)
{
    for(u8 i=0;i<8;i++)
    {
        SDA=dat >> 7;//寫時序,先改變SDA,然後拉高SCL拉低SCL電平,完成寫入操作
        dat <<=1;
        delay5us();
        SCL=1;
        delay5us();
        SCL=0;
        delay5us();
    }
        SDA=1;
        delay5us();
        SCL=1;
        delay5us();
        SCL=0;
        delay5us();
}
/*讀取數據,先拉高SCL,在中間將SDA改變讀取一位的數據,再拉低SCL,反覆讀取8位*/
static u8 I2C_read_byte(void)
{
    u8 dat=0;
    SDA=1;
    delay5us();
    SCL=0;
    delay5us();
    for(u8 i=0;i<8;i++)
    {
        SCL=1;//先拉高時鐘電平
        delay5us();
        dat <<=1;
        dat |=SDA;//或非將最低位替換爲SDA的大小(0,1)
        delay5us();
        SCL=0;//拉低時鐘電平
        delay5us();
    }
    return dat;
}
/*測試數據是否被寫入,試着將它發送回來,此函數與Uart串口通信連接*/
void I2CWrite(u8 addr,u8 dat)
{
    I2C_start();
    I2C_send_byte(AT24C02_WRITE_ADDR);//發送寫器件地址
    I2C_send_byte(addr);//發送要寫入的內部存儲器的地址
    I2C_send_byte(dat);//發送要寫入的數據
    I2C_stop();
}

u8 I2CRead(u8 addr)
{
    u8 dat;
    I2C_start();
    I2C_send_byte(AT24C02_WRITE_ADDR);//發送寫器件地址
    I2C_send_byte(addr);//發送要讀取的內部存儲器的地址

    I2C_start();
    I2C_send_byte(AT24C02_READ_ADDR);//發送讀器件地址
    dat=I2C_read_byte();
    I2C_stop();

    return dat;
}

static void delay5us(void)   //誤差 0us
{
    unsigned char a;
    for(a=1;a>0;a--);
}

  • SPI協議與AD轉換
    通過模數轉換,我們就能夠實現與外界環境的交互,這是計算機從外部獲取信息的重要能力,比如通過溫度感應器測量溫度,煙霧檢測器進行煙霧報警。首先模數轉換(AD轉換),是將環境中的模擬信號,也就是連續的無法測量的信號進行離散化,其實就是將連續的線轉化成成離散的點,這樣就可以被計算機識別,從而可以進行計算。這些轉換的概念還是很好理解的,而這些轉換過程都是通過一個叫XPT2046的芯片完成的,這個芯片就是專門處理模數轉換和數模轉換這個過程的,所以這個過程也是需要與我們的51單片機進行交互的。
    交互就又要涉及到通信協議了,這次使用的則是SPI協議,SPI協議呢不同於I2C的兩條線,總共有需要控制4條線,串行時鐘線DCLK、輸入線DIN、輸出線DOUT、總線CS,由於熟悉過了I2C協議,所以這次我只需要看數據手冊就將程序主體完成了,但還有一些細節還是需要參考一下的,像是在哪裏需要延時,這些操作我是能理解,但什麼時候該用該延時多久我就不知道了,就像是上次對使用I2C協議的24C02進行編程,在進行線操作時都需要一個5微妙的延時,而這次卻不需要。
    在這裏插入圖片描述
    看一下SPI的時序圖,通過了解各線作用,我們開始編程。首先我們將CS線由高電平拉爲低電平,這個CS總線我理解爲一個保護線,CS線控制着整個輸入輸出過程,只有它爲低電平時才能進行讀入讀取操作。而DCLK的作用和I2C作用很像,通過它的上升或者下降沿來讀入讀取數據。DIN,DOUT,輸入輸出線,好理解,就像是I2C協議裏SDA線拆成了兩個,在輸入時只需對DIN按位輸入,在讀取時對DOUT按位讀取。
    但是這個讀入的數據是什麼呢,是一個8位數據,控制了這個XPT2046的工作機制,輸入第一個8位數據,第一位S作爲起始位,接下來三位A2,A1,A0選擇通道,第四位MODE設置ADC的分辨率,MODE=0將是12位模式,MODE=1爲8位模式,接下來一位是SER/DFR,單端模式設置爲1,差分模式設置爲0,最後兩位PD1,PD0,11代表總處於供電狀態,00代表低功耗狀態。所以按照本次程序的需要,我們應設置爲10011111,也就是16進制數0x9F。
/*這是項目中實現AD轉換芯片XPT2046功能的函數文件*/
#include "common.h"
#include "XPT2046.h"

static void SPI_WRITE(u8 dat);
static void XPT2046_INIT_IN(void);
static u8 SPI_READ8(void);
static u8 SPI_READ12(void);

static void delay5us(void)   //誤差 0us
{
    unsigned char a;
    for(a=1;a>0;a--);
}
/*初始化,輸入第一個8位數據,第一位S
作爲起始位,接下來三位A2,A1,A0選擇地址,
第四位MODE設置ADC的分辨率,MODE=0
將是12位模式,MODE=1爲8位模式,接下來
一位是SER/DFR,單端模式設置爲1,差分模式
設置爲0,最後兩位PD1,PD0,11代表總處於供
電狀態,00代表低功耗狀態
按照本次程序的需要,我們應設置爲10011111,
也就是0x9F,爲了方便修改,專門寫一個函數
可以輸入任意8位數據。
*/
static void SPI_WRITE(u8 dat)
{
    u8 i=0;
    DCLK=0;
    for(i=0;i<8;i++)
    {
        DIN=dat >> 7;
        dat <<=1;
        DCLK=1;
        DCLK=0;
    }
}

static u8 SPI_READ8(void)
{
    u8 i=0,dat=0;
    DCLK=0;
    for(i=0;i<8;i++)
    {
        dat<<=1;
        DCLK=1;
        DCLK=0;
        dat |= DOUT;
    }
    return dat;
}

static u8 SPI_READ12(void)
{
    u8 i=0,dat=0;
    for(i=0;i<12;i++)
    {
        dat<<=1;
        DCLK=1;
        DCLK=0;
        dat |= DOUT;
    }
    return dat;
}

u8 XPT2046_WRITE(void)
{
    u8 i=0,val=0;
    DCLK=0;
    CS=0;
    SPI_WRITE(0x9F);
    for(i=0;i<6;i++);//延時

    DCLK=1;
    _nop_();
    _nop_();

    DCLK=0;
    _nop_();
    _nop_();

    val=SPI_READ8();
    CS=1;

    return val;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章