找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 9931|回复: 26
收起左侧

原创:0.96寸单色OLED绘制电子钟表盘的实现

  [复制链接]
ID:351097 发表于 2020-2-27 11:55 | 显示全部楼层 |阅读模式
    一般很多人做电子钟都是那种纯数字显示的,这种比较容易实现,而且51黑电子论坛也有很多例子借鉴,重复去实现也没有什么新意。我花了2天的时间,写程序实现一个像样的电子钟表盘的制作,再加上DS3231时钟芯片之后,就实现一个电子钟的制作。 白色.gif
    接下来,我就详细讲解一下电子钟盘的实现过程。
    钟表是由圆形的钟盘和三条指针构成,要在OLED上实现绘制,需要用到画点函数、取点函数、画线函数、画圆函数、刷新函数。那首先讲一下OLED的图形驱动部分。
    一般我们要操作OLED显示内容,都是直接将往OLED的某行某页写数据,这种方法简单直接,效率比较高。但是,对于像素的细致操作是很不友好的。比如无法通过SPI读取某个像素点的状态,那就无法实现部分自定义像素点的反转。为了更方便操作像素点,这里用使用了一个1024字节的大数组来作为OLED的显存,每个字节可以操控8个像素点,总共可以操控1024×8=8192个像素点,刚好对应0.96寸OLED的128×64的分辨率。实现显存的好处是方便修改操作像素点,每个像素点的状态都一目了然。修改好显存的内容后,直接将显存的数据传到OLED就可以刷新画面了。显存数组的定义如下:
    uint8 OLED_DISPLAY[8][128]; //显示缓冲数组(总共可以表示8192个像素点)
    为了方便定位和操作像素点,使用坐标轴的思想,引入x轴和y轴,其中x轴的范围为0-127(128个像素点),y轴的范围为0-63(64个像素点)。画点函数实现如下:
/**************************************************************************************************
*@函数            OLED_SetPixel
*
*@简述            OLED设置坐标像素点状态
*
*@输入参数        x - x坐标
*                y - y坐标
*                PixelValue - 像素点状态(1:填充,0:清空)
*
*@参数            无
*
*输出参数
*
*无
*
*@返回     无
*说明:规定OLED显示区域左上角顶点处为坐标原点(0,0),
*      x坐标增长方向:向右→
*      y坐标增长方向:向下↓
*      坐标原点(0,0)对应OLED_DISPLAY[0][0],即第零页第一个像素点
*      坐标(127,63)为OLED屏幕又下角顶点
**************************************************************************************************
*/
void OLED_SetPixel(int32 x, int32 y, int32 PixelValue)
{
    int32 pos,bx,temp=0;

    pos = y/8;//计算y坐标所在页
    bx = y%8;//计算位偏移
    temp = 1<<bx;
    if(PixelValue)
    {
        OLED_DISPLAY[pos][x]|=temp;
    }
    else
    {
        OLED_DISPLAY[pos][x]&=~temp;          
    }
}

    很容易看出,OLED_SetPixel函数操作的对象就是显存数组,要点亮某个点,就是将显存的某个字节某个位置1。当然,由于只是修改显存的内容,还没有将显存更新到OLED中,所以不会在OLED不会显示点亮某个点。有画点函数,那肯定也要有取点函数。取点函数,实际上就是读取显存的某个字节某个位的状态。取点函数的实现如下所示:
/**************************************************************************************************
*@函数            OLED_GetPixel
*
*@简述            获取坐标像素点状态
*
*@输入参数        x - x坐标
*                y - y坐标
*
*@参数            无
*
*输出参数
*
*无
*
*@返回          PixelValue - 像素点状态
**************************************************************************************************
*/
int32 OLED_GetPixel(int32 x, int32 y)
{
    int32 pos,bx,temp=0;

    pos = y/8;//计算y坐标所在页
    bx = y%8;//计算位偏移
    temp = 1<<bx;

    if(OLED_DISPLAY[pos][x] & temp)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

    接下来是刷新函数,刷新函数是将显存的数据传输到OLED中,以便实现OLED画面的更新。刷新函数和实现如下所示:
/**************************************************************************************************
*@函数            OLED_Refresh_Display
*
*@简述            OLED更新显示
*
*@输入参数  
*
*@参数            无
*
*输出参数
*
*无
*
*@返回     无
**************************************************************************************************
*/
void OLED_Refresh_Display(void)
{
    uint8 *value;
    value = (uint8*)OLED_DISPLAY;//二级指针转为一级指针
    OLED_WR_Byte (0xb0,OLED_CMD);//开始页:0
    OLED_WR_Byte (0x00,OLED_CMD); //开始列低地址为0
    OLED_WR_Byte (0x10,OLED_CMD); //开始列高地址为0
    OLED_WR_Bytes(value,1024);
}

    先设定好起始页和起始列地址,然后一次性将1024个字节写入到OLED中。由于OLED_DISPLAY为二维指针,需要强制转成一维指针才能传入OLED_WR_Bytes。接下来是画线函数,这里的画线函数涉及到了计算机图形学的内容,采用Bresenham直线算法思想。画线函数的实现如下:
/**************************************************************************************************
*@函数            OLED_DrawLine
*
*@简述            OLED画一条线
*
*@输入参数        iStartX - 起点x坐标
*                iStartY - 起点y坐标
*                iEndX - 终点x坐标
*                iEndY - 终点y坐标
*                fill - 填充(0:不填充,1:填充)
*
*@参数            无
*
*输出参数
*
*无
*
*@返回     无
**************************************************************************************************
*/
void OLED_DrawLine(int16 iStartX, int16 iStartY, int16 iEndX, int16 iEndY, int16 fill)
{
    /*----------------------------------*/
    /* Variable Declaration                                */
    /*----------------------------------*/
    int16  iDx, iDy;
    int16  iIncX, iIncY;
    int16  iErrX = 0, iErrY = 0;
    int16  iDs;
    int16  iCurrentPosX, iCurrentPosY;

    /*----------------------------------*/
    /* Initialize                                                */
    /*----------------------------------*/
    iErrX = 0;
    iErrY = 0;
    iDx = iEndX - iStartX; //X轴差值
    iDy = iEndY - iStartY; //Y轴差值
    iCurrentPosX = iStartX;
    iCurrentPosY = iStartY;

    if(iDx > 0) //X轴差值大于0
    {
        iIncX = 1;
    }
    else
    {
        if(iDx == 0) //X轴差值等于0
        {
            iIncX = 0;
        }
        else   //X轴差值小于0
        {
            iIncX = -1;
            iDx = -iDx; //iDx取反
        }
    }

    if(iDy > 0) //Y轴差值大于0
    {
        iIncY = 1;
    }
    else
    {
        if(iDy == 0) //Y轴差值等于0
        {
            iIncY = 0;
        }
        else    //Y轴差值小于0
        {
            iIncY = -1;
            iDy = -iDy;
        }
    }

    if(iDx > iDy) //斜率小于45°
    {
        iDs = iDx;
    }
    else     //斜率大于等于45°
    {
        iDs = iDy;
    }

    /*----------------------------------*/
    /* Process                                                        */
    /*----------------------------------*/
    for(uint8 i = 0; i <= iDs+1; i++)
    {
        OLED_SetPixel(iCurrentPosX,iCurrentPosY, fill);//当前位置画点
        iErrX += iDx; //X轴偏移
        if(iErrX > iDs)
        {
            iErrX -= iDs;
            iCurrentPosX += iIncX;
        }
        iErrY += iDy; //Y轴偏移
        if(iErrY > iDs)
        {
            iErrY -= iDs;
            iCurrentPosY += iIncY;
        }
    }
}

    Bresenham直线算法实现直线的绘制只用到了简单的加法运算,计算机可以快速生成直线。这里不花篇幅讲解Bresenham直线算法,感兴趣的可以百度查询。OLED_DrawLine函数只要传入起点、终点和填充状态就可以绘制一条实线或者空线。钟表盘指针的显现和消除就可以用这个函来实现。 接下来是画圆函数,画圆函数是基于中点画圆法思想实现的,画圆函数实现如下所示:
/**************************************************************************************************
*@函数            OLED_DrawCircle
*
*@简述            OLED画圆
*
*@输入参数        uiCx - 圆心x坐标
*                uiCy - 圆心y坐标
*                uiRadius - 半径
*                eEdgeColor - 边缘填充(0:不填充,1:填充)
*                eFillColor - 圆内区域填充(0:不填充,1:填充)
*
*@参数            无
*
*输出参数
*
*无
*
*@返回     无
**************************************************************************************************
*/
void OLED_DrawCircle(uint8 uiCx, uint8 uiCy, uint8 uiRadius, uint8 eEdgeColor, uint8 eFillColor)
{
     /*----------------------------------*/
    /* Variable Declaration                                */
    /*----------------------------------*/
    uint8 uiPosXOffset, uiPosYOffset;
    int16 uiPosXOffset_Old, uiPosYOffset_Old;
    int16 iXChange, iYChange, iRadiusError;

    /*----------------------------------*/
    /* Initialize                                                */
    /*----------------------------------*/
    uiPosXOffset                                = uiRadius;
    uiPosYOffset                                = 0;
    uiPosXOffset_Old                        = 0xFFFF;
    uiPosYOffset_Old                        = 0xFFFF;
    iXChange                                        = 1 - 2 * uiRadius;
    iYChange                                        = 1;
    iRadiusError                                = 0;

    /*----------------------------------*/
    /* Process                                                        */
    /*----------------------------------*/
    if(uiRadius < 1) //半径小于1
    {
        OLED_SetPixel(uiCx, uiCy, eEdgeColor);
    }
    else
    {
        while(uiPosXOffset >= uiPosYOffset)
        {
            if((uiPosXOffset_Old != uiPosXOffset) || (uiPosYOffset_Old != uiPosYOffset) )
            {
                // Fill the circle 填充圈圈
                if((uiRadius > 1) && (eFillColor != 2) && (uiPosXOffset_Old != uiPosXOffset))
                {

                    OLED_DrawLine(uiCx-uiPosXOffset, uiCy-uiPosYOffset+1, uiCx-uiPosXOffset, uiCy+uiPosYOffset-1, eFillColor);
                    OLED_DrawLine(uiCx+uiPosXOffset, uiCy-uiPosYOffset+1, uiCx+uiPosXOffset, uiCy+uiPosYOffset-1, eFillColor);
                    uiPosXOffset_Old = uiPosXOffset;
                }
                OLED_DrawLine(uiCx-uiPosYOffset, uiCy-uiPosXOffset+1, uiCx-uiPosYOffset, uiCy+uiPosXOffset-1, eFillColor);
                OLED_DrawLine(uiCx+uiPosYOffset, uiCy-uiPosXOffset+1, uiCx+uiPosYOffset, uiCy+uiPosXOffset-1, eFillColor);
                uiPosYOffset_Old = uiPosYOffset;

                // Draw edge.
                OLED_SetPixel(uiCx+uiPosXOffset, uiCy+uiPosYOffset, eEdgeColor);
                OLED_SetPixel(uiCx-uiPosXOffset, uiCy+uiPosYOffset, eEdgeColor);
                OLED_SetPixel(uiCx-uiPosXOffset, uiCy-uiPosYOffset, eEdgeColor);
                OLED_SetPixel(uiCx+uiPosXOffset, uiCy-uiPosYOffset, eEdgeColor);
                OLED_SetPixel(uiCx+uiPosYOffset, uiCy+uiPosXOffset, eEdgeColor);
                OLED_SetPixel(uiCx-uiPosYOffset, uiCy+uiPosXOffset, eEdgeColor);
                OLED_SetPixel(uiCx-uiPosYOffset, uiCy-uiPosXOffset, eEdgeColor);
                OLED_SetPixel(uiCx+uiPosYOffset, uiCy-uiPosXOffset, eEdgeColor);
            }
            uiPosYOffset++;
            iRadiusError += iYChange;
            iYChange += 2;
            if ((2 * iRadiusError + iXChange) > 0)
            {
                uiPosXOffset--;
                iRadiusError += iXChange;
                iXChange += 2;
            }
        }
    }
}

    这个画圆函数也只是用到简单的加法运算,不涉及到浮点运算,不依赖于<math.h>数学库。除了可以画圆圈的边缘线,还可以填充圆形内部区域。可以用画圆函数绘制钟表盘的外形。
    上面介绍了OLED图形驱动的几个基本图形操作函数,接下来就利用上面的函数来制作电子钟表盘。电子钟表盘的制作难点是指针的位置处理,两点可以确定一条直线,指针的一端是转轴,另一端指向刻度点的方向。转轴的坐标是保持不变的,要实现指针的转动,需要将指针的另一端指向一下一个刻度点的坐标。钟表有60个刻度点,只要找出60个刻度点对应的坐标,就能实现指针的转动。圆是上下左右对称的,只要找出四分之一圆区域的15个刻度点,就不难算出其他45个刻度点。现在的问题是如何计算出这15个刻度点的坐标?
    很容易想到联立直线方程和圆方向就可以交点计算出来。直线方程有15条,每条倾斜角度相差6°,直线斜率可以用tanθ表示,将查表后记录到数组中方便查询:
uint32 angle_tan[] = {
//0°  6°  12°  18°  24°  30°  36°  42°  48°   54°    60°  66°   72°   78°   84°    90°
  0,1051,2126,3249,4452,5773,7265,9004,11106,13763,17320,22460,30776,47046,95143,0xffffffff
};//tanθ扩大1000倍

    通过调用OLED_DrawCircle函数,可以将圆形绘制出来,然后调用OLED_GetPixel函数,遍历显存的数据,可以把构成圆形的像素点的坐标找出来。只要将像素点的坐标带入到直线方程,就能确定哪个像素点可以作为刻度点。直线点斜式方程y=kx+b,为了消除b常量方便计算,将圆心定为坐标原点。那么直线方程就是正比例函数y=kx,其中斜率k可用tanθ表示。寻找刻度点的实现如下所示:
/**************************************************************************************************
*@函数            clock_calculate_coordinate
*
*@简述            计算针轨迹右下部数字15-30的坐标
*
*@输入参数        clock_hand - 类别:时针、分针、秒针
*                length - 指针长度
*
*@参数            无
*
*@返回     
**************************************************************************************************
*/
static void clock_calculate_coordinate(uint8 clock_hand,uint8 length)
{
    uint8 x,y;
    uint8 rad = 0;
    uint16 *coordinate_array;
    double value = 0;
    double angel = 0;
    double cal_x,cal_y;
    double all_value;

    OLED_Clear_Ram();//清显存

    OLED_DrawCircle(CIRCLE_X,CIRCLE_Y,length,1,0);//绘制轨迹
    rad = length;

    switch(clock_hand) //判断指针类型
    {
        case SECOND_HAND:  //秒针
          coordinate_array = second_coordinate;
          break;
        case MINUTE_HAND: //分针
          coordinate_array = minute_coordinate;
          break;
        case HOUR_HAND:  //时针
          coordinate_array = hour_coordinate;
          break;
        default:
          break;
    }

    for(uint8 k = 0; k < 16; k++) //计算轨迹右下部15-30的坐标
    {
       angel = angle_tan[k];
       all_value = 977889999;
       for(uint8 i = CIRCLE_X - 1; i <= CIRCLE_X + rad; i++)
       {
          for(uint8 j = CIRCLE_Y - 1; j <= CIRCLE_Y + rad; j++)
          {
              if(OLED_GetPixel(i,j)) //扫描针的轨迹
              {              
                  cal_x = i-CIRCLE_X;
                  cal_y = j-CIRCLE_Y;
                  value =cal_y*10000 - angel*cal_x;
                  if(value < 0)//负数处理
                  {
                      value = -value;
                  }
                  if(all_value - value> 0) //寻找最合适的坐标
                  {
                      all_value = value;
                      x = i;
                      y = j;                           
                  }                                                
              }
          }
       }
      coordinate_array[k] = y;  //记录y坐标
      coordinate_array[k] <<= 8;
      coordinate_array[k] |= x; //记录x坐标
    }
    OLED_Clear_Ram();//清显存
}   

    选择计算数字15-30的坐标的原因主要是这个区域坐标x和坐标y都是正数,比较好处理。程序分别计算了不同长度的秒针、分针和时针的刻度点坐标,后面就可以根据刻度点来实现秒针、分针和时针的转动。接下来是根据计算保持的刻度点坐标,计算出剩下45个刻度点坐标。区域指圆形的四个扇形区域,钟表上就是0-15、15-30、30-45、45-60这四个区域。对传入的数字先做区域标记,再转成15-30之间的数,这样方便对应刻度点坐标。再根据区域,将刻度点坐标做对称换算即可。
    下面动图测试了一下秒针,可以看到可以360°旋转。说明刻度点坐标都在圆形边沿。
测试1.gif

    圆形主要是显示秒针跑的轨迹而特意绘制出来的,实际在计算出15个坐标后,可以把圆形轨迹删除。分针和时针也是类似的操作,只是轨迹是不同半径的圆而已。可以通过修改如下定义的宏:
#define SECOND_HAND_LENGTH   28    //秒针长度
#define MINUTE_HAND_LENGTH   27    //分针长度
#define HOUR_HAND_LENGTH     18    //时针长度

  再根据秒针、时针和分针的简单关系,程序实现如下:
/**************************************************************************************************
*@函数            clock_show_time
*
*@简述            表盘显示时间函数
*
*@输入参数         time - 时间结构体
*                 state - 状态
*               
*@参数            无
*
*输出参数         无
*
*@返回     
**************************************************************************************************
*/
void clock_show_time(clock_time time,uint8 state)
{
    uint8 x,y;

    if(time.hours > 12) //如果为24小时
    {
      time.hours -= 12;//转为12小时制
    }

    if(state)
    {
        OLED_ShowChar(CIRCLE_X-5,2,'1',12); //数字1
        OLED_ShowChar(CIRCLE_X,2,'2',12); //数字2
        OLED_ShowChar(CIRCLE_X-2,50,'6',12);//数字6
        OLED_ShowChar(CIRCLE_X+24,25,'3',12);//数字3
        OLED_ShowChar(CIRCLE_X-29,25,'9',12);//数字9

        OLED_DrawCircle(CIRCLE_X,CIRCLE_Y,2,1,1);//绘制转轴
    }

    clock_get_coordinate(time.hours*5+time.minutes/12,HOUR_HAND,&x,&y);
    OLED_DrawLine(CIRCLE_X,CIRCLE_Y,x,y,state); //绘制时针

    clock_get_coordinate(time.minutes,MINUTE_HAND,&x,&y);
    OLED_DrawLine(CIRCLE_X,CIRCLE_Y,x,y,state); //绘制分针

    clock_get_coordinate(time.seconds,SECOND_HAND,&x,&y);
    OLED_DrawLine(CIRCLE_X,CIRCLE_Y,x,y,state); //绘制秒针
}

    clock_show_time可以根据所传入的时间结构体来绘制三条指针,参数state是绘制和擦除指针用的。下面是时钟运行函数:
/**************************************************************************************************
*@函数            clock_run
*
*@简述            运行表盘函数
*
*@输入参数         time - 时间结构体
*               
*@参数            无
*
*输出参数         无
*
*@返回     
**************************************************************************************************
*/
void clock_run(clock_time time)
{
    clock_show_time(pre_time,0);//擦除前一次时间轨迹
    clock_show_time(time,1);//显示当前时间
    pre_time = time;//记录最新时间
}

    定义一个全局结构体变量pre_time 来记录上次时间。每次更新时间前,会先把上次的指针轨迹擦除,再绘制新的指针轨迹。下面的动图是秒针、分针和时针的运行过程。
测试2.gif

    外围有12个小点,分别代表数字1-数字12,其中4个小点被数字挡住了。要生成这12个小点,也是比较简单。先将其看成指针的轨迹来生成计算坐标,再从60个坐标中取出数字1-12数字这12个坐标点,把12个坐标点画上,就可以了。效果如下图所示:
刻度盘.jpg

    钟表盘的制作基本算完成了,再加上DS3231时钟芯片,就可以实时显示时间了。效果如下图所示:
成品图.jpg





回复

使用道具 举报

ID:351097 发表于 2020-6-1 15:20 | 显示全部楼层
工程是基于IAR for 8051,完整的工程已经上传到github,链接地址:github.com/sms-wyt/OLED_CLOCK,如果有需要的,可以自行git clone下来。
回复

使用道具 举报

ID:156220 发表于 2020-2-28 10:07 | 显示全部楼层
谢谢楼主的经验分享,学习了,
回复

使用道具 举报

ID:476527 发表于 2020-2-28 13:45 | 显示全部楼层
我前些日子正有这个想法,没想到楼主已经实现了,马克一下,有时间再看
回复

使用道具 举报

ID:207943 发表于 2020-2-28 15:38 | 显示全部楼层
谢谢楼主无私分享,时钟很不错,喜欢,学习了!
回复

使用道具 举报

ID:71535 发表于 2020-2-28 16:06 | 显示全部楼层
这个要顶,虽然还不太明白,要好好学习。
回复

使用道具 举报

ID:631668 发表于 2020-3-6 11:31 | 显示全部楼层
感谢楼主,最近在愁这个
回复

使用道具 举报

ID:35873 发表于 2020-3-6 11:59 | 显示全部楼层
赞  不错  对玩屏的新手大有帮助   
回复

使用道具 举报

ID:686739 发表于 2020-3-6 12:10 来自手机 | 显示全部楼层
谢谢分享
回复

使用道具 举报

ID:706822 发表于 2020-3-12 10:09 | 显示全部楼层
谢谢分享
回复

使用道具 举报

ID:715893 发表于 2020-4-16 21:49 | 显示全部楼层
请问有OLED_DISPLAY这些函数的介绍嘛,想看整个代码 可以嘛。想学一下显存的使用方法
回复

使用道具 举报

ID:351097 发表于 2020-4-18 13:06 | 显示全部楼层
大神666666 发表于 2020-4-16 21:49
请问有OLED_DISPLAY这些函数的介绍嘛,想看整个代码 可以嘛。想学一下显存的使用方法

关于OLED的显示基本操作代码都在上面罗列出来了。OLED_DISPLAY是一个1k大小的数组,也就是显存。对显存的基本操作,只要看OLED_SetPixel、OLED_GetPixel、 OLED_Refresh_Display这三个函数即可。多看几次就明白了。
回复

使用道具 举报

ID:715893 发表于 2020-4-19 14:06 | 显示全部楼层
没有你 发表于 2020-4-18 13:06
关于OLED的显示基本操作代码都在上面罗列出来了。OLED_DISPLAY是一个1k大小的数组,也就是显存。对显存的 ...

OLED_WR_Bytes这些还是不懂,主要想学习显存的使用方法
回复

使用道具 举报

ID:394719 发表于 2020-5-15 10:11 | 显示全部楼层
楼主,请问能把全部代码文件分享一下吗?
回复

使用道具 举报

ID:88256 发表于 2020-5-29 10:46 | 显示全部楼层
谢谢分享!学习一下
回复

使用道具 举报

ID:138443 发表于 2020-5-31 09:24 | 显示全部楼层
怎么不上传整套源码呢???
回复

使用道具 举报

ID:283908 发表于 2020-5-31 10:45 | 显示全部楼层
学习了,论坛有你更精彩,感谢分享。
回复

使用道具 举报

ID:428114 发表于 2020-5-31 16:16 | 显示全部楼层
楼主很给力啊
回复

使用道具 举报

ID:351097 发表于 2020-6-1 16:31 | 显示全部楼层
lizhendong 发表于 2020-5-31 09:24
怎么不上传整套源码呢???

看第一条置顶评论
回复

使用道具 举报

ID:628703 发表于 2020-6-2 16:15 | 显示全部楼层
好东西,谢谢楼主分享!
回复

使用道具 举报

ID:740383 发表于 2020-6-3 11:26 | 显示全部楼层
好资源
回复

使用道具 举报

ID:361734 发表于 2020-6-19 11:24 | 显示全部楼层
楼主请问下是用的什么型号的单片机, 有那么大的RAM供使用的 ;
回复

使用道具 举报

ID:117310 发表于 2020-6-19 20:02 | 显示全部楼层
感谢分享! 对于OLED画线问题也是困扰了我很久,代码下载了研究研究,先谢谢!
回复

使用道具 举报

ID:351097 发表于 2020-6-21 14:31 | 显示全部楼层
starv 发表于 2020-6-19 11:24
楼主请问下是用的什么型号的单片机, 有那么大的RAM供使用的 ;

用STC8A8K64S4
回复

使用道具 举报

ID:272269 发表于 2021-7-23 14:44 | 显示全部楼层
楼主请问一下关于更新的问题  如果一只表针落在 6 这个数字上 到下一次跑动 这一只表针就会向前跑 现在问题来了 在上一次的表针的位置通常来说都是算法消失处理 但是 现在落在6上 这样子 常规处理就会把6这个数字给消失一点  请问这个问题你是这么克服的  请问是周期性的 更新那个表盘 呢 还是有其他方法可以解决这个问题  我也做过这个实验 使用stc12c做的
回复

使用道具 举报

ID:351097 发表于 2021-7-23 17:23 | 显示全部楼层
jizi 发表于 2021-7-23 14:44
楼主请问一下关于更新的问题  如果一只表针落在 6 这个数字上 到下一次跑动 这一只表针就会向前跑 现在问题 ...

如果图省事,每次指针移动都更新表盘。如果要细致处理,比如指针覆盖到数字6的情况,则要先重绘制数字6,再绘制指针。
回复

使用道具 举报

ID:79094 发表于 2024-3-30 00:32 | 显示全部楼层
看起来 很厉害   大神很多啊
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表