基于51单片机简易操作系统设计

前言

看了很久的操作系统原理,ucos源码也看了大半,但是感觉总是懵懵懂懂,用句流行的网络用语就是始终上不了车,后来在网上被人推荐了一篇文章《建立一个属于自己的操作系统》,这篇文章真的非常好,也附有源码,但不知道是不是我找的文章有差错还是啥,我根据文章提供的源码贴代码,根本无法编译,然后开始读代码修改代码最后成功编译但是在硬件平台运行根本不行。后来又断断续续看ucos源码,反正各种什么数据结构啊的通信什么的让人头痛,后来大学的单片机原理完课,学校安排课设,我选了时钟定时器(有点像闹钟),这种开环的裸机开发没什么难度,闲着也是闲着于是从新捡起几个月前没有完成的os,这次重新开坑,代码完全自己敲,从基本功能开始一步步实现os,像《建立一个属于自己的操作系统》介绍的一样。忙活了两天也终于成功了,并且成功地将时钟定时器移植到自己的os上跑了,说实话在os跑比裸机的前后台的效果好很多(os上跑按按键与现实感觉是同步进行的,前后台的效果在按按键的时候数码显示是会黑屏的),但是51的硬件资源太少,只有128个字节的ram,所以此次设计没有统一的任务通信接口,只能实现基本的优先级,延时服务或者轮询服务。在自己成功地建立一个属于自己os后在看ucos的源码更加顺畅,以前一直搞不懂的任务通信也能明白一二(不过也得谢谢任哲写的那本《嵌入式操作系统基础ucOS-Ⅱ和Linux(第2版)》),废话不多说,正文如下:

ps:第一次写这种博客,写的不好望谅解,由于《建立一个属于自己的操作系统》本身就写的很详细,所以我只写出os建立的核心部分。

正文

1,任务人口地址:在os中,是在不直接用程序名(参数)这种方式调用任务。那怎样呢?这部分《建立一个属于自己的操作系统》讲的非常详细,大家自行搜阅。

2,任务调度:学过单片机原理的都知道,cpu中有sp与pc两个特殊的寄存器,sp是堆栈指针,在51中它可以指向数据区的任意单元,PC是程序计数器,它始终保存下一条程序指令的地址。51C语言是可以直接操控sp的,但是pc不行,所以要想办法间接操控pc,对的,就是通过压栈和弹栈实现,在程序执行发生断点时(调动子程序或中断),cpu会自动将pc的值进行压栈,返回断点时会自动将栈顶的值弹回pc,这就是关键,如果在弹回前,我们修改sp,不就可以间接操控pc了吗!这样就可以将cpu执行其他任务了;

3,人工堆栈:操作系统原理中有一点非常重要,就是上下文切换,所以每个任务必须有属于自己的堆栈,称为人工堆栈。人工堆栈的建立非常讲究,不能短也不能太长,短了会是溢出会可能修改其他任务的人工堆栈,产生调度紊乱。太长会浪费空间,尤其是像51这种硬件资源本就少的单片机。堆栈的空间的预留是通过数组来划分的。在建立任务时,要对堆栈初始化(这也很关键),将任务入口地址压到最底部(不同的单片机情况不同,这里以51为例,后面的也是),然后sp指向正确的堆栈位置(不同的单片机情况不同,要保存的寄存器个数不同),个人在设计中发现,为了不让sp越界,最好将堆栈最底部单元预留出来,避免浪费可以用来保存任务信息,比如堆栈使用情况。

void Task_Creak(void (*pfun)(void),INT8U *pStack,INT8U Task_ID)
{

    INT8U *pSt;
     OS_ENTER_CRITICAL();
     pSt=pStack;
    *(++pSt)=(INT16U)pfun;
    *(++pSt)=(INT16U)pfun>>8;
    os_tcb[Task_ID].OSTCBSP=(INT8U)pSt+13;
    OS_Task_List|=OSMapTbl[Task_ID];
     os_tcb[Task_ID].OSTCBDly=0;
     OS_EXIT_CRITICAL();
}

4,任务控制块:和人工堆栈一样每个任务也有属于自己的任务控制块,根据系统需求成员定义不同,对于自由延时服务的os,只需要一个保存任务SP的成员变量和保存延时时间的成员变量。

typedef struct{
      INT8U OSTCBSP;
      INT8U  OSTCBDly;
}OS_TCB;

5,系统时间:也叫时钟节拍,是系统的心脏,有硬件产生,51可以用定时器产生毫秒级中断。

void StartTicker(void)
{
TMOD=0x01;
TH0=0x0d8;
TL0=0x0f0;
ET0=1;
TR0=1;

}

6,系统延时函数接口:用于任务延时,在延时的时候让cpu去执行其他任务,提高cpu的效率(通过实践,我个人觉得这也是软实时实现的缘由),在这个函数中要完成sp保存,将任务踢出就绪表,然后调度。

void OSTimeDly(INT8U ticks)
    {
        OS_ENTER_CRITICAL();
        os_tcb[CurID].OSTCBDly=ticks;
        OS_Task_List&=~OSMapTbl[CurID];
        OS_EXIT_CRITICAL();
        OS_TASK_SW();
 }

7,调度函数:有两种,一种是普通的调度,用于延时调度,所以要插入汇编语言前后分别将现场保护和现场恢复,还有就是完成获取最高任务和sp获取。一种是中断级别的调度,

用于中断服务程序,由于C语言编译成汇编时编译器会自动现场保护,所以只要在调度函数中只要现场恢复,还有就是在完成获取最高任务和sp获取前,要完成sp保存,将任务踢出就绪表。

void OS_TASK_SW(void)
{
  INT8U i;
  EA=0;

  #pragma asm
  PUSH     ACC
    PUSH     B
    PUSH     DPH
    PUSH     DPL
    PUSH     PSW
    MOV      PSW,#00H
    PUSH     AR0
    PUSH     AR1
    PUSH     AR2
    PUSH     AR3
    PUSH     AR4
    PUSH     AR5
    PUSH     AR6
    PUSH     AR7
    #pragma endasm
 os_tcb[CurID].OSTCBSP=SP;

  for(i=0;i<MAX_TASK;i++)
  {
  if(OS_Task_List&OSMapTbl[i])
    {
        break;
     }
  }
  CurID=i;
  SP=os_tcb[CurID].OSTCBSP;

    #pragma asm
    POP      AR7
    POP      AR6
    POP      AR5
    POP      AR4
    POP      AR3
    POP      AR2
    POP      AR1
    POP      AR0
    POP      PSW
    POP      DPL
    POP      DPH
    POP      B
  POP      ACC
    #pragma endasm

   EA=1;
    #pragma asm
         RETI;
    #pragma endasm

}
void TickInterrupt(void)
 {

    INT8U i;
//    SP-=2;

    for(i=0;i<MAX_TASK;i++)
    {

    if(os_tcb[i].OSTCBDly>0)
    {
     os_tcb[i].OSTCBDly--;
    if(os_tcb[i].OSTCBDly==0)
     OS_Task_List|=OSMapTbl[i];
     }

     }

     SP-=2;
     os_tcb[CurID].OSTCBSP=SP;
  //OS_Task_List&=~OSMap[CurID];
  for(i=0;i<MAX_TASK;i++)
  {
  if(OS_Task_List&OSMapTbl[i])
    {
        break;
     }
  }
  CurID=i;
  SP=os_tcb[CurID].OSTCBSP;               

  #pragma asm
    POP      AR7
    POP      AR6
    POP      AR5
    POP      AR4
    POP      AR3
    POP      AR2
    POP      AR1
    POP      AR0
    POP      PSW
    POP      DPL
    POP      DPH
    POP      B
    POP      ACC
    #pragma endasm
         EA=1;
    #pragma asm
    RETI
    #pragma endasm
 }

8,sp控制:在调度的过程中,必须保证将任务断点的入口地址保存在堆栈最底部(预留单元上面),再次在任务调度过程中难免会调用其他函数再次压栈,而且可能不在会这个断点,

所以在被调用的这个程序中将sp下调两位。所以在os设计中sp的控制必须十分小心,不能任务调度一定会紊乱。

9.任务函数:os中必须存在一个不能主动申请调度的任务,称为系统任务,为了cpu在没有任何任务的时候有事可做,个人在实践中发现没有这种任务os会出错。

还有就是要把握任务集的可调度性(其中受任务优先级设计影响)。

10:就绪表:就绪表的设计要是至关重要,《操作系统原理》自阅。

最后:本人菜鸟一枚,有错见谅,谢谢观阅!

ps:以后有机会会贴上工程代码及效果图。

时间: 2024-10-02 00:43:56

基于51单片机简易操作系统设计的相关文章

基于51单片机的12864驱动

/**************dis_12864.h***************/ #include <reg52.h> #ifndef __DIS_12864_H__ #define __DIS_12864_H__ #define uchar unsigned char #define uint  unsigned int /*12864端口定义*/ #define LCD_data  P0             //数据口 sbit LCD_RS  =  P1^0;          

[原创]基于51单片机的红外遥控课程设计

[注]: 一眨眼,大学接近尾声,具有找工作需要,所以把大学做的电子设计“劣作”放上来.希望考研失意,还能赶上“好工作”的春招班车.如果大伙有什么工作推荐也可以联系我哦,因为一年考研少接触了这方面,所以难免有些生疏.但请相信我!给我机会我会很认真学的! 邮箱:[email protected] 转载请注明出处呀! 基于51单片机的红外遥控课程设计 目录 第一章 设计简介... 3 第二章 系统方案... 3 一.设计方案对比... 3 二.方案设计... 4 第三章 硬件设计... 5 一.红外遥

基于51单片机的温度控制系统毕设

分享一下单片机毕设课程计参考资料33个,献给大四的朋友,毕业设计就不用愁了,如果有什么不懂的地方可以加群大家一起交流:813238832资料下载的地址:https://bbs.usoftchina.com/thread-208012-1-1.html 1.CDMA通信系统中的接入信道部分进行仿真与分析2.USB接口设计3.毕业设计(论文)OFDM通信系统基带数据4.电气工程系毕业设计开题报告5.电信运营商收入保障系统设计与实现6.基于51单片机的16×16点阵(滚动显示)7.基于51单片机的LE

基于51单片机DS18B20测温LCD1602显示可设时设温调时的项目

一.前言 1.基于51单片机DS18B20测温LCD1602显示可设时设温调时的项目包括用Keil软件编写单片机C语言程序和用Proteus软件仿真单片机外围电路 2.基于51单片机DS18B20测温LCD1602显示可设时设温调时的项目构思 (1).声明程序变量思维导图 (2).程序子函数思维导图 (3).程序主函数思维导图 二.基于51单片机DS18B20测温LCD1602显示可设时设温调时的项目的Keil软件编写的单片机C语言程序 1 #include<reg52.h>//声明51单片机

基于51单片机的万年历(算法实现)

基于51单片机的万年历,用到了单片机独立键盘.数码管.LED灯模块实现. 想要简单还是DS1302好用. 1 /************************************************** 2 3 作者:纟彖氵戋 博客:http://www.cnblogs.com/yllinux/ 4 5 时间:2017年6月7日 6 7 目标:利用单片机独立键盘.数码管.LED灯模块实现万年历(算法实现) 8 9 ************************************

基于51单片机的无线测温系统

本51项目基于STC89C52MCU,温度传感器为DS18B20,显示模块用的是LCD1602,无线模块用的是Nodemcu. 项目用到的编程语言:C,C++,Lua. 实现思路是这样,DS18B20测温,然后数据串行传送给51单片机,然后51通过串口将数据传送给Nodemcu,Nodemcu通过其WIFI模块将数据发送给上位机,上位机上的程序是用Qt编写的GUI.(这里无线传输采用的是无连接的UDP协议) 1.DS18B20温度测量模块 DS18B20是单总线器件,所以时序要求非常严格,程序编

单片机课程设计——基于51单片机温湿度检测系统的设计与实现

1 #include <reg52.h> 2 #include "1602.h" 3 #include "dht.h" 4 #include "2402.h" 5 6 //定义三个LED灯 7 sbit Led_qushi=P1^6; //去湿灯 8 sbit Led_jiangwen=P1^5; //降温灯 9 sbit Led_shengwen=P1^4; //升温灯 10 sbit Key_TH1 = P3^2; 11 sbit

基于51单片机的独立按键和矩阵按键用法

------------恢复内容开始------------ 主要实现如图所示的功能 将主函数以外的函数全部放在qiyu.h文件中 1 //qiyu.h 2 #define KEY_PORT P1 3 #define led P2 4 #define unchar unsigned char 5 #define uint unsigned int 6 7 sbit Buz = P1^5; 8 sbit LED = P2^0; 9 sbit K1 = P3^0; 10 sbit K2 = P3^1

基于51单片机8位数码管(74HC595芯片)的使用方法

#include <AT89X51.H> #include <intrins.h> // 函数原形定义 #define uchar unsigned char #define uint unsigned int void main (void); // 主函数 void LED_OUT(uchar X); // LED单字节串行移位函数 void LED_print (uchar p, uchar x) ; unsigned char code LED_0F[]; // LED字模