单片机学习小组
直播中

h1654155275.5753

7年用户 1100经验值
私信 关注

请问STM32 8080/SPI如何驱动OLED?

请问STM32  8080/SPI如何驱动OLED?

回帖(1)

望艳妮

2022-2-17 10:44:58
OLED简介
OLED,即有机发光二极管(Organic Light-Emitting Diode),又称为有机电激光显示(Organic Electroluminesence Display, OELD)。 OLED 由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。
LCD 都需要背光,而 OLED 不需要,因为它是自发光的。这样同样的显示, OLED 效果要来得好一些。 以目前的技术, OLED 的尺寸还难以大型化,但是分辨率确可以做到很高。
本文选用0.96寸OLED,该模块有以下特点:
1)尺寸小,显示尺寸为 0.96 寸,而模块的尺寸仅为 27mm*26mm 大小;
2)高分辨率,该模块的分辨率为 128x64。
3)多种接口方式,OLED模块提供了总共 4 种接口包括: 6800、 8080 两种并行接口方式、 4线 SPI 接口方式以及 IIC 接口方式(只需要 2 根线就可以控制 OLED 了!),本文选用的是SPI接口;
4) 不需要高压,直接接 3.3V 就可以工作了;
==注意:==该模块不和 5.0V 接口兼容,所以请大家在使用的时候一定要小心,别直接接到 5V 的系统上去,否则可能烧坏模块。

D0(SCLK):串行时钟线。
D1(SDIN):串行数据线。
RST:硬复位 OLED。
D/C:命令/数据标志(0,读写命令; 1,读写数据)。
CS:OLED 片选信号。
每个数据长度均为 8 位,在 SCLK 的上升沿,数据从 SDIN 移入到SSD1306,并且是高位在前的。 DC 线还是用作命令/数据的标志线。在 4 线 SPI 模式下,写操作的时序如图所示:

OLED模块显存

SSD1306显存总共为128*64bit,SSD1306将这些显存分为8页,每页包含128个字节,总共8页,这也刚好是128x64的点阵大小。

即每页包含8行,操作显示时以页为单位操作显示。
SSD1306的命令

第一个命令为 0X81,用于设置对比度的,这个命令包含了两个字节,第一个 0X81 为命令,随后发送的一个字节为要设置的对比度的值。这个值设置得越大屏幕就越亮。
第二个命令为 0XAE/0XAF。 0XAE 为关闭显示命令; 0XAF 为开启显示命令。
第三个命令为 0X8D,该指令也包含 2 个字节,第一个为命令字,第二个为设置值,第二个字节的 BIT2 表示电荷泵的开关状态,该位为 1,则开启电荷泵,为 0 则关闭。在模块初始化的时候,这个必须要开启,否则是看不到屏幕显示的。
第四个命令为 0XB0~B7,该命令用于设置页地址,其低三位的值对应着 GRAM 的页地址。
第五个指令为 0X00~0X0F,该指令用于设置显示时的起始列地址低四位。
第六个指令为 0X10~0X1F,该指令用于设置显示时的起始列地址高四位
程序显示原理

在STM32的内部建立一个缓存(共128x8个字节),每次修改显示时,只是修改STM32上的缓存(实际上就是SRAM),在修改完了之后,一次性把STM32上的缓存数据写入到OLED的GRAM。
1、引脚连接

D0–PC6
D1–PC7
RST–PG15
D/C–PD6
CS–PB7

OLED初始化
void OLED_Init(void)
{                           
  GPIO_InitTypeDef  GPIO_InitStructure;
       
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOC|RCC_AHB1Periph_GPIOD|RCC_AHB1Periph_GPIOE|RCC_AHB1Periph_GPIOG, ENABLE);//使能时钟
  
#if OLED_MODE==1                //8080并口模式       
       
        //GPIO初始化
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 ;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
       
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7 ;       
        GPIO_Init(GPIOB, &GPIO_InitStructure);


  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_11;       
        GPIO_Init(GPIOC, &GPIO_InitStructure);       
  
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;       
        GPIO_Init(GPIOD, &GPIO_InitStructure);
       
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_5;       
        GPIO_Init(GPIOE, &GPIO_InitStructure);       
       
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;       
        GPIO_Init(GPIOG, &GPIO_InitStructure);       

       
        OLED_WR=1;
        OLED_RD=1;
#else                                        //使用4线SPI 串口模式


  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 ;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_Init(GPIOB, &GPIO_InitStructure);
       
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;       
        GPIO_Init(GPIOC, &GPIO_InitStructure);               


        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;       
        GPIO_Init(GPIOD, &GPIO_InitStructure);       
       
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;       
        GPIO_Init(GPIOG, &GPIO_InitStructure);
       
        OLED_SDIN=1;
        OLED_SCLK=1;
#endif
        OLED_CS=1;
        OLED_RS=1;         
       
        OLED_RST=0;
        delay_ms(100);
        OLED_RST=1;
                                          
        OLED_WR_Byte(0xAE,OLED_CMD); //关闭显示
        OLED_WR_Byte(0xD5,OLED_CMD); //设置时钟分频因子,震荡频率
        OLED_WR_Byte(80,OLED_CMD);   //[3:0],分频因子;[7:4],震荡频率
        OLED_WR_Byte(0xA8,OLED_CMD); //设置驱动路数
        OLED_WR_Byte(0X3F,OLED_CMD); //默认0x3F(1/64)
        OLED_WR_Byte(0xD3,OLED_CMD); //设置显示偏移
        OLED_WR_Byte(0X00,OLED_CMD); //默认为0


        OLED_WR_Byte(0x40,OLED_CMD); //设置显示开始行[5:0],行数.
                                                                                                            
        OLED_WR_Byte(0x8D,OLED_CMD); //电荷泵设置
        OLED_WR_Byte(0x14,OLED_CMD); //bit2,开启/关闭
        OLED_WR_Byte(0x20,OLED_CMD); //设置内存地址模式
        OLED_WR_Byte(0x02,OLED_CMD); //[1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10;
        OLED_WR_Byte(0xA1,OLED_CMD); //段重定义设置,bit0:0,0->0;1,0->127;
        OLED_WR_Byte(0xC0,OLED_CMD); //设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数
        OLED_WR_Byte(0xDA,OLED_CMD); //设置COM硬件引脚配置
        OLED_WR_Byte(0x12,OLED_CMD); //[5:4]配置
                 
        OLED_WR_Byte(0x81,OLED_CMD); //对比度设置
        OLED_WR_Byte(0xEF,OLED_CMD); //1~255;默认0x7F(亮度设置,越大越亮)
        OLED_WR_Byte(0xD9,OLED_CMD); //设置预充电周期
        OLED_WR_Byte(0xf1,OLED_CMD); //[3:0],PHASE 1;[7:4],PHASE 2;
        OLED_WR_Byte(0xDB,OLED_CMD); //设置VCOMH  电压倍率
        OLED_WR_Byte(0x30,OLED_CMD); //[6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc;


        OLED_WR_Byte(0xA4,OLED_CMD); //全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏)
        OLED_WR_Byte(0xA6,OLED_CMD); //设置显示方式;bit0:1,反向显示;0,正常显示                                                              
        OLED_WR_Byte(0xAF,OLED_CMD); //开启显示         
        OLED_Clear();//清屏
}  


OLED更新缓存,显示内容
我们在 STM32F4内部定义了一个块GRAM:u8 OLED_GRAM[128][8];此部分 GRAM 对应 OLED 模块上的 GRAM。在操作的时候,我们只要修改 STM32F4 内部的 GRAM 就可以了,然后通过 OLED_Refresh_Gram 函数把 GRAM 一次刷新到 OLED 的 GRAM 上。


u8 OLED_GRAM[128][8];         


//        更新显存到OLED         
void OLED_Refresh_Gram(void)
{
        u8 i,n;                    
        for(i=0;i<8;i++)  
        {  
                OLED_WR_Byte (0xb0+i,OLED_CMD);    //设置页地址(0~7)
                OLED_WR_Byte (0x00,OLED_CMD);      //设置显示位置—列低地址
                OLED_WR_Byte (0x10,OLED_CMD);      //设置显示位置—列高地址
                for(n=0;n<128;n++)         //对每行128列 写字节
                OLED_WR_Byte(OLED_GRAM[n],OLED_DATA);
        }   

OLED_GRAM[128][8]中的 128 代表列数(x 坐标),而 8 代表的是页, 每页又包含 8 行, 总共 64 行(y 坐标)。从高到低对应行数从小到大。比如,我们要在 x=100, y=29 这个点写入1,则可以用这个句子实现:
                             OLED_GRAM[100][4]|=1<<2;
1
一个通用的在点(x, y)置 1 表达式为:


                        OLED_GRAM[x][7-y/8]|=1<<(7-y%8);
1
其中 x 的范围为:0~127; y 的范围为: 0~63。


OLED写一个字节
//dat:要写的数据/命令
//cmd:数据/命令标志  0,表示命令;1,表示数据
void OLED_WR_Byte(u8 dat,u8 cmd)
{       
        u8 i;                          
        OLED_RS=cmd; //写命令
        OLED_CS=0;        //拉低进行片选          
        for(i=0;i<8;i++)   //因为是串行,每次需要写8位
        {                          
                OLED_SCLK=0;
                if(dat&0x80)OLED_SDIN=1;
                else OLED_SDIN=0;
                OLED_SCLK=1;   //开启时钟
                dat<<=1;     //传送数据
        }                                 
        OLED_CS=1;        //禁止片选          
        OLED_RS=1;             
}
OLED画点函数
void OLED_DrawPoint(u8 x,u8 y,u8 t)   //t=1为该点显示,t=0为该点不显示
{
        u8 pos,bx,temp=0;
        if(x>127||y>63)return;//超出范围了
        pos=7-y/8;
        bx=y%8;
        temp=1<<(7-bx);
        if(t)OLED_GRAM[x][pos]|=temp;
        else OLED_GRAM[x][pos]&=~temp;            
}


OLED字符显示
OLED的字符显示需要用到字符提取软件制作字符码表,请看后续博客


//x:0~127
//y:0~63
//mode:0,反白显示;1,正常显示                         
//size:选择字体 12/16/24
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode)
{                                  
        u8 temp,t,t1;
        u8 y0=y;
        u8 csize=(size/8+((size%8)?1:0))*(size/2);                //得到字体一个字符对应点阵集所占的字节数
        chr=chr-' ';//得到偏移后的值                 
    for(t=0;t     {   
                if(size==12)temp=asc2_1206[chr][t];                  //调用 1206 字体   表示每个字符占OLED   12行6列
                else if(size==16)temp=asc2_1608[chr][t];        //调用 1608 字体
                else if(size==24)temp=asc2_2412[chr][t];        //调用 2412 字体
                else return;                                                                //没有的字库
        for(t1=0;t1<8;t1++)
                {
                        if(temp&0x80)OLED_DrawPoint(x,y,mode);
                        else OLED_DrawPoint(x,y,!mode);
                        temp<<=1;
                        y++;
                        if((y-y0)==size)
                        {
                                y=y0;
                                x++;
                                break;
                        }
                }           
    }         
}


主函数
int main(void)
{
        u8 t=0;
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组 2
        delay_init(168);   
        uart_init(115200);       
        LED_Init();                       
        OLED_Init();       
               
        /*
        //OLED 满屏检验屏幕坏点程序
  for(x=0;x<128;x++)
        {for(y=0;y<64;y++)
        OLED_DrawPoint(x,y,1);
        }
        OLED_Refresh_Gram();//更新显示到OLED       
  */               
    OLED_ShowString(0,0,"HhL",24);  
        OLED_ShowString(0,24, "0.96' OLED TEST",16);  
        OLED_ShowString(0,40,"ATOM 2020/3/6",12);  
        OLED_ShowString(0,52,"ASCII:",12);  
        OLED_ShowString(64,52,"CODE:",12);  
        OLED_Refresh_Gram();//更新显示到 OLED         
        t=' ';  
        while(1)
        {               
                OLED_ShowChar(36,52,t,12,1);//显示 ASCII 字符       
                OLED_ShowNum(94,52,t,3,12);        //显示 ASCII 字符的码值   
                OLED_Refresh_Gram();        //更新显示到 OLED
                t++;
                if(t>'~')t=' ';  
                delay_ms(500);
                LED0=!LED0;
        }
}


上传连接好硬件,上传程序显示效果如下:

可以看到显示中有部分行无法显示,此时需要检测是程序问题还是显示屏的问题,进行满屏显示检测,主程序中有满屏坏点检测程序,检测效果如下:

从图中可以看出并非程序的问题而是硬件损坏导致,因为疫情导致,一直在家中,手边没有新的OLED因此不能更换,敬请谅解。
举报

更多回帖

×
20
完善资料,
赚取积分