自己动手写一个简易操作系统(基于51单片机)
背景介绍
大一学了51单片机,对于单片机的一些常用外设有了一定的了解。之后,大家都在说当前最流行的单片机是stm32,所以我抽出了暑假的时间的时间学习了stm32单片机,刚开始学的时候真的很痛苦,在坚持了一个星期之后,我慢慢找到了自信,stm32单片机实际上和51是一样的,只是需要配置的寄存器多了一点。在刚开始学的时候,经常在配置的时候无法配置完全,导致无法得到预期的实验效果,但是实际上,大家没必要过分纠结于配置,完全可以直接参考别人使用该功能的配置方式。我们应该将心思放在功能的开发上,而不是纠结于前期简单的配置。在熟悉使用stm32之后,开始接触操作系统ucos,过程中一直觉得自己似懂非懂,所以我在想为什么我自己不利用51写一个简易操作系统,来加深自己的理解。初期写出的操作系统不用考虑通信等高级功能,只需要写出可以调度多个任务的操作系统即可,下面给大家介绍一下我自己写的操作系统 (写的不太好,仅供大家参考)。
系统实现
实现简易操作系统,主要需要实现三个函数:
创建任务函数(将定义的任务的执行入口保存起来,供调度使用)任务延时函数(每一个任务执行后,都需要加入延时函数,否则低优先级的任务没有机会执行)中断调度函数(提供时间片调度)1 创建任务函数介绍
int OSTaskCreate(unsigned int Task, unsigned char* pStack, unsigned char TaskID)
{
unsigned char i = 0, j = 0;
*pStack++ = Task & 0xFF; //低八位地址(51单片机入栈向上,出栈向下)
*pStack = Task >> 8; //高八位地址
os_enter_critical();
TaskCB[TaskID].OSTaskStackButtom = (unsigned char)pStack + 13;
TaskCB[TaskID].OSWaitTick = 0;
TASK_READY(TaskID); //将该优先级的任务在全局变量中标记为就绪
os_exit_critical();
return 0;
}
入口参数:
unsigned int Task ---- 任务函数的入口地址
unsigned char* pStack ---- 任务函数的堆栈,主要用来保存现场参数
unsigned char TaskID ----- 任务优先级
2 任务延时函数
void OSTimeDly(unsigned int time)
{
TaskCB[CurrentTaskID].OSWaitTick = time; //将任务的延时时间赋值给任务控制块
task_sw(); //任务调度
}
static void task_sw()
{
os_enter_critical();
TASK_BLOCK(CurrentTaskID); //将当前任务的就绪状态取消
#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
TaskCB[CurrentTaskID].OSTaskStackButtom = SP ; //将当前任务的堆栈位置保存,用于下次恢复该任务
CurrentTaskID = Task_High(); //找出处于就绪态的最高优先级的任务
SP = TaskCB[CurrentTaskID].OSTaskStackButtom;
#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
os_exit_critical(); //离开时会把SP的当前位置的值送入PC指针,所以最高优先级的任务得以运行
}
3 中断调度函数
void TF0_isr() interrupt 1
{
TH0 = 56320/256;
TL0 = 56320%256;
TaskCB[CurrentTaskID].OSTaskStackButtom = SP; //被中断的任务的现场已经压入堆栈,所以只需保存SP
CurrentTaskID = Task_Ready_High(); //取出就绪中优先级最高的任务
SP = TaskCB[CurrentTaskID].OSTaskStackButtom;
#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
RETI
#pragma endasm
}
总结:
以上三个函数就是一个简易操作系统的关键函数,大家可以自己动手实现一下。这对大家学习ucos有很大的帮助,同时也为学习linux系统打下基础。
后续我会继续完善这个简易操作系统,添加通信等功能,希望大家可以关注我,共同进步。
基于proteus的51单片机开发实例16-简易秒表
1. 基于proteus的51单片机开发实例16-简易秒表
1.1. 实验目的
本实例中我们来加深对51单片机定时器/计数器的理解,及定时器定时功能的使用方法,利用定时功能来实现一个简易秒表,该秒表通过两位数码管显示0~99秒的计时,并且可以通过按键实现秒表的启动、停止、清零。
图1 简易秒表电路
1.2. 设计思路
之所以说是简易秒表,因为我们平时所见到的秒表不仅有秒时间的变化,还有毫秒的变化,如下面的动图所示。而我们实现的秒表只有秒数的变化,没有毫秒级的变化。
图2 更精确的秒表
本例的设计思路是使用一个按键来控制秒表的启动、停止、清零功能。具体实现过程是:第一次按下按键,秒表开始工作,两位数码管从00开始显示,每秒显示的数字加1,一直加到99,然后再从00开始显示;当第二次按下按键,数码管的显示停止,同时数码管正在显示的数字不再变化;第三次按下按键,数码管的数字清零,变为显示00。
1.3. 基础知识
本例设计的基础知识有定时器的工作原理;数码管的显示原理,按键检测识别原理,这些我们在之前已经学习过了。
本例的另一个重点是程序代码比之前的代码复杂了,代码中涉及了端口位定义,位变量定义,数组的定义及初始化,函数的定义,C语言中switch case语句的使用等等;我们将在代码部分一一解释。
1.4. 电路设计
本实例的电路图如图1所示。单片机的P0和P1口接两个共阳极数码管,用于秒表显示。按键连接到P3.4端口,P3.4口在按键未按下时处于高电平,按键按下后,变为低电平。
1.5. 程序设计
本实例的程序代码如下所示。
这个程序代码中有很多知识点,我们来学习一下。
1、头文件包含#include <AT89x52.h>
这个语句的作用是将预定义好的51单片机的端口定义、寄存器定义等各种信息包含进来。例如我们在程序中用到的P3^4,EA,TH0等,我们之所以可以直接使用这些名称,就是因为在这个头文件中已经帮我们定义好了。如果程序中没有这条头文件包含的语句,则凡是用到这些名称的语句都会报错。
图3 51单片机头文件部分内容
2、位定义
在程序中有这些语句sbit K1 = P3^4; bit Key_State; 这两个都是位定义语句,只不过两者还有些区别,sbit是定义端口的某一位,而bit则定义一个位数据,这个位数据只有两个值0或1。
3、数组的定义和初始化
看一下这个语句unsigned char DSY_CODE[]={
0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90
}
这个语句实现的功能就是在定义的同时初始化了数组中各元素的值。细心的人可能会发现,这个数组中元素个数没有定义,这是C语言的一个特色:数组定义的同时初始化的话,如果不定义数组元素的个数,那么默认按照初始化的值的个数设置。
再看这个unsigned char ViewData[2]; 只是定义了数组,没有初始化数组元素的值,所以必须指明数组元素的个数,并且如果程序中要对这个数组赋值的话,只能一个一个的赋值,ViewData[0]=0xC0;ViewData[1]=0xC0;而不能直接这样赋值ViewData[2]={0xc0,0xc0};这就是数组定义的同时初始化和数组只定义不初始化时的区别。
4、全局变量
全局变量必须在所有函数之前定义,并且全局变量在程序运行期间,它的值是不变的,除非程序中人为改变了这个值。
5、函数的定义和声明
函数可以在调用之前先声明,然后再调用它的函数之后定义,也可以在声明的同时定义,本例中就是在声明的同时定义。函数的声明只要一句话,例如void Key_Event_Handle(void);而函数的定义是指将函数要实现的功能写出来,具体说就是把函数的内容补充完善。
6、switch case语句
switch case语句属于判断语句,switch后面括号中的变量就是判断条件,这个判断条件只能是整数,不能是小数。每个case语句后面的功能执行完后,最好加一个break语句,以跳出整个判断结构,否则只要下面的case条件满足,就一直执行,这样容易造成混乱。
关于51单片机C语言编程的一些知识,今天就先说到这里,后面我们会有更多了解。
1.6. 实例仿真
编译程序后,将生成的hex文件载入proteus电路的单片机中,开始仿真,仿真时随时按下按键(要默记按键是第几次按下),观察两位数码管显示数字的变化,充分理解该实例的功能实现。
下面视频是本实例的仿真过程。
视频加载中...
1.7. 总结
通过本实例的学习,我们更加熟悉了51单片机定时器/计数器的定时功能,同时也更多的了解了51单片机C语言程序设计中的端口位定义,位变量定义,数组的定义及初始化,函数的定义,C语言中switch case语句的使用等等编程方法,这对我们继续深入掌握编程知识很有用处。
相关问答
用 51单片机 写一个简单的脉冲计数程序-ZOL问答#include
可以,先买个手机模块,比如上海移远的M35,插上电话卡,用单片机控制它就可以打电...不建议直接用51单片机来做手机,因为51系列是低级的嵌入式处理器,主要用于简单...
用c++怎么编写 51单片机 程序,可以这么样编写吗?可以的。在51单片机的嵌入式C语言中,指针同样是被支持的。所以在单片机上一样可以使用指针操作,具体使用方法,与标准C语言并没有不同。不过需要注意的是,使用...
自动寻迹智能小车怎么做啊具体要求如下 51单片机 ?接循迹用的光电传感器,用单片机判断,驱动电机执行。传感器越多越好。以比较奇葩的单路传感器为例,0驱动左轮,1驱动右轮,就可以沿黑线一侧摇摆前进。这么简单...
家里有许多手机屏,请问能用 51单片机 驱动,现实一些简单的图像吗?可是可以,但是涉及到连接口还有其他的问题,51点彩屏我还没学到,但是点大液晶就有点麻烦了,看了一下别人的,估计程序有点麻烦可是可以,但是涉及到连接口还有其...
基于proteus的 51 系列 单片机 怎样运行仿真?你好!很高兴为你解答,下面给你仔细介绍!proteus是一个仿真软件,可以在proteus里面仿真51单片机的实验,这样解决了自己制作和焊接单片机的电路,把编写好多...
51单片机 io控制方式?共有两种控制方式:1,无条件送方式无条件传送也称为同步程序传送.只有那些一直为数据I/O传送作好准备的外部设备,才能使用无条件传送方式.因为在进行I/O操作...
51单片机 编程方法?从2个方面来解答:1.硬件2.软件一、硬件1.熟悉常用的元器件,如果你不知道哪些,找一个51开发板,把原理图上的元器件全部熟悉一遍,知道他们的工作原理和使...
mcs 51单片机 循环指令程序编写?MCS-51单片机常见的循环指令有JC/JNC、DJNZ和CJNE等,下面是一个简单的循环指令程序编写示例:```ORG0;程序入口地址MOVR0,#10H;将初值10H赋给R0寄存器...
51单片机 初学者该怎么学?51单片机初学者学习步骤:1.第一步:基础理论知识学习。单片机编程用C语言或汇编语言都可以,但是我建议用C语言比较好,模块化管理编程方便,移植性强,适合编写...