编写第一个裸机程序

一. ARM裸机之Makefile

  1.1. Makefile 分析

led.bin: led.o 
    arm-linux-ld -Ttext 0x0 -o led.elf $^
    arm-linux-objcopy -O binary led.elf led.bin
    arm-linux-objdump -D led.elf > led_elf.dis
    gcc mkv210_image.c -o mkx210
    ./mkx210 led.bin 210.bin
    
%.o : %.S
    arm-linux-gcc -o $@ $< -c

%.o : %.c
    arm-linux-gcc -o $@ $< -c 

clean:
    rm *.o *.elf *.bin *.dis mkx210 -f

    
    
View Code

    1.1.1. arm-linux-ld -Ttext 0x0 -o led.elf $^ 

      a. -Ttext 0x0将链接起始地址设定为0x00

      b. -o led.elf表示链接后生成的elf文件

      c. $^表示所有的依赖文件

      PS: Makefile有三个非常有用的变量。分别是$@,$^,$<代表的意义分别是:

        $@--目标文件,$^--所有的依赖文件,$<--第一个依赖文件。

        示例:

main: main.o mytool1.o mytool2.o 
    gcc -o main main.o mytool1.o mytool2.o 
        #等效于 gcc -o $@ $^
View Code

    1.1.2. arm-linux-objcopy -O binary led.elf led.bin

      a. arm-linux-objcopy 被用来复制一个目标文件的内容到另一个文件中.此选项可以进行格式的转换.在实际编程的,用的最多的就是将ELF格式的可执行文件转换为二进制文件

      b. 此命令是将elf文件转为二进制bin文件

    1.1.3. arm-linux-objdump -D led.elf > led_elf.dis

      a. 此命令是将elf文件反编译成汇编文件

    1.1.4. gcc mkv210_image.c -o mkx210

      a. 此语句是编译mkv210_image.c文件

      b. mkv210_image.c这个程序其实最终不是在开发板上执行的,而是在主机linux中执行的,因此编译这个程序用gcc而不是用arm-linux-gcc。

    1.1.5. ./mkx210 led.bin 210.bin

      a. 此命令是在linux主机中运行mkx210程序。

      b. led.bin 210.bin是运行此程序传入的参数,  argc = 3; argv[0] = "./mkx210" argv[1] = led.bin argv[2] = 210.bin

      c. 目的是通过执行这个mkx210 程序而由led.bin得到210.bin。(210.bin(其实进入了16字节的头)是通过SD卡启动时的裸机镜像,这个镜像需要由led.bin来加工的到,加工的具体方法和原理要看mkv210_image.c)

      d. 210启动后先执行内部iROM中的BL0,BL0执行完后会根据OMpin的配置选择一个外部设备来启动(有很多,我们实际使用的有2个:usb启动和SD卡启动)。在usb启动时内部BL0读取到BL1后不做校验,直接从BL1的实质内部0xd0020010开始执行,因此usb启动的景象led.bin不需要头信息,因此我们从usb启动时直接将镜像下载到0xd0020010去执行即可,不管头信息了;从SD启动时,BL0会首先读取sd卡得到完整的镜像(完整指的是led.bin和16字节的头),然后BL0会自己根据你的实际镜像(指led.bin)来计算一个校验和checksum,然后和你完整镜像的头部中的checksum来比对。如果对应则执行BL1,如果不对应则启动失败(会转入执行2st启动,即SD2启动。如果这里已经是2st启动了,这里校验通不过就死定了)。 

/*
 * mkv210_image.c的主要作用就是由usb启动时使用的led.bin制作得到由sd卡启动的镜像210.bin
 *
 * 本文件来自于友善之臂的裸机教程,据友善之臂的文档中讲述,本文件是一个热心网友提供,在此表示感谢。
 */
/* 在BL0阶段,Irom内固化的代码读取nandflash或SD卡前16K的内容,
 * 并比对前16字节中的校验和是否正确,正确则继续,错误则停止。
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define BUFSIZE                 (16*1024)
#define IMG_SIZE                (16*1024)
#define SPL_HEADER_SIZE         16
//#define SPL_HEADER              "S5PC110 HEADER  "
#define SPL_HEADER              "****************"

int main (int argc, char *argv[])
{
    FILE        *fp;
    char        *Buf, *a;
    int        BufLen;
    int        nbytes, fileLen;
    unsigned int    checksum, count;
    int        i;
    
    // 1. 3个参数
    if (argc != 3)
    {
        printf("Usage: %s <source file> <destination file>\n", argv[0]);
        return -1;
    }

    // 2. 分配16K的buffer
    BufLen = BUFSIZE;
    Buf = (char *)malloc(BufLen);
    if (!Buf)
    {
        printf("Alloc buffer failed!\n");
        return -1;
    }

    memset(Buf, 0x00, BufLen);

    // 3. 读源bin到buffer
    // 3.1 打开源bin
    fp = fopen(argv[1], "rb");
    if( fp == NULL)
    {
        printf("source file open error\n");
        free(Buf);
        return -1;
    }
    // 3.2 获取源bin长度
    fseek(fp, 0L, SEEK_END);                                // 定位到文件尾
    fileLen = ftell(fp);                                    // 得到文件长度
    fseek(fp, 0L, SEEK_SET);                                // 再次定位到文件头
    // 3.3 源bin长度不得超过16K-16byte
    count = (fileLen < (IMG_SIZE - SPL_HEADER_SIZE))
        ? fileLen : (IMG_SIZE - SPL_HEADER_SIZE);
    // 3.4 buffer[0~15]存放"S5PC110 HEADER  "
    memcpy(&Buf[0], SPL_HEADER, SPL_HEADER_SIZE);
    // 3.5 读源bin到buffer[16]
    nbytes = fread(Buf + SPL_HEADER_SIZE, 1, count, fp);
    if ( nbytes != count )
    {
        printf("source file read error\n");
        free(Buf);
        fclose(fp);
        return -1;
    }
    fclose(fp);

    // 4. 计算校验和
     // 4.1 从第16byte开始统计buffer中共有几个1
    // 4.1 从第16byte开始计算,把buffer中所有的字节数据加和起来得到的结果
    a = Buf + SPL_HEADER_SIZE;
    for(i = 0, checksum = 0; i < IMG_SIZE - SPL_HEADER_SIZE; i++)
        checksum += (0x000000FF) & *a++;
    // 4.2 将校验和保存在buffer[8~15]
    a = Buf + 8;                            // Buf是210.bin的起始地址,+8表示向后位移2个字,也就是说写入到第3个字
    *( (unsigned int *)a ) = checksum;

    // 5. 拷贝buffer中的内容到目的bin
    // 5.1 打开目的bin
    fp = fopen(argv[2], "wb");
    if (fp == NULL)
    {
        printf("destination file open error\n");
        free(Buf);
        return -1;
    }
    // 5.2 将16k的buffer拷贝到目的bin中
    a = Buf;
    nbytes    = fwrite( a, 1, BufLen, fp);
    if ( nbytes != BufLen )
    {
        printf("destination file write error\n");
        free(Buf);
        fclose(fp);
        return -1;
    }

    free(Buf);
    fclose(fp);

    return 0;
}
View Code

    1.1.6 目标文件:%.o : 

      a. 由依赖文件.S生成.o文件

%.o : %.S
    arm-linux-gcc -o $@ $< -c

      b. 由依赖文件.c生成.o文件

%.o : %.c
    arm-linux-gcc -o $@ $< -c 

    1.1.7. 目标clean

      a. 当make clean时便会执行rm命令!但并不用生成目标文件

clean:
    rm *.o *.elf *.bin *.dis mkx210 -f

二. 其他工程文件

  2.1. write2sd脚本文件

    a. 该文件很简单,就是将210.bin烧录到sd卡中

#!/bin/sh
sudo dd iflag=dsync oflag=dsync if=210.bin of=/dev/sdb seek=1

  2.2. led.S文件

    a. 此文件下才是真正的裸机程序

    b. 用 b . 来实现死循环, bl比b多做一步,在跳转前,bl会把当前位置保存在r14(即lr寄存器),当跳转代码结束后,用mov pc,lr指令跳回来,这实际上就是C语言执行函数的用法,

汇编里调子程序都用bl,执行完子函数后,可以用mov pc,lr跳回来

    c. 用.global把_start链接属性改为外部,汇编程序是从_start开始执行的

/* 
 * file name :led.S
 * author:   MUSK
 * description:test led code
*/
.global _start
_start:
    ldr r1, =0xE0200240  @PORT GROUP GPJ0 CONTROL REGISTER address
    ldr r0, =0x00111000  @config corresponding gpio 
    str r0, [r1]
    
while:    
    ldr r1, =0xE0200244  @Port Group GPJ0 Control Register address
    ldr r0, =~(0x01<< 3 )  @confid register data
    str r0,[r1]
    
    bl delay
    
    ldr r1, =0xE0200244  @Port Group GPJ0 Control Register address
    ldr r0, =~(0x01<< 4 )  @confid register data
    str r0,[r1]
    
    bl delay
    
    ldr r1, =0xE0200244  @Port Group GPJ0 Control Register address
    ldr r0, =~(0x01<< 5 )  @confid register data
    str r0,[r1]
    
    bl delay
    
    ldr r1, =0xE0200244  @Port Group GPJ0 Control Register address
    ldr r0, =~(0x01<< 4 )  @confid register data
    str r0,[r1]
    
    bl delay
    
    b while    @the same with while(1);

// delay function    
delay:
    ldr r2, = 9000000
    ldr r3, = 0x00
delay_loop:
    sub r2, r2, #1     @r2--
    cmp r2, r3         @compare r2 r3
    bne delay_loop
    mov pc,lr
View Code

三. 编译烧录程序

  3.1. 通过USB烧录,使用window下dwn软件烧录

    3.1.1 编译生成目标文件

      a. 使用make命令完成编译链接以及生成目标文件

      PS: 当make后面没有带目标时,默认是使用Makefile中第一个目标

root@ubuntu:/mnt/hgfs/windows_share/baseC/lesson1.4.13-LED# ls
led.S  Makefile  mkv210_image.c  write2sd  说明.txt
root@ubuntu:/mnt/hgfs/windows_share/baseC/lesson1.4.13-LED# make
arm-linux-gcc -o led.o led.S -c
arm-linux-ld -Ttext 0x0 -o led.elf led.o
arm-linux-objcopy -O binary led.elf led.bin
arm-linux-objdump -D led.elf > led_elf.dis
gcc mkv210_image.c -o mkx210
./mkx210 led.bin 210.bin
root@ubuntu:/mnt/hgfs/windows_share/baseC/lesson1.4.13-LED# ls
210.bin  led.elf      led.o  Makefile        mkx210    说明.txt
led.bin  led_elf.dis  led.S  mkv210_image.c  write2sd
root@ubuntu:/mnt/hgfs/windows_share/baseC/lesson1.4.13-LED# 
View Code

    3.1.2. 使用dnw工具烧录  

      a. 烧录地址:0xd0020010

      b. 烧录文件是led.bin  

  3.2. 通过SD卡烧录,使用write2sd脚本

    3.2.1. 编译生成目标文件(同3.1.1.)

    3.2.1. SD卡烧录使用的文件是x210.bin,此文件带16字节校验头

root@ubuntu:/mnt/hgfs/windows_share/baseC/lesson1.4.13-LED# ls /dev/sd*
/dev/sda  /dev/sda1  /dev/sda2  /dev/sda5  /dev/sdb  /dev/sdb1
root@ubuntu:/mnt/hgfs/windows_share/baseC/lesson1.4.13-LED# ./write2sd 
32+0 records in
32+0 records out
16384 bytes (16 kB) copied, 0.143708 s, 114 kB/s
root@ubuntu:/mnt/hgfs/windows_share/baseC/lesson1.4.13-LED# 
View Code

 

 

 

 

      

 

   

 

posted @ 2019-01-06 22:47  三七鸽  阅读(1543)  评论(0编辑  收藏  举报