Open imuncle opened 4 years ago
我平时最喜欢玩的是中景园的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,不支持读取屏上某一点的显示内容。
图形用户界面(Graphical User Interface,简称 GUI,又称图形用户接口)是指采用图形方式显示的计算机操作用户界面。目前网上已经有很多成熟的专门针对嵌入式系统的GUI,比如emWIN,MicroWindows,MiniGui,ZLG AWTK等。
实现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++; } }
我这里简单创建了一个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
@keven-chain 可能是超出了51单片机的内存限制
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显示出来,效率也提高了不少。
操作点
直接上代码
读取某点的像素值:
画线
画水平线
画垂直线
画任意两点之间的直线
画矩形
画实心矩形
画圆
使用Bresenham法画圆
显示字符
我的字库如下:
显示字符串的函数:
GUI的简单应用
我这里简单创建了一个Windows窗口的显示库:
调用如下:
最终效果如下:
参考