技术文档

基于51单片机简易 自己动手写一个简易操作系统(基于51单片机)

小编 2024-11-24 技术文档 23 0

自己动手写一个简易操作系统(基于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#defineuintunsignedintuintnum=0,pnum=0;sbitkey=P1^0;bitflag=0;void...

可不可以用 51单片机 制作一个手机-ZOL问答

可以,先买个手机模块,比如上海移远的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语言比较好,模块化管理编程方便,移植性强,适合编写...

猜你喜欢