zoukankan      html  css  js  c++  java
  • 灵感手环第一步——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]!='')
        {
            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进制码表。
    显示效果:











  • 相关阅读:
    Javascript:window.close()不起作用?
    为什么全部width:100%浏览器边缘存在留白?
    hello world
    【Vue】在Vue事件中是如何使用event对象的?
    【Vue】特殊属性is
    【Vue】过滤器
    【Vue源码】Vue不能检测到对象属性的添加或删除
    【VueRouter】vue路由跳转打开新窗口
    【VueRouter】切换路由后,新页面滚动到顶部或保持原先的滚动位置——scrollBehavior
    【VueRouter】前端路由的两种模式
  • 原文地址:https://www.cnblogs.com/qsyll0916/p/8088102.html
Copyright © 2011-2022 走看看