大家好,我是程序員小哈。
問題由來
前兩天有網(wǎng)友留言,能否做一個(gè)顯示波形的實(shí)例,之前也有人提過類似問題,那么今天我們就來安排一下。
問題分析
我們在網(wǎng)上經(jīng)常能看到一些大佬用0.96寸OLED制作的迷你示波器。
制作這個(gè)mini示波器,界面中的曲線繪制是一個(gè)難點(diǎn)。
小哈哥的主要工作是做上位機(jī)VC++開發(fā)的,由于要做譜圖顯示,所以也用到了曲線的繪制,下圖中就是使用VC++進(jìn)行曲線繪制的部分代碼,我們可以看出,曲線是由一系列首尾相連的直線組成,所以要想繪制曲線,首先要實(shí)現(xiàn)移動(dòng)到線條起點(diǎn)
的函數(shù)MoveTo,以及實(shí)現(xiàn)畫線
的函數(shù)LineTo。
因?yàn)橹本€(其實(shí)說成線段更好)是由多個(gè)點(diǎn)組成,所以我們要實(shí)現(xiàn)畫線的函數(shù),只要實(shí)現(xiàn)畫點(diǎn)的函數(shù)即可,然后在計(jì)算出來的位置依次畫點(diǎn),即可實(shí)現(xiàn)直線的繪制。
實(shí)現(xiàn)目標(biāo)
- 實(shí)現(xiàn)畫點(diǎn)函數(shù)封裝畫點(diǎn)函數(shù),進(jìn)而實(shí)現(xiàn)畫線函數(shù)繪制一個(gè)三角形
顯示原理
OLED的核心驅(qū)動(dòng)芯片是SSD1306,單片機(jī)與SSD1306通信,SSD1306再驅(qū)動(dòng)OLED點(diǎn)亮對(duì)應(yīng)的OLED像素點(diǎn)。
要想實(shí)現(xiàn)繪制三角形,我們就要先實(shí)現(xiàn)畫點(diǎn)和繪制直線的函數(shù),在這之前,我們先了解一下OLED的顯示原理。
OLED的構(gòu)造
OLED由128*64個(gè)像素組成,64行和128列。
圖中每個(gè)晶格表示一位圖像數(shù)據(jù),這些像素點(diǎn)對(duì)應(yīng)SSD1306內(nèi)部的一個(gè)GDDRAM數(shù)據(jù)內(nèi)存,它有128*8字節(jié),即128*64bit,每一個(gè)位對(duì)應(yīng)一個(gè)像素點(diǎn)。
其中,每8行組成一個(gè)PAGE,該OLED一共分為8個(gè)PAGE(PAGE0~PAGE7)。
我們控制顯示的內(nèi)容,只需要控制SSD1306的內(nèi)部GDDRAM即可。下面是封裝的刷新GDDRAM的函數(shù),其中 unsigned char OLED_GRAM[128][8];
中緩存的就是待顯示的內(nèi)容,我們先將要顯示的內(nèi)容賦值給這個(gè)數(shù)組,然后將這個(gè)數(shù)組整體寫入GDDRAM即可,如果這個(gè)數(shù)組內(nèi)的數(shù)據(jù)都為0,則相當(dāng)于將顯示屏清屏(不顯示內(nèi)容)。
void OLED_Refresh_Gram(void)
{
unsigned char i,n;
for(i=0;i<8;i++)
{
OLED_WR_Byte(0xb0+i,OLED_CMD); //設(shè)置頁地址(0~7)
OLED_WR_Byte(0x00,OLED_CMD); //設(shè)置顯示位置—列低地址
OLED_WR_Byte(0x10,OLED_CMD); //設(shè)置顯示位置—列高地址
for(n=0;n<128;n++) //寫一PAGE的GDDRAM數(shù)據(jù)
{
OLED_WR_Byte(OLED_GRAM[n][i],1);
}
}
}
畫點(diǎn)函數(shù)
由于 OLED_WR_Byte(OLED_GRAM[n][i],1);
函數(shù)一次操作一個(gè)字節(jié),所以我們不能一次控制一個(gè)像素點(diǎn),只能8個(gè)像素點(diǎn)一起控制;而且是垂直方向掃描控制;如下圖所示。因此垂直方向坐標(biāo)可選為0~7;(8*8=64);水平方向可選坐標(biāo)0~127。
我們封裝的畫點(diǎn)函數(shù),即隨便給一個(gè)點(diǎn)的坐標(biāo)(x,y),我們要計(jì)算出,這個(gè)像素點(diǎn)所屬的PAGE,然后看控制的是這列8個(gè)像素(對(duì)應(yīng)一個(gè)字節(jié)數(shù)據(jù))中的哪一個(gè)(對(duì)應(yīng)1 bit數(shù)據(jù))。
void OLED_DrawDot(unsigned char x,unsigned char y,unsigned char t)
{
unsigned char pos,bx,temp=0;
// 此OLED的分辨率為128*64,橫坐標(biāo)大于127,縱坐標(biāo)大于63,則參數(shù)非法
if(x>127||y>63) return;
// 因?yàn)榇薕LED是按頁顯示,每頁8個(gè)像素,所以/8用于計(jì)算待顯示的點(diǎn)在哪頁中
pos=(y)/8;
// 一列中有8個(gè)像素,所以計(jì)算一下待顯示的點(diǎn),在當(dāng)前列中的第幾個(gè)點(diǎn)
bx=y%8;
// 移位,讓temp的第bx位為1
temp=1<<(bx);
if(t)
OLED_GRAM[x][pos]|=temp; //第bx位,置1,其他位值不變
else
OLED_GRAM[x][pos]&=~temp; //第bx位,置0,其他位值不變
// 刷新整個(gè)液晶屏
OLED_Refresh_Gram();
}
參數(shù)說明:
- x:顯示的橫坐標(biāo),即一行128個(gè)像素中的哪一個(gè)像素點(diǎn)y:顯示的縱坐標(biāo),即一列64個(gè)像素中的哪一個(gè)像素點(diǎn)t:0表示該像素不顯示,1表示該像素顯示
畫線函數(shù)
畫點(diǎn)的函數(shù)我們已經(jīng)實(shí)現(xiàn)了,那么要想畫一條直線,我們就要計(jì)算出直線上都有哪些點(diǎn),將直線上的點(diǎn)依次用畫點(diǎn)函數(shù)繪制出來,即完成了直線的繪制。
那怎么來求得直線上的任意點(diǎn)的坐標(biāo)呢?如下圖所示,一般繪制一個(gè)直線都會(huì)給兩個(gè)已知點(diǎn)(x1,y1),(x2,y2),有了這兩個(gè)點(diǎn)的坐標(biāo),我們就可以求出這條直線的斜率,然后根據(jù)這個(gè)斜率和橫軸的范圍(x1,x2),依次代入直線方程,即可求出所有直線上的點(diǎn)的坐標(biāo)。
我們舉個(gè)栗子,加深一下理解:
如果(x1=32,y1=48),(x2=96,y2=16),那么:
曲線斜率:K =(16-48)/(96-32)= -0.5
==>該曲線上任意點(diǎn)坐標(biāo)關(guān)系為:y = 48+K*(x – 32)
如果x=64, 因?yàn)?K=-0.5,所以 y=48-0.5*(64-32)=32 。
所以,(x=64,y=32)。
利用斜率法,我們封裝畫線函數(shù)如下:
void OLED_DrawLine(unsigned int x1, unsigned int y1, unsigned int x2,unsigned int y2)
{
unsigned int t;
int offset_x,offset_y;
int incx,incy,uRow,uCol;
float K = 0.0f;
offset_x=x2-x1;
offset_y=y2-y1;
uRow=x1;
uCol=y1;
if(offset_x>0)
incx=1;
else if(offset_x==0)
incx=0; //垂直線
else
{
incx=-1;
offset_x=-offset_x;
}
if(offset_y>0)
incy=1;
else if(offset_y==0)
incy=0; //水平線
else
{
incy=-1;
offset_y=-offset_y;
}
if(incx==0)
{
for(t=0;t<=offset_y+1;t++ )
{
OLED_DrawDot(uRow,uCol+t*incy,1);
}
}
else if(incy==0)
{
for(t=0;t<=offset_x+1;t++ )
{
OLED_DrawDot(uRow+t*incx,uCol,1);
}
}
else
{
K = (float)(((float)y2-(float)y1)*1.000/((float)x2-(float)x1));
printf("K=%.3frn",K);
for(t=0;t<=offset_x+1;t++ )
{
printf("X=%d,Y=%drn",uRow+t,(u8)(uCol+t*K));
OLED_DrawDot(uRow+t,(u8)(uCol+t*K),1);
}
}
}
注意:因?yàn)樗骄€和垂直線比較特殊,所以上面函數(shù)中對(duì)這兩種情況進(jìn)行了單獨(dú)的繪制,沒有使用斜率法計(jì)算直線上的坐標(biāo)。
結(jié)果展示
我們按如下坐標(biāo)繪制一個(gè)三角形:
有了畫線函數(shù),我們只要將上面三個(gè)點(diǎn)的坐標(biāo)依次代入畫線函數(shù)即可,繪制三角形的代碼具體如下所示:
OLED_DrawLine(32, 48, 96,16);
OLED_DrawLine(96, 16, 96,48);
OLED_DrawLine(96, 48, 32,48);
編譯代碼生成結(jié)果如下:
上面的板子使用的是綜合實(shí)例《基于手勢控制的吸油煙機(jī)》的PCB板(暫時(shí)尚未完成,全部驗(yàn)證完畢會(huì)開源)。