加入星计划,您可以享受以下权益:

  • 创作内容快速变现
  • 行业影响力扩散
  • 作品版权保护
  • 300W+ 专业用户
  • 1.5W+ 优质创作者
  • 5000+ 长期合作伙伴
立即加入
  • 正文
    • 问题由来
    • 问题分析
    • 实现目标
    • 显示原理
    • 画点函数
    • 画线函数
    • 结果展示
  • 相关推荐
  • 电子产业图谱
申请入驻 产业图谱

让示波器数据显示更直观——OLED曲线显示

2021/11/12
830
阅读需 5 分钟
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

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

问题由来

前两天有网友留言,能否做一个显示波形的实例,之前也有人提过类似问题,那么今天我们就来安排一下。

问题分析

我们在网上经常能看到一些大佬用0.96寸OLED制作的迷你示波器

 

制作这个mini示波器,界面中的曲线绘制是一个难点。

小哈哥的主要工作是做上位机VC++开发的,由于要做谱图显示,所以也用到了曲线的绘制,下图中就是使用VC++进行曲线绘制的部分代码,我们可以看出,曲线是由一系列首尾相连的直线组成,所以要想绘制曲线,首先要实现移动到线条起点的函数MoveTo,以及实现画线的函数LineTo。

 

因为直线(其实说成线段更好)是由多个点组成,所以我们要实现画线的函数,只要实现画点的函数即可,然后在计算出来的位置依次画点,即可实现直线的绘制。

实现目标

  • 实现画点函数封装画点函数,进而实现画线函数绘制一个三角形

显示原理

OLED的核心驱动芯片是SSD1306,单片机与SSD1306通信,SSD1306再驱动OLED点亮对应的OLED像素点

要想实现绘制三角形,我们就要先实现画点和绘制直线的函数,在这之前,我们先了解一下OLED的显示原理。

OLED的构造

OLED由128*64个像素组成,64行和128列。

图中每个晶格表示一位图像数据,这些像素点对应SSD1306内部的一个GDDRAM数据内存,它有128*8字节,即128*64bit,每一个位对应一个像素点。

其中,每8行组成一个PAGE,该OLED一共分为8个PAGE(PAGE0~PAGE7)。

我们控制显示的内容,只需要控制SSD1306的内部GDDRAM即可。下面是封装的刷新GDDRAM的函数,其中 unsigned char OLED_GRAM[128][8]; 中缓存的就是待显示的内容,我们先将要显示的内容赋值给这个数组,然后将这个数组整体写入GDDRAM即可,如果这个数组内的数据都为0,则相当于将显示屏清屏(不显示内容)。

void OLED_Refresh_Gram(void)
{
    unsigned char i,n;
    for(i=0;i<8;i++)
    {
        OLED_WR_Byte(0xb0+i,OLED_CMD);  //设置页地址(0~7)
        OLED_WR_Byte(0x00,OLED_CMD);    //设置显示位置—列低地址
        OLED_WR_Byte(0x10,OLED_CMD);    //设置显示位置—列高地址  
         
        for(n=0;n<128;n++)  //写一PAGE的GDDRAM数据
        {
            OLED_WR_Byte(OLED_GRAM[n][i],1);
        }
    }
}

画点函数

由于 OLED_WR_Byte(OLED_GRAM[n][i],1); 函数一次操作一个字节,所以我们不能一次控制一个像素点,只能8个像素点一起控制;而且是垂直方向扫描控制;如下图所示。因此垂直方向坐标可选为0~7;(8*8=64);水平方向可选坐标0~127。

我们封装的画点函数,即随便给一个点的坐标(x,y),我们要计算出,这个像素点所属的PAGE,然后看控制的是这列8个像素(对应一个字节数据)中的哪一个(对应1 bit数据)。

void OLED_DrawDot(unsigned char x,unsigned char y,unsigned char t)
{
 unsigned char pos,bx,temp=0;
    
 // 此OLED的分辨率为128*64,横坐标大于127,纵坐标大于63,则参数非法 
    
 if(x>127||y>63) return;
    
 // 因为此OLED是按页显示,每页8个像素,所以/8用于计算待显示的点在哪页中
 pos=(y)/8;
    
 // 一列中有8个像素,所以计算一下待显示的点,在当前列中的第几个点
 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,其他位值不变
        
 // 刷新整个液晶屏
 OLED_Refresh_Gram(); 
}

参数说明:

  • x:显示的横坐标,即一行128个像素中的哪一个像素点y:显示的纵坐标,即一列64个像素中的哪一个像素点t:0表示该像素不显示,1表示该像素显示

画线函数

画点的函数我们已经实现了,那么要想画一条直线,我们就要计算出直线上都有哪些点,将直线上的点依次用画点函数绘制出来,即完成了直线的绘制。

那怎么来求得直线上的任意点的坐标呢?如下图所示,一般绘制一个直线都会给两个已知点(x1,y1),(x2,y2),有了这两个点的坐标,我们就可以求出这条直线的斜率,然后根据这个斜率和横轴的范围(x1,x2),依次代入直线方程,即可求出所有直线上的点的坐标。

我们举个栗子,加深一下理解:

如果(x1=32,y1=48),(x2=96,y2=16),那么:

曲线斜率:K =(16-48)/(96-32)= -0.5==>该曲线上任意点坐标关系为:y = 48+K*(x – 32)

如果x=64, 因为 K=-0.5,所以 y=48-0.5*(64-32)=32 。

所以,(x=64,y=32)。

利用斜率法,我们封装画线函数如下:

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);
  }
 }
}

注意:因为水平线和垂直线比较特殊,所以上面函数中对这两种情况进行了单独的绘制,没有使用斜率法计算直线上的坐标。

结果展示

我们按如下坐标绘制一个三角形:

有了画线函数,我们只要将上面三个点的坐标依次代入画线函数即可,绘制三角形的代码具体如下所示:

OLED_DrawLine(32, 48, 96,16);
OLED_DrawLine(96, 16, 96,48);
OLED_DrawLine(96, 48, 32,48);

编译代码生成结果如下:

上面的板子使用的是综合实例《基于手势控制的吸油烟机》的PCB板(暂时尚未完成,全部验证完毕会开源)。

相关推荐

电子产业图谱

公众号『嵌入式从0到1』,号主:程序员小哈,是一个软硬件全栈开发工程师(12年工作经验的老司机),电子发烧友论坛鸿蒙版块版主,公众号内容专注于嵌入式学习。坚持原创,写有图、有视频的保姆级教程文章,篇篇有干货。做一个讲清楚,说明白,大家学得会的交流平台。