美文网首页
ARM裸机程序之start.S和main.c解读

ARM裸机程序之start.S和main.c解读

作者: LoveDuoer | 来源:发表于2020-03-28 18:07 被阅读0次

    本文接上篇《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驱动的时候再说。

    相关文章

      网友评论

          本文标题:ARM裸机程序之start.S和main.c解读

          本文链接:https://www.haomeiwen.com/subject/mzjsuhtx.html