写过单片机程序都知道,在main函数的结尾会有个 while(1) 循环,它就是单片机的归宿。

  在不考虑中断的情况下,整个单片机的最根本任务就是这个循环,由于在主函数里,在此我们称它为“主循环”,认为main函数及其调用的所有子函数(以及子函数调用的子函数)都在“主进程”里。

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

  这种结构基本上都是在main函数开始完成一些初始化,然后在主循环里周期性地调用一些函数:

1
2
3
4
5
6
7
8
9
10
void main()
{
    /*模块初始化*/
    while(1)
    {
        Fun1();
        Fun2();
        ......
    }
}

  在初学单片机时,大部分精力都放在单片机和各个模块的驱动上,所以在开始相当长的一段时间里采用的都是这种程序结构。

  而Fun1、Fun2……这些函数完成的功能也都是比较简单的,每个函数完成一个简单的小功能,然后顺序执行就可以组合完成某个功能。

  这些函数虽然功能简单,但是占用CPU资源不一定少,比如最简单的一个独立按键扫描程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sbit key=P1^0;
unsigned char keyscan()	//返回0代表按下,1代表没按下
{
    if(key==0)	//说明按键按下
    {
	delay5(1);	//延时5ms去抖
	if(key==0)	//确认按键按下
	{
            while(key==0);//等待按键释放
            return 0;
        }
    }
    return 1;
}

  这是一个功能很简单的函数,主函数调用它进行按键扫描。注意到:
  1、这个程序里有一个5ms延时函数,在延时的这段时间里单片机运行一些无意义的指令消耗时间。在此期间其他任务得不到运行,整个进程阻塞在延时函数这个地方。
  2、如果按键一直按下没有释放的话,程序将停留在while(key==0); 处,此时主进程里的其他函数都得不到执行。

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

  第一点,正如这种结构的名称是“顺序调用”,任务之间的运行顺序是固定不变的,各个任务之间没有优先级的区别,它只适合完成周期性循环的工作。
  第二点,一个任务运行时,其他任务得不到执行。如果其中某个任务由于某种原因卡住了,它将阻塞整个进程的运行。
  第二点能否接受呢,要看具体情况。比如每个任务需要5ms的执行时间(内部可能有一些必要的延时函数),总共四个任务。如果整个单片机系统完成的只是简单的人机交互之类的功能,这是完全可以接受的,因为我们根本察觉不到每个任务在分开运行,在我们看来它们就是并行的。但是如果完成的是像通信协议之类的驱动的话,这是接受不了的,某个任务在执行的过程中可能其他任务有更迫切的需求。

  任务执行的并行与否是相对而言的,要根据具体的情况。如果我们的要求不高,用这种简单的结构当然是最方便的,但是这种简单的结构也确实存在很多不足,有很多可以改进的地方。这是接下来几章要讨论的问题。

  在此我们明确一下这种结构的特点:
  1、由主循环调用的任务的执行顺序是固定的。
  2、由主循环调用的任务都只能单独地运行,进入一个任务,就不能处理其他任务。
  3、这些任务执行时间一般会比较长(相对后面几章改造过的任务函数而言),某一个任务里面的延时函数会造成整个进程被延时。

  在操作系统的书籍中把这种结构也叫做“前后台系统”,他们把主循环称作后台,中断称作前台。用操作系统的语言来说“应用程序是一个无限的循环,循环中调用相应的函数完成相应的功能”。由于函数是循环调用的,所以“在最坏情况下的任务级响应时间取决于整个循环的执行时间”。
  本文在这里没有考虑中断,也就纯粹讨论的是后台系统。

  第2章将介绍如何构建复杂的后台系统结构,并将程序框架和任务函数分立,明确系统的构建和任务函数的实现在整个单片机编程系统中的区别。而不要混为一谈盲目的构建系统。
  在接下来的第3章到第6章将会发现程序的主体逐渐从后台转移到了前台,在转移的过程中会对任务函数进行改造,并明确这些改造的目的和优势。

Comments