基於ARM板s3c2440---SPI協議

SPI簡介

SPI是串行外設接口(Serial Peripheral Interface)的縮寫。SPI,是一種高速的,全雙工,同步的通信總線,並且在芯片的管腳上只佔用四根線,節約了芯片的管腳,同時爲PCB的佈局上節省空間,提供方便,正是出於這種簡單易用的特性,如今越來越多的芯片集成了這種通信協議,比如AT91RM9200。
簡單的說,就是SPI是一種告訴的協議,相當於IIC,只是IIC是兩根線(時鐘線SCL,數據地址線SDA)而SPI是四根線(SCK時鐘信號,DO輸出線,DI輸入線,CSn片選信號),同樣可以掛在多個SPI高速設備。
在這裏插入圖片描述

數據傳輸

在這裏插入圖片描述
數據傳輸由CPOL,CPHA兩個控制器決定,CPOL決定電平啓示狀態是低電平還是高電平,CPHA決定在第一個沿採樣還是第二個沿採樣
在這裏插入圖片描述

通過SPI協議實現對OLED屏幕的顯示

在這裏插入圖片描述

OLED顯示原理

在這裏插入圖片描述
屏幕分辨率是12864,如同LCD一樣,我們需要一個顯存來存放數據,這個顯存在內存中開闢,一個字節八位,分別表示該列元素的亮(1)或者滅(0)
在這裏插入圖片描述
在這裏插入圖片描述
按16
8的大小在屏幕上顯示,一行爲一個page,在光標移動到底8列時,光標歸爲0,page+1,實現第二個page的寫操作

通過GPIO模擬SPI的數據收發

在這裏插入圖片描述
原理圖中使用到GPG3567和GPF1,所以需要設置S3C2440相應的管腳的輸入輸出模式,除開GPG5爲輸入模式,其餘管腳都是輸出模式。
在這裏插入圖片描述
在這裏插入圖片描述

static void SPI_GPIO_INIT(void)
{
    /*GPF1 OLED_CSn OUTPUT*/    /*主機是輸出模式*/
    GPFCON &= ~(3<<2);
    GPFCON |= (1<<2);
    GPFDAT |= (1<<1);    /*因爲接了OLED 和 FLASH  如果同時爲0  會產生衝突*/
    /*GPG2 FLASH_CSn  OUTPUT*/
    /*GPG4 OLED_DC OUTPUT*/
    /*GPG5 SPIMISO INPUT*/    /*MI:MASTER INPUT*/
    /*GPG6 SPIMOSI OUTPUT*/
    /*GPG7 SPICLK OUTPUT*/
    GPGCON &= ~((3<<4)|(3<<8)|(3<<12)|(3<<14)|(3<<10));
    GPGCON |= ((1<<4)|(1<<8)|(1<<12)|(1<<14));
    GPGDAT |=(1<<2);    /*因爲接了OLED 和 FLASH  如果同時爲0  會產生衝突*/
                        /*CSn低電平有效*/
                        /*都不片選,使其在使用時裁片選*/
}

由於芯片上接入了FLASH和oled的片選信號,所以在GPIO的初始化時,需要將兩個片選信號都拉高,避免默認狀態衝突。

OLED初始化

void oledinit(void)
{
    /*向OLED發命令初始化*/
    /*SPEC UG手冊提供的初始化命令*/
    OLEDWriteCmd(0xAE); /*display off*/ 
    OLEDWriteCmd(0x00); /*set lower column address*/ 
    OLEDWriteCmd(0x10); /*set higher column address*/ 
    OLEDWriteCmd(0x40); /*set display start line*/ 
    OLEDWriteCmd(0xB0); /*set page address*/ 
    OLEDWriteCmd(0x81); /*contract control*/ 
    OLEDWriteCmd(0x66); /*128*/ 
    OLEDWriteCmd(0xA1); /*set segment remap*/ 
    OLEDWriteCmd(0xA6); /*normal / reverse*/ 
    OLEDWriteCmd(0xA8); /*multiplex ratio*/ 
    OLEDWriteCmd(0x3F); /*duty = 1/64*/ 
    OLEDWriteCmd(0xC8); /*Com scan direction*/ 
    OLEDWriteCmd(0xD3); /*set display offset*/ 
    OLEDWriteCmd(0x00); 
    OLEDWriteCmd(0xD5); /*set osc division*/ 
    OLEDWriteCmd(0x80); 
    OLEDWriteCmd(0xD9); /*set pre-charge period*/ 
    OLEDWriteCmd(0x1f); 
    OLEDWriteCmd(0xDA); /*set COM pins*/ 
    OLEDWriteCmd(0x12); 
    OLEDWriteCmd(0xdb); /*set vcomh*/ 
    OLEDWriteCmd(0x30); 
    OLEDWriteCmd(0x8d); /*set charge pump enable*/ 
    OLEDWriteCmd(0x14);

    OLEDPAGEADDRMODE();
    OLEDCLEAR();
    OLEDWriteCmd(0xAF); /*display ON*/
}

在OLED初始中實現對地址採用Page address mode
在這裏插入圖片描述

static void OLEDPAGEADDRMODE(void)
{
    OLEDWriteCmd(0x20);
    OLEDWriteCmd(0x02);
}

清屏幕

static void OLEDCLEAR(void)
{
    int page,col,i;
    for(page = 0; page <8;page++)
    {
        OLEDSETPOS(page,0);
        for(i=0;i<128;i++)
        {
            OLEDWriteDAT(0);
        }
    }
}

地址/數據模式

OLED_DC:高電平表示數據,低電平表示數據。

/*命令*/
static void OLEDWriteCmd(unsigned int cmd)
{
    OLED_SET_DC(0); /*COMMAND*/
    OLED_SET_CS(0); /*選中OLED*/

    SPISENDBYTE(cmd);

    OLED_SET_CS(1); /*取消選中OLED*/
    OLED_SET_DC(1); /*COMMAND*/
}
/*地址*/
static void OLEDWriteDAT(unsigned int dat)
{
    OLED_SET_DC(1); /*data*/
    OLED_SET_CS(0); /*選中OLED*/

    SPISENDBYTE(dat);
    
    OLED_SET_CS(1); /*取消選中OLED*/
    OLED_SET_DC(1); /*data*/
}

根據傳入0/1設置相應的GPIO口輸出高低電平

static void OLED_SET_DC(char val)
{
    if(val)
    {
        GPGDAT |= (1<<4);
    }
    else
    {
        GPGDAT &= ~(1<<4);
    }
    
}

片選信號

static void OLED_SET_CS(char val)
{
    if(val)
    {
        GPFDAT |= (1<<1);
    }
    else
    {
        GPFDAT &= ~(1<<1);
    }
    
}

數據傳輸

void SPISENDBYTE(unsigned char val)
{
    int i;

    for(i=0;i<8;i++)
    {
        SPI_SET_CLK(0);
        SPI_SET_DO(val & 0x80);
        SPI_SET_CLK(1);
        val <<= 1;
    }
}

時鐘設置

static void SPI_SET_CLK(char val)
{
    if(val)
    {
        GPGDAT |= (1<<7);
    }
    else
    {
        GPGDAT &= ~(1<<7);
    }
    
}

數據輸出--------按位傳遞,熊高位到低位

static void SPI_SET_DO(char val)
{
    if(val)
    {
        GPGDAT |= (1<<6);
    }
    else
    {
        GPGDAT &= ~(1<<6);
    }
    
}

程序到這裏已經實現了CPU的數據傳出,接下來需要將數據收到,以及打印到OLED的相應位置

void oledprint(int page,int col,char *str)
{
    int i = 0;
    while(str[i])
    {
        OLEDPUTCHAR(page,col,str[i]);
        col += 8;
        if(col > 127)
        {
            page += 2;
            col = 0;
        }
        i++;
    }
}

一個字節佔8列,16行(16*8)
在這裏插入圖片描述

void OLEDPUTCHAR(int page,int col,char c)
{
    int i=0;
    /*字摸*/
    const unsigned char *dots = oled_asc2_8x16[c - ' '];
    /*發給OLED*/
    OLEDSETPOS(page,col);
    /*發出8字節數據*/
    for(i=0;i<8;i++)
    {
        OLEDWriteDAT(dots[i]);
    }
    OLEDSETPOS(page+1,col);
    /*發出8字節數據*/
    for(i=0;i<8;i++)
    {
        OLEDWriteDAT(dots[i+8]);    /*原因是字符數組二維數組是16個爲一組,前8個是上一排的,後8個是下一排*/
    }
}

在這裏插入圖片描述

static void OLEDSETPOS(int page, int col)
{
    OLEDWriteCmd(0xb0 + page);      /*page address*/

    OLEDWriteCmd(col & 0xf);        /*Set Lower Column Start Address for Page Addressing Mode*/
    OLEDWriteCmd(0x10 +(col>>4));   /*Set Higher Column Start Address for Page Addressing Mode*/
}

通過SPI協議實現對FLASH的操作

讀廠家ID、設備ID

在這裏插入圖片描述
首先發送0x90,然後發出3個字節的0(是一個24位的數據,所以需要分割爲3個字節來發送),然後GPG5就有數據讀入。

void SPIflashreadID(int *pMID,int *pDID)
{
    SPI_FLASH_set_cs(0);    /*選中SPIflash*/

    SPISENDBYTE(0X90);
    SPIFlashSendAddr(0);      /*當發完這些指令和地址是,GPGDAT5上就有數據傳入*/
    *pMID = SPIRecvByte();
    *pDID = SPIRecvByte();
    SPI_FLASH_set_cs(1);    /*取消選中SPIflash*/
}
static void SPIFlashSendAddr(unsigned int addr)
{
    SPISENDBYTE(addr>>16);      /*地址由24位組成,先發高高8位*/
    SPISENDBYTE((addr>>8)&0xff);      /*地址由24位組成,再發高8位*/
    SPISENDBYTE(addr&0xff);      /*地址由24位組成,最後發低8位*/

}
static void SPI_FLASH_set_cs(char val)
{
    if(val)
    {
        GPGDAT |= (1<<2);       /*不選中*/
    }
    else
    {
        GPGDAT &= ~(1<<2);      /*選中*/
    }
    
}
/*主機傳入*/
static char SPI_SET_DI(void)
{
    /*需要判定GPGDAT第5位是什麼數據*/
    if(GPGDAT &(1<<5))
    {
        return 1;
    }
    else
    {
        return 0;
    }
}
unsigned char SPIRecvByte(void)
{
    int i;
    unsigned char val = 0;  /*用於存放本字節的東西*/
    for(i=0;i<8;i++)
    {
        val <<= 1;
        SPI_SET_CLK(0);
        if(SPI_SET_DI())
        {   
            val |= 1;       /*數據爲1就或1,否則就是0,前面左移已經產生了,所以不用賦值*/
        }
        SPI_SET_CLK(1);
    }
    return val;
}

對FLASH進行讀寫

在這裏插入圖片描述
對FLASH的寫操作,需先進進行寫使能,取出狀態寄存器的保護,然後擦除,然後寫入
寫使能:
在這裏插入圖片描述
寫保護:
在這裏插入圖片描述

static void SPIFlashWriteEnable(int enable)
{
    if (enable)
    {
        SPI_FLASH_set_cs(0);
        SPISENDBYTE(0x06);
        SPI_FLASH_set_cs(1);
    }
    else
    {
        SPI_FLASH_set_cs(0);
        SPISENDBYTE(0x04);
        SPI_FLASH_set_cs(1);
    }
}

讀狀態寄存器
在這裏插入圖片描述
發送0x05命令,接下來就可以讀數據了

static unsigned char SPIFlashReadStatusReg1(void)
{
    unsigned char val;
    SPI_FLASH_set_cs(0);
    SPISENDBYTE(0x05);
    val = SPIRecvByte();
    SPI_FLASH_set_cs(1);
    return val;
}
static unsigned char SPIFlashReadStatusReg2(void)
{
    unsigned char val;
    SPI_FLASH_set_cs(0);
    SPISENDBYTE(0x35);
    val = SPIRecvByte();
    SPI_FLASH_set_cs(1);
    return val;
}

寫狀態寄存器

static void SPIFlashWriteStatusReg(unsigned char reg1, unsigned char reg2)
{    
    SPIFlashWriteEnable(1);  
    
    SPI_FLASH_set_cs(0);
    SPISENDBYTE(0x01);
    SPISENDBYTE(reg1);
    SPISENDBYTE(reg2);
    SPI_FLASH_set_cs(1);

    SPIFlashWaitWhenBusy();
}

根據狀態寄存器1的第0位可以判定是寫數據已經完成。1表示正在進行,0表示已經完成

static void SPIFlashWaitWhenBusy(void)
{
    while (SPIFlashReadStatusReg1() & 1);
}

去除對狀態寄存器的保護
在這裏插入圖片描述
在這裏插入圖片描述
將SRP0,SRP1置0就能去除對狀態寄存器的保護

static void SPIFlashClearProtectForStatusReg(void)
{
    unsigned char reg1, reg2;

    reg1 = SPIFlashReadStatusReg1();
    reg2 = SPIFlashReadStatusReg2();

    reg1 &= ~(1<<7);
    reg2 &= ~(1<<0);

    SPIFlashWriteStatusReg(reg1, reg2);
}

去除對數據域的保護
在這裏插入圖片描述
將BP2,BP1,BP0三位清0就可以去除保護

static void SPIFlashClearProtectForData(void)
{
    /* cmp=0,bp2,1,0=0b000 */
    unsigned char reg1, reg2;

    reg1 = SPIFlashReadStatusReg1();
    reg2 = SPIFlashReadStatusReg2();

    reg1 &= ~(7<<2);
    reg2 &= ~(1<<6);

    SPIFlashWriteStatusReg(reg1, reg2);
}

擦除

在這裏插入圖片描述
擦除4k大小的區域
在這裏插入圖片描述

void SPIFlashEraseSector(unsigned int addr)
{
    SPIFlashWriteEnable(1);  

    SPI_FLASH_set_cs(0);
    SPISENDBYTE(0x20);
    SPIFlashSendAddr(addr);
    SPI_FLASH_set_cs(1);

    SPIFlashWaitWhenBusy();
}

燒寫

在這裏插入圖片描述
在這裏插入圖片描述

/* program */
void SPIFlashProgram(unsigned int addr, unsigned char *buf, int len)
{
    int i;
    
    SPIFlashWriteEnable(1);  

    SPI_FLASH_set_cs(0);
    SPISENDBYTE(0x02);
    SPIFlashSendAddr(addr);

    for (i = 0; i < len; i++)
        SPISENDBYTE(buf[i]);
    
    SPI_FLASH_set_cs(1);

    SPIFlashWaitWhenBusy();
    
}

讀數據
在這裏插入圖片描述
在這裏插入圖片描述

void SPIFlashRead(unsigned int addr, unsigned char *buf, int len)
{
    int i;
    
    SPI_FLASH_set_cs(0);
    SPISENDBYTE(0x03);
    SPIFlashSendAddr(addr);
    for (i = 0; i < len; i++)
        buf[i] = SPIRecvByte();

    SPI_FLASH_set_cs(1);    
}

使用SPI控制器取代GPIO模擬

同樣是使用相同的管家,只是不再手動操作相應的GPIO管腳,實現0/1的輸出,而是操作SPI控制器,讓控制器自動進行相應的數據收發。
在這裏插入圖片描述
SPI控制器初始化
在這裏插入圖片描述
將GPGCON中的456位設置爲SPI模式,其餘不變

static void SPI_GPIO_Init(void)
{
    /*GPF1 OLED_CSn OUTPUT*/    /*主機是輸出模式*/
    GPFCON &= ~(3<<(1*2));
    GPFCON |= (1<<(1*2));
    GPFDAT |= (1<<1);       /*因爲接了OLED 和 FLASH  如果同時爲0  會產生衝突*/

    /*GPG2 FLASH_CSn  OUTPUT*/
    /*GPG4 OLED_DC OUTPUT*/
    /*GPG5 SPIMISO INPUT*/    /*MI:MASTER INPUT*/
    /*GPG6 SPIMOSI OUTPUT*/     /*這裏所有的輸入輸出都是站在主機CPU的角度談的*/
    /*GPG7 SPICLK OUTPUT*/
    GPGCON &= ~((3<<(2*2)) | (3<<(4*2)) | (3<<(5*2)) | (3<<(6*2)) | (3<<(7*2)));
    GPGCON |= ((1<<(2*2)) | (1<<(4*2)) | (3<<(5*2)) | (3<<(6*2)) | (3<<(7*2)));
    GPGDAT |= (1<<2);       /*因爲接了OLED 和 FLASH  如果同時爲0  會產生衝突*/
                            /*CSn低電平有效*/
                            /*都不片選,使其在使用時裁片選*/
}

SPI控制器初始化,設置相應的時鐘,因爲是自動傳輸,所以時鐘(也就是波特率)需要預先設置。
在這裏插入圖片描述
在這裏插入圖片描述

static void SPIControllerInit(void)
{
    /* OLED  : 100ns, 10MHz
    * FLASH : 104MHz
    * 取10MHz
    * 10 = 50 / 2 / (Prescaler value + 1)
    * Prescaler value = 1.5 = 2
    * Baud rate = 50/2/3=8.3MHz
    */
    SPPRE0 = 2;
    SPPRE1 = 2;

    /* [6:5] : 00, polling mode
    * [4]   : 1 = enable 
    * [3]   : 1 = master
    * [2]   : 0
    * [1]   : 0 = format A
    * [0]   : 0 = normal mode
    */
    SPCON0 = (1<<4) | (1<<3);
    SPCON1 = (1<<4) | (1<<3);
    
}

收發數據只需要將數據寫給SPSTA1寄存器就可以

在這裏插入圖片描述
由於使用的是SPIMISO1,所以使用的是第二控制器

void SPISENDBYTE(unsigned char val)
{
    while (!(SPSTA1 & 1));
    SPTDAT1 = val;    
}
//判定SPSTA1第一位十位準備就緒,然後寫數據
unsigned char SPIRecvByte(void)
{
    SPTDAT1 = 0xff;    
    while (!(SPSTA1 & 1));
    return SPRDAT1;    
}

首先將0xff寫入SPTDAT1 ,然後判定SPSTA1第一位十位準備就緒,然後讀數據
特別注意:這裏的數據寄存器(SPRDATn)是單字符寄存器,所以在地址聲明時需要定義爲單字節。

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