老刘示波器项目开发经验学习

       最近通过B站下载到“老刘爱鼓捣”的开源资料,是基于51单片机+OLED做的示波器,自己在复现过程中也付出了许多时间和经济成本,现将所学程序中的知识总结如下:

1、如果有需要资料,可自行B站搜索,如也要复现则首先建议直接买51开发板,加上外围器件,我自己买51芯片焊接时,容易出现无法烧写程序和OLED显示屏不亮的情况。

2、通过EEProm 储存 读取设定参数,并且将检查合法性作为一个函数,统一检查各参数。

bit Save_Options()
{
    uint8 ops[15];
    uint8 *p;
    check_Options();
    p = ops;
    *p++ = Lsb >> 8;       //дÈëLsb¸ß8λ uint16
    *p++ = Lsb;            //дÈëLsbµÍ8λ
    *p++ = PlotMode;       //дÈë»æͼģʽ bit
    *p++ = ScaleH;         //дÈëʱ¼äÇø¼ä char
    *p++ = ScaleV_Auto;    //¶ÁÈ¡×Ô¶¯Á¿³Ì±êÖ¾ bit
    *p++ = RulerVMax >> 8; //дÈë×ÝÖáµçѹ¸ß8λ int16
    *p++ = RulerVMax;      //дÈë×ÝÖáµçѹµÍ8λ
    *p++ = RulerVMin >> 8; //дÈë×ÝÖáµçѹ¸ß8λ int16
    *p++ = RulerVMin;      //дÈë×ÝÖáµçѹµÍ8λ
    *p++ = TriLevel >> 8;  //дÈë´¥·¢Öµ¸ß8λ int16
    *p++ = TriLevel;       //дÈë´¥·¢ÖµµÍ8λ
    *p++ = TriMode;        //дÈë´¥·¢·½Ê½ int8
    *p++ = TriSlope;       //дÈë´¥·¢·½Ïò bit
    *p++ = WaveScroll;     //дÈ벨Ðιö¶¯±êÖ¾ bit
    *p = OLED_Brightness;  //дÈëOLEDÁÁ¶È uint8

    //	printf("Lsb=%hu\r\n",Lsb);
    //	printf("DrawMode=%X\r\n",PlotMode);
    //	printf("ScaleH=%bd\r\n",ScaleH);
    //	printf("ScaleV_Auto=%X\r\n",ScaleV_Auto);
    //	printf("RulerVMax=%hd\r\n",RulerVMax);
    //	printf("RulerVMin=%hd\r\n",RulerVMin);
    //	printf("TriLevel=%hd\r\n",TriLevel);
    //	printf("TriMode=%bd\r\n",TriMode);
    //	printf("TriSlope=%X\r\n",TriSlope);
    //	printf("WaveScroll=%X\r\n",WaveScroll);
    //	printf("OLED_Brightness=%bu\r\n",OLED_Brightness);
    //	printf("\r\n");
    return EEPROM_Save(ops, sizeof(ops) / sizeof(ops[0]));
}
void Read_Options()
{
    uint8 ops[15];
    uint8 *p;
    p = ops;
    EEPROM_Read(ops, sizeof(ops) / sizeof(ops[0]));
    Lsb = *p++;           //¶ÁÈ¡Lsb¸ß8λ uint16
    Lsb <<= 8;            //½«µÍ8λÒƵ½¸ß°Ëλ
    Lsb |= *p++;          //¶ÁÈ¡LsbµÍ8λ
    PlotMode = *p++;      //¶ÁÈ¡»æͼģʽ bit
    ScaleH = *p++;        //¶Áȡʱ¼äÇø¼ä char
    ScaleV_Auto = *p++;   //¶ÁÈ¡×Ô¶¯Á¿³Ì±êÖ¾ bit
    RulerVMax = *p++;     //¶ÁÈ¡×ÝÖáµçѹ×î´óÖµ¸ß8λ int16
    RulerVMax <<= 8;      //½«µÍ8λÒƵ½¸ß°Ëλ
    RulerVMax |= *p++;    //¶ÁÈ¡×ÝÖáµçѹ×î´óÖµµÍ8λ
    RulerVMin = *p++;     //¶ÁÈ¡×ÝÖáµçѹ×îС¸ß8λ int16
    RulerVMin <<= 8;      //½«µÍ8λÒƵ½¸ß°Ëλ
    RulerVMin |= *p++;    //¶ÁÈ¡×ÝÖáµçѹ×îСµÍ8λ
    TriLevel = *p++;      //¶ÁÈ¡´¥·¢Öµ¸ß8λ int16
    TriLevel <<= 8;       //½«µÍ8λÒƵ½¸ß°Ëλ
    TriLevel |= *p++;     //¶ÁÈ¡´¥·¢ÖµµÍ8λ
    TriMode = *p++;       //¶ÁÈ¡´¥·¢·½Ê½ int8
    TriSlope = *p++;      //¶ÁÈ¡´¥·¢·½Ïò bit
    WaveScroll = *p++;    //¶ÁÈ¡²¨Ðιö¶¯±êÖ¾ bit
    OLED_Brightness = *p; //¶ÁÈ¡OLEDÁÁ¶È uint8

    check_Options(); //¼ì²éÑ¡ÏîºÏ·¨ÐÔ
    //	printf("Lsb=%hu\r\n",Lsb);
    //	printf("DrawMode=%X\r\n",PlotMode);
    //	printf("ScaleH=%bd\r\n",ScaleH);
    //	printf("ScaleV_Auto=%X\r\n",ScaleV_Auto);
    //	printf("RulerVMax=%hd\r\n",RulerVMax);
    //	printf("RulerVMin=%hd\r\n",RulerVMin);
    //	printf("TriLevel=%hd\r\n",TriLevel);
    //	printf("TriMode=%bd\r\n",TriMode);
    //	printf("TriSlope=%X\r\n",TriSlope);
    //	printf("WaveScroll=%X\r\n",WaveScroll);
    //	printf("OLED_Brightness=%bu\r\n",OLED_Brightness);
    //	printf("\r\n");
}

数组加指针配合,指向数组的指针。

 EEPROM_Read(ops, sizeof(ops) / sizeof(ops[0]));

3、将全局的变量、标志位、数组都放在global.h中,在各个文件中包含该头文件。

4、51单片机的关键字

bit                 位标量声明                 声明一个位标量或位类型的函数

sbit                 位标量声明                 声明一个可位寻址变量

sfr                特殊功能寄存器声明  声明一个特殊功能寄存器

sfr16             特殊功能寄存器声明  声明一个16位的特殊功能寄存器

关键字参考:https://blog.csdn.net/s3olo/article/details/7712586 

关键字code是51单片机特有关键字,用unsigned int 或signed char等定义的变量都存储在单片机的RAM中,程序中可以随意更改这些变量的值。而运用code关键字修饰下定义的变量,比如unsigned char code i;,它们则存储在单片机程序存储空间FLASH中,节省单片机RAM资源,但在程序中不能更改这些变量的值。————————————————原文链接:https://blog.csdn.net/m0_46559794/article/details/108006698

uint16 *GetBGV(void)
{
    uint16 *BGV;
    BGV = (uint16 code *)BGV_ADR;
    return BGV;
}

以上程序取BGV寄存器中储存的参考电压数据。

5、OLED的显示操作 在该文件中总结了OLED写数据、写命令,画点、划线、画图的库,如下两个函数是OLED操作最底层函数,其他画点、画线、画字符串、写数字都是存在一个数组buf中,遍历OLED所有点,调用下列函数最终实现绘制。

void OLED_Write_Command(uint8 dat)
{
    uint8 i;
    OLED_DC_Clr();
    OLED_CS_Clr();

    for (i = 0; i < 8; i++)
    {
        OLED_SCLK_Clr();

        if (dat & 0x80) //ÅжÏÊý¾Ý×î¸ßλÊÇ1»¹ÊÇ0
        {
            OLED_SDIN_Set();
        }
        else
            OLED_SDIN_Clr();

        OLED_SCLK_Set();
        dat <<= 1;
    }

    OLED_CS_Set();
    //    OLED_DC_Set();
}

/*  дÊý¾Ý
    Write Data  */
void OLED_Write_Data(uint8 dat)
{
    uint8 i;
    OLED_DC_Set();
    OLED_CS_Clr();

    for (i = 0; i < 8; i++)
    {
        OLED_SCLK_Clr();

        if (dat & 0x80) //ÅжÏÊý¾Ý×î¸ßλÊÇ1»¹ÊÇ0
        {
            OLED_SDIN_Set();
        }
        else
            OLED_SDIN_Clr();

        OLED_SCLK_Set();
        dat <<= 1;
    }

    OLED_CS_Set();
    //    OLED_DC_Set();
}

6、全局变量统一在前面加g 或者加_ ,比如 _flag.

7、绘制一个像素点

/*  绘制一个像素至缓存
    Draw one pixel to buffer    */
void OLED_DrawPixel(uint8 x, uint8 y)
{
    uint8 mask;
    uint8 *pBuf;

    if (_x > WIDTH - 1)
    {
        _x = 0;
        _y += 1;
    }
    if (_y > HEIGHT - 1)
    {
        _y = 0;
    }

    pBuf = &_buf[(y >> 3) * WIDTH + x];
    mask = 1 << (y & 7);
    if (_OLED_Reverse)
    {
        *pBuf++ &= ~mask;
    }
    else
    {
        *pBuf++ |= mask;
    }
}
   pBuf = &_buf[(y >> 3) * WIDTH + x];
    mask = 1 << (y & 7);

 以上两句是写像素至缓存关键,即在_buf中储存相应的值,假设写的点为(4,8),则注意是从_x=0,_y=0开始写的,8是4列第9个点,写入的为一个字节的数据mask,y>>3是判断其所在的页,如果是第8个点(0-7是第一页),则是在第二页。_buf是一个线性数组,存第二页的数则应该是加上第一页的列数,即1*128+4即为要存的位置。

8、统一显示屏幕上所有内容

/*  将缓存内容显示到屏幕上
    Send buffer to display  */
void OLED_Display(void)
{
    uint8 i, j;
    uint8 *pBuf;
    pBuf = _buf;

    for (j = 0; j < PAGES; j++)
    {
        for (i = 0; i < WIDTH; i++)
        {
            OLED_Write_Data(*pBuf++);
        }
    }
}

9、旋转编码器的使用

EC11编码器资料参见如下的链接:

​​​​​​http://www.best-dz.com/Technical/info_itemid_1461.html#:~:text=ec11%E7%BC%96%E7%A0%81%E5%99%A8%E6%98%AF%E7%94%B1%E4%B8%80%E4%B8%AA%E4%B8%AD%E5%BF%83%E6%9C%89%E8%BD%B4%E7%9A%84%E5%85%89%E7%94%B5%E7%A0%81%E7%9B%98%EF%BC%8C%E5%85%B6%E4%B8%BB%E8%A6%81%E5%88%A9%E7%94%A8%E5%85%89%E7%94%B5%E8%BD%AC%E6%8D%A2%E5%8E%9F%E7%90%86%E8%BE%93%E5%87%BA%E4%B8%89%E7%BB%84%E6%96%B9%E6%B3%A2%E8%84%89%E5%86%B2A%E3%80%81B%E5%92%8CZ%E7%9B%B8%E3%80%82,%E4%B8%BA%E4%BA%86%E6%96%B9%E4%BE%BF%E5%9C%B0%E5%88%A4%E6%96%AD%E5%87%BA%E6%97%8B%E8%BD%AC%E6%96%B9%E5%90%91%EF%BC%8CA%E3%80%81B%E4%B8%A4%E7%BB%84%E8%84%89%E5%86%B2%E7%9B%B8%E4%BD%8D%E5%B7%AE%E5%BA%94%E4%B8%BA90o%E3%80%82%20%E8%80%8C%E4%B8%94ec11%E7%BC%96%E7%A0%81%E5%99%A8%E8%BD%B4%E8%BD%AC%E4%B8%80%E5%9C%88%E4%BC%9A%E8%BE%93%E5%87%BA%E5%9B%BA%E5%AE%9A%E7%9A%84%E8%84%89%E5%86%B2%EF%BC%8C%E5%9B%A0%E6%AD%A4%E7%BC%96%E7%A0%81%E5%99%A8%E5%85%89%E6%A0%85%E7%9A%84%E7%BA%BF%E6%95%B0%E5%86%B3%E5%AE%9A%E4%BA%86%E8%84%89%E5%86%B2%E6%95%B0%E3%80%82

P3.3为编码器按键信号,即普通按键一样操作;P3.2和P3.4为编码器脉冲信号,

P3.3链接到外部中断1,

P3.2连接外部中断0,

编码器p3.4 连接普通IO口。 

void Scan_EC11(void)可以判断方向,但其要在INT0中断中使用才可以,因为其只是判断p3.2和p3.4是否相等,单独使用不可判断方向,当在INT0中使用时,INT0中断说明P3.2脚脉冲变化,该处中断设置为上升沿和下降沿均触发。根据波形图,设A-C之间为p3.2,在第一个下降沿触发了中断,在中断中,当p3.2和p3.4(B-C)状态相同(都为低电平),此时可以判断为正传,当状态不同时判断为反转。

旋转编码器还有一个开关信号,指示按下和弹起两个状态。于是可以组合出正传、反转、按下、弹起、按下正传、按下反转、等多种组合方式。

图源于:旋转编码开关 EC11 的工作原理_lovelijiapu的博客-CSDN博客

 

sbit EC11_KEY = P3 ^ 3; //编码器按键连接外部中断1 connect to External Interrupt 1
sbit EC11_A = P3 ^ 2;   //编码器A脉冲连接外部中断0 connect to External Interrupt 0
sbit EC11_B = P3 ^ 4;   //编码器B脉冲连接普通IO口 connect to GPIO


void Scan_EC11(void)
{
    /* 延时去抖动
       Delay to remove jitter */
    Delay500us();

    /* 正转
       Clockwise */
    if (EC11_A != EC11_B)
    {
        Change_Val(1);
    }

    /* 反转
       Anticlockwise */
    else if (EC11_A == EC11_B)
    {
        Change_Val(0);
    }
}

/* 编码器旋转中断
   Interrput for Encoder Rotated */
void INT0_interrupt(void) interrupt INT_0
{
    Delay500us();

    /* 编码器是否被按下
       Whether the Encoder is pressed */
    if (!EC11_KEY)
        EC11PressAndRotate = 1;
    else
        EC11PressAndRotate = 0;

    Scan_EC11();
    ADCInterrupt = 1;
    DisplayUpdate = 1;
    IE1 = 0; //清零外部中断1标志位
    IE0 = 0; //清零外部中断0标志位
}

/* 编码器点击中断
   Interrput for Encoder Pressed */
void INT1_interrupt(void) interrupt INT_1
{
    Delay50ms();
    if (!EC11_KEY)
    {
        EC11PressAndRotate = 0;

        /*  长按编码器按键 - 切换主界面和设置界面
            Long presse Encoder - Switch main interface and settings interface */
        if (Delay800ms_Long_Press())
        {
            InSettings = ~InSettings;

            /*  进入设置界面
                Enter Settings  */
            if (InSettings)
            {
                DisplayUpdate = 1;
                UpdateVbat = 1;
                TF0 = 0; //清零定时器0溢出标志
                TR0 = 1; //定时器0开始计时,开始电池电压信息更新计时
                IE0 = 0; //清零外部中断0中断标志
                EX0 = 1; //开启外部中断0(编码器旋转)
            }

            /*  回到主界面
                Retrurn to main interface   */
            else
            {
                TR0 = 0; //清零定时器0溢出标志
                TF0 = 0; //定时器0停止计时,停止电池电压信息更新计时
                WaveFreq = 0;
                TriFail = 0;
                VMax = 0;
                VMin = 0;
                DisplayUpdate = 1;
                WaveUpdate = 1;
                ClearWave = 0;
            }
            ADCInterrupt = 1;
        }

        /*  按住编码器同时旋转
            Rotate Encoder while pressing  */
        else if (EC11PressAndRotate)
        {
            /* 由编码器旋转中断执行操作
               Operations Performed by Interrupt of Encoder Rotation */
        }

        /* 双击编码器按键 - 在主界面,滚动波形和选项模式之间切换
           Double click Encoder - Switch between Waveform Scroll Mode and Parameter Mode in Main Interface */
        else if (Delay300ms_Double_Click())
        {
            /* 主界面
               Main Interface*/
            if (!InSettings)
            {
                WaveScroll = ~WaveScroll;
                OptionChanged = 1;
                ADCInterrupt = 1;
                DisplayUpdate = 1;
                ClearWave = 0;
            }
        }

        /*  单击编码器按键 - 在主界面,切换Stop/Run状态
            Single click Encoder - Switch Run/Stop in main interface    */
        else if (!InSettings)
        {
            EX0 = 0;
            ADCRunning = ~ADCRunning;
            if (ADCRunning)
            {
                WaveUpdate = 1;
                ClearWave = 1;
            }
            else
            {
                DisplayUpdate = 1;
                WaveUpdate = 1;
            }
            ADCInterrupt = 1;
            IE0 = 0;
            IE1 = 0;
        }
    }
    IE1 = 0;
}

 if (Delay800ms_Long_Press())  编码器长按,直接在if()中调用有返回值的函数,函数中如果编码器弹起或者旋转则返回,如果800ms内没变化则返回1。

bit Delay800ms_Long_Press() //@27.000MHz
{
    unsigned char i, j, k;
    _nop_();
    _nop_();
    i = 110;
    j = 148;
    k = 166;

    do
    {
        do
        {
            /* 编码器松开或转动
               Encoder released or rotated */
            if (EC11_KEY || EC11PressAndRotate)
                return 0;

            while (--k)
                ;
        } while (--j);
    } while (--i);

    return 1;
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值