手动实现51单片机函数切换

发布者:SereneSpirit最新更新时间:2024-08-22 来源: cnblogs关键字:51单片机 手机看文章 扫描二维码
随时随地手机看文章

一、前言

为什么要研究单片机函数切换的过程?实际上是我在20年暑假时给51单片机写了一个简单的实时操作系统,具有简单的抢占式内核调度功能,虽然很简单,但我还是想把实现的过程分享出来,这篇文章是其中的内容之一,有兴趣的同学可以先了解一下,点个关注收藏,后面持续更新!

二、函数切换原理

在使用C语言编写51单片机的程序时,如果我们在函数一中调用另外一个函数,只需要添加一行 函数名+括号及参数 就可以执行另外一个函数,就就像下面的例子:

int main(void){
	int a=0;
	Fun1(a);
	Fun2(a);		
    return 0;}

在main函数中直接调用Fun1,Fun2函数,然后程序就会跳转。但是问题来了,函数是怎么跳转的呢?在函数跳转的过程中51单片机的寄存器是如何变换的呢?

实际上,函数的切换过程其实就是将当前函数的运行状态和数据以及返回地址等保存到堆栈,然后读取新函数的运行状态和数据,PC(程序计数器)再跳转到调用函数的地址执行对应的函数,这些操作其实都是在对51单片机的寄存器进行操作,具体用到的几个寄存器如下:

寄存器功能
R0-R7工作寄存器R0~R7:存储当前程序的 “环境“
DPH数据地址指针(高8位):DPH和DPL组合在一起使用,用它来访问外部数据存储器中的任一单元,也可以作为通用寄存器来用
DPL数据地址指针(低8位):DPH和DPL组合在一起使用,用它来访问外部数据存储器中的任一单元,也可以作为通用寄存器来用
PSW程序状态字:里面放了CPU工作时的很多状态,可以了解CPU的当前状态
BB寄存器:在做乘、除法时放乘数或除数
ACC累加器:运算寄存器
SP堆栈指针:指向堆栈操作的栈顶地址,是8位计数器
PC程序计数器:指向下一条待执行的指令

下面我们来用汇编手动编写一个函数切换函数,然后在定时器中断中调用,不停的切换两个函数,编写前先了解一下切换框架和使用到的汇编代码

  • POP出栈指令

    弹出堆栈数据到data,然后SP指针减一

    POP data
  • PUSH压栈指令

    先把SP指针加一,然后将data数据压入堆栈

    PUSH data
  • RET返回指令

    把弹出堆栈两个字节的数据到PC,指向下一个程序的执行地址

三、函数切换代码实现

函数代码我们使用51单片机作为运行平台,在主函数中通过切换函数1切换到函数1,函数1是一个死循环,之后我们在函数1里面调用函数切换2切换到函数2运行,函数2延时一段时间后再切换回1,一直循环下去;代码如下:

定义用到的函数:

void task1(void); //函数1

void task2(void); //函数2

void delay(unsigned short time);//延时函数


定义用到的变量和类型

unsigned char a; //函数一运行的标志

unsigned char b;  //函数二运行的标志

unsigned char task1_stack[20];    //函数堆栈

unsigned char task2_stack[20];    //函数堆栈

//声明函数控制块结构体

typedef struct

{         

unsigned char Task_SP;      //函数堆栈指针

}TASK_TCB;

//定义TCB

TASK_TCB task1_tcb;

TASK_TCB task2_tcb;


编写main函数主体初始化,此处定义两个函数控制块tcb,用来存放函数的堆栈指针(函数的堆栈其实就是一个数组,用来保存函数的运行数据),然后我们在将函数的入口地址保存在堆栈的最低两位,接着将SP指针向上偏移14位,因为我们要保存的寄存器加起来有13位,同时在一开始要把函数入口保存在堆栈所以是14位

而切换到函数的时候是要先从函数堆栈出栈,所以预先偏移14位地址,main函数代码如下:

void main(void){
    //保存堆栈指针和函数入口
	task1_tcb.Task_SP = task1_stack;
	task1_stack[0]= (unsigned char)task1;
	task1_stack[0]= (unsigned char)task1>>8;
    //偏移堆栈
	task1_tcb.Task_SP += 14;
    //保存堆栈指针和函数入口
	task2_tcb.Task_SP = task2_stack;
	task2_stack[0]= (unsigned char)task2;
	task2_stack[0]= (unsigned char)task2>>8;
    //偏移堆栈
	task2_tcb.Task_SP += 14;
    //切换到函数1
	Task_Sched_1();
	while(1);}

编写函数1和函数2实体

void task1(void) {
	while(1)
	{
		a=1;
		b=0;
		delay(100);		//延时
		Task_Sched_2();//切换到函数2
	}}void task2(void){
	while(1)
	{
		a=0;
		b=1;
		delay(100);//延时
		Task_Sched_1();//切换到函数1
	}}

编写函数切换函数

切换到函数1

void Task_Sched_1(void){
		__asm PUSH ACC       //保护当前寄存器,压栈
		__asm PUSH B
		__asm PUSH PSW
		__asm PUSH DPL
		__asm PUSH DPH
		__asm PUSH 0         //0-7为工作寄存器
		__asm PUSH 1
		__asm PUSH 2
		__asm PUSH 3
		__asm PUSH 4
		__asm PUSH 5
		__asm PUSH 6
		__asm PUSH 7
		SP = (task1_tcb.Task_SP);
		__asm POP 7         //恢复目标函数寄存器
		__asm POP 6
		__asm POP 5
		__asm POP 4
		__asm POP 3
		__asm POP 2
		__asm POP 1
		__asm POP 0
		__asm POP DPH
		__asm POP DPL
		__asm POP PSW
		__asm POP B
		__asm POP ACC}

切换到函数2

void Task_Sched_2(void){
		__asm PUSH ACC       //保护当前寄存器,压栈
		__asm PUSH B
		__asm PUSH PSW
		__asm PUSH DPL
		__asm PUSH DPH
		__asm PUSH 0         //0-7为工作寄存器
		__asm PUSH 1
		__asm PUSH 2
		__asm PUSH 3
		__asm PUSH 4
		__asm PUSH 5
		__asm PUSH 6
		__asm PUSH 7
		SP = (task2_tcb.Task_SP);
		__asm POP 7         //恢复目标函数寄存器
		__asm POP 6
		__asm POP 5
		__asm POP 4
		__asm POP 3
		__asm POP 2
		__asm POP 1
		__asm POP 0
		__asm POP DPH
		__asm POP DPL
		__asm POP PSW
		__asm POP B
		__asm POP ACC}

注意此处的切换函数使用汇编编译,主要内容就是保存当前函数的运行环境到函数堆栈,然后从下一个函数的堆栈读取其运行环境,切换代码我写在一个os.c文件里面,编译前需要汇编编译,步骤如下:

右击文件->options

20210808163102

开启嵌入汇编程序,使C语言中可以编译汇编代码,加__asm声明一下是汇编就行

20210808163124

四、实验现象

函数1中把a取1,b取0,而函数2相反,当这两个函数交叉运行时a和b的波形应该相反,所以仿真后结果如下,手动切换函数完成

20210808161548


关键字:51单片机 引用地址:手动实现51单片机函数切换

上一篇:51单片机定时器、串口、中断
下一篇:51单片机串口应用实例(汇编)

推荐阅读最新更新时间:2026-03-20 11:05

基于51单片机的温度控制proteus仿真
功能介绍: 0.本系统采用STC89C52作为单片机 1.LCD1602液晶实时显示当前温度及温度范围设定 2.超过温度设定范围将启动加热棒加热或风扇冷却 3.超过设定温度将声光报警 4.按键可设置温度范围 5.采用DC002作为电源接口可直接输入5V给整个系统供电 原理图: PCB: 主程序: #include reg52.h //包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义 #include stdio.h #include 18b20.h #include lcd1602.h #include delay.h #define SETDOWN 0x01 #define SETUP 0x02
[单片机]
仿真设计|基于51单片机的停车场系统(带时间显示)
具体实现功能 (1)根据红外传感器原理,用两个按键代替驶入检测红外传感器和驶出检测红外传感器,驶入按键按下表示有车进入停车场,驶出按键按下表示车辆离开。 (2)LCD1602实时显示当前时间,按键切换后,显示停车场驶入车辆数、驶出车辆数、现有车辆数及剩余停车位数,总共16个车位,指示灯指示具体的车位占用情况。 (3)可以手动设置现有车辆数及剩余车位数;车位满后将报警提示。 仿真演示视频: https://www.bilibili.com/video/BV1YnyfYzEFp/ 设计介绍 51单片机简介 51单片是一种低功耗、高性能CMOS-8位微控制器,拥有灵巧的8位CPU和可编程Flash,使得51单片机为众多嵌入式控制
[单片机]
51单片机笔记模块篇2 -- 单路继电器
继电器在电路上是比较常见的元器件,继电器的作用也比较多,比较常用的是作为开关和放大作用。例如,有一个加热装置需要12V供电,但是还需要单片机来控制它,单片机只能输出5V的电压,这时候就要用到继电器。由于每种继电器长得都不太一样,所以这里只放原理图,如图 单路继电器原理 如图所示,单路继电器的本质就是一个单刀双掷开关,在触点未吸合时公共端与常闭端连接,当触点吸合时,公共端与常开端连接在一起。电路导通,负载开始工作。 以这种继电器为例: 黑色插针是输入端,标有VCC(VIN),GND,IN,其中IN需要和单片机的引脚相连. NO,即nomal open,常开触点,继电器线圈未通电时断开。 NC,即nomal close,常闭触
[单片机]
仿真设计|基于51单片机的商用电子计价秤设计
具体实现功能 (1)通电时电子秤进入欢迎界面,显示“欢迎使用电子秤 设计学生、班级学号、指导教师、设计日期”等信息; (2)LM4229显示模块显示当前称重台上物品重量; (3)当矩阵按键输入对应商品的代码编号,在LM4229上可以看到相应商品的名称、单价、总重、总价格等信息; (4)在称量的过程中,若物品重量超出电子秤的称重范围(0—4.99Kg),蜂鸣器警报,警示物品超重。 设计介绍 51单片机简介 51单片是一种低功耗、高性能CMOS-8位微控制器,拥有灵巧的8位CPU和可编程Flash,使得51单片机为众多嵌入式控制应用系统提供灵活、高效的解决方案。 本设计所使用的芯片可兼容以下所有的51系列单片机(包括AT
[单片机]
仿真设计|基于51单片机的多模式音乐跑马灯
具体实现功能 1、16个发光二极管做跑马灯,跑马灯有10种模式。2、按键可以切换跑马灯模式,且跑马灯速度可以用按键进行控制。3、数码管显示当前的跑马灯。4、当跑马灯处于一种模式时,音乐响起,音乐至少有3首,并可以对其进行切换。 设计介绍 51单片机简介 51单片是一种低功耗、高性能CMOS-8位微控制器,拥有灵巧的8位CPU和可编程Flash,使得51单片机为众多嵌入式控制应用系统提供灵活、高效的解决方案。 本设计所使用的芯片可兼容以下所有的51系列单片机(包括AT系列和STC系列)。 资料内容 仿真实现(protues8.7) 本设计利用protues8.7软件实现仿真设计,具体如图 程序(Keil5) 本设计利
[单片机]
仿真设计|基于51单片机的智能路灯仿真
具体实现功能 (1)使用2个LED灯模拟路灯,设定节能时间段为晚上00:00到早上6:00,路灯只亮一个。若声音传感器检测到有声响或人体红外感应传感器检测到外界有行人或汽车经过,则另一个灯也开启,延迟10s后重新变为一亮一灭的状态;(2)可以设置路灯开启时间段。如设置19: 00-20: 00,在此时段中,2个LED灯一直亮。其他除节能模式之外的时间段,则需根据光强和声响或人体红外感应来打开路灯;(3)在不是提前设置的时间里时,如果外界环境光线昏暗,且有声响或外界有行人、汽车经过,则路灯点亮,延时10s后,路灯熄灭;(4)在应该点亮路灯的时候,如果路灯没亮,或亮度不够,确定此时路灯发生故障,系统发出报警信息,蜂鸣器报警,以示提醒;
[单片机]
《逗比小憨憨51单片机Proteus仿真系列》第16期基于单片机的走马灯设计与仿真
源代码: #include reg52.h unsigned char RunMode; void Delay1ms(unsigned int count) { unsigned int i,j; for(i=0;i count;i++) for(j=0;j 120;j++); } unsigned char code LEDDisplayCode = { 0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8, //0~7 0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E,0xFF}; void Display(unsigned
[单片机]
《逗比小憨憨51单片机Proteus仿真系列》第15期基于单片机的LCD12864显示图片实验
源代码: #include reg52.h #include intrins.h #define LcdDataPort P2 typedef unsigned char u8; typedef unsigned int u16; sbit Busy = P2^7; sbit Reset = P3^0; sbit RS = P3^1; sbit E = P3^2; sbit RW = P3^3; sbit CS1 = P3^4; sbit CS2 = P3^5; const u8 code table1 =
[单片机]
小广播
最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

厂商技术中心

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2026 EEWORLD.com.cn, Inc. All rights reserved