zoukankan      html  css  js  c++  java
  • ARM 裸机程序学习 03 发送SOS信号(汇编 + C)

      之前的两个示例程序都是完全由汇编编写的,而这次的示例程序由汇编和C语言写成并编译。

      汇编自有汇编的优点,比如你可以非常清楚地知道CPU在执行每条指令时到底做了什么,怎么做的,数据到底保存在哪儿等等,但是缺点也很明显,写汇编代码太痛苦,而且代码也不易阅读,所以C语言出现了。//其实,还包括C++

      从Code Warrior 的DebugRel Setting 中可以看到,Code Warrior 应该支持汇编,C ,C++ 三种语言。此外,在利用汇编和C语言编程时,要记得两个编译器都要设置成对应的ARM处理器架构。

      程序方面,这次要写的是一个发送SOS信号的程序(莫尔斯码:...---... ,即三短三长三短)。输出方式有很多,比如上两篇文章提到的LED灯或者蜂鸣器。考虑到扰民问题,决定使用LED作为输出。当然,这个示例代码的关注点仅仅是如何从汇编代码跳转到C代码。

    汇编代码如下:

    init.s

    1     AREA |DATA|,CODE,READONLY
    2     ENTRY
    3     
    4     LDR R13, =0x34000000
    5     IMPORT ledMain
    6     b ledMain
    7     
    8     END

      这段汇编代码非常简单:

    4:LDR伪指令(有等号“=”),将 0x34000000 存入R13 寄存器,为堆栈初始化。

    5:IMPORT伪指令,说明了一个跳转符号。比如,上两篇提到过的START,BEEPON 等。可以通过B 跳转指令来控制程序的执行顺序。而当这个符号并不在本文件中时,需要用IMPORT 来说明。从汇编代码跳转到C 代码,就需要这个指令。符号名称即为C 函数的函数名。

    6:B 跳转指令,跳转到ledMain 处。即调用C 代码的ledMain 函数处。

    有关第4行:

      在ARM 中,R13寄存器常用作堆栈寄存器。之前的纯汇编代码并没有用到R13,但也能运行。说明逻辑程序是并不一定需要一个堆栈。而这里之所以又用到了堆栈寄存器,是因为C 语言所编写的程序必须要一个堆栈。就是说,是C 需要堆栈,而不是ARM 需要。

      那么,如何确定这个初始化的值呢?答案不唯一,甚至可以随便一点。

    一、C 程序的布局

      首先来看看C 程序的布局:

      如图,一般而言,C 程序具有固定的布局格式。一般而言,正在运行的程序从内存地址低到高分为如下几个“段”:

      1. 文本段(TEXT),存放代码以及常量,这些数据无论何时,都不会改变。也有人会把常量单独分为一个“常量段”,这也不无道理。简单起见,因为他们不会改变的特性,我把它们一起当做文本段。

    注意:C 语言中,用CONST 声明的是一个“只读变量”,它本质上还是变量而非常量,利用指针,是可以改变其值的。

    而只有那些在程序中被“写死”的数字符号才是真正意义上的常量。

      2. 数据段(DATA),存放在程序运行之初,已经初始化赋值过的变量。其中有全局变量以及静态变量(static)。

      3. BSS段(BSS),存放程序运行之初,尚未初始化赋值的变量。其中有全局变量以及静态变量。上图中写道会被初始化为0,但实际并不一定都是。比如C 中的野指针就是一个例子,很可能存放着垃圾数据。

      4. 堆(HEAP),动态储存区,只有程序执行时才会出现。常用的MALLOC 所分配的内存就在这个地方。增长方向为从内存地址低到高向上。

      5. 堆栈(STACK),动态存储区,和堆一样,只有程序执行时才会出现。在不考虑编译优化的情况下,只要调用函数,就会压栈。子程序返回,则出栈。增长方向和堆相反,为从内存地址高到低向下。另外,堆就是堆(HEAP),堆栈就是栈(STACK)。两者只是名字比较容易混淆,但是作用完全不一样。

    一般而言,局部变量都会被存放在堆栈里。这也说明了为什么局部变量在子程序返回时就被销毁而不可用。

    而ARM 在这个地方会有个特别之处:如果函数调用的参数在4个以内,会利用寄存器R0~R3来传递。只有超过部分才会利用栈来传递。

     

    相关文章链接: C 程序内存分布ARM函数调用时参数传递规则

    二、堆栈寄存器的初始化

      知道了程序在内存中的布局后,如何确定堆栈寄存器也变得有点眉目了。规则我觉得就两条:

      1.地址在内存范围里;2.堆栈不和数据段撞车。

      先讲第1条:首先要明确一点,此时我们的程序并没有启动内存管理单元MMU,真正的内存地址得看S3C2440的储存空间映射图:

      s3c2440有2种启动方式,从NOR FLASH 启动(左)和从NAND FLASH 启动(右)。

      从图中看到,并不是所有的地址都是所谓的“内存地址”。甚至地址也不一定是连续的。ARM 使用统一编址,就是乱七八糟设备的地址都混杂在一起的编址方式(- -)。所以,仔细观察上面这张图,应该立刻发现,我们得把堆栈指针设置到内存地址范围内,而不是随便挑一个自己喜欢的地址。

      具体的例子:64M的SDRAM作为内存,地址空间映射到BANK6,那么内存地址范围就是 0x30000000~0x34000000 (64*1024*1024=67108864,即 0x04000000)

      理论上而言,如果希望CPU运行起来非常简单,通电就可以,内存,硬盘什么都可以不要。但是,此时CPU仅仅是出于“运行”状态,不会去执行任何操作——因为没有指令可以执行。于是,我们给它加上了内存。这样,CPU可以从内存中得到指令,从而执行。并将执行结果放到内存里去。

      此时的计算机可以执行具体操作了,但是仍然不能完成任务。因为内存一掉电数据就丢失——想想一下只能一直开着的电脑。这依然不科学。于是,我们又加上了硬盘,里面存放着掉电也不会丢失的指令和数据。

      这样,计算机一启动,先把硬盘里的指令和数据放到内存,然后CPU从内存里获取指令和数据并执行,把结果返回到内存,再保存到硬盘。这样,关机后,硬盘里也存放着你希望得到的数据。

      s3c2440 拥有一块4K 的片内RAM ,可以看做是一个内存。但并没有任何片内ROM,也就是没有硬盘。在s3c2440 以NOR FLASH 启动时,NOR FLASH 相当于一块速度非常快的硬盘。它好像内存一样,CPU 直接从NOR FLASH 获取指令和数据,然后执行。而NAND FLASH 启动时,由于NAND FLASH 非常慢,相当于硬盘。在这种启动方式下,s3c2440 自动把NAND FLASH 最开始的4K 内容复制到片内RAM,然后再执行。

      所以,按NAND FLASH 启动时,堆栈寄存器可以设为片内RAM 的最大地址:0x1000(4K)或者属于SDRAM的地址,比如本例的 0x34000000。而千万不要设置成其他地址,比如ROM 的地址或者其他奇怪的地址。

      第2条堆栈不和数据段撞车:这就很好理解了。因为堆栈空间是向下增长,也就是向数据段(DATA)方向增长的。如果堆栈里的数据跑到数据段里把数据覆盖,肯定是出问题。当然,这也可以成为破解或者黑客的一种手段。

      本人使用AXD+JLINK来运行程序,实际操作下来 0x1000 和 0x34000000 都可以。

    剩下的是C 代码:

    ledMain.c

     1 #define GPBCON (*(volatile unsigned *)0x56000010)  //定义IO口地址
     2 #define GPBDAT (*(volatile unsigned *)0x56000014)
     3 #define GPBUP (*(volatile unsigned *)0x56000018)
     4 
     5 //延迟函数,通过让CPU空转实现
     6 void Delay(int x)
     7 {
     8     unsigned a;
     9     
    10     while(x)
    11     {
    12         for(a=0xffff;a>0x0;a--);
    13         x--;
    14     }
    15 }
    16 
    17 //LED灯亮灭部分,可参照上两篇文章
    18 void ledup()
    19 {
    20     GPBDAT=~(1<<5);
    21 }
    22 
    23 
    24 void leddown(void)
    25 {
    26     GPBDAT=~0;
    27 }
    28 
    29 //控制亮起时间长短和亮灭次数
    30 void ledFreq(int t,int count)
    31 {    
    32     while(count--)
    33     {
    34         ledup();
    35         Delay(t);
    36         leddown();
    37         Delay(7);
    38     }
    39 }
    40 
    41 //主函数
    42 void ledMain()
    43 {
    44     GPBCON=0xddd7fc;  //对IO口的设置可以参考前两篇文章
    45     GPBUP=0x0;
    46     GPBDAT=~0x0;
    47     
    48     while(1)
    49     { 
    50         ledFreq(5,3);
    51         Delay(30);
    52         
    53         ledFreq(15,3);
    54         Delay(30);
    55         
    56         ledFreq(5,3);
    57         Delay(30);
    58     }
    59 }

      本文涉及内容较多,如有谬误或者疏漏,敬请提出!

    /*
     * Yiling Zhou
     * Shanghai, China
     * -.-- .. .-.. .. -. --. / --.. .... --- ..-
     * ... .... .- -. --. .... .- .. --..-- / -.-. .... .. -. .-
     */
    
  • 相关阅读:
    PageObject小结
    python函数默认参数坑
    编译Android 8.0系统 并刷入pixel
    CF 289 F. Progress Monitoring DP计数
    EDU 61 F. Clear the String 区间dp
    Educational Codeforces Round 55 G 最小割
    Educational Codeforces Round 55 E 分治
    hdu 6430 bitset暴力
    AC自动机+DP codeforces86C
    CF895C dp/线性基
  • 原文地址:https://www.cnblogs.com/pastgift/p/2476158.html
Copyright © 2011-2022 走看看