靈感手環第一步——0.96寸OLED顯示實驗


這算是我這個系列的第一篇博客吧。首先要解決的就是屏幕顯示問題。我選擇了目前新興起的OLED顯示模塊。

OLED(OrganicLightEmittingDiode),中文譯作有機發光二極管,目前被廣泛的應用於移動設備甚至電視上。它既擁有超快的響應速度和輕薄的優勢,又存在壽命與對大尺寸支持不足的瓶頸。

OLED的優點
1、厚度可以小於1毫米,僅為LCD屏幕的1/3,並且重量也更輕;
2、固態機構,沒有液體物質,因此抗震性能更好,不怕摔;
3、幾乎沒有可視角度的問題,即使在很大的視角下觀看,畫面仍然不失真;
4、響應時間是LCD的千分之一,顯示運動畫面絕對不會有拖影的現象;
5、低溫特性好,在零下40度時仍能正常顯示,而LCD則無法做到;
6、制造工藝簡單,成本更低;
7、發光效率更高,能耗比LCD要低;
8、能夠在不同材質的基板上制造,可以做成能彎曲的柔軟顯示器。

OLED的缺點
1、壽命通常只有5000小時,要低於LCD至少1萬小時的壽命;
2、不能實現大尺寸屏幕的量產,因此目前只適用於便攜類的數碼類產品;
3、存在色彩純度不夠的問題,不容易顯示出鮮艷、濃郁的色彩。                             ******************【摘自百度

                                                                       

首先,該模塊采用SPI  或  IIC 通信方式,最多占用5個IO口。我使用的是7針模塊,采用4線SPI 通信方式。

該模塊有以下特點:

1. 模塊有單色和雙色可選,單色為純藍色,雙色為黃藍雙色(本人選用單色);
2. 顯示尺寸為0.96寸
3. 分辨率為128*64
4. 多種接口方式,該模塊提供了總共 5 種接口包括: 6800、 8080 兩種並行接口方式、 3線或4線的SPI接口,IIC接口方式
5. 不需要高壓,直接接3.3V就可以工作;(可以與stm32的引腳直接相接)

該模塊內部采用SSD1306驅動,顯存為128*64bit大小, SSD1306將全部顯存分為8頁,每頁128字節

SSD1306分頁

OLED相當於64行128列點陣,每個像素點,0點亮,1熄滅 
OLED將縱向64行分為8頁,每頁8行

該實驗的難點就在於理解取模的ASCII碼表與寫入程序的關系。下面我們來詳細分析一下。

首先根據這個官方給出的設置格式,我們采用列行式,就是先取列,再取行。比如我們取個大寫的   “A”  的字模。

   

{0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20} ,/*"A",0*/
/* (8 X 16 , 宋體 )*/

分析:
第一個0X00------表示第一列前8個像素,從高位向低位,也就是從下往上寫,全滅,所以是0X00,所以在SPI_Write()函數中,是從高位往低位寫的。
第二個0X00------表示第二列前8個像素,同上。
第三個0XC0-->  1100 0000,從高位往低位,正好下面兩個像素亮了。
后面都是這樣分析,大家可以自己對一下。
也就是說按照他的設置,這個取模軟件取的是按照從高位往低位取,前8個字節是第一頁的所有像素狀態。一共可以取128個字節。因為每一頁有128列,8行。但是這個大寫字母和漢字不一樣,他的寬度是漢字的一半,所以生成的ASCII碼表只有16個,一列,因為前8個字節是第一頁的,后8個字節是第二頁的,一個16*16的漢字需要占用兩頁(16行),16列。

下面是節選的顯示漢字的程序分析:

我使用STM32F103C8T6對該模塊進行驅動,程序修改自中景園科技官方驅動程序。親測可用。

OLED引腳介紹:
    CS:OLED片選信號
    RST:OLED復位端口
    DC: 命令/數據選擇端口(0:讀寫命令, 1: 讀寫數據)
    SCLK(D0):串口時鍾線
    SDIN(D1): 串口數據線 

關於SPI的相關知識,可以參見這篇博客:http://www.cnblogs.com/qsyll0916/p/8053905.html

首先是SPI.C,包含了對該模塊的各種操作,就是對SPI 寫進行符合OLED的包裝。寫字符,寫數字,寫字符串,可調顯示字體大小,但是需要包含兩個ASCII字庫。

#include "spi.h"
#include "word.h"  //字庫頭文件

#define OLED_Order 0       //定義寫命令
#define OLED_Data  1        //定義寫數據  

//盡在內部調用函數
static u32 oled_pow(u8 m,u8 n);
static void OLED_GPIO_INIT(void);
static void SPI_Write(u8 data, u8 Mode);
static void OLED_Coord(u8 x, u8 y);  

//使用管腳初始化
static void OLED_GPIO_INIT(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;  
      
    //開啟GPIOD的時鍾  
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    //設置GPIO的基本參數  
    GPIO_InitStruct.GPIO_Pin = OLED_CS_PIN | OLED_RST_PIN | OLED_DC_PIN | OLED_D0_PIN | OLED_D1_PIN;  
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;    //推挽輸出  
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;    //輸出速度50MHz  
      
    GPIO_Init(OLED_PORT, &GPIO_InitStruct);  
      
    GPIO_SetBits(OLED_PORT, OLED_CS_PIN | OLED_RST_PIN | OLED_DC_PIN | OLED_D0_PIN | OLED_D1_PIN);  
}

/*    SPI寫數據/命令 
 *    Mode :OLED_Order:寫命令   OLED_Data:寫數據 
 *    data :數據/命令 
*/  
static void SPI_Write(u8 data, u8 Mode)  
{      
    u8 i = 0; 

    if(Mode)  
    {  
        OLED_DC(1);        //DC引腳輸入高,表示寫數據  
    }  
    else  
    {  
        OLED_DC(0);        //DC引腳輸入低,表示寫命令  
    }  
    OLED_CS(0);            //CS引腳輸入低,片選使能  
    for(i = 0; i < 8; i++)  
    {  
        OLED_D0(0);        //D0引腳輸入低  
        if(data&0x80)    //判斷傳輸的數據最高位為1還是0  
        {  
            OLED_D1(1);    //D1引腳輸入高  
        }  
        else  
        {  
            OLED_D1(0);    //D1引腳輸入低  
        }  
        OLED_D0(1);        //D1引腳輸入高  
        data<<=1;        //將數據左移一位  
    }  
    OLED_DC(1);            //DC引腳輸入低  
    OLED_CS(1);            //CS引腳輸入高,片選失能  
}  

/*        設置OLED屏的顯示坐標 
 *      X : 表示OLED的水平坐標(0—127) 
 *      Y : 表示OLED的頁(0—7)     
*/  
static void OLED_Coord(u8 x, u8 y)  
{      
    SPI_Write((0xb0 + y) ,OLED_Order); 
    SPI_Write((((x & 0xf0)>>4) | 0x10), OLED_Order);//高4位  
    SPI_Write((x & 0x0f)|0x01, OLED_Order);//低4位  
}  
//清屏,一開始這里寫錯了,把寫命令寫成了寫數據,導致清屏不正確,發現屏幕上有很多噪點,說明沒有清屏成功。
void OLED_Clear(void)  
{  
    u8 i = 0, j = 0;  

    for(i = 0; i < 8; i++)  
    {  
        SPI_Write(0xb0 + i,OLED_Order);
        SPI_Write(0x00,OLED_Order);
        SPI_Write(0x10,OLED_Order);
        for(j = 0; j < 128; j++)  
        {  
            SPI_Write(0x00, OLED_Data);  
        }  
    }  
}  

//關oled顯示
void OLED_Display_Off(void)  
{
    SPI_Write(0x8D,OLED_Order);
    SPI_Write(0x10,OLED_Order);
    SPI_Write(0xAE,OLED_Order);
}  
//開oled顯示
void OLED_Display_On(void)
{  
    //電荷泵設置(初始化時必須打開,否則看不到顯示)
    SPI_Write(0x8D, OLED_Order);  
    SPI_Write(0x14, OLED_Order);//bit2   0:關閉        1:打開  
    SPI_Write(0xAF, OLED_Order);//0xAF:開顯示      
}  

//oled參數初始化
void OLED_Init(void)  
{  
    OLED_GPIO_INIT();  //端口初始化
      
    OLED_RST(1);     
    delay_ms(100);  
    OLED_RST(0);  
    delay_ms(100);  
    OLED_RST(1);
    
    SPI_Write(0xAE, OLED_Order);//0xAE:關顯示     
      
    SPI_Write(0x00, OLED_Order);//設置低列地址  
    SPI_Write(0x10, OLED_Order);//設置高列地址  
      
    //設置行顯示的開始地址(0-63)  
    //40-47: (01xxxxx)  
    SPI_Write(0x40, OLED_Order);
      
    //設置對比度  
    SPI_Write(0x81, OLED_Order);
    SPI_Write(0xff, OLED_Order);//這個值越大,屏幕越亮(和上條指令一起使用)(0x00-0xff)  
      
    SPI_Write(0xA1, OLED_Order);//0xA1: 左右反置,  0xA0: 正常顯示(默認0xA0)  
    SPI_Write(0xC8, OLED_Order);//0xC8: 上下反置,  0xC0: 正常顯示(默認0xC0)  
      
    //0xA6: 表示正常顯示(在面板上1表示點亮,0表示不亮)  
    //0xA7: 表示逆顯示(在面板上0表示點亮,1表示不亮)  
    SPI_Write(0xA6, OLED_Order);  
      
    SPI_Write(0xA8, OLED_Order);//設置多路復用率(1-64)  
    SPI_Write(0x3F, OLED_Order);//(0x01-0x3f)(默認為3f)  
      
      
    //設置顯示抵消移位映射內存計數器  
    SPI_Write(0xD3, OLED_Order);  
    SPI_Write(0x00, OLED_Order);//(0x00-0x3f)(默認為0x00)  
      
    //設置顯示時鍾分頻因子/振盪器頻率  
    SPI_Write(0xD5, OLED_Order);  
    //低4位定義顯示時鍾(屏幕的刷新時間)(默認:0000)分頻因子= [3:0]+1  
    //高4位定義振盪器頻率(默認:1000)  
    SPI_Write(0x80, OLED_Order);//  
      
    //時鍾預充電周期  
    SPI_Write(0xD9, OLED_Order);  
    SPI_Write(0xF1, OLED_Order);//[3:0],PHASE 1;   [7:4] PHASE 2  
      
    //設置COM硬件應腳配置  
    SPI_Write(0xDA, OLED_Order);  
    SPI_Write(0x12, OLED_Order);//[5:4]  默認:01  
      
    SPI_Write(0xDB, OLED_Order);//  
    SPI_Write(0x40, OLED_Order);//  
      
    //設置內存尋址方式  
    SPI_Write(0x20, OLED_Order);  
    //00: 表示水平尋址方式  
    //01: 表示垂直尋址方式  
    //10: 表示頁尋址方式(默認方式)  
    SPI_Write(0x02, OLED_Order);//      
      
    //電荷泵設置(初始化時必須打開,否則看不到顯示)  
    SPI_Write(0x8D, OLED_Order);  
    SPI_Write(0x14, OLED_Order);//bit2   0:關閉        1:打開  
      
    //設置是否全部顯示 0xA4: 禁止全部顯示  
    SPI_Write(0xA4, OLED_Order);  
  
    //0xA6: 表示正常顯示(在面板上1表示點亮,0表示不亮)  
    //0xA7: 表示逆顯示(在面板上0表示點亮,1表示不亮)  
    SPI_Write(0xA6, OLED_Order);//  
      
    SPI_Write(0xAF, OLED_Order);//0xAF:開顯示     
    SPI_Write(0xAF, OLED_Order); //不知道為什么要寫兩次
    
    OLED_Clear();
    OLED_Coord(0,0);
}  

//顯示漢字,設置坐標,
void OLED_ShowChinese(u8 x, u8 y, u8 chinese)  
{  
    u8 t,adder=0;
    
    OLED_Coord(x,y);

    for(t=0;t<16;t++)  //每行16個元素,一個字需要兩行字符串
    {
        SPI_Write(Hzk[2*chinese][t],OLED_Data);
        adder+=1;
     }    
    
    OLED_Coord(x,y+1);
     
    for(t=0;t<16;t++)
    {    
        SPI_Write(Hzk[2*chinese+1][t],OLED_Data);
        adder+=1;
      }    
} 


//在指定位置顯示一個字符,包括部分字符
//x:0~127
//y:0~63
//mode:0,反白顯示;1,正常顯示
//size:選擇字體 16/12 
void OLED_ShowChar(u8 x, u8 y, u8 chr)  
{  
    unsigned char c=0,   i=0;    
    
    c = chr - ' ';    //得到偏移后的值
    
        if(x > Max_Column - 1)
            {x=0;y=y+2;}
            
        if(SIZE ==16)  //8*16字符
        {
            OLED_Coord(x,y);
            
            for(i=0;i<8;i++)
                SPI_Write(F8X16[c*16+i],OLED_Data);
            
            OLED_Coord(x,y+1);
            
            for(i=0;i<8;i++)
                SPI_Write(F8X16[c*16+i+8],OLED_Data);
        }
        else    //6*8字符
        {    
            OLED_Coord(x,y+1);
            
            for(i=0;i<6;i++)
                SPI_Write(F6x8[c][i],OLED_Data);
            
        }
}  

//顯示字符串
void OLED_Show_String(u8 x, u8 y, u8 *chr)
{
    u8 j=0;
    while (chr[j]!='\0')
    {
        OLED_ShowChar(x,y,chr[j]);

        x+= 8 ;

        if(x>120){x=0;y+=2;}  //自動換行寫

        j++;
    }
}


//m^n函數
static u32 oled_pow(u8 m,u8 n)
{
    u32 result = 1; 
    while(n--)result*=m;    
    return result;
}

//顯示數字
//x,y :起點坐標     
//len :數字的位數
//size:字體大小
//mode:模式    0,填充模式;1,疊加模式
//num:數值(0~4294967295);               
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size)
{             
    u8 t = 0, temp = 0;
    u8 enshow=0;
    
    for(t=0;t<len;t++)
    {
        temp=(num/oled_pow(10,len-t-1))%10;
        
        if(enshow==0&&t<(len-1))
        {
            if(temp==0)
            {
                OLED_ShowChar(x+(size/2)*t,y,' ');
                continue;
            }else enshow=1; 
              
        }
         OLED_ShowChar(x+(size/2)*t,y,temp+'0'); 
    }
} 

然后就是頭文件:SPI.h

#ifndef __SPI_H
#define __SPI_H

#include <stm32f10x.h>
#include "systick.h"

#define OLED_PORT   GPIOA

#define OLED_CS_PIN            GPIO_Pin_3
#define OLED_RST_PIN        GPIO_Pin_4
#define OLED_DC_PIN            GPIO_Pin_5
#define OLED_D0_PIN            GPIO_Pin_6
#define OLED_D1_PIN            GPIO_Pin_7

//X為1時對應GPIO端口輸出高電平,X為0時對應GPIO端口輸出低電平 
#define OLED_CS(X)   X?GPIO_SetBits(OLED_PORT, OLED_CS_PIN):GPIO_ResetBits(OLED_PORT, OLED_CS_PIN)  
  
#define OLED_RST(X)  X?GPIO_SetBits(OLED_PORT, OLED_RST_PIN):GPIO_ResetBits(OLED_PORT, OLED_RST_PIN)      
  
#define OLED_DC(X)   X?GPIO_SetBits(OLED_PORT, OLED_DC_PIN):GPIO_ResetBits(OLED_PORT, OLED_DC_PIN)  
  
#define OLED_D0(X)   X?GPIO_SetBits(OLED_PORT, OLED_D0_PIN):GPIO_ResetBits(OLED_PORT, OLED_D0_PIN)      
  
#define OLED_D1(X)   X?GPIO_SetBits(OLED_PORT, OLED_D1_PIN):GPIO_ResetBits(OLED_PORT, OLED_D1_PIN)      
  
//OLED模式設置
#define SIZE 16
//#define SIZE 8    //SIZE選擇英文字體的大小
#define XLevelL        0x00
#define XLevelH        0x10
#define Max_Column    128
#define Max_Row        64
#define    Brightness    0xFF 
#define X_WIDTH     128
#define Y_WIDTH     64

void OLED_Init(void);
void OLED_Clear(void); 
void OLED_Display_Off(void); 
void OLED_Display_On(void);
void OLED_ShowChinese(u8 x, u8 y, u8 chinese);
void OLED_ShowChar(u8 x, u8 y, u8 chr);
void OLED_Show_String(u8 x, u8 y, u8 *chr);  
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size);


#endif

寫完這兩個文件,就可以在主函數調用  OLED_ShowChinese() ,進行參數配置后,就可以顯示了。

具體調用方式為:OLED_ShowChinese(32,0,0);//錢

這個里面前兩個參數是進行顯示坐標選擇,第三個參數是選擇你的字庫里面第幾個漢字的行數。比如我選擇了 0,那么在我的字庫頭文件中前兩個ASCII碼表就是我要顯示漢字的ASCII碼表,這個碼表是采用字模軟件生成的。

char Hzk[][32]={

{0x20,0x10,0x2C,0xE7,0x24,0x24,0x00,0x90,0x90,0xFF,0x90,0x49,0x4A,0x48,0x40,0x00},
{0x01,0x01,0x01,0x7F,0x21,0x11,0x40,0x40,0x20,0x13,0x0C,0x14,0x22,0x41,0xF8,0x00},/*"錢",7*/
/* (16 X 16 , 宋體 )*/

使用PCtoLCD2002完美版進行取模。具體字模生成方式可參見中景園官方教程:https://wenku.baidu.com/view/42efcb877cd184254a353584.html




漢字生成為兩行16進制碼表。
顯示效果:












免責聲明!

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



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