加入星計(jì)劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴(kuò)散
  • 作品版權(quán)保護(hù)
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • 問題由來
    • 問題分析
    • 實(shí)現(xiàn)目標(biāo)
    • 顯示原理
    • 畫點(diǎn)函數(shù)
    • 畫線函數(shù)
    • 結(jié)果展示
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

讓示波器數(shù)據(jù)顯示更直觀——OLED曲線顯示

2021/11/12
830
閱讀需 5 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

大家好,我是程序員小哈。

問題由來

前兩天有網(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ì)開源)。

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

公眾號(hào)『嵌入式從0到1』,號(hào)主:程序員小哈,是一個(gè)軟硬件全棧開發(fā)工程師(12年工作經(jīng)驗(yàn)的老司機(jī)),電子發(fā)燒友論壇鴻蒙版塊版主,公眾號(hào)內(nèi)容專注于嵌入式學(xué)習(xí)。堅(jiān)持原創(chuàng),寫有圖、有視頻的保姆級(jí)教程文章,篇篇有干貨。做一個(gè)講清楚,說明白,大家學(xué)得會(huì)的交流平臺(tái)。