三木社区

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 713|回复: 0

简单的51单片机多任务操作系统(C51)

[复制链接]

1657

主题

1684

帖子

5684

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5684
发表于 2019-7-24 12:19:45 | 显示全部楼层 |阅读模式
  1. /*
  2. 简单的多任务操作系统
  3. 其实只有个任务调度切换,把说它是OS有点牵强,但它对于一些简单的开发应用来说,
  4. 简单也许就是最好的.尽情的扩展它吧.别忘了把你的成果分享给大家.
  5. 这是一个最简单的OS,一切以运行效率为重,经测试,切换一次任务仅20个机器周期,
  6. 也就是在标准51(工作于12M晶振)上20uS.
  7. 而为速度作出的牺牲是,为了给每个任务都分配一个私有堆栈,而占用了较多的内存.
  8. 作为补偿,多任务更容易安排程序逻辑,从而可以节省一些用于控制的变量.
  9. 任务槽越多,占用内存越多,但任务也越好安排,以实际需求合理安排任务数目.
  10. 一般来说,4个已足够.况且可以拿一个槽出来作为活动槽,换入换入一些临时任务.
  11. task_load(函数名,任务槽号)
  12. 装载任务
  13. os_start(任务槽号)
  14. 启动任务表.参数必须指向一个装载了的任务,否则系统会崩溃.
  15. task_switch()
  16. 切换到其它任务
  17. .编写任务函数注意事项:
  18. KEIL C编译器是假定用户使用单任务环境,所以在变量的使用上都未对多任务进行处理,
  19. 编写任务时应注意变量覆盖和代码重入问题.
  20. 1.覆盖:编译器为了节省内存,会给两个没用调用关系的函数分配同一内存地址作为变量空间.
  21. 这在单任务下是很合理的,但对于多任务来说,两个进程会互相干扰对方.
  22. 解决的方法是:凡作用域内会跨越task_switch()的变量,都使用static前辍,
  23. 保证其地址空间分配时的唯一性.
  24. 2.重入:重入并不是多任务下独有的问题,在单任务时,函数递归同样会导致重入,
  25. 即,一个函数的不同实例(或者叫作"复本")之间的变量覆盖问题.
  26. 解决的方法是:使用reentrant函数后辍(例如:void function1() reentrant{...}).当然,根本的办法还是避免重入,因为重入会带来巨大的目标代码量,
  27. 并极大降低运行效率.
  28. 3.额外提醒一句,在本例中,任务函数必须为一个死循环.退出函数会导致系统崩溃.
  29. .任务函数如果是用汇编写成或内嵌汇编,切换任务时应该注意什么问题?
  30. 由于KEIL C编译器在处理函数调用时的约定规则为"子函数有可能修改任务寄存器",
  31. 因此编译器在调用前已释放所有寄存器,子函数无需考虑保护任何寄存器.
  32. 这对于写惯汇编的人来说有点不习惯: 汇编习惯于在子程序中保护寄存器.
  33. 请注意一条原则:凡是需要跨越task_switch()的寄存器,全部需要保护(例如入栈).
  34. 根本解决办法还是,不要让寄存器跨越任务切换函数task_switch()
  35. 事实上这里要补充一下,正如前所说,由于编译器存在变量地址覆盖优化,
  36. 因此凡是非静态变量都不得跨越task_switch().
  37. 任务函数的书写:
  38. void 函数名(void)
  39. {        //任务函数必须定义为无参数型
  40.         while(1)
  41.         {
  42.                 //任务函数不得返回,必须为死循环
  43.                 //....这里写任务处理代码
  44.                 task_switch();//每执行一段时间任务,就释放CPU一下,
  45.                 让别的任务有机会运行.
  46.         }
  47. }
  48. 任务装载:
  49. task_load(函数名,任务槽号)
  50. 装载函数的动作可发生在任意时候,但通常是在main()中.要注意的是,
  51. 在本例中由于没考虑任务换出,
  52. 所以在执行os_start()前必须将所有任务槽装满.之后可以随意更换任务槽中的任务.
  53. 启动任务调度器:
  54. os_start(任务槽号)
  55. 调用该宏后,将从参数指定的任务槽开始执行任务调度.
  56. 本例为每切换一次任务需额外开销20个机器周期,用于迁移堆栈.
  57. */


  58. #include <reg52.h>

  59. sbit LED1 = P2 ^ 0;
  60. sbit LED2 = P2 ^ 1;
  61. void func1();
  62. void func2();

  63. /*============================以下为任务管理器代码============================*/

  64. //任务槽个数.在本例中并未考虑任务换入换出,所以实际运行的任务有多少个,
  65. //就定义多少个任务槽,不可多定义或少定义
  66. #define MAX_TASKS 5

  67. //任务的栈指针
  68. unsigned char idata task_sp[MAX_TASKS];

  69. //最大栈深.最低不得少于2个,保守值为12.
  70. //预估方法:以2为基数,每增加一层函数调用,加2字节.
  71. //如果其间可能发生中断,则还要再加上中断需要的栈深.
  72. //减小栈深的方法:1.尽量少嵌套子程序 2.调子程序前关中断.
  73. #define MAX_TASK_DEP 12

  74. unsigned char idata task_stack[MAX_TASKS][MAX_TASK_DEP];//任务堆栈.

  75. unsigned char task_id;                //当前活动任务号


  76. //任务切换函数(任务调度器)
  77. void task_switch()
  78. {
  79.     task_sp[task_id] = SP;                //保存当前任务的栈指针

  80.     if (++task_id == MAX_TASKS)        //任务号切换到下一个任务
  81.         task_id = 0;

  82.     SP = task_sp[task_id];                //将系统的栈指针指向下个任务的私栈
  83. }




  84. //任务装入函数.将指定的函数(参数1)装入指定(参数2)的任务槽中.
  85. //如果该槽中原来就有任务,则原任务丢失,但系统本身不会发生错误.
  86. //将各任务的函数地址的低字节和高字节分别入在
  87. //task_stack[任务号][0]和task_stack[任务号][1]中
  88. void task_load(unsigned int fn, unsigned char tid)
  89. {
  90.     //task_sp[tid] = task_stack[tid][1];
  91.     task_sp[tid] = task_stack[tid] + 1;
  92.     task_stack[tid][0] = (unsigned int)fn & 0xff;
  93.     task_stack[tid][1] = (unsigned int)fn >> 8;
  94. }

  95. //从指定的任务开始运行任务调度.调用该宏后,将永不返回.
  96. #define os_start(tid) {task_id = tid,SP = task_sp[tid];return;}




  97. /*============================以下为测试代码============================*/


  98. unsigned char stra[3], strb[3];//用于内存块复制测试的数组.


  99. //测试任务:复制内存块.每复制一个字节释放CPU一次
  100. void task1()
  101. {
  102.     //每复制一个字节释放CPU一次,控制循环的变量必须考虑覆盖
  103.     static unsigned char i;//如果将这个变量前的static去掉,会发生什么事?
  104.     i = 0;

  105.     while (1) //任务必须为死循环,不得退出函数,否则系统会崩溃
  106.     {
  107.         stra[i] = strb[i];
  108.         if (++i == sizeof(stra))
  109.             i = 0;

  110.         //变量i在这里跨越了task_switch(),因此它必须定义为静态(static),
  111.         //否则它将会被其它进程修改,因为在另一个进程里也会用到该变量所占用的地址.
  112.         task_switch();//释放CPU一会儿,让其它进程有机会运行.如果去掉该行,则别的进程永远不会被调用到
  113.     }
  114. }

  115. //测试任务:复制内存块.每复制一个字节释放CPU一次.
  116. void task2()
  117. {
  118.     //每复制一个字节释放CPU一次,控制循环的变量必须考虑覆盖
  119.     static unsigned char i;//如果将这个变量前的static去掉,将会发生覆盖问题.
  120.     //task1()和task2()会被编译器分配到同一个内存地址上,当两个任务同时运行时,i的值就会被两个任务改来改去
  121.     i = 0;

  122.     while (1) //任务必须为死循环,不得退出函数,否则系统会崩溃
  123.     {
  124.         stra[i] = strb[i];
  125.         if (++i == sizeof(stra))
  126.             i = 0;

  127.         //变量i在这里跨越了task_switch(),因此它必须定义为静态(static),
  128.         //否则它将会被其它进程修改,因为在另一个进程里也会用到该变量所占用的地址.
  129.         task_switch();//释放CPU一会儿,让其它进程有机会运行.如果去掉该行,则别的进程永远不会被调用到
  130.     }
  131. }

  132. //测试任务:复制内存块.复制完所有字节后释放CPU一次.
  133. void task3()
  134. {
  135.     //复制全部字节后才释放CPU,控制循环的变量不须考虑覆盖
  136.     unsigned char i;//这个变量前不需要加static,
  137.     //因为在它的作用域内并没有释放过CPU

  138.     while (1) //任务必须为死循环,不得退出函数,否则系统会崩溃
  139.     {
  140.         i = sizeof(stra);
  141.         do
  142.         {
  143.             stra[i-1] = strb[i-1];
  144.         }
  145.         while (--i);

  146.         //变量i在这里已完成它的使命,所以无需定义为静态.
  147.         //你甚至可以定义为寄存器型(regiter)
  148.         task_switch();//释放CPU一会儿,让其它进程有机会运行.如果去掉该行,
  149.         //则别的进程永远不会被调用到
  150.     }
  151. }



  152. /*
  153. my first task
  154. */

  155. void func1()
  156. {
  157.     static unsigned char data i;
  158.     i = 0;

  159.     while (1)
  160.     {
  161.         //25ms
  162.         if (i < 250)
  163.         {
  164.             i++;
  165.         }
  166.         if (i >= 250)
  167.         {
  168.             LED1 = ~LED1;
  169.             i = 0;
  170.         }
  171.         task_switch();
  172.     }
  173. }

  174. //经过仿真计算得j=10,即等待1ms
  175. void func2()
  176. {
  177.     static unsigned int data j;
  178.     j = 0;

  179.     while (1)
  180.     {
  181.         //65ms
  182.         if (j < 650)
  183.         {
  184.             j++;
  185.         }
  186.         if (j >= 650)
  187.         {
  188.             LED2 = ~LED2;
  189.             j = 0;
  190.         }
  191.         task_switch();
  192.     }
  193. }

  194. /*
  195. keyscan task
  196. void task3()
  197. {
  198.         while(1)
  199.         {
  200.         i = 5;
  201.         do
  202.                 {
  203.                 sigl = !sigl;
  204.         }while(--i);
  205.         task_switch();
  206.     }
  207. }
  208. */

  209. /*
  210. void func1()
  211. {
  212.     register char data i;
  213.     while(1)
  214.         {
  215.         i = 5;
  216.         do
  217.                 {
  218.                 sigl = !sigl;
  219.         }while(--i);
  220.         task_switch();
  221.     }
  222. }
  223. void func2()
  224. {
  225.     register char data i;
  226.     while(1)
  227.         {
  228.         i = 5;
  229.         do
  230.                 {
  231.                 sigl = !sigl;
  232.         }while(--i);
  233.         task_switch();
  234.     }
  235. }
  236. */

  237. void main()
  238. {
  239.     //在这个示例里并没有考虑任务的换入换出,所以任务槽必须全部用完,否则系统会崩溃.
  240.     //这里装载了三个任务,因此在定义MAX_TASKS时也必须定义为5
  241.     task_load(task1, 0);//将task1函数装入0号槽
  242.     task_load(task2, 1);//将task2函数装入1号槽
  243.     task_load(task3, 2);//将task3函数装入2号槽
  244.     task_load(func1, 3);//将task3函数装入3号槽
  245.     task_load(func2, 4);//将task3函数装入4号槽

  246.     os_start(0);//启动任务调度,并从0号槽开始运行.参数改为1,则首先运行1号槽.
  247.     //调用该宏后,程序流将永不再返回main(),也就是说,该语句行之后的所有语句都不被执行到.
  248. }
  249. ---------------------
  250. 作者:liming0931
  251. 来源:CSDN
  252. 原文:https://blog.csdn.net/liming0931/article/details/81603619
  253. 版权声明:本文为博主原创文章,转载请附上博文链接!
复制代码


回复

使用道具 举报

Archiver|手机版|小黑屋|三木电子社区 ( 辽ICP备11000133号-4 )

辽公网安备 21021702000620号

GMT+8, 2026-1-13 00:49 , Processed in 0.029146 second(s), 23 queries .

Powered by Discuz! X3.3

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表