一、设计需求
在清翔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++)
{
^^^^^
keil工程下载地址:
RTOS复杂应用.zip
(100.71 KB, 下载次数: 113)
|