热度 18
2016-2-16 16:39
1148 次阅读|
2 个评论
首先我们来了解下什么是msOS。 操作系统( Operating System)简称OS,而msOS则是一种名叫ms的操作系统,相应的还有Linux、 uClinux 、WinCE、PalmOS、Symbian、 eCos 、uCOS-II、 VxWorks 、pSOS、Nucleus、ThreadX 、Rtems 、QNX、INTEGRITY、OSE、C Executive。msOS是由王绍伟从ucOS 中精简出来的,它基于C语言,采用C#的编程风格,遵循高效率、低耦合思想。 平时我们编程往往是一个 main 函数,里面有一个 while(1)大循环,很多功能比如按键、IO 访问等都放在这儿处理,此外还有一些中断的处理功能,这类最原始的程序结构通俗的讲叫裸奔,学术名词叫前后台软件架构。中断在前,大循环处理在后。 不得不说基于中断的前后台结构实时性较好,而且简单易用,但当函数有些地方需要延时的程序段时,在延时期间内CPU处于空转状态,不可避免的造成了CPU资源浪费。为解决这一问题,嵌入式编程引入了系统节拍、消息机制、任务切换等新元素。 所谓的系统节拍,就是开一个定时器,设置每隔一段时间发生一次中断,这段时间就叫系统节拍。系统节拍的长短根据实际情况而定,我们这次所学习的msOS定的节拍是10ms,也即每过10ms发生一次中断,在中断程序里面执行一些例行操作,比如键盘扫描、运行一次实时性要求不是很强、执行周期较短的外接设备。 当在系统节拍发生期间检测到有效的新消息时,OS将消息类型以及消息数据抛入消息队列,等待执行。基于51内核的msOS的消息队列最多可存放四个消息,且不支持多任务切换,只能一个一个的读取队列消息,执行完一个任务后方可执行下一任务,详细的消息机制介绍请参看上传的学习文档。 MS3 程序流程图: 下面我们来根据程序进一步学习。 首先我们来看下main函数: void main(void) //main函数 { ushort idata messageData; byte idata type; //type :消息的种类 byte idata value; //value : 消息数据 Initialize(); //系统及相关设备初始化 while(true) { messageData = PendMessageQueue(); /*等待消息,当消息队列为空时,会一直停留在PendMessageQueue()函数中,此时每过10ms发生一次中断,程序跳转到中断服务子程序中,当从键盘中检测到有效的新消息时,会把消息类型(KeyMessageType-按键消息)和消息数据(即检测到的按键值)抛入消息队列中,消息队列不再为空,退出 PendMessageQueue()函数,在大循环中会根据消息的消息类型对消息数据进行相应的处理 */ type = GetMessageType(messageData); //获取消息类型 value = GetMessageData(messageData); //获取消息数据 switch(type) { case KeyMessageType: //按键类型消息 #if 1 //默认函数指针方式处理 (*MmiFunction)(value); //函数指针处理方式 #else KeyProcess(value); //状态机处理方式 #endif break; #if 1 case UsartMessageType: //串口消息类型 UsartProcess(value); break; #endif //请填充消息及处理函数 default: //软件定时器处理 Function(messageData); break; } } } 我们再来看看系统节拍到来时要执行的服务子程序: void SystemTickInterruptHandler(void) interrupt 5 /*开T2定时器,用 { STC12C5A60S2芯片的同学注意,这款单片机没有T2定时器,可改用STC89C52,不想换芯片的可改用定时器0,定时器1已用做串口的波特率发生器*/ ET2 = 0; //进入中断先关掉T2定时器 TF2 = 0; //相应标志位清零 KeySystemTickService(); /*按键例行服务程序。按键例行程序其中有效的按键值是在发生两次系统节拍后得到的。平常我们在第一次按键的时候会有个10ms的消抖时间,消抖后再确定这次按键是否有效,最终得到按键值。而在这里,当第一次检测到按键时会保留当前按键值,在第二次系统节拍到来时将第二次得到的按键值与第一次得到的按键值进行比对,判断有效后才会将这个消息抛入消息队列*/ RtcSystemTickService(); //时钟例行服务程序 TimerSystemTickService(); /*软件定时器例行服务程序,软件定时器只要求大家看懂其流程,具体怎样运用先不予考虑*/ /*例行程序,使用者可根据需要添加*/ ET2 = 1; //打开T2定时器 } 在进入按键例行服务程序前我们先看下 按键扫描 函数: static byte ScanPin(void) /* 4*4矩阵按键扫描函数,static用来声明这是{ 一个内部函数,只能由当前的.c文件调用,其他.C文件里不能调用 该函数*/ byte scanValue; //此变量用于保存这次扫描后得到的按键值 P1 = 0x0F; //P17~P14置低 Delay(1); //延时,延时极短,用于电平稳定 scanValue = P1; //读取低4位按键值 P1 = 0xF0; //P17~P14置低 Delay(1); //延时,等待电平稳定 scanValue = scanValue | P1 ; //读取高四位按键值并与低四位合并 /*在裸奔程序中,我们通过循环将I/O口的高四位/低四位中的一位电位置低,然后检测低四位/高四位是否有变化,有变化是先消抖10ms,再确认按键是否有效,整个过程给我们的感觉就是一次过。而在这里,是第一次检测到I/O口的某一位有变化时,先保存该值,然后退出中断,返回大循环,在下一次系统节拍到来时,将这次的按键值与第一次的比对,最后得到有效按键值。也就是本来用于消抖的10ms现在用来执行其他操作,这样就避免了原来消抖CPU空转的情况*/ return(scanValue); //得到的按键值返回给此函数的调用者并保存 } 运行上述函数一次便能得到一个按键值,当两次按键值相等时,按键值有效。下面是按键例行程序: void KeySystemTickService(void) { byte scanValue; scanValue = ScanPin(); //调用按键扫描函数 if (scanValue == invalid) /*判断是否释放按键,变量invalid = { 0xff,只有按键释放方满足条件执行以下程序*/ if (DoubleHitCounter) //防止双击计数器 { DoubleHitCounter--; ScanCounter = 0; ScanValueSave = invalid; return; } if (ScanValueSave == invalid) //判断按键值是否有效 { ScanCounter = 0; return; } if (ScanCounter == LongInterval) /*判断为长按键,发送按键消息,分长按键和短按键方便矩阵键盘的复用*/ { PostMessage(KeyMessageType, RemapLongKey(ScanValueSave)); } else if (ScanCounter ShortInterval) //判断为短按键,发送短按键消息 { PostMessage(KeyMessageType, RemapKey(ScanValueSave)); } ScanCounter = 0; ScanValueSave = invalid; DoubleHitCounter = DoubleHitInterval; //设定防止双击计数值 } else //有按键动作时执行以下程序 { ScanCounter++; if (ScanCounter == 1) //存储第一次按键值 { ScanValueSave = scanValue; } else if (ScanCounter == 2) { if (scanValue != ScanValueSave) /*判断前后两次按键值是否相 { 等*/ ScanValueSave = invalid; ScanCounter = 0; } } else if (ScanCounter = LongInterval) //防止计数值溢出循环 { ScanCounter = LongInterval; } } } 我们再来走一遍流程: 首先是系统设备(串口、图形交互界面、系统时钟、消息队列、系统节拍)的初始化,然后进入大循环,在大循环里面,程序会先判断消息队列里是否有消息,若消息队列为空会在此等待,直至有消息抛入,当有消息抛入时,程序会根据取出的消息类型对消息数据进行相应的处理,在处理时,可能需要的时间比较长,而在此期间,若系统节拍到来,则暂停当前任务,进入中断服务子程序也即系统节拍服务,若此时有新的消息抛入,退出中断,回到主函数的时候,会先把未完成的任务完成,然后把读取消息队列,执行新任务,这是主函数部分。在系统节拍服务里面(软件定时器例行、时钟例行服务程序暂不讨论),通过扫描键盘等设备来确认是否有新消息,当检测到有效的消息时将消息抛入消息队列,消息队列是连接前台(中断)和后台(主函数)的桥梁。 msOS是一个比较简单的微系统,它算不上真正的实时系统(RTOS),真正的RTOS是支持多任务切换的,并且可以通过识别任务的优先级,优先执行级别高的任务。当有新的级别较高的任务时,系统会把当前未完成的任务挂起,相应的数据入栈保存,新任务执行完之后再继续执行该任务。当然这只是其中的一点,有兴趣的可以查阅相关资料进行学习。