欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

解析ARM裸机程序的start.S和main.c

最编程 2024-08-13 15:41:50
...

本文接上篇《ARM裸机程序之Makefile解读》,继续研究一下imx6ull裸机程序的启动程序start.S和应用主程序main.c。

启动代码

汇编代码start.S我都做了注释,应该可以看出这段代码的作用和原理。

.text
.global  _start
_start:                 

    ldr  sp, =0x80200000  // 设置栈指针

    bl clean_bss  // 跳转bss清零函数
    
    bl main  // 跳转到应用程序主函数

halt:
    b  halt 

clean_bss:
    /* 清除bss段,即写0 */
    ldr r1, =__bss_start  // bss段起始地址,赋值给r1寄存器
    ldr r2, =__bss_end    // bss段结束地址,赋值给r3寄存器
    mov r3, #0     // r3寄存器赋值为0
clean_loop:        // 清零循环
    str r3, [r1]   // 将r3中的值(即0)写到r1中所存地址的位置
    add r1, r1, #4 // 相当于r1 = r1 + 4,即地址前移
    cmp r1, r2     // 判断是否到达bss段结束地址
    bne clean_loop // 上面判断=0的话,继续跳到clean_loop
    
    mov pc, lr     // clean_bss函数返回

拓展阅读:

  1. ldr与mov的作用与区别

ARM是RISC结构,数据从内存到CPU之间的移动只能通过L/S指令来完成,也就是ldr/str指令。比如想把数据从内存中某处读取到寄存器中,只能使用ldr,比如:
ldr r0, 0x12345678
就是把0x12345678这个地址中的值存放到r0中。而mov不能干这个活,mov只能在寄存器之间移动数据,或者把立即数移动到寄存器中,这个和x86这种CISC架构的芯片区别最大的地方。x86中没有ldr这种指令,因为x86的mov指令可以将数据从内存中移动到寄存器中。另外还有一个就是ldr伪指令,虽然ldr伪指令和ARM的ldr指令很像,但是作用不太一样。ldr伪指令可以在立即数前加上=,以表示把一个地址写到某寄存器中,比如:
ldr r0, =0x12345678
这样,就把0x12345678这个地址写到r0中了。所以,ldr伪指令和mov是比较相似的,只不过mov指令限制了立即数的长度为8位,也就是不能超过255,而ldr伪指令没有这个限制。如果使用ldr伪指令时,后面跟的立即数没有超过8位,那么在实际汇编的时候该ldr伪指令是被转换为mov指令的。

  1. 需要清零BSS段的原因
    我们知道BSS段保存未初始化的全局变量和静态局部变量,而为什么会有BSS段呢?其实也是为了节省空间,要知道,一般对于初始化过的全局/静态变量,除了要给变量分配空间,还要给变量的值分配空间。但对于没有初始化的全局变量,如果也这样分配,由于没有初值,岂不是浪费存储空间。所以就把所有没有初值的全局/静态变量单独放到一片区域,即BSS段。
    对于BSS段的变量,为了防止直接使用而引发未定义的问题,一般编译器约定俗成的会在启动代码中将BSS段置为0,所以,BSS段中的变量初值也就变成0了。
主程序

主程序分两部分,功能很简单,就是循环亮、灭一个led灯。

#include "led.h"

static volatile unsigned int *CCM_CCGR1                              ;
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
static volatile unsigned int *GPIO5_GDIR                             ;
static volatile unsigned int *GPIO5_DR                               ;

/**********************************************************************
 * 函数名称: led_init
 * 功能描述: 初始化LED引脚,就是把它设置为输出引脚
 * 输入参数: 无
 * 输出参数: 无
 * 返 回 值: 无
 ***********************************************************************/
void led_init(void)
{
    unsigned int val;
    
    CCM_CCGR1                               = (volatile unsigned int *)(0x20C406C);
    IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = (volatile unsigned int *)(0x2290014);
    GPIO5_GDIR                              = (volatile unsigned int *)(0x020AC000 + 0x4);
    GPIO5_DR                                = (volatile unsigned int *)(0x020AC000);

    /* GPIO5_IO03 */
    /* a. 使能GPIO5
     * set CCM to enable GPIO5
     * CCM_CCGR1[CG15] 0x20C406C
     * bit[31:30] = 0b11
     */
    *CCM_CCGR1 |= (3<<30);
    
    /* b. 设置GPIO5_IO03用于GPIO
     * set IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3
     *      to configure GPIO5_IO03 as GPIO
     * IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3  0x2290014
     * bit[3:0] = 0b0101 alt5
     */
    val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
    val &= ~(0xf);
    val |= (5);
    *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;
    
    
    /* c. 设置GPIO5_IO03作为output引脚
     * set GPIO5_GDIR to configure GPIO5_IO03 as output
     * GPIO5_GDIR  0x020AC000 + 0x4
     * bit[3] = 0b1
     */
    *GPIO5_GDIR |= (1<<3);

}

/**********************************************************************
 * 函数名称: led_ctl
 * 功能描述: 设置LED状态
 * 输入参数: 
 *     on : 1-LED点亮, 0-LED熄灭
 * 输出参数: 无
 * 返 回 值: 无
 ***********************************************************************/
void led_ctl(int on)
{
    if (on) /* on: output 0*/
    {
        /* d. 设置GPIO5_DR输出低电平
         * set GPIO5_DR to configure GPIO5_IO03 output 0
         * GPIO5_DR 0x020AC000 + 0
         * bit[3] = 0b0
         */
        *GPIO5_DR &= ~(1<<3);
    }
    else  /* off: output 1*/
    {
        /* e. 设置GPIO5_IO3输出高电平
         * set GPIO5_DR to configure GPIO5_IO03 output 1
         * GPIO5_DR 0x020AC000 + 0
         * bit[3] = 0b1
         */ 
        *GPIO5_DR |= (1<<3);
    }
}
#include "led.h"

void delay(volatile unsigned int d)
{
    while(d--);
}


int  main()
{
    led_init();

    while(1)
    {
        led_ctl(1);              // 灯亮
        delay(1000000);          // 延时
        led_ctl(0);              // 灯灭
        delay(1000000);          // 延时
    }
                    
    return 0;
}

代码比较简单,按照注释基本也能明白什么意思。过程主要是配置led所连接的GPIO管脚为输出状态,控制该GPIO的输出值即可控制led灯的亮灭。方法主要是通过配置GPIO控制器的寄存器来实现,具体参照数据手册就行,按照相应的数据位进行赋值。
需要注意的是,裸机程序包括单片机程序跟Linux驱动程序是不一样的,因为没有虚拟地址管理,裸机程序要操作寄存器必须直接读写寄存器的物理地址,而Linux驱动程序都是先将物理地址映射到虚拟地址,再进行读写的。更多的区别,后续讨论Linux驱动的时候再说。