文章目錄
- 手動實作51單片機函式切換
- 一、前言
- 二、函式切換原理
- 三、函式切換代碼實作
- 四、實驗現象
手動實作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的當前狀態 |
| 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

開啟嵌入匯編程式,使C語言中可以編譯匯編代碼,加__asm宣告一下是匯編就行

四、實驗現象
函式1中把a取1,b取0,而函式2相反,當這兩個函式交叉運行時a和b的波形應該相反,所以仿真后結果如下,手動切換函式完成

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/293006.html
標籤:其他
下一篇:2021-08-08
