1. 程式人生 > >向韋東山學:spi協議+oled裸板程式

向韋東山學:spi協議+oled裸板程式

go spi 系列— spi協議+oled裸板程式

作者:titer1 
聯絡:1307316一九六八(僅接受簡訊) 
宣告:本文采用以下協議進行授權: 自由轉載-非商用-非衍生-保持署名|Creative Commons BY-NC-ND 3.0 ,轉載請註明作者及出處。

本文所有程式碼版權歸原作者所有 
目錄 
Alt text

圖說 spi 簡要原理

Alt text

  • 基礎 資料傳輸原理 dataoutput + clock + selections

圖說 arm2240 + spi

2440系統如何使用spi的 
Alt text

tips:同步後 ,ctrl+ alt +enter 預覽

圖說 結合di引腳說明不同spi傳輸場景

當arm讀取spi裝置時,

這是 spi transfer format細節, 
送完一個位元組,下一個時鐘週期沒有到來之前,di內容存在差異 
詳細如圖: 
Alt text

韋大大說,時鐘週末沒來之前的事情 不用在乎,在乎時鐘邊緣

還有時鐘的極性差別,如下圖橙色標示: 
Alt text
上圖我看來,就是時鐘開始時電平的高/低

至於如何選定正確的極性作為開始,要看外接晶片的約定

圖說 晶片 clock極性

結合 SPEC UG-2864TMBEG01 手冊,可以看到圖中時鐘起始選擇可高可低,不用擔心 
Alt text

韋大大說,arm spi format a/b 
不過外接的spi晶片 DO資料是在上升沿由外接晶片鎖存住。

那麼 ,最終arm 2440可選的format格式剩下為; 
Alt text

Alt text

以上場景區分是 arm的 cpol /cpha的值

下面開始程式碼了 
當前是 spi第一課第一節,修改程式碼在 spi_i2c_adc裡面

程式碼實戰

內容框架

Alt text

一個檔案負責 顯示, 
一個檔案負責傳輸 
目標實現函式有:

SPIInit();
OLEDInit();
OLEDPrint();

主要分析內容來源是:原始碼source\裸板\01th_spi_i2c_adc_jz2440_oled

oled.c

簡單來說就是OLED開頭的函式實現,在這裡。

從晶片SPEC UG-2864TMBEG01 手冊看來,vcc產生是 內部dc上拉結果? 
改裝晶片手冊內部的初始化設定

oledinit 向oled傳送命令

詳細的cpu和晶片的連線圖如下,這次跟之前給出的連線圖差別在,給出data/控制訊號切換的方法

Alt text


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); /*  */
}

//入口函式
void OLEDInit(void)
{
    /* 向OLED發命令以初始化 */
    OLEDWriteCmd(0xAE); /*display off*/ 
...
    OLEDWriteCmd(0x14); 
...    
}

繼續看 oled控制器 (?)的手冊 SSD1306-Revision 1.1 (Charge Pump)

細節:實現 dc +cs 片選訊號

這裡 dc對應gpg4 ,cs對應gpf1

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

###實現 Oledprint 
關鍵在 oled 地址設定+ 資料設定 
這裡採用的取地址方式是:頁定址 
視訊記憶體這裡大小為:128*64 
一行64位元組,分為8頁,每頁8行

手冊解釋 詳細看 
10 COMMAND DESCRIPTIONS 
10.1 Fundamental Command 
10.1.1 Set Lower Column Start Address for Page Addressing Mode (00h~0Fh) 
10.1.2 Set Higher Column Start Address for Page Addressing Mode (10h~1Fh) 
10.1.3 Set Memory Addressing Mode (20h) 
There are 3 different memory addressing mode in SSD1306: page addressing mode, 
horizontal addressing mode and vertical addressing mode. 

情景如圖: 
Alt text

中文總結如下:

  • 先發送 列地址 高位
  • 再發送 列地址 低位
  • 配置定址模式

再說如何放到Led顯示螢幕上去的

看手冊 
Alt text
看最終螢幕上的佈局,這裡就可以理解為什麼要兩行了,下面說明相關程式碼

//每一個位元組的字模資料都分解為高4位+低4位傳輸
//之所以相加:因為是bit 0:1 ,後面處理了高位和低位
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 */
}

Alt text 
下面的函式OLEDPutChar形象的說明了繪製字模的過程。



//涵蓋字元轉換為字模
/* 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++;
    }
}

其他oled函式

oled設定page模式 
oled清屏函式

此處暫時忽略。

!總結

至此:字元–》字模點陣–》spi 資料傳輸的過程就通過以上看到

OLEDPrint -->OLEDPutChar -->( OLEDWriteDat-->OLEDWriteCmd)

!細節 實現spisendbyte 重要的

cloc對應gpg7 do對應gpd6,非常有趣重要的是上升沿的do實現

 SPI_Set_CLK(0);
 SPI_Set_DO(val & 0x80);//典型取高位
 SPI_Set_CLK(1);

完整的SPISendByte邏輯在下面

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

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

}

gpio_spi.c 聯通arm和spi晶片

首先看spi晶片的電路圖 
Alt text
然後使用gpio模擬spi,尤其注意裡面的註釋,這裡比較細節就是gpio方向

/* 用GPIO模擬SPI */
//called by SPIInit
static void SPI_GPIO_Init(void)
{
    /* GPF1 OLED_CSn output */
    GPFCON &= ~(3<<(1*2)); 
    GPFCON |= (1<<(1*2));
    GPFDAT |= (1<<1); //設定為高電平,保證片選引腳pin1(GPF1 OLED_CSn)和其他相關引腳不同時為0,

    /* 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);//設定為高電平,保證片選引腳pin2(GPG2 FLASH_CSn) 和其他相關引腳不同時為0,
}

當前進度是在 1-1 30分鐘位置

其他

作者所使用的程式碼在 spi_i2c_adc相關資料夾中