|
1602+C8051F020做的基于合作式调度的简易多功能测试仪
-----显示时间、ADC、PWM、频率检测、DA输出、温度显示等
第一次发帖。
三个多星期前,偶尔在网上查资料,发现了这个网站。很高兴在这里认识了那么多高尚而有激_情而又志同道合的朋友,虽然我们互不相识,但你们乐于分享知识,经验,脚踏实地而又不懈的追求完美,让我充分的感到技术能给人生带来愉悦。在这个浮躁的社会,这里实在是一片世外桃园。
话说回来,我一般是个较懒的人,就是典型的“看帖不喜欢回的人”,专业的需要,一段时间上过CSDN等网站,感觉很郁闷,没有贬低他们的意思,觉得他们做事情太过于功利。辛苦写的文章,发N个帖子,换来可怜的一点积分,网络不好,一不小心就光了。也曾购买过虚拟币,也曾上垃圾网站买过坏开发板-------这些事情太多太多,不一而足。艰难的出生背景,清贫的学生时代,垃圾的学校教育,一切一切,成长是件很艰难的事情。
多谢这里的开源,感谢阿莫,也谢谢各位素未谋面的发烧友公布自己的程序和经验,在参考你们的程序的同时,也想把自己的程序和调试经验一起与大家分享,相互交流,共同进步。
前不久在中关村淘到了一个焊错了脚的1602,勉强能用,苦口婆心的讨价后,15元买回家,同学那里借来的开发板,实验室角落捡来的4X4键盘,白天在实验室干活,深夜回到寝室,弄个把小时后睡觉,前后弄了一个星期左右,终于把一个C8051F020+1602的完整的程序写出来了。
程序是基于合作式调度器来进行任务切换的,控制器是C8051f020。
实现了:
显示时间,可以动态调整时间(操作系统实现,非常精确,并且在单片机执行其他任务时也可以同时并行执行)
显示八路ADC采样,并在1602上显示出来
输出两路DA信号,一路负责LCD背光的调整,还有一路向外输出。所有的DA输出值都通过键盘输入,可以在2430mV内调整,在LCD上显示出来,精确度在5mV以内。(因为是直接接的开发板,不可能很精确)
测量频率,从1HZ到6.55MHZ,1602上显示的有效数字是四位。
输出不同占空比的PWM,从0到100,可以通过键盘调整。
输出当前的芯片的温度(是单片机的内部的温度传感器)
目前用了7个任务,如果需要的话,可以再增加。片内的RAM用了170Byte左右。020芯片上有4K的片内DRAM,应该可以做很多的事情。(说明一下,我自己移植的UC/OS-II在020上完全可以跑起来,需要的读者可以把邮箱告诉我,我给你们发过去)
说明一下,这个合作式模板是我参考一本书写的,《基于时间触发的嵌入式操作系统》,这本书非常好,想读者们推荐一下。由于是图书馆借的书,还了后我根据记忆写的,没有注释,不是很规范,如果入门级读者要深究的话,推荐去看原书,讲的非常好。
任务分为几个模块,各位读者大人可以参考一下。
************************************/
#include "MAIN.H"
#include "PORT.H"
#include "DeviceInit.h"
#include "DAC.h"
#include "LCD.h"
#include "Key.h"
#include "Time.h"
#include "Temp.h"
#include "sch51.h"
#include "scan.h"
#include "FreRecv.h"
#include "FreSend.h"
void main(void)
{
DeviceInit();
DACInit();
KeyInit();
LCDInit();
Task_Init();
/* 函数名 第一次执行时延时的节拍数 任务运行间隔的节拍数*/
/* 任务运行间隔的节拍数为N+1,例如间隔为9,则实际的间隔为9+1*/
Task_Add(TempUpdate,100,99); //温度扫描更新
Task_Add(TimeUpdate,1,9); //时钟更新
Task_Add(DACUpdate,2,9); //DA更新,可以调节LCD亮度和一个DA输出
Task_Add(StateUpdate,3,0); //按键扫描,判定系统的状态,以及LCD显示,
Task_Add(DisplayBufferData,4,0); //LCD显示,必须调用(可以放到StateUpdate函数里)
Task_Add(FreSendUpdate,5,99); //输出占空比不同的PWM脉冲波
Task_Add(FreRecvUpdate,99,0); //检测脉冲的频率
Task_Start();
while(1)
{
Task_Dispatch();
}
}
用操作系统的好处在与程序的结构非常好,一个任务一个源文件加一个头文件,不管是结构还是可移植性都非常好。
我用了七个任务,
Task_Add(TempUpdate,100,99); //温度扫描更新
Task_Add(TimeUpdate,1,9); //时钟更新
Task_Add(DACUpdate,2,9); //DA更新
Task_Add(StateUpdate,3,0); //按键扫描,判定系统的状态,以及LCD显示,
Task_Add(DisplayBufferData,4,0); //LCD显示,必须调用(可以放到StateUpdate函数里)
Task_Add(FreSendUpdate,5,99); //输出占空比不同的PWM脉冲波
Task_Add(FreRecvUpdate,99,0); //检测脉冲的频率
例如Time的任务部分:
/***********************************
初始化部分,显示的是完整的一副时钟画面,
显示的初始值为18:00:00,闹钟的初始值为
08:00:00,index存放的是时钟显示的位置
************************************/
void TimeInit(void)
{
static bit TimeInitBit=1;
if(TimeInitBit)
{
Time.Sec=0x00;
Time.Min=0x00;
Time.Hour=0x12;
TimeInitBit=0;
}
if(TimeFlag)
{
WriteDataToBuffer(Time.index,Time0);
ShowData(0x16|0x60,Time.Hour);
WriteCharToBuffer(0x18,':');
ShowData(0x19|0x60,Time.Min);
WriteCharToBuffer(0x1b,':');
ShowData(0x1c|0x60,Time.Sec);
CursionLocate=0x16;
CURSION_ON;
}
}
/***********************************
刷新时钟显示,每到一个固定的时间,刷新
分秒时,若到了时间,更新显示。要想完成
显示时间,每隔单位时间调用该函数即可。
按下光标键,则光标会左右移动,若按下
了加减键,则光标键所在的数字会加减,
当到了最大值后,再按下会一直持续这个
最大值
************************************/
void TimeUpdate(void)
{ //先显示时间
static char TimeLocate=0; //保存的是光标的额位置
static char TimeCount=0; //每10ms刷新一次,所以每100次运行该程序就是一秒
if(TimeCount++==10)
{
TimeCount=0;
Time.Sec++;
if(TimeFlag)ShowData(0x1c|0x60,Time.Sec); //一秒到了,刷新显示
if(Time.Sec==60)
{
Time.Sec=0;
Time.Min++;
if(TimeFlag)ShowData(0x19|0x60,Time.Min); //一分到了,刷新显示
if(Time.Min==60)
{
Time.Min=0;
Time.Hour++;
if(TimeFlag)ShowData(0x16|0x60,Time.Hour); //一小时到了,刷新显示
if(Time.Hour==24)
{
Time.Hour=0;
}
}
}
}
if(TimeFlag)
{
if(KeyValue=='#') //如果'#'键按下,光标右移
{
if(TimeLocate++>=5)TimeLocate=5;
KeyValue=0;
TimeLocate|=0x80;
}
if(KeyValue=='*') //如果'*'键按下,光标右移
{
if(TimeLocate--<=0)TimeLocate=0;
KeyValue=0;
TimeLocate|=0x80;
}
if(TimeLocate&0x80) //执行光标移动
{
CURSION_ON;
TimeLocate&=0x7f;
switch(TimeLocate)
{
case 0:
CursionLocate=0x16;
break;
case 1:
CursionLocate=0x17;
break;
case 2:
CursionLocate=0x19;
break;
case 3:
CursionLocate=0x1a;
break;
case 4:
CursionLocate=0x1c;
break;
case 5:
CursionLocate=0x1d;
break;
default:break;
}
}
if(KeyValue=='8') //如果'*'键按下,光标所在的数字加一
{
CURSION_ON;
KeyValue=0;
switch(TimeLocate)
{
case 0: Time.Hour+=10;
if(Time.Hour>=23)Time.Hour=23;
break;
case 1: Time.Hour+=1;
if(Time.Hour>=23)Time.Hour=23;
break;
case 2: Time.Min+=10;
if(Time.Min>=59)Time.Min=59;
break;
case 3: Time.Min+=1;
if(Time.Min>=59)Time.Min=59;
break;
case 4: Time.Sec+=10;
if(Time.Sec>=59)Time.Sec=59;
break;
case 5: Time.Sec+=1;
if(Time.Sec>=59)Time.Sec=59;
break;
default: break;
} //更新显示
ShowData(0x16|0x60,Time.Hour);
WriteCharToBuffer(0x18,':');
ShowData(0x19|0x60,Time.Min);
WriteCharToBuffer(0x1b,':');
ShowData(0x1c|0x60,Time.Sec);
}
if(KeyValue=='0') //如果'0'键按下,光标所在数字减一
{
CURSION_ON;
KeyValue=0;
switch(TimeLocate)
{
case 0: Time.Hour-=10;
if(Time.Hour<=0)Time.Hour=0;
break;
case 1: Time.Hour-=1;
if(Time.Hour<=0)Time.Hour=0;
break;
case 2: Time.Min-=10;
if(Time.Min<=0)Time.Min=0;
break;
case 3: Time.Min-=1;
if(Time.Min<=0)Time.Min=0;
break;
case 4: Time.Sec-=10;
if(Time.Sec<=0)Time.Sec=0;
break;
case 5: Time.Sec-=1;
if(Time.Sec<=0)Time.Sec=0;
break;
default: break;
} //更新显示
ShowData(0x16|0x60,Time.Hour);
WriteCharToBuffer(0x18,':');
ShowData(0x19|0x60,Time.Min);
WriteCharToBuffer(0x1b,':');
ShowData(0x1c|0x60,Time.Sec);
}
}
}
各位读者看完程序后会发觉几乎所有的功能函数的源文件都是遵照上述文件的格式来编写的。白天要做老师布置的该死的机械的机械图,凌晨趁室友们睡了后挑灯夜战,这种模块化的程序结构也省了不少事情,一天一个任务出来了,感觉非常好。
有一点需要提醒读者的是,由于程序采用的是基于时间片轮询的程序结构,所以LCD和其他的任务都是一次调度只运行一次,运行完后把CPU交给别人,如一个功能函数可能需要刷新所有的LCD显示字符,这个任务的显示执行时间可能需要超过10ms,所以采用的是轮询结构,即建立一个显示缓冲区以后,每次任务调度时就会检查这个缓冲区里是否还有有效的字符未发送出去,若有,发送出去,将发送缓冲区的指针加一。所以要刷新一个16X2的液晶屏,需要32次调度。
现在在编写注释的过程中,当时写程序,调程序卡壳的一个个片段历历在目。在下机械出身,至今菜鸟一个,第一次发表主题,希望个位大侠不要板砖,在程序的结构方面多提些建议,在下不胜感激!
我在WORD里写的,图片很大,远远超过了300K,太晚了,我明天再另外想办法传上来。 |
|