单片机 小操作系统_操作系统与单片机操作系统的区别 - CSDN
  • 单片机中嵌入操作系统的利弊早在20世纪60年代,就已经有人开始研究和开发嵌入式操作系统。但直到最近,它才在国内被越来越多的提及,在通信、电子、自动化等需要实时处理的领域所日益显现的重要性吸引了人们越来越...
    在单片机中嵌入操作系统的利弊
    早在20世纪60年代,就已经有人开始研究和开发嵌入式操作系统。但直到最近,它才在国内被越来越多的提及,在通信、电子、自动化等需要实时处理的领域所日益显现的重要性吸引了人们越来越多的注意力。但是,人们所谈论的往往是一些著名的商业内核,诸如VxWorks、PSOS等。这些商业内核性能优越,但价格昂贵,主要用于16位和32位处理器中,针对国内大部分用户使用的51系列8位单片机,可以选择免费的uC/OS-II。


    uC/OS-II的特点

      1.uC/OS-II是由Labrosse先生编写的一个开放式内核,最主要的特点就是源码公开。这一点对于用户来说可谓利弊各半,好处在于,一方面它是免费的,另一方面用户可以根据自己的需要对它进行修改。缺点在于它缺乏必要的支持,没有功能强大的软件包,用户通常需要自己编写驱动程序,特别是如果用户使用的是不太常用的单片机,还必须自己编写移植程序。

      2.uC/OS-II是一个占先式的内核,即已经准备就绪的高优先级任务可以剥夺正在运行的低优先级任务的CPU使用权。这个特点使得它的实时性比非占先式的内核要好。通常我们都是在中断服务程序中使高优先级任务进入就绪态(例如发信号),这样退出中断服务程序后,将进行任务切换,高优先级任务将被执行。拿51单片机为例,比较一下就可以发现这样做的好处。假如需要用中断方式采集一批数据并进行处理,在传统的编程方法中不能在中断服务程序中进行复杂的数据处理,因为这会使得关中断时间过长。所以经常采用的方法是置一标志位,然后退出中断。由于主程序是循环执行的,所以它总有机会检测到这一标志并转到数据处理程序中去。但是因为无法确定发生中断时程序到底执行到了什么地方,也就无法判断要经过多长时间数据处理程序才会执行,中断响应时间无法确定,系统的实时性不强。如果使用uC/OS-II的话,只要把数据处理程序的优先级设定得高一些,并在中断服务程序中使它进入就绪态,中断结束后数据处理程序就会被立即执行。这样可以把中断响应时间限制在一定的范围内。对于一些对中断响应时间有严格要求的系统,这是必不可少的。但应该指出的是如果数据处理程序简单,这样做就未必合适。因为uC/OS-II要求在中断服务程序末尾使用OSINTEXIT函数以判断是否进行任务切换,这需要花费一定的时间。

      3.uC/OS-II和大家所熟知的Linux等分时操作系统不同,它不支持时间片轮转法。uC/OS-II是一个基于优先级的实时操作系统,每个任务的优先级必须不同,分析它的源码会发现,uC/OS-II把任务的优先级当做任务的标识来使用,如果优先级相同,任务将无法区分。进入就绪态的优先级最高的任务首先得到CPU的使用权,只有等它交出CPU的使用权后,其他任务才可以被执行。所以它只能说是多任务,不能说是多进程,至少不是我们所熟悉的那种多进程。显而易见,如果只考虑实时性,它当然比分时系统好,它可以保证重要任务总是优先占有CPU。但是在系统中,重要任务毕竟是有限的,这就使得划分其他任务的优先权变成了一个让人费神的问题。另外,有些任务交替执行反而对用户更有利。例如,用单片机控制两小块显示屏时,无论是编程者还是使用者肯定希望它们同时工作,而不是显示完一块显示屏的信息以后再显示另一块显示屏的信息。这时候,要是uC/OS-II即支持优先级法又支持时间片轮转法就更合适了。

      4.uC/OS-II对共享资源提供了保护机制。正如上文所提到的,uC/OS-II是一个支持多任务的操作系统。一个完整的程序可以划分成几个任务,不同的任务执行不同的功能。这样,一个任务就相当于模块化设计中的一个子模块。在任务中添加代码时,只要不是共享资源就不必担心互相之间有影响。而对于共享资源(比如串口),uC/OS-II也提供了很好的解决办法。一般情况下使用的是信号量的方法。简单地说,先创建一个信号量并对它进行初始化。当一个任务需要使用一个共享资源时,它必须先申请得到这个信号量,而一旦得到了此信号量,那就只有等使用完了该资源,信号量才会被释放。在这个过程中即使有优先权更高的任务进入了就绪态,因为无法得到此信号量,也不能使用该资源。这个特点的好处显而易见,例如当显示屏正在显示信息的时候,外部产生了一个中断,而在中断服务程序中需要显示屏显示其他信息。这样,退出中断服务程序后,原有的信息就可能被破坏了。而在uC/OS-II中采用信号量的方法时,只有显示屏把原有信息显示完毕后才可以显示新信息,从而可以避免这个现象。不过,采用这种方法是以牺牲系统的实时性为代价的。如果显示原有信息需要耗费大量时间,系统只好等待。从结果上看,等于延长了中断响应时间,这对于未显示信息是报警信息的情况,无疑是致命的。发生这种情况,在uC/OS-II中称为优先级反转,就是高优先级任务必须等待低优先级任务的完成。在上述情况下,在两个任务之间发生优先级反转是无法避免的。所以在使用uC/OS-II时,必须对所开发的系统了解清楚,才能决定对于某种共享资源是否使用信号量。


    uC/OS-II在单片机使用中的一些特点
      1.在单片机系统中嵌入uC/OS-II将增强系统的可靠性,并使得调试程序变得简单。以往传统的单片机开发工作中经常遇到程序跑飞或是陷入死循环。可以用看门狗解决程序跑飞问题,而对于后一种情况,尤其是其中牵扯到复杂数学计算的话,只有设置断点,耗费大量时间来慢慢分析。如果在系统中嵌入uC/OS-II的话,事情就简单多了。可以把整个程序分成许多任务,每个任务相对独立,然后在每个任务中设置超时函数,时间用完以后,任务必须交出CPU的使用权。即使一个任务发生问题,也不会影响其他任务的运行。这样既提高了系统的可靠性,同时也使得调试程序变得容易。

      2.在单片机系统中嵌入uC/OS-II将增加系统的开销。现在所使用的51单片机,一般是指87C51或者89C51,其片内都带有一定的RAM和ROM。对于一些简单的程序,如果采用传统的编程方法,已经不需要外扩存储器了。如果在其中嵌入uC/OS-II的话,在只需要使用任务调度、任务切换、信号量处理、延时或超时服务的情况下,也不需要外扩ROM了,但是外扩RAM是必须的。由于uC/OS-II是可裁减的操作系统,其所需要的RAM大小就取决于操作系统功能的多少。举例来说,uC/OS-II允许用户定义最大任务数。由于每建立一个任务,都要产生一个与之相对应的数据结构TCB,该数据结构要占用很大一部分内存空间。所以在定义最大任务数时,一定要考虑实际情况的需要。如果定得过大,势必会造成不必要的浪费。嵌入uC/OS-II以后,总的RAM需求可以由如下表达式得出:

      RAM总需求=应用程序的RAM需求+内核数据区的RAM需求+(任务栈需求+最大中断嵌套栈需求)·任务数
    所幸的是,uC/OS-II可以对每个任务分别定义堆栈空间的大小,开发人员可根据任务的实际需求来进行栈空间的分配。但在RAM容量有限的情况下,还是应该注意一下对大型数组、数据结构和函数的使用,别忘了,函数的形参也是要推入堆栈的。

      3.uC/OS-II的移植也是一件需要值得注意的工作。如果没有现成的移植实例的话,就必须自己来编写移植代码。虽然只需要改动两个文件,但仍需要对相应的微处理器比较熟悉才行,最好参照已有的移植实例。另外,即使有移植实例,在编程前最好也要阅读一下,因为里面牵扯到堆栈操作。在编写中断服务程序时,把寄存器推入堆栈的顺序必须与移植代码中的顺序相对应。 

      4.和其他一些著名的嵌入式操作系统不同,uC/OS-II在单片机系统中的启动过程比较简单,不像有些操作系统那样,需要把内核编译成一个映像文件写入ROM中,上电复位后,再从ROM中把文件加载到RAM中去,然后再运行应用程序。uC/OS-II的内核是和应用程序放在一起编译成一个文件的,使用者只需要把这个文件转换成HEX格式,写入ROM中就可以了,上电后,会像普通的单片机程序一样运行。 

    结语
      由以上介绍可以看出,uC/OS-II具有免费、使用简单、可靠性高、实时性好等优点,但也有移植困难、缺乏必要的技术支持等缺点,尤其不像商用嵌入式系统那样得到广泛使用和持续的研究更新。但开放性又使得开发人员可以自行裁减和添加所需的功能,在许多应用领域发挥着独特的作用。当然,是否在单片机系统中嵌入uC/OS-II应视所开发的项目而定,对于一些简单的、低成本的项目来说,就没必要使用嵌入式操作系统了。
    展开全文
  • 自己在上学的时候,搞过2年的单片机STM32编程,当时还记得一心想在上面跑个操作系统,UCOS操作系统,但是当时自己为什么要上这个操作系统,不是特别的清楚,后来自己在实际工作中,做了应用软件,感觉有了一些深刻的...

    自己在上学的时候,搞过2年的单片机STM32编程,当时还记得一心想在上面跑个操作系统,UCOS操作系统,但是当时自己为什么要上这个操作系统,不是特别的清楚,后来自己在实际工作中,做了应用软件,感觉有了一些深刻的体会。

    1.上操作系统有很多资源可以利用,系统的内存管理,线程进程的使用。文件系统的使用,图形系统的使用,这个是你在没有上操作系统的时候不能使用的。

    2.对于多任务的处理更加简单了,原来比方说我了个机器人的程序,用裸机进行的,我有5个传感器,我还有一个液晶屏,还有1个按键,我就得用定时器来进行,进一个定时器,我就做一件事,主程序里只能做一件事,而如果我上了操作系统,我在主进程里就可以开多个线程,来同时做这样的事,不用我自己去管理这种处理顺序,由操作系统替我进行管理。

    下面的总结来自于一个博客文章,比我总结的要到位的多,转载过来

    原文的链接如下:https://blog.csdn.net/nicekwell/article/details/17177221

    本文是2013年写的,后来整理成了系统文章,请访问 http://nicekwell.net/ 查看单片机编程系列文章。

    以下是2013年原文:

    以前对单片机编程做过一些零散的总结,近两个月又学习了一下操作系统,对操作系统原理和实现有了初步了解。所以就想到写一篇长文总结,系统总结一下单片机编程和操作系统原理,作为一个从单片机编程到操作系统的过渡文章。

    本文介绍了各种单片机编程结构,可以算是本人单片机编程的一点小小的经验。从这些单片机编程结构的不断变化中逐渐过渡到操作系统,并在s3c6410上一步一步实现一个小操作系统。

    一开始想要作为一个帖子分享的,但是内容太多,所以做成了一个PDF文件,看上去比较正规,调理也清晰一点,但是内容也就停留在帖子的水平。希望能给从单片机往操作系统过渡的童鞋带来一点帮助,正如我学操作系统时也看了别人的文章一样。

    文章的PDF文件可以从http://download.csdn.net/detail/nicekwell/6671759或者http://pan.baidu.com/s/1elUFv下载到。

    以下是文章的前言和目录。

     

     

    前言

    在2011年年初,笔者开始了单片机的学习,那时还是学生的我已经深深地被单片机吸引。之后几乎放弃了学校课程,把所有精力都放在单片机上,参加了一些比赛,并取得不错的成绩。

    在开始工作之后,笔者从事单片机开发。在工作期间的高强度编程下,尝试了多种单片机编程结构,对单片机各种结构的构建做了分析和总结,并深刻感受到操作系统产生的必要性,对操作系统的探索有着越来越强的欲望。

    在工作一段时间之后,笔者决定辞职。我不想凭仅有的那么一点单片机知识做一个嵌入式码农,我渴望学习新的知识,掌握更高级的理论……好吧~至少做一个高级一点的码农。而下一步的目标就是——linux。

    但是笔者并没有直接进行linux的学习,因为操作系统对我来说还是个新事物,我甚至不知道操作系统和单片机编程的最本质区别是什么,于是我想先搞清楚操作系统的本质原理之后再进行linux的学习。笔者是物理学专业的,并没有系统学习过操作系统的概念。事实上很多介绍操作系统的书籍也没有提到操作系统的底层实现,更没有介绍它与单片机编程之间的关系。正如我在辞职的这段时间学完uCOS之后,对操作系统内核构建有了了解,但是却仍然不知底层的任务切换是如何实现的。

    However,经过两个月的学习,笔者终于清楚了操作系统的基本原理,也深刻体会到操作系统和单片机各种编程结构的区别和联系。于是我想到把之前的单片机编程经验和近期对操作系统的学习结合到一起,写一个从单片机到操作系统过渡的文章。一方面对单片机编程结构做一个系统总结,另一方面对操作系统原理也进行一次整理。把操作系统和单片机编程整合到一个知识体系中去,以便日后接受更多的操作系统内核知识。

     

    本文分为两部分——“单片机编程篇”和“操作系统篇”。

    单片机编程篇主要介绍单片机的各种编程结构,及其实现方法。是在假设已经可以驱动单片机和各个模块的前提下,讨论如何整合和使用这些资源,以实现功能。在这一篇中,将会看到程序主体逐渐从主函数转移到定时器,并且明确这些变化的目的。最后还会接触到面向对象的程序设计方式,并体会这种方式带来的好处。

    操作系统篇将会介绍操作系统的最基本的任务切换原理,以及操作系统是如何实现在单片机编程中难以实现的功能的。并在arm平台上实现任务切换和简单的操作系统。至于更复杂的操作系统内核构建本文没有多说,各种常用的操作系统内核都有很多资料可以查阅,不过我会向大家推荐一本在网上公开但没有出版的书——《底层工作者手册之嵌入式操作系统内核》,这本书详细阐述了操作系统内核的构建方法。

     

     

    目录

    第一篇  单片机编程

    第1章 主函数顺序调用     2

    1.1 主函数顺序调用的一般结构... 2

    1.2 主函数顺序调用结构的特点... 2

    第2章 界面函数结构     4

    2.1 界面函数一般结构... 4

    2.2 更高的角度分析这种结构... 5

    第3章 定时器分配任务     8

    3.1 用界面函数构成的基础框架... 8

    3.2 结合定时器编程分析... 9

    3.3 任务分割... 11

    3.4 定时器分配任务程序结构总结... 12

    第4章 占用式与非占用式程序结构分析     14

    4.1 什么是占用式程序... 14

    4.2 占用式程序的缺点... 14

    4.3 对占用式程序的改造... 15

    4.4 改造的本质... 16

    4.5 非占用式程序结构的优势... 17

    4.6 非占用式程序的一般结构... 17

    4.7 吐槽... 18

    第5章 定时器执行任务     19

    5.1 定时器执行任务的程序结构... 19

    5.2 定时器里面任务函数的特点... 20

    5.3 过程任务的定时器化... 21

    5.4 定时器执行任务程序结构总结... 25

    5.5 我们追求的是什么... 25

    第6章 面向对象思想+事件驱动结构     27

    6.1 对象和事件... 27

    6.2 C语言对一个对象的封装... 28

    6.3 事件分配机制... 31

    6.4 系统层构建... 33

    6.5 库函数... 33

    第二篇  操作系统

    第7章 为什么要有操作系统     35

    第8章 任务切换的具体工作     36

    8.1 CPU工作原理... 36

    8.2 任务切换做的事... 38

    第9章 在s3c6410上实现任务切换     39

    9.1 了解s3c6410的寄存器... 39

    9.2 要用到的几条汇编指令... 41

    9.3 在s3c6410上实现任务切换... 45

    9.4 在s3c6410上实现简单操作系统... 46
    -------------------

     

    展开全文
  • 后发现如下好文,后面再把它封装成类的话,就更方便使用了,我觉得这将会使很多只有操作系统能做的工作,普通单片机裸机也能完成,可以充分利用单片机的性能,降低整个电子行业的成本,使得例如机器人,虚拟现实等...

    在写完面向对象的单片机编程,就特别想在单片机编程引入一个多线程多任务的编程,把这个思想引入单片机。后发现如下好文,后面再把它封装成类的话,就更方便使用了,我觉得这将会使很多只有操作系统能做的工作,普通单片机裸机也能完成,可以充分利用单片机的性能,降低整个电子行业的成本,使得例如机器人,虚拟现实等高级终端设备成本降低,而进入人们的视线。

    原文:http://www.amobbs.com/thread-1398508-1-1.html
    前言

    想了很久,要不要写这篇文章?最后觉得对操作系统感兴趣的人还是很多,写吧.我不一定能造出玉,但我可以抛出砖.

    包括我在内的很多人都对51使用操作系统呈悲观态度,因为51的片上资源太少.但对于很多要求不高的系统来说,使用操作系统可以使代码变得更直观,易于维护,所以在51上仍有操作系统的生存机会.

    流行的uCos,Tiny51等,其实都不适合在2051这样的片子上用,占资源较多,唯有自已动手,以不变应万变,才能让51也有操作系统可用.这篇贴子的目的,是教会大家如何现场写一个OS,而不是给大家提供一个OS版本.提供的所有代码,也都是示例代码,所以不要因为它没什么功能就说LAJI之类的话.如果把功能写全了,一来估计你也不想看了,二来也失去灵活性没有价值了.

    下面的贴一个示例出来,可以清楚的看到,OS本身只有不到10行源代码,编译后的目标代码60字节,任务切换消耗为20个机器周期.相比之下,KEIL内嵌的TINY51目标代码为800字节,切换消耗100~700周期.唯一不足之处是,每个任务要占用掉十几字节的堆栈,所以任务数不能太多,用在128B内存的51里有点难度,但对于52来说问题不大.这套代码在36M主频的STC12C4052上实测,切换任务仅需2uS.

    #include <reg51.h> 
    
    #define MAX_TASKS 2       //任务槽个数.必须和实际任务数一至 
    #define MAX_TASK_DEP 12   //最大栈深.最低不得少于2个,保守值为12. 
    unsigned char idata task_stack[MAX_TASKS][MAX_TASK_DEP];//任务堆栈. 
    unsigned char task_id;    //当前活动任务号 
    unsigned char idata task_sp[MAX_TASKS];//栈指标存放数组
    
    //任务切换函数(任务调度器) 
    void task_switch(){ 
            task_sp[task_id] = SP; 
    
            if(++task_id == MAX_TASKS) 
                    task_id = 0; 
    
            SP = task_sp[task_id]; 
    } 
    
    //任务装入函数.将指定的函数(参数1)装入指定(参数2)的任务槽中.如果该槽中原来就有任务,则原任务丢失,但系统本身不会发生错误. 
    void task_load(unsigned int fn, unsigned char tid){ 
            task_sp[tid] = task_stack[tid] + 1; 
            task_stack[tid][0] = (unsigned int)fn & 0xff; 
            task_stack[tid][1] = (unsigned int)fn >> 8; 
    } 
    
    //从指定的任务开始运行任务调度.调用该宏后,将永不返回. 
    #define os_start(tid) {task_id = tid,SP = task_sp[tid];return;} 
    
    
    
    
    /*============================以下为测试代码============================*/ 
    
    void task1(){ 
            static unsigned char i; 
            while(1){ 
                    i++; 
                    task_switch();//编译后在这里打上断点 
            } 
    } 
    
    void task2(){ 
            static unsigned char j; 
            while(1){ 
                    j+=2; 
                    task_switch();//编译后在这里打上断点 
            } 
    } 
    
    void main(){ 
            //这里装载了两个任务,因此在定义MAX_TASKS时也必须定义为2 
            task_load(task1, 0);//将task1函数装入0号槽 
            task_load(task2, 1);//将task2函数装入1号槽 
            os_start(0); 
    } 

    这样一个简单的多任务系统虽然不能称得上真正的操作系统,但只要你了解了它的原理,就能轻易地将它扩展得非常强大,想知道要如何做吗?

    所附文件下载:

    从单任务到多任务并行系统的演变ourdev_378093.rar(文件大小:115K) (原文件名:演变.rar)
    一个最简单的多任务并行系统ourdev_378094.rar(文件大小:19K) (原文件名:mtask.rar)

    一.什么是操作系统?

    人脑比较容易接受”类比”这种表达方式,我就用”公交系统”来类比”操作系统”吧.

    当我们要解决一个问题的时候,是用某种处理手段去完成它,这就是我们常说的”方法”,计算机里叫”程序”(有时候也可以叫它”算法”).
    以出行为例,当我们要从A地走到B地的时候,可以走着去,也可以飞着去,可以走直线,也可以绕弯路,只要能从A地到B地,都叫作方法.这种从A地到B的需求,相当于计算机里的”任务”,而实现从A地到B地的方法,叫作”任务处理流程”

    很显然,这些走法中,并不是每种都合理,有些傻子都会采用的,有些是傻子都不采会用的.用计算机的话来说就是,有的任务处理流程好,有的任务处理流程好,有的处理流程差.
    可以归纳出这么几种真正算得上方法的方法:
    有些走法比较快速,适合于赶时间的人;有些走法比较省事,适合于懒人;有些走法比较便宜,适合于穷人.
    用计算机的话说就是,有些省CPU,有些流程简单,有些对系统资源要求低.

    现在我们可以看到一个问题:
    如果全世界所有的资源给你一个人用(单任务独占全部资源),那最适合你需求的方法就是好方法.但事实上要外出的人很多,例如10个人(10个任务),却只有1辆车(1套资源),这叫作”资源争用”.
    如果每个人都要使用最适合他需求的方法,那司机就只好给他们一人跑一趟了,而在任一时刻里,车上只有一个乘客.这叫作”顺序执行”,我们可以看到这种方法对系统资源的浪费是严重的.
    如果我们没有法力将1台车变成10台车来送这10个人,就只好制定一些机制和约定,让1台车看起来像10台车,来解决这个问题的办法想必大家都知道,那就是制定公交线路.
    最简单的办法是将所有旅客需要走的起点与终点串成一条线,车在这条线上开,乘客则自已决定上下车.这就是最简单的公交线路.它很差劲,但起码解决客人们对车争用.对应到计算机里,就是把所有任务的代码混在一起执行.
    这样做既不优异雅,也没效率,于是司机想了个办法,把这些客户叫到一起商量,将所有客人出行的起点与终点罗列出来,统计这些线路的使用频度,然后制定出公交线路:有些路线可以合并起来成为一条线路,而那些不能合并的路线,则另行开辟行车车次,这叫作”任务定义”.另外,对于人多路线,车次排多点,时间上也优先安排,这叫作”任务优先级”.
    经过这样的安排后,虽然仍只有一辆车,但运载能力却大多了.这套车次/路线的按排,就是一套”公交系统”.哈,知道什么叫操作系统了吧?它也就是这么样的一种约定.

    操作系统:

    我们先回过头归纳一下:
    汽车 系统资源.主要指的是CPU,当然还有其它,比如内存,定时器,中断源等.
    客户出行 任务
    正在走的路线 进程
    一个一个的运送旅客 顺序执行
    同时运送所有旅客 多任务并行
    按不同的使用频度制定路线并优先跑较繁忙的路线 任务优先级

    计算机内有各种资源,单从硬件上说,就有CPU,内存,定时器,中断源,I/O端口等.而且还会派生出来很多软件资源,例如消息池.
    操作系统的存在,就是为了让这些资源能被合理地分配.
    最后我们来总结一下,所谓操作系统,以我们目前权宜的理解就是:为”解决计算机资源争用而制定出的一种约定”.

    二.51上的操作系统

    对于一个操作系统来说,最重要的莫过于并行多任务.在这里要澄清一下,不要拿当年的DOS来说事,时代不同了.况且当年IBM和小比尔着急将PC搬上市,所以才抄袭PLM(好象是叫这个名吧?记不太清)搞了个今天看来很”粗制滥造”的DOS出来.看看当时真正的操作系统—UNIX,它还在纸上时就已经是多任务的了.

    对于我们PC来说,要实现多任务并不是什么问题,但换到MCU却很头痛:

    1.系统资源少
    在PC上,CPU主频以G为单位,内存以GB为单位,而MCU的主频通常只有十几M,内存则是Byts.在这么少的资源上同时运行多个任务,就意味着操作系统必须尽可能的少占用硬件资源.
    2.任务实时性要求高
    PC并不需要太关心实时性,因为PC上几乎所有的实时任务都被专门的硬件所接管,例如所有的声卡网卡显示上都内置有DSP以及大量的缓存.CPU只需坐在那里指手划脚告诉这些板卡如何应付实时信息就行了.
    而MCU不同,实时信息是靠CPU来处理的,缓存也非常有限,甚至没有缓存.一旦信息到达,CPU必须在极短的时间内响应,否则信息就会丢失.
    就拿串口通信来举例,在标准的PC架构里,巨大的内存允许将信息保存足够长的时间.而对于MCU来说内存有限,例如51仅有128字节内存,还要扣除掉寄存器组占用掉的8~32个字节,所以通常都仅用几个字节来缓冲.当然,你可以将数据的接收与处理的过程合并,但对于一个操作系统来说,不推荐这么做.
    假定以115200bps通信速率向MCU传数据,则每个字节的传送时间约为9uS,假定缓存为8字节,则串口处理任务必须在70uS内响应.

    这两个问题都指向了同一种解决思路:操作系统必须轻量轻量再轻量,最好是不占资源(那当然是做梦啦).

    可用于MCU的操作系统很多,但适合51(这里的51专指无扩展内存的51)几乎没有.前阵子见过一个”圈圈操作系统”,那是我所见过的操作系统里最轻量的,但仍有改进的余地.

    很多人认为,51根本不适合使用操作系统.其实我对这种说法并不完全接受,否则也没有这篇文章了.
    我的看法是,51不适合采用”通用操作系统”.所谓通用操作系统就是,不论你是什么样的应用需求,也不管你用什么芯片,只要你是51,通通用同一个操作系统.

    这种想法对于PC来说没问题,对于嵌入式来说也不错,对AVR来说还凑合,而对于51这种”贫穷型”的MCU来说,不行.
    怎样行?量体裁衣,现场根据需求构建一个操作系统出来!

    看到这里,估计很多人要翻白眼了,大体上两种:
    1.操作系统那么复杂,说造就造,当自已是神了?
    2.操作系统那么复杂,现场造一个会不会出BUG?
    哈哈,看清楚了?问题出在”复杂”上面,如果操作系统不复杂,问题不就解决了?

    事实上,很多人对操作系统的理解是片面的,操作系统不一定要做得很复杂很全面,就算仅个多任务并行管理能力,你也可以称它操作系统.
    只要你对多任务并行的原理有所了解,就不难现场写一个出来,而一旦你做到了这一点,为各任务间安排通信约定,使之发展成一个为你的应用系统量身定做的操作系统也就不难了.

    为了加深对操作系统的理解,可以看一看<<演变>>这份PPT,让你充分了解一个并行多任务是如何一步步从顺序流程演变过来的.里面还提到了很多人都在用的”状态机”,你会发现操作系统跟状态机从原理上其实是多么相似.会用状态机写程序,都能写出操作系统.

    三.我的第一个操作系统

    直接进入主题,先贴一个操作系统的示范出来.大家可以看到,原来操作系统可以做得么简单.
    当然,这里要申明一下,这玩意儿其实算不上真正的操作系统,它除了并行多任务并行外根本没有别的功能.但凡事都从简单开始,搞懂了它,就能根据应用需求,将它扩展成一个真正的操作系统.

    好了,代码来了.
    将下面的代码直接放到KEIL里编译,在每个task?()函数的”task_switch();”那里打上断点,就可以看到它们的确是”

    #include <reg51.h>
    
    #define MAX_TASKS 2       //任务槽个数.必须和实际任务数一至
    #define MAX_TASK_DEP 12   //最大栈深.最低不得少于2个,保守值为12.
    unsigned char idata task_stack[MAX_TASKS][MAX_TASK_DEP];//任务堆栈.
    unsigned char task_id;    //当前活动任务号
    
    
    //任务切换函数(任务调度器)
    void task_switch(){
            task_sp[task_id] = SP;
    
            if(++task_id == MAX_TASKS)
                    task_id = 0;
    
            SP = task_sp[task_id];
    }
    
    //任务装入函数.将指定的函数(参数1)装入指定(参数2)的任务槽中.如果该槽中原来就有任务,则原任务丢失,但系统本身不会发生错误.
    void task_load(unsigned int fn, unsigned char tid){
            task_sp[tid] = task_stack[tid] + 1;
            task_stack[tid][0] = (unsigned int)fn & 0xff;
            task_stack[tid][1] = (unsigned int)fn >> 8;
    }
    
    //从指定的任务开始运行任务调度.调用该宏后,将永不返回.
    #define os_start(tid) {task_id = tid,SP = task_sp[tid];return;}
    
    
    
    
    /*============================以下为测试代码============================*/
    
    void task1(){
            static unsigned char i;
            while(1){
                    i++;
                    task_switch();//编译后在这里打上断点
            }
    }
    
    void task2(){
            static unsigned char j;
            while(1){
                    j+=2;
                    task_switch();//编译后在这里打上断点
            }
    }
    
    void main(){
            //这里装载了两个任务,因此在定义MAX_TASKS时也必须定义为2
            task_load(task1, 0);//将task1函数装入0号槽
            task_load(task2, 1);//将task2函数装入1号槽
            os_start(0);
    }

    已经将代码作了简化,并删掉了大部分注释,大家可以直接下载源码包,里面完整的注解,并带KEIL工程文件,断点也打好了,直接按ctrl+f5就行了.在来看看这个多任务系统的原理:

    这个多任务系统准确来说,叫作”协同式多任务”.
    所谓”协同式”,指的是当一个任务持续运行而不释放资源时,其它任务是没有任何机会和方式获得运行机会,除非该任务主动释放CPU.
    在本例里,释放CPU是靠task_switch()来完成的.task_switch()函数是一个很特殊的函数,我们可以称它为”任务切换器”.
    要清楚任务是如何切换的,首先要回顾一下堆栈的相关知识.

    有个很简单的问题,因为它太简单了,所以相信大家都没留意过:
    我们知道,不论是CALL还是JMP,都是将当前的程序流打断,请问CALL和JMP的区别是什么?
    你会说:CALL可以RET,JMP不行.没错,但原因是啥呢?为啥CALL过去的就可以用RET跳回来,JMP过去的就不能用RET来跳回呢?

    很显然,CALL通过某种方法保存了打断前的某些信息,而在返回断点前执行的RET指令,就是用于取回这些信息.
    不用多说,大家都知道,”某些信息”就是PC指针,而”某种方法”就是压栈.
    很幸运,在51里,堆栈及堆栈指针都是可被任意修改的,只要你不怕死.那么假如在执行RET前将堆栈修改一下会如何?往下看:
    当程序执行CALL后,在子程序里将堆栈刚才压入的断点地址清除掉,并将一个函数的地址压入,那么执行完RET后,程序就跳到这个函数去了.
    事实上,只要我们在RET前将堆栈改掉,就能将程序跳到任务地方去,而不限于CALL里压入的地址.

    重点来了……
    首先我们得为每个任务单独开一块内存,这块内存专用于作为对应的任务的堆栈,想将CPU交给哪个任务,只需将栈指针指向谁内存块就行了.
    接下来我们构造一个这样的函数:

    当任务调用该函数时,将当前的堆栈指针保存一个变量里,并换上另一个任务的堆栈指针.这就是任务调度器了.

    OK了,现在我们只要正确的填充好这几个堆栈的原始内容,再调用这个函数,这个任务调度就能运行起来了.
    那么这几个堆栈里的原始内容是哪里来的呢?这就是”任务装载”函数要干的事了.

    在启动任务调度前将各个任务函数的入口地址放在上面所说的”任务专用的内存块”里就行了!对了,顺便说一下,这个”任务专用的内存块”叫作”私栈”,私栈的意思就是说,每个任务的堆栈都是私有的,每个任务都有一个自已的堆栈.

    话都说到这份上了,相信大家也明白要怎么做了:

    1.分配若干个内存块,每个内存块为若干字节:
    这里所说的”若干个内存块”就是私栈,要想同时运行几少个任务就得分配多少块.而”每个子内存块若干字节”就是栈深.记住,每调一层子程序需要2字节.如果不考虑中断,4层调用深度,也就是8字节栈深应该差不多了.

    unsigned char idata task_stack[MAX_TASKS][MAX_TASK_DEP]

    当然,还有件事不能忘,就是堆指针的保存处.不然光有堆栈怎么知道应该从哪个地址取数据啊
    unsigned char idata task_sp[MAX_TASKS]

    上面两项用于装任务信息的区域,我们给它个概念叫”任务槽”.有些人叫它”任务堆”,我觉得还是”槽”比较直观

    对了,还有任务号.不然怎么知道当前运行的是哪个任务呢?
    unsigned char task_id
    当前运行存放在1号槽的任务时,这个值就是1,运行2号槽的任务时,这个值就是2….

    2.构造任务调度函函数:
    void task_switch(){
    task_sp[task_id] = SP;//保存当前任务的栈指针

        if(++task_id == MAX_TASKS)//任务号切换到下一个任务
                task_id = 0;
    
        SP = task_sp[task_id];//将系统的栈指针指向下个任务的私栈.
    

    }

    3.装载任务:
    将各任务的函数地址的低字节和高字节分别入在
    task_stack[任务号][0]和task_stack[任务号][1]中:

    为了便于使用,写一个函数: task_load(函数名, 任务号)

    void task_load(unsigned int fn, unsigned char tid){
    task_sp[tid] = task_stack[tid] + 1;
    task_stack[tid][0] = (unsigned int)fn & 0xff;
    task_stack[tid][1] = (unsigned int)fn >> 8;
    }

    4.启动任务调度器:
    将栈指针指向任意一个任务的私栈,执行RET指令.注意,这可很有学问的哦,没玩过堆栈的人脑子有点转不弯:这一RET,RET到哪去了?嘿嘿,别忘了在RET前已经将堆栈指针指向一个函数的入口了.你别把RET看成RET,你把它看成是另一种类型的JMP就好理解了.

    SP = task_sp[任务号];
    return;

    做完这4件事后,任务”并行”执行就开始了.你可以象写普通函数一个写任务函数,只需(目前可以这么说)注意在适当的时候(例如以前调延时的地方)调用一下task_switch(),以让出CPU控制权给别的任务就行了.

    最后说下效率问题.
    这个多任务系统的开销是每次切换消耗20个机器周期(CALL和RET都算在内了),贵吗?不算贵,对于很多用状态机方式实现的多任务系统来说,其实效率还没这么高— case switch和if()可不像你想像中那么便宜.

    关于内存的消耗我要说的是,当然不能否认这种多任务机制的确很占内存.但建议大家不要老盯着编译器下面的那行字”DATA = XXXbyte”.那个值没意义,堆栈没算进去.关于比较省内存多任务机制,我将来会说到.
    概括来说,这个多任务系统适用于实时性要求较高而内存需求不大的应用场合,我在运行于36M主频的STC12C4052上实测了一把,切换一个任务不到3微秒.

    下回我们讲讲用KEIL写多任务函数时要注意的事项.
    下下回我们讲讲如何增强这个多任务系统,跑步进入操作系统时代.

    四.用KEIL写多任务系统的技巧与注意事项

    C51编译器很多,KEIL是其中比较流行的一种.我列出的所有例子都必须在KEIL中使用.为何?不是因为KEIL好所以用它(当然它的确很棒),而是因为这里面用到了KEIL的一些特性,如果换到其它编译器下,通过编译的倒不是问题,但运行起来可能是堆栈错位,上下文丢失等各种要命的错误,因为每种编译器的特性并不相同.所以在这里先说清楚这一点.
    但是,我开头已经说了,这套帖子的主要目的是阐述原理,只要你能把这几个例子消化掉,那么也能够自已动手写出适合其它编译器的OS.

    好了,说说KEIL的特性吧,先看下面的函数:

    sbit sigl = P1^7;
    void func1(){
    register char data i;
    i = 5;
    do{
    sigl = !sigl;
    }while(–i);
    }

    你会说,这个函数没什么特别的嘛!呵呵,别着急,你将它编译了,然后展开汇编代码再看看:

    193: void func1(){
    194: register char data i;
    195: i = 5;
    C:0x00C3 7F05 MOV R7,#0x05
    196: do{
    197: sigl = !sigl;
    C:0x00C5 B297 CPL sigl(0x90.7)
    198: }while(–i);
    C:0x00C7 DFFC DJNZ R7,C:00C5
    199: }
    C:0x00C9 22 RET

    看清楚了没?这个函数里用到了R7,却没有对R7进行保护!
    有人会跳起来了:这有什么值得奇怪的,因为上层函数里没用到R7啊.呵呵,你说的没错,但只说对了一半:事实上,KEIL编译器里作了约定,在调子函数前会尽可能释放掉所有寄存器.通常性况下,除了中断函数外,其它函数里都可以任意修改所有寄存器而无需先压栈保护(其实并不是这样,但现在暂时这样认为,饭要一口一口吃嘛,我很快会说到的).
    这个特性有什么用呢?有!当我们调用任务切换函数时,要保护的对象里可以把所有的寄存器排除掉了,就是说,只需要保护堆栈即可!

    现在我们回过头来看看之前例子里的任务切换函数:

    void task_switch(){
    task_sp[task_id] = SP;//保存当前任务的栈指针

        if(++task_id == MAX_TASKS)//任务号切换到下一个任务
                task_id = 0;
    
        SP = task_sp[task_id];//将系统的栈指针指向下个任务的私栈.
    

    }

    看到没,一个寄存器也没保护,展开汇编看看,的确没保护寄存器.

    好了,现在要给大家泼冷水了,看下面两个函数:

    void func1(){
    register char data i;
    i = 5;
    do{
    sigl = !sigl;
    }while(–i);
    }
    void func2(){
    register char data i;
    i = 5;
    do{
    func1();
    }while(–i);
    }

    父函数fun2()里调用func1(),展开汇编代码看看:
    193: void func1(){
    194: register char data i;
    195: i = 5;
    C:0x00C3 7F05 MOV R7,#0x05
    196: do{
    197: sigl = !sigl;
    C:0x00C5 B297 CPL sigl(0x90.7)
    198: }while(–i);
    C:0x00C7 DFFC DJNZ R7,C:00C5
    199: }
    C:0x00C9 22 RET
    200: void func2(){
    201: register char data i;
    202: i = 5;
    C:0x00CA 7E05 MOV R6,#0x05
    203: do{
    204: func1();
    C:0x00CC 11C3 ACALL func1(C:00C3)
    205: }while(–i);
    C:0x00CE DEFC DJNZ R6,C:00CC
    206: }
    C:0x00D0 22 RET

    看清楚没?函数func2()里的变量使用了寄存器R6,而在func1和func2里都没保护.
    听到这里,你可能又要跳一跳了:func1()里并没有用到R6,干嘛要保护?没错,但编译器是怎么知道func1()没用到R6的呢?是从调用关系里推测出来的.
    一点都没错,KEIL会根据函数间的直接调用关系为各函数分配寄存器,既不用保护,又不会冲突,KEIL好棒哦!!等一下,先别高兴,换到多任务的环境里再试试:

    void func1(){
    register char data i;
    i = 5;
    do{
    sigl = !sigl;
    }while(–i);
    }
    void func2(){
    register char data i;
    i = 5;
    do{
    sigl = !sigl;
    }while(–i);
    }

    展开汇编代码看看:

    193: void func1(){
    194: register char data i;
    195: i = 5;
    C:0x00C3 7F05 MOV R7,#0x05
    196: do{
    197: sigl = !sigl;
    C:0x00C5 B297 CPL sigl(0x90.7)
    198: }while(–i);
    C:0x00C7 DFFC DJNZ R7,C:00C5
    199: }
    C:0x00C9 22 RET
    200: void func2(){
    201: register char data i;
    202: i = 5;
    C:0x00CA 7F05 MOV R7,#0x05
    203: do{
    204: sigl = !sigl;
    C:0x00CC B297 CPL sigl(0x90.7)
    205: }while(–i);
    C:0x00CE DFFC DJNZ R7,C:00CC
    206: }
    C:0x00D0 22 RET

    看到了吧?哈哈,这回神仙也算不出来了.因为两个函数没有了直接调用的关系,所以编译器认为它们之间不会产生冲突,结果分配了一对互相冲突的寄存器,当任务从func1()切换到func2()时,func1()中的寄存器内容就给破坏掉了.大家可以试着去编译一下下面的程序:

    sbit sigl = P1^7;
    void func1(){
    register char data i;
    i = 5;
    do{
    sigl = !sigl;
    task_switch();
    }while(–i);
    }
    void func2(){
    register char data i;
    i = 5;
    do{
    sigl = !sigl;
    task_switch();
    }while(–i);
    }

    我们这里只是示例,所以仍可以通过手工分配不同的寄存器避免寄存器冲突,但在真实的应用中,由于任务间的切换是非常随机的,我们无法预知某个时刻哪个寄存器不会冲突,所以分配不同寄存器的方法不可取.那么,要怎么办呢?
    这样就行了:

    sbit sigl = P1^7;
    void func1(){
    static char data i;
    while(1){
    i = 5;
    do{
    sigl = !sigl;
    task_switch();
    }while(–i);
    }
    }
    void func2(){
    static char data i;
    while(1){
    i = 5;
    do{
    sigl = !sigl;
    task_switch();
    }while(–i);
    }
    }

    将两个函数中的变量通通改成静态就行了.还可以这么做:

    sbit sigl = P1^7;
    void func1(){
    register char data i;
    while(1){
    i = 5;
    do{
    sigl = !sigl;
    }while(–i);
    task_switch();
    }
    }
    void func2(){
    register char data i;
    while(1){
    i = 5;
    do{
    sigl = !sigl;
    }while(–i);
    task_switch();
    }
    }

    即,在变量的作用域内不切换任务,等变量用完了,再切换任务.此时虽然两个任务仍然会互相破坏对方的寄存器内容,但对方已经不关心寄存器里的内容了.

    以上所说的,就是”变量覆盖”的问题.现在我们系统地说说关于”变量覆盖”.

    变量分两种,一种是全局变量,一种是局部变量(在这里,寄存器变量算到局部变量里).
    对于全局变量,每个变量都会分配到单独的地址.
    而对于局部变量,KEIL会做一个”覆盖优化”,即没有直接调用关系的函数的变量共用空间.由于不是同时使用,所以不会冲突,这对内存小的51来说,是好事.
    但现在我们进入多任务的世界了,这就意味着两个没有直接调用关系的函数其实是并列执行的,空间不能共用了.怎么办呢?一种笨办法是关掉覆盖优化功能.呵呵,的确很笨.

    比较简单易行一个解决办法是,不关闭覆盖优化,但将那些在作用域内需要跨越任务(换句话说就是在变量用完前会调用task_switch()函数的)变量通通改成静态(static)即可.这里要对初学者提一下,”静态”你可以理解为”全局”,因为它的地址空间一直保留,但它又不是全局,它只能在定义它的那个花括号对{}里访问.
    静态变量有个副作用,就是即使函数退出了,仍会占着内存.所以写任务函数的时候,尽量在变量作用域结束后才切换任务,除非这个变量的作用域很长(时间上长),会影响到其它任务的实时性.只有在这种情况下才考虑在变量作用域内跨越任务,并将变量申明为静态.
    事实上,只要编程思路比较清析,很少有变量需要跨越任务的.就是说,静态变量并不多.

    说完了”覆盖”我们再说说”重入”.
    所谓重入,就是一个函数在同一时刻有两个不同的进程复本.对初学者来说可能不好理解,我举个例子吧:
    有一个函数在主程序会被调用,在中断里也会被调用,假如正当在主程序里调用时,中断发生了,会发生什么情况?

    void func1(){
    static char data i;
    i = 5;
    do{
    sigl = !sigl;
    }while(–i);
    }

    假定func1()正执行到i=3时,中断发生,一旦中断调用到func1()时,i的值就被破坏了,当中断结束后,i == 0.

    以上说的是在传统的单任务系统中,所以重入的机率不是很大.但在多任务系统中,很容易发生重入,看下面的例子:
    void func1(){
    ….
    delay();
    ….
    }
    void func2(){
    ….
    delay();
    ….
    }
    void delay(){
    static unsigned char i;//注意这里是申明为static,不申明static的话会发生覆盖问题.而申明为static会发生重入问题.麻烦啊
    for(i=0;i<10;i++)
    task_switch();
    }

    两个并行执行的任务都调用了delay(),这就叫重入.问题在于重入后的两个复本都依赖变量i来控制循环,而该变量跨越了任务,这样,两个任务都会修改i值了.
    重入只能以防为主,就是说尽量不要让重入发生,比如将代码改成下面的#define delay() {static unsigned char i; for(i=0;i<10;i++) task_switch();}//i仍定义为static,但实际上已经不是同一个函数了,所以分配的地址不同.
    void func1(){
    ….
    delay();
    ….
    }
    void func2(){
    ….
    delay();
    ….
    }代替函数,就意味着每个调用处都是一个独立的代码复本,那么两个delay实际使用的内存地址也就不同了,重入问题消失.
    但这种方法带来的问题是,每调用一次delay(),都会产生一个delay的目标代码,如果delay的代码很多,那就会造成大量的rom空间占用.有其它办法没?

    本人所知有限,只有最后一招了:
    void delay() reentrant{
    unsigned char i;
    for(i=0;i<10;i++)
    task_switch();
    }
    加入reentrant申明后,该函数就可以支持重入.但小心使用,申明为重入后,函数效率极低!

    最后附带说下中断.因为没太多可说的,就不单独开章了.
    中断跟普通的写法没什么区别,只不过在目前所示例的多任务系统里因为有堆栈的压力,所以要使用using来减少对堆栈的使用(顺便提下,也不要调用子函数,同样是为了减轻堆栈压力)
    用using,必须用#pragma NOAREGS关闭掉绝对寄存器访问,如果中断里非要调用函数,连同函数也要放在#pragma NOAREGS的作用域内.如例所示:

    #pragma SAVE
    #pragma NOAREGS  //使用using时必须将绝对寄存器访问关闭
    void clock_timer(void) interrupt 1 using 1 //使用using是为了减轻堆栈的压力
    }
    #pragma RESTORE

    改成上面的写法后,中断固定占用4个字节堆栈.就是说,如果你在不用中断时任务栈深定为8的话,现在就要定为8+4 = 12了.
    另外说句废话,中断里处理的事一定要少,做个标记就行了,剩下的事交给对应的任务去处理.

    现在小结一下:

    切换任务时要保证没有寄存器跨越任务,否则产生任务间寄存器覆盖. 使用静态变量解决
    切换任务时要保证没有变量跨越任务,否则产生任务间地址空间(变量)覆盖. 使用静态变量解决
    两个不同的任务不要调用同时调用同一个函数,否则产生重入覆盖. 使用重入申明解决

    展开全文
  • 以前对单片机编程做过一些零散的总结,近两个月又学习了一下操作系统,对操作系统原理和实现有了...从这些单片机编程结构的不断变化中逐渐过渡到操作系统,并在s3c6410上一步一步实现一个小操作系统。 一开始想要作为一

    本文是2013年写的,后来整理成了系统文章,请访问 http://nicekwell.net/ 查看单片机编程系列文章。

    以下是2013年原文:


    以前对单片机编程做过一些零散的总结,近两个月又学习了一下操作系统,对操作系统原理和实现有了初步了解。所以就想到写一篇长文总结,系统总结一下单片机编程和操作系统原理,作为一个从单片机编程到操作系统的过渡文章。

    本文介绍了各种单片机编程结构,可以算是本人单片机编程的一点小小的经验。从这些单片机编程结构的不断变化中逐渐过渡到操作系统,并在s3c6410上一步一步实现一个小操作系统。

    一开始想要作为一个帖子分享的,但是内容太多,所以做成了一个PDF文件,看上去比较正规,调理也清晰一点,但是内容也就停留在帖子的水平。希望能给从单片机往操作系统过渡的童鞋带来一点帮助,正如我学操作系统时也看了别人的文章一样。

    文章的PDF文件可以从http://download.csdn.net/detail/nicekwell/6671759或者http://pan.baidu.com/s/1elUFv下载到。

    以下是文章的前言和目录。

     

     

    前言

    在2011年年初,笔者开始了单片机的学习,那时还是学生的我已经深深地被单片机吸引。之后几乎放弃了学校课程,把所有精力都放在单片机上,参加了一些比赛,并取得不错的成绩。

    在开始工作之后,笔者从事单片机开发。在工作期间的高强度编程下,尝试了多种单片机编程结构,对单片机各种结构的构建做了分析和总结,并深刻感受到操作系统产生的必要性,对操作系统的探索有着越来越强的欲望。

    在工作一段时间之后,笔者决定辞职。我不想凭仅有的那么一点单片机知识做一个嵌入式码农,我渴望学习新的知识,掌握更高级的理论……好吧~至少做一个高级一点的码农。而下一步的目标就是——linux。

    但是笔者并没有直接进行linux的学习,因为操作系统对我来说还是个新事物,我甚至不知道操作系统和单片机编程的最本质区别是什么,于是我想先搞清楚操作系统的本质原理之后再进行linux的学习。笔者是物理学专业的,并没有系统学习过操作系统的概念。事实上很多介绍操作系统的书籍也没有提到操作系统的底层实现,更没有介绍它与单片机编程之间的关系。正如我在辞职的这段时间学完uCOS之后,对操作系统内核构建有了了解,但是却仍然不知底层的任务切换是如何实现的。

    However,经过两个月的学习,笔者终于清楚了操作系统的基本原理,也深刻体会到操作系统和单片机各种编程结构的区别和联系。于是我想到把之前的单片机编程经验和近期对操作系统的学习结合到一起,写一个从单片机到操作系统过渡的文章。一方面对单片机编程结构做一个系统总结,另一方面对操作系统原理也进行一次整理。把操作系统和单片机编程整合到一个知识体系中去,以便日后接受更多的操作系统内核知识。

     

    本文分为两部分——“单片机编程篇”和“操作系统篇”。

    单片机编程篇主要介绍单片机的各种编程结构,及其实现方法。是在假设已经可以驱动单片机和各个模块的前提下,讨论如何整合和使用这些资源,以实现功能。在这一篇中,将会看到程序主体逐渐从主函数转移到定时器,并且明确这些变化的目的。最后还会接触到面向对象的程序设计方式,并体会这种方式带来的好处。

    操作系统篇将会介绍操作系统的最基本的任务切换原理,以及操作系统是如何实现在单片机编程中难以实现的功能的。并在arm平台上实现任务切换和简单的操作系统。至于更复杂的操作系统内核构建本文没有多说,各种常用的操作系统内核都有很多资料可以查阅,不过我会向大家推荐一本在网上公开但没有出版的书——《底层工作者手册之嵌入式操作系统内核》,这本书详细阐述了操作系统内核的构建方法。

     

     

    目录

    第一篇  单片机编程

    第1章 主函数顺序调用     2

    1.1 主函数顺序调用的一般结构... 2

    1.2 主函数顺序调用结构的特点... 2

    第2章 界面函数结构     4

    2.1 界面函数一般结构... 4

    2.2 更高的角度分析这种结构... 5

    第3章 定时器分配任务     8

    3.1 用界面函数构成的基础框架... 8

    3.2 结合定时器编程分析... 9

    3.3 任务分割... 11

    3.4 定时器分配任务程序结构总结... 12

    第4章 占用式与非占用式程序结构分析     14

    4.1 什么是占用式程序... 14

    4.2 占用式程序的缺点... 14

    4.3 对占用式程序的改造... 15

    4.4 改造的本质... 16

    4.5 非占用式程序结构的优势... 17

    4.6 非占用式程序的一般结构... 17

    4.7 吐槽... 18

    第5章 定时器执行任务     19

    5.1 定时器执行任务的程序结构... 19

    5.2 定时器里面任务函数的特点... 20

    5.3 过程任务的定时器化... 21

    5.4 定时器执行任务程序结构总结... 25

    5.5 我们追求的是什么... 25

    第6章 面向对象思想+事件驱动结构     27

    6.1 对象和事件... 27

    6.2 C语言对一个对象的封装... 28

    6.3 事件分配机制... 31

    6.4 系统层构建... 33

    6.5 库函数... 33

    第二篇  操作系统

    第7章 为什么要有操作系统     35

    第8章 任务切换的具体工作     36

    8.1 CPU工作原理... 36

    8.2 任务切换做的事... 38

    第9章 在s3c6410上实现任务切换     39

    9.1 了解s3c6410的寄存器... 39

    9.2 要用到的几条汇编指令... 41

    9.3 在s3c6410上实现任务切换... 45

    9.4 在s3c6410上实现简单操作系统... 46

     

     

     

    展开全文
  • 从前面的文章,我们知道,(单核)单片机某一时刻只能干一件事,会造成单片机资源的浪费,而且还有可能响应不够及时,所以,在比较庞大的程序或者是要求实时性比较高的情况下,我们可以移植操作系统。因为这种情况下...
  • 单片机最小系统

    2017-05-05 10:23:48
    单片机最小系统,或者称为最小应用系统,是指用最少的元件组成的单片机可以工作的系统. 对51系列单片机来说,最小系统一般应该包括:单片机、晶振电路、复位电路. 下面给出一个51单片机的最小系统电路图. ...
  • 单片机最小系统,或者称为单片机最小应用系统,是指用最少的元件组成的单片机可以工作的系统.对51系列单片机来说,最小系统一般应该包括:单片机、晶振电路、复位电路.一.先说一下51单片机的引脚: 总线型 DIP40 引脚...
  • 星光操作系统是基于51单片机的嵌入式操作系统,为单片机开发提供软件平台,其特性如下: (1) 支持AT89S52、STC89C52RC、STC90C58RDPlus、STC12C5A60S2、STC15F2K60S2、STC15W4K32S4、STC8A...
  • 单片机工作的前提是为它添加必要的外围电路以构成单片机最小系统。初学者可能对 单片机最小系统感觉很神秘,其实单片机最小系统很简单,就是能使单片机工作的最少的器件构成的系统。最小系统虽然简单,但是却是...
  • 首先讨论各种单片机操作系统的关系 芯片,是指内含集成电路的硅片。 单片机(Micro-controllers)是一种集成电路芯片,是把中央处理器CPU、存储器、多种I/O口和中断系统、定时器/计数器等功能(可能还包括模拟多...
  • * 嵌入式实时操作系统是什么? * 不可不知的细小知识点 * 基于时间触发模式的编程思想 一、单片机与前后台系统 单片机程序通常由一个while循环和中断机制组成,while循环是后台,而中断则为前台。具体执行...
  • 本文将介绍如何自制一个51单片机最小系统及一些附加模块。最终制成的系统将具有烧录程序,运行程序等功能。 先放两张张最终成品如下  ​  (正面)  (反面--锡接走线法) 提醒读者,下载口的布局...
  • 51单片机最小系统

    2014-07-25 15:37:14
    单片机最小系统,或者称为最小应用系统,是指用最少的元件组成的单片机可以工作的系统. 对51系列单片机来说,最小系统一般应该包括:单片机、晶振电路、复位电路. 下面给出一个51单片机的最小系统电路图. ...
  • 单片机的最小系统就是让单片机能正常工作并发挥其功能时所必须的组成部分,也可理解为单片机正常运行的最小环境。 其主要构成为四部分: 1.单片机芯片 2.系统电源 3.时钟电路 4.复位电路 这四个部分不可缺少...
  • 简单的多任务操作系统 其实只有个任务调度切换,把说它是OS有点牵强,但它对于一些简单的开发应用来说, 简单也许就是最好的.尽情的扩展它吧.别忘了把你的成果分享给大家. 这是一个最简单的OS,一切以运行效率为重,经...
  • 想了很久,要不要写这篇文章?最后觉得对操作系统感兴趣的人还是很多,写吧....包括我在内的很多人都对51...但对于很多要求不高的系统来说,使用操作系统可以使代码变得更直观,易于维护,所以在51上仍有操作系统的生存机会.
  • 印象笔记链接:单片机最小系统 原理图详解 PCB操作总结(图解) - CSDN资源下载 - 点击打开链接
  • 关注【电子开发圈】微信公众号,一起学习吧!...电子DIY、Arduino、51单片机、STM32单片机、FPGA…… 电子百科、开发技术、职业经验、趣味知识、科技头条、设备拆机…… 点击链接,免费下载100G+电子设计学习资料! ...
  • 在学校自学51单片机,c语言、stm32 、ucos RTOS等;原先找了一份测试工程师的工作,1700元一月。人生多姿多彩,没想到回校当教师了。起初我很开心,以为能好好的学习专业,如今我错了,我变得懒惰了。 我热爱电子...
1 2 3 4 5 ... 20
收藏数 37,785
精华内容 15,114
关键字:

单片机 小操作系统