imuncle / imuncle.github.io

大叔的个人小站
https://imuncle.github.io/
78 stars 17 forks source link

OLED简单GUI开发 #84

Open imuncle opened 4 years ago

imuncle commented 4 years ago

OLED简介

我平时最喜欢玩的是中景园的0.96寸的单色OLED,有IIC通信和SPI通信两款,上面的驱动芯片是SSD1306,用起来非常方便。

image

该OLED的分辨率是128×64,显示内容时纵坐标上都是以页(page)为单位进行操作,所以这个0.96寸的OLED纵向为8个page。若有一个图标占用了2个page,那么在这个图标2个page的上下空白部分,不能显示其它内容。因为要在这个page的空白部分显示其它内容的话,会擦除这个page上已存在的内容。

有一种方法可以实现在空白部分显示其它内容,就是用并行接口去控制点阵屏,并行接口可以读取屏RAM的显示内容。我们进行写page之前,先把page里面的数据读出来,进行或操作之后再写,这样就保留了之前的图标内容。这种方法会占用MCU很多的IO口。更关键的是,我并没有并行接口,我只有IIC或者SPI,不支持读取屏上某一点的显示内容。

GUI

图形用户界面(Graphical User Interface,简称 GUI,又称图形用户接口)是指采用图形方式显示的计算机操作用户界面。目前网上已经有很多成熟的专门针对嵌入式系统的GUI,比如emWIN,MicroWindows,MiniGui,ZLG AWTK等。

实现GUI的基础就是实现画点、画线、画圆、画矩形、填充、显示文字等操作,所以下面我就具体讲怎么实现这些基本功能。

OLED GUI

我使用的办法是在MCU里面创建一个和屏大小相同的数组,不过因为OLED屏上每一个像素点只有两个状态,亮或不亮,所以我完全可以用一位二进制数来表示,所以我使用了128×8的数组来存储OLED的像素点数据。所有的GUI操作均是直接操作这个数组,最后再统一将这个数组传给OLED显示出来,效率也提高了不少。

uint8_t OLED_GRAM[128][8];

操作点

直接上代码

//画点
//x:0~127
//y:0~63
//t:1 填充 0 清空
void OLED_DrawPoint(uint8_t x,uint8_t y,uint8_t t)
{
    uint8_t pos,bx,temp=0;
    if(x>127||y>63)return;//超出范围了
    pos=y/8;
    bx=y%8;
    temp=1<<bx;
    if(t)OLED_GRAM[x][pos]|=temp;
    else OLED_GRAM[x][pos]&=~temp;
}

读取某点的像素值:

uint8_t  OLED_ReadPoint(uint8_t x, uint8_t y)
{
    uint8_t pos,bx,temp=0;
    if(x>127||y>63)return 0;//超出范围了
    pos=y/8;
    bx=y%8;
    temp=1<<bx;
    return OLED_GRAM[x][pos]&temp;
}

画线

画矩形

void  OLED_Rectangle(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t color)
{   OLED_HLine(x0, y0, x1, color);
    OLED_HLine(x0, y1, x1, color);
    OLED_RLine(x0, y0, y1, color);
    OLED_RLine(x1, y0, y1, color);
}

画圆

使用Bresenham法画圆

void  OLED_Circle(uint8_t x0, uint8_t y0, uint8_t r, uint8_t color)
{   signed short  draw_x0, draw_y0; 
    signed short  draw_x1, draw_y1;
    signed short  draw_x2, draw_y2;
    signed short  draw_x3, draw_y3;
    signed short  draw_x4, draw_y4;
    signed short  draw_x5, draw_y5;
    signed short  draw_x6, draw_y6;
    signed short  draw_x7, draw_y7;
    signed short  xx, yy;   

    signed short  di;   

    if(0==r) return;

    draw_x0 = draw_x1 = x0;
    draw_y0 = draw_y1 = y0 + r;
    if(draw_y0<GUI_LCM_YMAX) OLED_DrawPoint(draw_x0, draw_y0, color);

    draw_x2 = draw_x3 = x0;
    draw_y2 = draw_y3 = y0 - r;
    if(draw_y2>=0) OLED_DrawPoint(draw_x2, draw_y2, color);

    draw_x4 = draw_x6 = x0 + r;
    draw_y4 = draw_y6 = y0;
    if(draw_x4<GUI_LCM_XMAX) OLED_DrawPoint(draw_x4, draw_y4, color);   

    draw_x5 = draw_x7 = x0 - r;
    draw_y5 = draw_y7 = y0;
    if(draw_x5>=0) OLED_DrawPoint(draw_x5, draw_y5, color);
    if(1==r) return;

    di = 3 - 2*r;

    xx = 0;
    yy = r;
    while(xx<yy)
    {   if(di<0)
        {   di += 4*xx + 6;
        }
        else
        {   di += 4*(xx - yy) + 10;

            yy--;
            draw_y0--;
            draw_y1--;
            draw_y2++;
            draw_y3++;
            draw_x4--;
            draw_x5++;
            draw_x6--;
            draw_x7++;
        }

        xx++;
        draw_x0++;
        draw_x1--;
        draw_x2++;
        draw_x3--;
        draw_y4++;
        draw_y5++;
        draw_y6--;
        draw_y7--;

        if( (draw_x0<=GUI_LCM_XMAX)&&(draw_y0>=0) )
        {   OLED_DrawPoint(draw_x0, draw_y0, color);
        }
        if( (draw_x1>=0)&&(draw_y1>=0) )
        {   OLED_DrawPoint(draw_x1, draw_y1, color);
        }
        if( (draw_x2<=GUI_LCM_XMAX)&&(draw_y2<=GUI_LCM_YMAX) )
        {   OLED_DrawPoint(draw_x2, draw_y2, color);
        }
        if( (draw_x3>=0)&&(draw_y3<=GUI_LCM_YMAX) )
        {   OLED_DrawPoint(draw_x3, draw_y3, color);
        }
        if( (draw_x4<=GUI_LCM_XMAX)&&(draw_y4>=0) )
        {   OLED_DrawPoint(draw_x4, draw_y4, color);
        }
        if( (draw_x5>=0)&&(draw_y5>=0) )
        {   OLED_DrawPoint(draw_x5, draw_y5, color);
        }
        if( (draw_x6<=GUI_LCM_XMAX)&&(draw_y6<=GUI_LCM_YMAX) )
        {   OLED_DrawPoint(draw_x6, draw_y6, color);
        }
        if( (draw_x7>=0)&&(draw_y7<=GUI_LCM_YMAX) )
        {   OLED_DrawPoint(draw_x7, draw_y7, color);
        }
    }
}

显示字符

我的字库如下:

/************************************6*8************************************/
const unsigned char F6x8[][6] =     
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,// sp
0x00, 0x00, 0x00, 0x2f, 0x00, 0x00,// !
0x00, 0x00, 0x07, 0x00, 0x07, 0x00,// "
0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14,// #
0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12,// $
0x00, 0x62, 0x64, 0x08, 0x13, 0x23,// %
0x00, 0x36, 0x49, 0x55, 0x22, 0x50,// &
0x00, 0x00, 0x05, 0x03, 0x00, 0x00,// '
0x00, 0x00, 0x1c, 0x22, 0x41, 0x00,// (
0x00, 0x00, 0x41, 0x22, 0x1c, 0x00,// )
0x00, 0x14, 0x08, 0x3E, 0x08, 0x14,// *
0x00, 0x08, 0x08, 0x3E, 0x08, 0x08,// +
0x00, 0x00, 0x00, 0xA0, 0x60, 0x00,// ,
0x00, 0x08, 0x08, 0x08, 0x08, 0x08,// -
0x00, 0x00, 0x60, 0x60, 0x00, 0x00,// .
0x00, 0x20, 0x10, 0x08, 0x04, 0x02,// /
0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E,// 0
0x00, 0x00, 0x42, 0x7F, 0x40, 0x00,// 1
0x00, 0x42, 0x61, 0x51, 0x49, 0x46,// 2
0x00, 0x21, 0x41, 0x45, 0x4B, 0x31,// 3
0x00, 0x18, 0x14, 0x12, 0x7F, 0x10,// 4
0x00, 0x27, 0x45, 0x45, 0x45, 0x39,// 5
0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30,// 6
0x00, 0x01, 0x71, 0x09, 0x05, 0x03,// 7
0x00, 0x36, 0x49, 0x49, 0x49, 0x36,// 8
0x00, 0x06, 0x49, 0x49, 0x29, 0x1E,// 9
0x00, 0x00, 0x36, 0x36, 0x00, 0x00,// :
0x00, 0x00, 0x56, 0x36, 0x00, 0x00,// ;
0x00, 0x08, 0x14, 0x22, 0x41, 0x00,// <
0x00, 0x14, 0x14, 0x14, 0x14, 0x14,// =
0x00, 0x00, 0x41, 0x22, 0x14, 0x08,// >
0x00, 0x02, 0x01, 0x51, 0x09, 0x06,// ?
0x00, 0x32, 0x49, 0x59, 0x51, 0x3E,// @
0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C,// A
0x00, 0x7F, 0x49, 0x49, 0x49, 0x36,// B
0x00, 0x3E, 0x41, 0x41, 0x41, 0x22,// C
0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C,// D
0x00, 0x7F, 0x49, 0x49, 0x49, 0x41,// E
0x00, 0x7F, 0x09, 0x09, 0x09, 0x01,// F
0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A,// G
0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F,// H
0x00, 0x00, 0x41, 0x7F, 0x41, 0x00,// I
0x00, 0x20, 0x40, 0x41, 0x3F, 0x01,// J
0x00, 0x7F, 0x08, 0x14, 0x22, 0x41,// K
0x00, 0x7F, 0x40, 0x40, 0x40, 0x40,// L
0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F,// M
0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F,// N
0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E,// O
0x00, 0x7F, 0x09, 0x09, 0x09, 0x06,// P
0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E,// Q
0x00, 0x7F, 0x09, 0x19, 0x29, 0x46,// R
0x00, 0x46, 0x49, 0x49, 0x49, 0x31,// S
0x00, 0x01, 0x01, 0x7F, 0x01, 0x01,// T
0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F,// U
0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F,// V
0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F,// W
0x00, 0x63, 0x14, 0x08, 0x14, 0x63,// X
0x00, 0x07, 0x08, 0x70, 0x08, 0x07,// Y
0x00, 0x61, 0x51, 0x49, 0x45, 0x43,// Z
0x00, 0x00, 0x7F, 0x41, 0x41, 0x00,// [
0x00, 0x55, 0x2A, 0x55, 0x2A, 0x55,// 55
0x00, 0x00, 0x41, 0x41, 0x7F, 0x00,// ]
0x00, 0x04, 0x02, 0x01, 0x02, 0x04,// ^
0x00, 0x40, 0x40, 0x40, 0x40, 0x40,// _
0x00, 0x00, 0x01, 0x02, 0x04, 0x00,// '
0x00, 0x20, 0x54, 0x54, 0x54, 0x78,// a
0x00, 0x7F, 0x48, 0x44, 0x44, 0x38,// b
0x00, 0x38, 0x44, 0x44, 0x44, 0x20,// c
0x00, 0x38, 0x44, 0x44, 0x48, 0x7F,// d
0x00, 0x38, 0x54, 0x54, 0x54, 0x18,// e
0x00, 0x08, 0x7E, 0x09, 0x01, 0x02,// f
0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C,// g
0x00, 0x7F, 0x08, 0x04, 0x04, 0x78,// h
0x00, 0x00, 0x44, 0x7D, 0x40, 0x00,// i
0x00, 0x40, 0x80, 0x84, 0x7D, 0x00,// j
0x00, 0x7F, 0x10, 0x28, 0x44, 0x00,// k
0x00, 0x00, 0x41, 0x7F, 0x40, 0x00,// l
0x00, 0x7C, 0x04, 0x18, 0x04, 0x78,// m
0x00, 0x7C, 0x08, 0x04, 0x04, 0x78,// n
0x00, 0x38, 0x44, 0x44, 0x44, 0x38,// o
0x00, 0xFC, 0x24, 0x24, 0x24, 0x18,// p
0x00, 0x18, 0x24, 0x24, 0x18, 0xFC,// q
0x00, 0x7C, 0x08, 0x04, 0x04, 0x08,// r
0x00, 0x48, 0x54, 0x54, 0x54, 0x20,// s
0x00, 0x04, 0x3F, 0x44, 0x40, 0x20,// t
0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C,// u
0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C,// v
0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C,// w
0x00, 0x44, 0x28, 0x10, 0x28, 0x44,// x
0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C,// y
0x00, 0x44, 0x64, 0x54, 0x4C, 0x44,// z
0x14, 0x14, 0x14, 0x14, 0x14, 0x14,// horiz lines
};

显示字符串的函数:

uint8_t  GUI_PutChar(uint8_t x, uint8_t y, uint8_t ch)
{  uint8_t   font_dat;
   uint8_t   i, j;
   uint8_t  bakc;

   if( x>(GUI_LCM_XMAX-8) ) return(0);
   if( y>(GUI_LCM_YMAX-8) ) return(0);
   ch=ch-' ';
   for(i=0; i<6; i++)
   {
      font_dat = F6x8[ch][i];

      for(j=0; j<8; j++)
         bakc = font_dat&(1<<j);
         OLED_DrawPoint(x, y, bakc);       
         y++;
      }

      x++;
      y -= 8;
   }

   return(1);
}

void GUI_ShowString(uint16_t x, uint16_t y, uint8_t * ch)
{
    uint8_t i = 0;
    while(ch[i] != '\0')
    {
        GUI_PutChar(x,y,ch[i]);
        x+=6;
        i++;
    }
}

GUI的简单应用

我这里简单创建了一个Windows窗口的显示库:

typedef  struct
{  uint8_t  x;          //窗口位置(左上角的x坐标)
   uint8_t  y;          //窗口位置(左上角的y坐标)

   uint8_t  with;       //窗口宽度
   uint8_t  hight;      //窗口高度

   uint8_t   *title;        //定义标题栏指针(标题字符为ASCII字符串,最大个数受窗口显示)
   uint8_t   *state;        //定义状态栏指针(若为空时则不显示状态栏)
} WINDOWS;

uint8_t  GUI_WindowsDraw(WINDOWS *win)
{
   uint8_t  *str;
   signed short  bak, bak1, bak2;

   if( ( (win->with)<20 ) || ( (win->hight)<20 ) ) return(0);
   if( (win->x + win->with ) > GUI_LCM_XMAX ) return(0);
   if( (win->y + win->hight ) > GUI_LCM_YMAX ) return(0);

   /* 开始画窗口 */
   OLED_RectangleFill(win->x, win->y, win->x + win->with - 1, win->y + win->hight - 1, 0);
   OLED_Rectangle(win->x, win->y, win->x + win->with - 1, win->y + win->hight - 1, 1);
   OLED_HLine(win->x, win->y + 12, win->x + win->with - 1, 1);
   OLED_RLine(win->x + 12, win->y, win->y + 12, 1);
   OLED_Line(win->x, win->y, win->x + 12, win->y + 12, 1);
   OLED_Line(win->x + 12, win->y, win->x, win->y + 12, 1);

   /* 写标题 */
   if( win->title != NULL ) 
   {  str = win->title;
      bak = win->x + 15;
      bak1 = win->y + 3;
      bak2 = win->x + win->with -1;

      while(1)                      
      {  if( (bak+8) > bak2 ) break;
         if(*str=='\0') break;

         GUI_PutChar(bak, bak1, *str++);
         bak += 6;
      }
   }

   /* 写状态栏 */
   if( win->state != NULL )
   {  if( win->hight < 40) return(0);
      /*画状态栏 */
      OLED_HLine(win->x, win->y + win->hight - 11, win->x + win->with - 1, 1);

      str = win->state;
      bak = win->x + 3;
      bak1 = win->y + win->hight - 9;
      bak2 = win->x + win->with -1;

      while(1)                      
      {  if( (bak+8) > bak2 ) break;
         if(*str=='\0') break;

         GUI_PutChar(bak, bak1, *str++);
         bak += 6;
      }      
   }

   return(1);

}

调用如下:

uint8_t head[]={"Win_XP"};
uint8_t text[]={"Well Done !"};

Win_Dis(10,10,100,45,head,text);

最终效果如下: image

参考

LUCKandII commented 4 years ago

舒服了😍

imuncle commented 4 years ago

找到一个很强大的OLED GUI库,u8g2,以及有一篇挺详细的API解读:玩转u8g2 OLED库,一篇就够

qzl-core1 commented 4 years ago

这个很棒,但是不知道为什么移植到51单片机上就不行了0.0

imuncle commented 4 years ago

这个很棒,但是不知道为什么移植到51单片机上就不行了0.0

@keven-chain 可能是超出了51单片机的内存限制