编写第一个裸机程序

一. 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 [email protected] $< -c

%.o : %.c
    arm-linux-gcc -o [email protected] $< -c 

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

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

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

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

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

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

        [email protected]目标文件,$^--所有的依赖文件,$<--第一个依赖文件。

        示例:

main: main.o mytool1.o mytool2.o
    gcc -o main main.o mytool1.o mytool2.o
        #等效于 gcc -o [email protected] $^

    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;
}

    1.1.6 目标文件:%.o :

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

%.o : %.S
    arm-linux-gcc -o [email protected] $< -c

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

%.o : %.c
    arm-linux-gcc -o [email protected] $< -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

三. 编译烧录程序

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

    3.1.1 编译生成目标文件

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

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

[email protected]:/mnt/hgfs/windows_share/baseC/lesson1.4.13-LED# ls
led.S  Makefile  mkv210_image.c  write2sd  说明.txt
[email protected]:/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
[email protected]:/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
[email protected]:/mnt/hgfs/windows_share/baseC/lesson1.4.13-LED# 

    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字节校验头

[email protected]:/mnt/hgfs/windows_share/baseC/lesson1.4.13-LED# ls /dev/sd*
/dev/sda  /dev/sda1  /dev/sda2  /dev/sda5  /dev/sdb  /dev/sdb1
[email protected]:/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
[email protected]:/mnt/hgfs/windows_share/baseC/lesson1.4.13-LED# 

      

   

原文地址:https://www.cnblogs.com/linux-37ge/p/10230844.html

时间: 2024-10-12 16:05:31

编写第一个裸机程序的相关文章

Xamarin iOS编写第一个应用程序创建工程

Xamarin iOS编写第一个应用程序创建工程 在Xcode以及Xamarin安装好后,就可以在Xamarin Studio中编写程序了.本节将主要讲解在Xamarin Studio中如何进行工程的创建以及编写代码等内容XamariniOS编写第一个应用程序创建工程本文选自Xamarin iOS开发实战大学霸. 1.3.1  创建工程 XamariniOS编写第一个应用程序创建工程本文选自Xamarin iOS开发实战大学霸,很多的开发工具,在编写代码之前,都必须要创建一个工程,如Visual

微信小程序-----安装,编写第一个小程序和运行到手机端

第一步: 微信公众平台注册账号,并选择小程序,网址:mp.weixin.qq.com 填写相关信息,如:主体类型(个人或者企业) AppID  在开发中都是用的到的,服务器域名在网络请求也是用的到的. 完成信息之后,下载开发工具: 下载地址: https://mp.weixin.qq.com/debug/wxadoc/dev/devtools/download.html 选择自己需要的版本,下载直接安装,和平常的软件安装一样,直接运行 用微信扫码登录即可,然后点击添加项目, 输入申请的AppID

【DirectX11-Tutorial】编写第一个窗口程序creating a window

本系列主要参考此博客的文章l,同时会加上一点个人实践过程. ========================================== 分割线 ========================================== 这篇文章主要分为三个部分,首先是看一下创建窗口完整的代码,然后了解程序运作的主要细节,最后如何修改这些代码. 接下来开始第一个窗口程序,上一个程序中使用WinMain()函数开始整个程序,这一次将结合另一个函数WinProc(),这个函数将处理程序每一个系统运

DIOCP之编写第一个应用程序(三)

Client 设计功能如下: 1.建立与服务器连接 2.请求连接时,加密密码,采用Base64编码 3.时时发送心跳告诉服务器在线 4.进行相关的数据处理与交互 第一步:创建一个VCL-Forms Application(创建一个标准VCL程序) 第二步:编写一个公共单元用于与Server通讯,因为客户端一般由多个窗体组成,所以需要一个公共连接类,不能放入窗体,不然窗体关闭,这个连接也会被关闭 以下单元是彩蛋的单元,我简化了,保留部分,用于学习,这个部分主要是向服务器发送请求 unit Clie

DIOCP之编写第一个应用程序(一)

Server 设计功能如下: 1.支持客户端登录 2.连接数据库进行操作 3.推送信息 4.限制文件上传大小 第一步:创建一个VCL-Forms Application(创建一个标准VCL程序) 第二步:引用必要的单元文件:diocp_coder_tcpServer, diocp_tcp_server,  SimpleMsgPack,uMyClientContext(uMyClientContext为扩展clientcontext,自己编写),uDIOCPStreamCoder(引用编码器与解码

DIOCP之编写第一个应用程序(二)

构建client界面: 构建界面要比写代码更难爱,不是专业UI设计太丑,先有个界面,好写代码,客户端代码与界面设计思想:界面与数据之间分离处理,不能要接收数据的地方写代码,不然以后修改程序会死人的.

mini2440第一个裸机程序

mini2440上的LED接口 /* * 功能:实现LED1灯循环亮灭 * LED1--GPB5 * LED2--GPB6 * LED3--GPB7 * LED4--GPB8 */ #define rGPBCON (*(volatile unsigned long *) 0x56000010) #define rGPBDAT (*(volatile unsigned long *) 0x56000014) #define Led1_On 0x1DF #define Led2_On 0x1BF #

ARM裸机程序之LED灯

从3月份开始看arm的裸机程序,到现在一个半月了,做到后来,发现自己越做到综合的程序,越吃力,还是得回头看看,为了最终写一个bootloader打下点基础吧,所以以这篇文章为开始,总结一下之前做过的裸机程序,希望会有所收获,我用的开发板是友善之臂的mini2440. 首先声明一点我都是在linux环境下写裸机程序的,因为这样能知道更多底层的东西,不过在这里面写就是有点不方便的是调试的时候,所以你程序要是调试不出来,你得把程序移植到一些编译器里调试(请注意移植的时候格式有点不同),所以偶尔在kei

第一个单片机程序(C言语编写)

忙活了半天,预备任务做了那么多,终于要编写我们的程序代码了.假如学过 C 言语的话,你应当很轻松的跟着我的编程本人写出来,假如没学过 C 言语也没紧要,你先照着我的抄,我会在适宜的地位写出来对 C 言语语法的说明,如许抄几回后再看看说明,就应当很明确了,抄的时分必定要仔细,特别标点符号弗成以搞错.第一个单片机程序: #include <reg52.h> //包括特别功用存放器界说的头文件 sbit LED = P0^0; //位地址声明,留意:sbit 必需小写.P 大写! void main