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

发布时间:2024-08-22  

一、前言

为什么要研究单片机函数切换的过程?实际上是我在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的当前状态
B B寄存器:在做乘、除法时放乘数或除数
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


文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>