你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【连载】从单片机到操作系统①

[复制链接]
xiaojie0513 发布时间:2018-5-12 12:52
本帖最后由 xiaojie0513 于 2018-6-8 11:22 编辑

一上来就贴个代码:
我们熟悉的单片机编程:
  • void main()
  • {
  • /*模块初始化*/
  •     while(1)
  •     {
  •         Fun1();
  •         Fun2();
  •         ……
  •     }
  • }
这种结构基本上都是在main函数开始完成一些初始化,然后在主循环里周期性地调用一些函数。

这应该是最常见的结构了吧,学过单片机的都知道在 main 函数里面的那个“while(1)”。
笔者也对这个 while(1) 印象深刻,因为它让我明白了单片机程序运行的归宿就在这。
在不考虑中断的情况下,整个单片机的最根本任务就是这个 while(1) 循环。在此称它为“主
循环”,认为 main 函数及其调用的所有子函数(以及子函数再次调用的函数……)都在一个“主进程”里。

在初学单片机时,大部分精力都放在单片机和各个模块的驱动上,所以在开始相当长的
一段时间里采用的都是这种程序结构。
而 Fun1、Fun2……这些函数完成的功能也都是比较简单的,每个函数完成一个简单的
小功能,然后顺序执行就可以组合完成某个功能。
需要强调的是,这些函数虽然功能简单,但是占用 CPU 资源不一定少,比如最简单的
一个独立按键扫描程序:
  • 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;
  • }

注意到这个程序里有一个 5ms 延时函数,在延时的这段时间里单片机运行一些无意义
的指令消耗时间。在此期间其他任务得不到运行,整个进程阻塞在延时函数这个地方。并且,
如果按键一直按下没有释放的话,程序将停留在 while(key==0); 处。
简单来说就是,系统一直在等你的释放,而单片机运算速度特别快,这就是占用了单片机的所有资源了,这样子,下一个任务在你不释放的时候就没办法得到运行。就必须得等到上一个任务做完了才能做下一个任务。这样子做,就是最低效率的了。

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

首先,正如它的名称是“顺序调用”,任务之间的运行顺序是固定不变的,当然不可能有优先级区别,它只适合完成那些周期性循环的工作。
单片机在这个任务运行的时候,其他任务是得不到运行的。并且如果这个任务由于某种原因卡住了,它将阻塞整个进程的运行。
任务执行的并行与否是相对而言的,要根据具体的情况。如果我们的要求不高,当然用这种简单的结构是最方便的了,但是这种简单的结构也确实存在很多不足,有很多可以改进的地方。

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

主循环调用任务函数的一种非常常用的结构。到目前为止,在主进程的构建方面用得非常多。有点也是有很多的。起码看起来界面好看很多,并且带有了逻辑性的东西。
如:
  • unsigned char FlagPage=0;
  • void main()
  • {
  • /*初始化*/
  • while(1)
  • {
  •     if(FlagPage==0)
  •         Task0();
  •     else if(FlagPage==1)
  •         Task1();
  •     else if(FlagPage==2)
  •         Task2();
  •     ……
  • }
  • }
  • void Task0()
  • {
  • /*Do Something*/
  •     while(1)
  •     {
  •         /*Do Something*/
  •         if(FlagPage!=0)
  •         return;
  •     }
  • }
  • void Task1()
  • {
  • /*Do Something*/
  •     while(1)
  •     {
  •         /*Do Something*/
  •         if(FlagPage!=1)
  •         return;
  •     }
  • }
  • void Task2()
  • {
  •     /*Do Something*/
  •     while(1)
  •     {
  •         /*Do Something*/
  •         if(FlagPage!=2)
  •         return;
  •     }
  • }
可以看到,主循环其实不进行任何实际功能的处理,它完成的只是调用各个任务函数。
对于比较大型复杂的系统,main 函数的主循环里根本不放要实际处理的代码,而是把所有任务函数归到一起,根据选择进入相应的任务函数,当处理完该任务之后又会回到主循环,由主循环再次分配任务。
此时主循环的作用就是调配任务(当然用来调配任务的主循环本身也是一个最基本的任务),而在被调配的任务里面可能还会再次被该任务调配的子任务。
再来看看被调用的任务函数,这些函数已经不只是完成一些简单功能了,它并不是执行一些固定操作后返回,每个任务函数都有自己的一套控制逻辑,并且“不那么容易返回”。
这些任务函数同属于一个进程,但是同一时刻只有一个可以运行。当进入某个函数时,
可以说进程被这个函数阻塞,其他函数得不到运行。但这也就是我们需要的效果,因为每个函数都有自己的一套控制逻辑,完全不需要考虑其他界面函数。而在函数退出时,可以由该函数本身指定下一个要进入的函数,或者本来就是由于外部修改了 FlagPage 变量才导致该函数退出的。


这种程序结构特别适合于多种“界面”的功能。一般情况下,主进程不会停留在主循环里,而是偶尔退出到主循环重新分配下一个将要进入的函数,大部分时间会停留在某个界面函数里。
此外,这些函数之间有一些公共变量,这些变量的作用就是被各个函数使用,甚至用于
函数间通信,辅助完成这些函数之间的逻辑结构的构建。比如 1.1 节中的那个重要的 FlagPage变量,这个标志变量就指明了当前工作于哪种工作模式下,任何函数(包括中断进程中的函数)都可以通过改变此变量来切换工作模式。
也有一些与函数对应的用于完成特定功能的变量。比如用于数码管或者显示屏显示的现
存,这些显存是有特定用处的,一般其他函数不会使用(但确实是公共变量,是可以被使用
的)。

将它们明确分类一下,整个系统都有哪些东西呢:

1、整体的程序框架是由各个界面函数和少数关键的全局变量构建起来的。这是构成系
统的主体框架。
2、每个界面函数在完成特定功能时,会携带一些为自己服务的“私有的”变量和函数。
3、为整个框架服务的还有一些常用的变量和函数,它们完成的是一些通用功能,可以
把它们理解为“库函数”。

以上都是对一个进程的结构的讨论,并没有涉及到中断。

未完待续。。。。。。

文章部分内容引用自:http://nicekwell.net

收藏 3 评论4 发布时间:2018-5-12 12:52

举报

4个回答
alexwang123 回答时间:2018-5-13 15:12:10
看着还不错
middfat 回答时间:2018-5-14 00:57:02
签到 签到
海迹天涯 回答时间:2018-6-11 10:37:06
收藏了,有空学习
stm1024 回答时间:2018-6-11 11:28:36
继续

所属标签

STM32团队

意法半导体微控制器和微处理器拥有广泛的产品线,包含低成本的8位单片机和基于ARM® Cortex®-M0、M0+、M3、M4、M33、M7及A7内核并具备丰富外设选择的32位微控制器及微处理器


最新内容

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版