RTX51tiny 复杂应用---时钟、温度显示、按键解析

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Zd_c000/article/details/86231499

keil工程下载地址:https://www.lanzous.com/i2u6g5e

 

一、设计需求

在清翔51单片机开发板上,用带RTOS的方式实现下面的功能:

1. 板上有8个段码LED,左边的4个做一个计时器,显示“MSS.Z”,其中M为分钟,SS为秒,Z为n×0.1秒;右侧的4个,其中3个显示从18B20采集的实时温度,最后1个显示键盘的按键值,按键延时显示按键0.3秒(也就是松开按键之后还继续显示0.3秒);

2. 在板上的LCD1602上,构建一个HH:MM:SS 时分秒的钟表,同时将DS18B20的转换结果也显示在这里。

二、题目分析

面对一个复杂的题目,我们需要逐步实现需要的功能。先实现一个独立的功能,然后再增加功能,慢慢就能达到完整的要求。在实现独立功能的时候要考虑代码的可综合性,要尽量构建易于被调用的函数,在后期将会大大方面我们的综合优化。

编写代码的时候,尽量养成良好的习惯。对于一个庞大的工程,其必定可以分割为很多小模块。本题中用到了数码管、LCD1602、DS18B20、矩阵键盘这些外设,我们可以单独构建这些模块的驱动函数,后面再加以综合。

在创建好RTOS工程之后,在工程文件夹下再创建一个hardware文件夹,然后在这个文件夹里为每个外设都创建一个文件夹,如下图所示。

每一个外设都对应一个C文件和H文件,这两个文件放置在各自的文件夹中。这里还需要注意在keil中需要将这几个文件夹添加到编译路径中,要不然软件找不到H文件。在option中的incliude path处添加需要的路径。

做完这些准备工作后,我们就可以来分模块来构建我们的代码了。

三、模块分析

3.1 数码管

3.1.1 数码管显示函数

在使用的51开发板上,一共有8个数码管。这里我们可以编写一个函数,让其每一位可以单独显示任意数字。入口参数即为每一位要显示的数字,后面需要用到显示的地方,只需要传入对应的变量即可。比如构造如下的函数:

void play_SMG(unsigned char led7,led6,led5,led4,led3,led2,led1,led0);

在实际使用的时候,比如想让数码管显示1234567,就只需这样写:play_SMG(1,2,3,4,5,6,7)。一般我们都是要求数码管动态显示的,这时我们把对应的变量传入相应的位置即可。

函数内部的详细代码比较简单参见附录。

3.1.2 数码管闪烁问题

使用RTOS的时候,由于程序是轮循执行的。假如在一个任务中让数码管显示,就存在两个问题。

  • 轮循时间间隔太短,数码管来不及全部显示。
  • 任务数量太多,下一次显示间隔太长。

这里要搞清楚轮循的时间怎么计算。轮循时每个任务执行的时间是固定的,这个时间具体可以通过INT_CLOCK和TIMESHARING相乘得到。默认的INT_CLOCK = 10000,TIMESHARING = 5。也就是每个任务执行时间为50ms。

不光是数码管,每个任务在执行的时候都存在这个问题。在调试数码管的时候,会发现闪烁严重,就是上面两个因素的影响。相比之下,后者影响更大。假如有4个任务,按照默认的时间片设定,每次显示之后就要等待至少150ms。这个间隔内数码管是不显示的,就会看到数码管间歇性亮,或者很晃眼。

这时候可以通过更改时间片来获得比较好的显示效果。

3.2 时钟

3.2.1 产生时间基准

我们可以通过让数码管显示一个时钟来验证我们的函数正确性,同时这也是题目要求的一部分。

对于时钟的构建,可以先设定一个最小的时间基准,比如100ms,每个100ms对应的变量自加一次。10个100ms之后即1s的时候,代表秒的变量自加一次。当秒个位加到9的时候,十位加一,当59秒的时候再过1秒,秒清零同时分的个位加一……,这个过程写起来比较繁琐,需要细心不要搞错了。

这里的主要问题是怎么产生100ms的时间间隔。我们可以在一个任务中产生我们的时钟。100ms的间隔用延时函数来产生。

关于RTOS的延时,系统中给了os_wait2( ),这个函数有两个输入参数,详细可以看帮助文档。这里需要注意一个tick代表多长时间,这个可以在Conf_tny.51文件中查看,通过INT_CLOCK的值来计算,默认值为10000,如果使用12M的晶振,那么这里就是10ms,也就是说如果我们写了os_wait2( K_TMO,1),就是代表延时10ms。我们会发现这里最小的延时单位只能是10ms。可以更改INT_CLOCK的值来减小延时单位长度。这里我将INT_CLOCK的值改为了1000,那么一个延时单位就是1ms。需要注意,os_wait2( )中的参数类型是unsigned char,意味着我们最大只能写255,如果需要更长的延时,可以通过for循环来构建。

3.2.2 时钟显示

定义用来表示时钟的变量:uchar H1,H0,M1,M0,S1,S0,Z;

H、M、S分别为时分秒,Z为n×0.1秒。时钟的产生比较简单,就是需要的逻辑性比较强,详细的代码参见附录。

然后如下编写job0的代码:

void job0 (void) _task_ 0

{

    os_create_task (1);

    while (1)

    {

          play_SMG(M0,S1,S0,Z,0,0,0,0);     

    }

}

可以看到前四个数码管显示了计时器的值,后四个数码管一直显示0。这说明我们的代码是没有问题的。对于时钟的产生,这里采用了比较直观的方式去产生,易于理解,但是写起来需要注意代码的逻辑性。我们也可以尝试使用更简洁的方式去构建。

通过这个过程我们也可以发现代码的综合其实并不难,就是一步一步添加功能的过程。

3.3 键盘显示

3.3.1 矩阵键盘扫描

矩阵键盘扫描的函数有很多,它的原理网上也讲的很多,不再赘述。

很多时候我们要做的并不是自己如何去写一个器件的驱动程序,是如何把别人拿过来用。看懂别人的代码,可以改善并融合到自己的程序中是最好的。

假如现在手头有一个键盘扫描的代码,当检测到某个按键按下的时候会返回一个值,不同的按键按下有不同的返回值。该怎么将其加入到我们的代码中呢?

这里可以创建一个任务用来进行按键检测,并创建一个变量key用来表示按键值。通过不同的按键返回值来给key进行赋值。截取部分代码如下:

void keys (void) _task_ 3

{

    while (1)

    {

        switch( KeyScan() )

        {

            //第一行键值码

            case 0xee: key = 0;       break;

            case 0xde: key = 1;       break;

            case 0xbe: key = 2;       break;

            case 0x7e: key = 3;       break;

            ……

        }

    }

}

在任务三中key的值将根据按键的值改变。接下来我们只需要将按键的值送给数码管显示即可。play_SMG(M0,S1,S0,Z,0,0,0,key),将key放在数码管显示函数的最后一位。这样我们又完成了一个功能的添加,也没有改变程序原本运行的状态。

3.3.2 清除按键显示

在题目中要求按键延时显示按键0.3秒(也就是松开按键之后还继续显示0.3秒);也就是在手松开0.3秒之后要清除显示。

这里可以设置一个标志位flag,当按键按下的时候让flag置1,并且延时300ms后将flag清0。这样我们就可以通过flag的状态判断按键有没有按下,并且flag是滞后于手松开300ms刷新的。具体的代码如下:

unsigned char KeyScan() //带返回值的子函数

{

   unsigned char i;

      unsigned char cord_l,cord_h;//声明列线和行线的值的储存变量

      P3 = 0x0f;//0000 1111

      if( (P3 & 0x0f) != 0x0f)//判断是否有按键按下

      {

        os_wait2(K_TMO,5);//软件消抖

             if( (P3 & 0x0f) != 0x0f)//判断是否有按键按下

             {

                      cord_h = P3 & 0x0f;// 储存行线值

                      P3 = cord_l | 0xf0;

                      cord_l = P3 & 0xf0;// 储存列线值

            flag = 1;

            return (cord_l + cord_h);//返回键值码                    

             }    

      }

    if(flag)

    {

        for(i = 0; i < 3; i++)

        {

            os_wait2(K_TMO,100);

        }

        flag = 0;

    }

    return 0;

}

为了得到题目要求的显示效果,我们需要稍微更改之前的数码管显示代码,在对数码管最后一位进行显示之前先判断flag是否为1,是1则显示否则就不显示。

修改的部分如下:

if(flag)

{

    P0=wei[0];   

    wela=1;

    wela=0;  

    P0=table[led0];  

    dula=1;

    dula=0;

    os_wait2(K_TMO,1);

}

3.4 DS18B20

3.4.1 温度采集实现

对于温度采集函数的编写,可以参考键盘扫描。我们要做的是如何把别人的代码添加到自己的程序中而不影响原本的功能。

对于详细的温度采集代码不再进行讲解。这里只说明如何将代码进行综合。创建一个任务用来进行温度采集。

这个任务不断进行温度采集,然后刷新A1,A2,A3的值。A1,A2,A3分别为温度值的十位、个位以及小数点后一位。然后将A1,A2,A3的值添加到数码管显示函数的对应位置即可。

void dsd18b20 (void) _task_ 2

{

    char i;

    while (1)

    {

        sendChangeCmd();   //发送开始采集指令

        temp = tmp();       //得到温度值
        

        A1=temp/100;

        A2t=temp%100;

        A2=A2t/10;

        A3=A2t%10;

    }

}

下载进去之会发现温度值显示经常会出现乱码。当拔掉DS18B20之后发现显示相同的乱码。推测是因为温度传感器没有正常通讯。

3.4.2 原子操作

DS18B20需要遵循严格的时序,轮循的方式会打乱正常的通讯。所以需要通过原子操作,对关键节点进行保护。即关掉总中断,让RTOS暂时停止工作,等关键代码执行完毕再打开中断。在DS18B20进行复位,写入数据,发送数据的时候需要进行保护。

比如在进行复位操作的时候可以如下编写:

void dsreset(void)

{

    uint i;

    EA = 0;   //关闭总中断

    DS=0;

    i=103;

    while(i>0)i--;

    DS=1;

    i=4;

    while(i>0)i--;

    EA = 1;  //打开总中断

}

在写入一个位和读取一个位的地方也应该按照上面的方式进行保护。

3.4.3 温度采集频率

温度采集一次是需要时间的,即发送一次采集指令之后需要延时一会,等本次采集完成再进行下一次采集。如果不加延时会有两个后果,一是温度采集会出错,二是这样会进行频繁的进行原子操作,对RTOS的正常运行有非常大的影响。所以采集一次之后要加入一个延时,比如1s采集一次。这样不会影响正常的温度显示,同时又能保证系统的正常运作。

实测如果不进行采集时间控制,温度值会经常出错,并且数码管的闪烁很严重,甚至不能正常显示。加入延时之后有了很大改善。

3.5 LCD1602

3.5.1 引脚冲突问题

在我们使用的开发板上,1602的数据口跟数码管的数据口是共用的。在进行液晶显示的时候需要将数码管的锁存器关闭,液晶显示完毕之后再对数码管进行操作。

但是还有一个问题,1602的部分控制引脚跟按键的端口是冲突的。在进行按键扫描的时候必定会使液晶显示失常。由于液晶显示跟按键扫描实在两个任务中执行的,所以这个冲突是必然存在的。在解决过程中尝试在液晶显示的时候将按键扫描的任务delete,当液晶显示完毕之后再create。但是这样导致数码管异常闪烁。没有找到解决方案。

所以尝试将1602外接。这里将数据口接到P1,LCD_RS 接到P2^0; LCD_RW 接到 P2^1; LCD_EN接到P2^4;然后将程序中的引脚定义进行更改。

3.5.2 数据显示

1602的驱动函数也有很多,这里只需要将其综合到我们的程序中。当然代码的质量需要我们判断一下,有时候还需要我们自己进行完善修改。

要显示的值在之前都已经得到了,这里只需要编写一个函数将其显示出来即可,截取部分代码如下:

void play_LCD()

{

    lcd_pos(0x00);  //光标定位在第一行


    LCD1602_Write_Dat(H1 + 0x30);   //显示时

    LCD1602_Write_Dat(H0 + 0x30);

    LCD1602_Write_Dat(0x3a);

    ……

}

在进行液晶初始化的时候,由于有较长的延时,也需要用原子操作保护一下。如果不加保护,屏幕不能正常显示。

四、总体方案设计

本次所做的设计综合难度是比较大的。在平时的学习中可能更多的掌握了单个模块的用法,但是像这种比较复杂的系统锻炼比较少。在面对这种问题的时候我们不要慌,先大概有个框架,然后一步一步来,逐步添加需要的代码进去。比如上面模块分析的过程就是在逐步完善代码。

一定要具有大局观,在进行模块代码编写的时候要便于我们调用,也就是代码的可综合性。在程序编写过程中,先对单个模块进行测试,勤看现象,测试通过之后再逐步添加功能上去。

五、实验现象

全部完成之后可以看到数码管跟LCD1602在同步进行计时,还有温度的显示,当按下按键,会看到数码管最后一位显示按键的数值并保持一会后清除。至此,完成了所有的设计需求。

六、总结

通过本次实践,对RTOS有了初步的认识,同时也锻炼了自己编写复杂代码的能力,收获很大。在以后的学习实践中,也要勤动手,勤思考,提升自己的能力。

展开阅读全文

没有更多推荐了,返回首页