找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

259

积分

0

好友

37

主题
发表于 昨天 23:48 | 查看: 1| 回复: 0

在进行基本的C51编程实验时,编写了一个简单的程序如下:

#include <REGX51.H>
void test(num) {
    switch(num) {
        case 1: 
            P2_0=0; 
            P2_1=0; 
            break;
    }
}
void main(void) {
    test(1);
}

程序执行完之后,可以看到实验板上有两个LED被点亮,但另外六个LED居然微微发亮。

图片

如果在主程序中,增加一个无限循环:while(1);,则电路板上的就不再会出现“微微点亮”的现象了。

图片 图片

如上图,实验板上后面六个LED就不再点亮了。上面两种情况的区别,在于第二个程序中主函数main()始终没有退出,而第一个程序,main()函数退出了。这个现象提示我们,LED微微点亮应该与主函数退出之后,单片机的状态有关。

那么核心问题就是:对于普通的嵌入式系统,C语言编程中main()函数退出之后,程序去哪儿了?

程序去哪儿了?

上面实验使用的是C51的编译器,在一块C51开发板上进行。程序没有按照嵌入式开发的惯例——在主函数void main(void)中利用循环将程序控制住,因此出现了令人迷惑的结果。

1. 开天辟地:程序的启动

对于C语言编程来说,所有的用户程序世界都是从主函数 main() 开始的。给用户程序“开天辟地”的准备工作,是由一小段启动代码STARTUP.A51完成的。

下面截取了 STARTUP.A51 代码的一段,可以看到在单片机 RESET 之后,它做了一系列初始化工作(如初始化全局变量、堆栈指针等),之后便直接跳转至:?C_START

...
LJMP    ?C_START
...

实际上,?C_START 就对应着跳转到用户的 main() 函数。这个过程在相关博文中通过调试得到了验证:程序通过LJMP指令进入main()函数。

由于进入main()函数采用的是长跳转指令,因此通常情况下,main()函数是不会返回到启动程序STARTUP.A51的。那么,当main()函数执行完毕,程序到底去哪了呢?

2. 世界尽头:main()的终结

在博文单片机C语言while(1)的问题中,作者分别对Keil编译器和PIC的MAPLAB编译器在main函数结束后的行为进行了反汇编分析。

3. Keil编译器的处理

在Keil编译器生成的代码中,main函数的最后,被增加了以下几行汇编代码:

MOV     R0, #0x7F
CLR     A
MOV     @R0, A
DJNZ    R0, (3)
MOV     SP, #0x0C
LJMP    main

这几条语句的作用是:

  • 前4条:将单片机内部RAM的前128个地址单元清零。
  • 第5条:重新初始化堆栈指针。
  • 第6条:使用长跳转指令,重新跳转到main函数的第一条指令开始执行。

这意味着,对于Keil编译器,当main()函数退出后,它会先清理内存,然后让程序从头开始再次执行,相当于一个软复位循环。

4. MAPLAB编译器的处理

在对PIC单片机的程序进行跟踪时,发现main()函数的最后一条语句是reset,也就是直接让单片机复位。这是MAPLAB编译器针对PIC单片机特点所添加的语句。

总结 如果单片机程序从main函数中退出,具体执行什么操作是由所使用的C语言编译器决定的。对于没有运行RTOS的嵌入式系统,程序开发中的主函数(main())通常需要通过while(1)等机制使其持续运行,它不应该有终点。了解编译器在背后的行为,有助于我们理解嵌入式系统更深层的运行机制,并规避一些意想不到的硬件状态问题。

参考资料

[1] 51单片机程序执行流程(STARTUP.A51管理Main函数的执行) [2] 51单片机程序执行流程(STARTUP.A51)

您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区(YunPan.Plus) ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-3 17:54 , Processed in 1.019282 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 CloudStack.

快速回复 返回顶部 返回列表