1. 程式人生 > 其它 >S3c2440裸機-spi程式設計-3.gpio模擬spi驅動OLED

S3c2440裸機-spi程式設計-3.gpio模擬spi驅動OLED

操作OLED,通過三條線(SCK、DO、CS)與OLED相連,這裡沒有DI是因為2440只會向OLED傳資料而不用接收資料。

gpio_spi.c來實現gpio模擬spi,負責spi通訊。對於OLED,有專門的指令和資料格式,要傳輸的資料內容,在oled.c這一層來實現,負責組織資料。

因此,我們需要實現以上兩個檔案。

1.SPI初始化

新建一個gpio_spi.c檔案,實現SPI初始化SPIInt()

1.1 GPIO init(pinmux管腳等配置)

上圖J3為板子pin2pin到OLED的底座。

GPF1作為OLED片選引腳,設定為輸出;
GPG4作為OLED的資料(Data)/命令(Command)選擇引腳,設定為輸出;
GPG5作為SPI的MISO,設定為輸入(實際用不到);
GPG6作為SPI的MOSI,設定為輸出;
GPG7作為SPI的時鐘CLK,設定為輸出;

根據gpio相關暫存器進行配置如下:用gpio配置成spi使用的各個引腳。

void SPIInit(void){
    /* 初始化引腳 */
    SPI_GPIO_Init();
}
static void SPI_GPIO_Init(void){
    /* GPF1 as OLED_CSn output */
    GPFCON &= ~(3<<(1*2));
    GPFCON |= (1<<(1*2));
    GPFDAT |= (1<<1);//取消OLED_CSn片選,pull up
   /* GPG2 FLASH_CSn output
    * GPG4 OLED_DC   output
    * GPG5 SPIMISO   input
    * GPG6 SPIMOSI   output
    * 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)) | (1<<(6*2)) | (1<<(7*2)));
    GPGDAT |= (1<<2);//取消FLASH_CSn 片選,pull up
}

2.OLED初始化

再新建一個oled.c檔案,以實現初始化OLEDInit(),這裡就對應power up sequence。

void OLEDInit(void){
    /* 向OLED發命令以初始化 */
    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);
}

D/C即資料(Data)/命令(Command)選擇引腳,它為高電平時,OLED即認為收到的是資料;它為低電平時,OLED即認為收到的是命令。先設定為命令模式,再片選OLED,再傳輸命令,再恢復成原來的模式和取消片選。

2.1 實現OLED寫功能

寫命令和寫資料:

static void OLEDWriteCmd(unsigned char cmd){
    OLED_Set_DC(0); /* command */
    OLED_Set_CS(0); /* select OLED */
    SPISendByte(cmd);
    OLED_Set_CS(1); /* de-select OLED */
    OLED_Set_DC(1); /*  gpio output default is pull up*/
}
static void OLEDWriteDat(unsigned char data){
    OLED_Set_DC(1); /* data*/
    OLED_Set_CS(0); /* select OLED */
SPISendByte(data);
OLED_Set_CS(1); /* de-select OLED */
}

命令模式和片選就是單純的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);
}

還剩下SPISendByte()函式,它屬於SPI協議,放在gpio_spi.c裡面:2.2 SPISendByte()

void SPISendByte(unsigned char val)

{

int i;

for (i = 0; i < 8; i++)

{

SPI_Set_CLK(0);

SPI_Set_DO(val & 0x80);//MSB

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);

}

傳送資料要滿足SPI的時序要求,參考前面的介紹:

SPISendByte是把一個byte資料從高位往低位依次傳送到DO。

spi配置模式0, 主控先設定CLK為低,由於是MSB, 先傳送高位,然後CLK為高,在CLK這個上升沿,DO的資料被鎖存,OLED就讀取了一位資料。接著左移一位,傳輸下一位。

通過SPI_Set_CLK()和SPI_Set_DO()配置SCK和DO的時序,用gpio模擬出了spi。至此,SPI初始化和OLED初始化就基本完成了,接下來就是OLED顯示部分。

這裡gpio模擬spi傳送時主控沒有加延時控制SCK的頻率,那是由於jz2440本身cpu執行就很慢,這裡不延時也是能滿足該款外設的spi傳輸時序,如果cpu很快,那麼需要控制spi時序。

2.驅動顯示OLED

如何在OLED上顯示一個字元?根據前面一節OLED面板的顯示原理。程式碼實現如下:

static void OLEDSetPos(int page, int col)

{

OLEDWriteCmd(0xB0 + page); /* page address */

OLEDWriteCmd(col & 0xf); /* Lower Column Start Address */

OLEDWriteCmd(0x10 + (col >> 4)); /* Lower Higher Start Address */

}

/* page: 0-7

* col : 0-127

* 字元: 8x16象素

*/

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]);

}

/* page: 0-7

* col : 0-127

* 字元: 8x16象素

*/

void OLEDPrint(int page, int col, char *str)

{

int i = 0;

while (str[i])

{

OLEDPutChar(page, col, str[i]);

col += 8;

if (col > 127)

{

col = 0;

page += 2;

}

i++;

}

}

static void OLEDSetPos(int page, int col)

{

OLEDWriteCmd(0xB0 + page); /* page address */

OLEDWriteCmd(col & 0xf); /* Lower Column Start Address */

OLEDWriteCmd(0x10 + (col >> 4)); /* Lower Higher Start Address */

}

static void OLEDClear(void)

{

int page, i;

for (page = 0; page < 8; page ++)

{

OLEDSetPos(page, 0);

for (i = 0; i < 128; i++)

OLEDWriteDat(0);

}

}

完整程式碼如下: