imuncle.github.io
imuncle.github.io copied to clipboard
OLED简单GUI开发
OLED简介
我平时最喜欢玩的是中景园的0.96寸的单色OLED,有IIC通信和SPI通信两款,上面的驱动芯片是SSD1306,用起来非常方便。

该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_HLine(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t color)
{
uint8_t temp;
if(x0>x1)
{
temp = x1;
x1 = x0;
x0 = temp;
}
do
{
OLED_DrawPoint(x0, y0, color);
x0++;
}
while(x1>=x0);
}
- 画垂直线
void OLED_RLine(uint8_t x0, uint8_t y0, uint8_t y1, uint8_t color)
{
uint8_t temp;
if(y0>y1)
{
temp = y1;
y1 = y0;
y0 = temp;
}
do
{
OLED_DrawPoint(x0, y0, color);
y0++;
}
while(y1>=y0);
}
- 画任意两点之间的直线
void OLED_Line(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t color)
{
signed short dx;
signed short dy;
signed char dx_sym;
signed char dy_sym;
signed short dx_x2;
signed short dy_x2;
signed char di;
dx = x1-x0;
dy = y1-y0;
if(dx>0)
{
dx_sym = 1;
}
else
{
if(dx<0)
{
dx_sym = -1;
}
else
{
OLED_RLine(x0, y0, y1, color);
return;
}
}
if(dy>0)
{
dy_sym = 1;
}
else
{
if(dy<0)
{
dy_sym = -1;
}
else
{
OLED_HLine(x0, y0, x1, color);
return;
}
}
dx = dx_sym * dx;
dy = dy_sym * dy;
dx_x2 = dx*2;
dy_x2 = dy*2;
if(dx>=dy)
{
di = dy_x2 - dx;
while(x0!=x1)
{
OLED_DrawPoint(x0, y0, color);
x0 += dx_sym;
if(di<0)
{ di += dy_x2;
}
else
{ di += dy_x2 - dx_x2;
y0 += dy_sym;
}
}
OLED_DrawPoint(x0, y0, color);
}
else
{ di = dx_x2 - dy;
while(y0!=y1)
{ OLED_DrawPoint(x0, y0, color);
y0 += dy_sym;
if(di<0)
{ di += dx_x2;
}
else
{ di += dx_x2 - dy_x2;
x0 += dx_sym;
}
}
OLED_DrawPoint(x0, y0, color);
}
}
画矩形
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);
}
- 画实心矩形
void OLED_RectangleFill(uint8_t x1,uint8_t y1,uint8_t x2,uint8_t y2,uint8_t dot)
{
uint8_t x,y,i;
if(x1>x2)
{
i = x1;
x1 = x2;
x2 = i;
}
if(y1>y2)
{
i = y1;
y1 = y2;
y2 = i;
}
for(x=x1; x<=x2; x++)
{
for(y=y1; y<=y2; y++)OLED_DrawPoint(x,y,dot);
}
}
画圆
使用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);
最终效果如下:

参考
舒服了😍
找到一个很强大的OLED GUI库,u8g2,以及有一篇挺详细的API解读:玩转u8g2 OLED库,一篇就够
这个很棒,但是不知道为什么移植到51单片机上就不行了0.0
这个很棒,但是不知道为什么移植到51单片机上就不行了0.0
@keven-chain 可能是超出了51单片机的内存限制