程序结束不了怎么办?程序结束后去哪儿了
程序结束不了怎么办?程序结束后去哪儿了 那么就剩下一个问题: 对于普通的嵌入式系统,C语言编程中 main()函数退出之后,程序去哪儿了? 上面两种情况的区别,在于第二个程序中 主循环 main() 函数始终没有退出,而第一个程序,main() 函数退出了。似乎前面LED 微微点亮 应该与 主函数 退出之后,单片机都干了些啥有关系。▲ 图1.1 实验板上的未点亮的LED居然微微发亮 如果在主程序中,增加一个无限循环:while(1); ,则电路板上的就不再会出现“微微点亮”的现象了。#include <REGX51.H> void test(num) { switch(num) { case 1: P2_0=0; P2_1=0; break; } } void main(void) { test(1); while(1); } ▲

简 介: 对于嵌入式系统,如果没有运行RTOS,那么程序开发中的 主函数(main())需要通过某种机制使其永远愉快的运行下去,它没有终点。如果想从main函数中退出,具体干什么是由所使用的C语言编译器决定的。 
关键词: C51,main,程序退出
今天在CSDN的 单片机led模块定义函数的问题[1] 中看到一个有趣的问题。
一个简单的C51程序如下:
#include <REGX51.H>
void test(num) {
    switch(num) {
        case 1: P2_0=0; P2_1=0; 
            break;
    }
}
void main(void) {
    test(1);
}
    
程序执行完之后,可以看到实验板上的有两个LED被点亮,另外六个居然微微发亮。

▲ 图1.1 实验板上的未点亮的LED居然微微发亮
如果在主程序中,增加一个无限循环:while(1); ,则电路板上的就不再会出现“微微点亮”的现象了。
#include <REGX51.H>
void test(num) {
    switch(num) {
        case 1: P2_0=0; P2_1=0; 
            break;
    }
}
void main(void) {
    test(1);
    while(1);
}
    

▲ 图1.2 实验板上后面六个LED就不再点亮了
上面两种情况的区别,在于第二个程序中 主循环 main() 函数始终没有退出,而第一个程序,main() 函数退出了。似乎前面LED 微微点亮 应该与 主函数 退出之后,单片机都干了些啥有关系。
那么就剩下一个问题: 对于普通的嵌入式系统,C语言编程中 main()函数退出之后,程序去哪儿了?
02 程序去哪儿了?从上面提问者书写的代码来看,应该是一位C51的爱好者,使用的是C51的编译器,在一款C51开发板上愉快的进行实验。他一开始没有安装嵌入式程序开发的惯例 在主程序void main(void) 中利用无限循环将程序控制在主程序函数中,就出现了前面实验结果中令人迷惑的情况。
注: 佩服他是一个胆大心细的人,观察还挺仔细的。
2.1 盘古开天辟地对于C语言编程来说,所有的用户程序世界是从主程序 main() 开始的。给用户程序开天辟地的任务是由 一小段 盘古代码 STARTUP.A51。
关于C51是如何启动的, 在如下两篇博文中也被测试说明:
- 51单片机程序执行流程(STARTUP.A51管理Main函数的执行)[2]
 
下面截取了 STARTUP.A51 代码的一段,可以看到盘古在单片机 RESET 之后做了点准备工作(初始化全局变量、堆栈指针)之后,就直接跳转至:?C_START
$NOMOD51
;------------------------------------------------------------------------------
;  This file is part of the C51 Compiler package
;  Copyright (c) 1988-2005 Keil Elektronik GmbH and Keil Software  Inc.
;  Version 8.01
;
;  *** <<< Use Configuration Wizard in Context Menu >>> ***
;------------------------------------------------------------------------------
;  STARTUP.A51:  This code is executed after processor reset.
;
;  To translate this file use A51 with the following invocation:
;
;     A51 STARTUP.A51
;
;  To link the modified STARTUP.OBJ file to your application use the following
;  Lx51 invocation:
;
;     Lx51 your object file list  STARTUP.OBJ  controls
;
;------------------------------------------------------------------------------
; Standard SFR Symbols 
ACC     DATA    0E0H
B       DATA    0F0H
SP      DATA    81H
DPL     DATA    82H
DPH     DATA    83H
                NAME    ?C_STARTUP
?C_C51STARTUP   SEGMENT   CODE
?STACK          SEGMENT   IDATA
                RSEG    ?STACK
                DS      1
                EXTRN CODE (?C_START)
                PUBLIC  ?C_STARTUP
                CSEG    AT      0
?C_STARTUP:     LJMP    STARTUP1
                RSEG    ?C_C51STARTUP
STARTUP1:
IF IDATALEN <> 0
                MOV     R0 #IDATALEN - 1
                CLR     A
IDATALOOP:      MOV     @R0 A
                DJNZ    R0 IDATALOOP
ENDIF
IF XDATALEN <> 0
                MOV     DPTR #XDATASTART
                MOV     R7 #LOW (XDATALEN)
  IF (LOW (XDATALEN)) <> 0
                MOV     R6 #(HIGH (XDATALEN))  1
  ELSE
                MOV     R6 #HIGH (XDATALEN)
  ENDIF
                CLR     A
XDATALOOP:      MOVX    @DPTR A
                INC     DPTR
                DJNZ    R7 XDATALOOP
                DJNZ    R6 XDATALOOP
ENDIF
IF PPAGEENABLE <> 0
                MOV     PPAGE_SFR #PPAGE
ENDIF
IF PDATALEN <> 0
                MOV     R0 #LOW (PDATASTART)
                MOV     R7 #LOW (PDATALEN)
                CLR     A
PDATALOOP:      MOVX    @R0 A
                INC     R0
                DJNZ    R7 PDATALOOP
ENDIF
IF IBPSTACK <> 0
EXTRN DATA (?C_IBP)
                MOV     ?C_IBP #LOW IBPSTACKTOP
ENDIF
IF XBPSTACK <> 0
EXTRN DATA (?C_XBP)
                MOV     ?C_XBP #HIGH XBPSTACKTOP
                MOV     ?C_XBP 1 #LOW XBPSTACKTOP
ENDIF
IF PBPSTACK <> 0
EXTRN DATA (?C_PBP)
                MOV     ?C_PBP #LOW PBPSTACKTOP
ENDIF
                MOV     SP #?STACK-1
; This code is required if you use L51_BANK.A51 with Banking Mode 4
;<h> Code Banking
; <q> Select Bank 0 for L51_BANK.A51 Mode 4
#if 0   
;     <i> Initialize bank mechanism to code bank 0 when using L51_BANK.A51 with Banking Mode 4.
EXTRN CODE (?B_SWITCH0)
                CALL    ?B_SWITCH0      ; init bank mechanism to code bank 0
#endif
;</h>
                LJMP    ?C_START
                END
    
上面的代码也被博文 51单片机程序执行流程(STARTUP.A51)[3] 中进行逐步调试跟踪验证过:

▲ 图2.1.1 显示LJMP C_START 就是进入 main() 程序
2.2 世界尽头由于进入main() 函数是长跳转,所以main函数是不会正常返回到启动程序 STARTUP.A51,那么程序去哪了?
在博文 单片机C语言while(1)的问题 中作者对于 KEIL编译器和PIC的 MAPLAB编译器对于main函数的最后时光进行了反汇编查看。
2.2.1 Keil编译器在main函数的最后,程序增加了一下几行代码:
MOV R0  #0x7F
CLR A
MOV @R0  A
DJNZ R0  (3)
MOV SP  #0x0C
LJMP main
    
这几条语句,前4条,是将我们单片机的内存的前128个地址清零,第5条,是定义堆栈,第6条,是将程序重新跳转到main函数的首行进行执行。
2.2.2 MAPLAB编译器PIC 单片机语言程序进行跟踪,发现main() 函数最后一条语句为 reset,也就是单片机直接复位,这是 MAPLAB编译器根据 PIC 单片机特点增加的复位语句。
※ 总 结 ※对于嵌入式系统,如果没有运行RTOS,那么程序开发中的 主函数(main())需要通过某种机制使其永远愉快的运行下去,它没有终点。
如果想从main函数中退出,具体干什么是由所使用的C语言编译器决定的。
参考资料[1]
单片机led模块定义函数的问题: https://ask.csdn.net/questions/7640604?utm_medium=distribute.pc_feed_v2.none-task-ask-ask_personrec_tag-3.pc_personrecdepth_1-utm_source=distribute.pc_feed_v2.none-task-ask-ask_personrec_tag-3.pc_personrec
[2]
51单片机程序执行流程(STARTUP.A51管理Main函数的执行): https://blog.csdn.net/ChenGuiGan/article/details/88769619
[3]
51单片机程序执行流程(STARTUP.A51): https://blog.csdn.net/tangsun999/article/details/45604507




