基于ARM的嵌入式Linux移植真实体验(2)――BootLoader

简介:


基于ARM的嵌入式Linux移植真实体验(2――BootLoader

宋宝华   [email]21cnbao@21cn.com[/email] 出处:dev.yesky.com
BootLoader指系统启动后,在操作系统内核运行之前运行的一段小程序。通过BootLoader,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。通常,BootLoader是严重地依赖于硬件而实现的,特别是在嵌入式世界。因此,在嵌入式世界里建立一个通用的 BootLoader 几乎是不可能的。尽管如此,我们仍然可以对BootLoader归纳出一些通用的概念来,以指导用户特定的BootLoader设计与实现。
BootLoader 的实现依赖于CPU的体系结构,因此大多数 BootLoader 都分为stage1 stage2 两大部分。依赖于CPU体系结构的代码,比如设备初始化代码等,通常都放在 stage1中,而且通常都用汇编语言来实现,以达到短小精悍的目的。而stage2 则通常用语言来实现,这样可以实现更复杂的功能,而且代码会具有更好的可读性和可移植性。
BootLoader  stage1 通常包括以下步骤:
Ø          硬件设备初始化;
Ø          为加载Boot Loaderstage2准备 RAM 空间;
Ø          拷贝Boot Loaderstage2 RAM空间中;
Ø          设置好堆栈;
Ø          跳转到 stage2  C 入口点。
Boot Loaderstage2通常包括以下步骤:            
Ø          初始化本阶段要使用到的硬件设备;
Ø          检测系统内存映射(memory map)
Ø          kernel 映像和根文件系统映像从flash上读到 RAM 空间中;
Ø          为内核设置启动参数;
Ø          调用内核。
本系统中的BootLoader参照韩国mizi公司的vivi进行修改。
1. 开发环境
我们购买了武汉创维特信息技术有限公司开发的具有自主知识产权的应用于嵌入式软件开发的集成软、硬件开发平台ADTARM Development Tools)它为基于ARM 核的嵌入式应用提供了一整套完备的开发方案,包括程序编辑、工程管理和设置、程序编译、程序调试等。
ADT嵌入式开发环境由ADT Emulator for ARM ADT IDE for ARM组成。ADT Emulator for ARM 通过JTAG 实现主机和目标机之间的调试支持功能。它无需目标存储器,不占用目标系统的任何端口资源。目标程序直接在目标板上运行,通过ARM 芯片的JTAG 边界扫描口进行调试,属于完全非插入式调试,其仿真效果接近真实系统。
ADT IDE for ARM 为用户提供高效明晰的图形化嵌入式应用软件开发环境,包括一整套完备的面向嵌入式系统的开发和调试工具:源码编辑器、工程管理器、工程编译器(编译器、汇编器和连接器)、集成调试环境、ADT Emulator for ARM 调试接口等。其界面同Microsoft Visual Studio 环境相似,用户可以在ADT IDE for ARM 集成开发环境中创建工程、打开工程,建立、打开和编辑文件,编译、连接、设置、运行、调试嵌入式应用程序。
ADT嵌入式软件开发环境采用主机-目标机交叉开发模型。ADT IDE for ARM 运行于主机端,而ADT Emulator for ARM 实现ADT IDE for ARM 与目标机之间的连接。开发时,首先由ADT IDE for ARM 编译连接生成目标代码,然后建立与ADT Emulator for ARM 之间的调试通道,调试通道建立成功后,就可以在ADT IDE for ARM 中通过ADT Emulator for ARM 控制目标板实现目标程序的调试,包括将目标代码下载到目标机中,控制程序运行,调试信息观察等等。
 
2.ARM 汇编
ARM本身属于RISC指令系统,指令条数就很少,而其编程又以C等高级语言为主,我们仅需要在Bootloader的第一阶段用到少量汇编指令:
1+-运算
ADD    r0,  r1,  r2 
―― r0  := r1  +  r2
SUB   r0,  r1,  r2  
―― r0  :=  r1  -  r2
其中的第二个操作数可以是一个立即数:
ADD r3, r3, #1
―― r3 := r3 + 1
第二个操作数还可以是位移操作后的结果:
ADD r3, r2, r1, LSL #3
―― r3 := r2 + 8.r1
2)位运算
AND  r0, r1, r2  
―― r0 := r1 and r2
ORR  r0, r1, r2
―― r0 := r1 or    r2
EOR  r0, r1, r2
―― r0 := r1 xor  r2
BIC   r0, r1, r2 
―― r0 := r1 and not r2
3)寄存器搬移
MOV  r0,  r2
―― r0  :=  r2
MVN  r0,  r2          
―― r0  :=  not  r2
4)比较
CMP r1, r2
―― set cc on r1 - r2
CMN r1, r2
―― set cc on r1 + r2
TST r1, r2
―― set cc on r1 and r2
TEQ r1, r2
―― set cc on r1 or r2
这些指令影响CPSR寄存器中的 (N, Z, C, V) 
5)内存操作
LDR r0, [r1]
――  r0 := mem [r1]
STR r0, [r1]
――  mem [r1] := r0
LDR r0, [r1, #4]
――  r0 := mem [r1+4]
LDR r0, [r1, #4] !
――  r0 := mem [r1+4]     r1 := r1 + 4
LDR r0, [r1], #4
――  r0 := mem [r1]       r1 := r1 +4
LDRB r0 , [r1]
――  r0 := mem8 [r1]
LDMIA r1, {r0, r2, r5}
――  r0 := mem [r1]    r2 := mem [r1+4]    r5 := mem [r1+8]
{..} 可以包括r0~r15中的所有寄存器,若包括r15 (PC)将导致程序的跳转。
6)控制流
1:
MOV r0, #0 ; initialize counter
LOOP:
   ADD r0, r0, #1 ; increment counter
   CMP r0, #10 ; compare with limit
   BNE LOOP ; repeat if not equal
2:
CMP r0, #5
ADDNE r1, r1, r0
SUBNE r1, r1, r2
――
if (r0 != 5) {
r1 := r1 + r0 - r2
}
3.BootLoader 第一阶段
3.1 硬件设备初始化
基本的硬件初始化工作包括:
Ø          屏蔽所有的中断;
Ø          设置CPU的速度和时钟频率;
Ø          RAM初始化;
Ø          初始化LED
ARM的中断向量表设置在0地址开始的8个字空间中,如下表:
每当其中的某个异常发生后即将PC值置到相应的中断向量处,每个中断向量处放置一个跳转指令到相应的中断服务程序去进行处理,中断向量表的程序如下:
@ 0x00: Reset
       b     Reset
@ 0x04: Undefined instruction exception
UndefEntryPoint:
       b     HandleUndef
@ 0x08: Software interrupt exception
SWIEntryPoint:
       b     HandleSWI
@ 0x0c: Prefetch Abort (Instruction Fetch Memory Abort)
PrefetchAbortEnteryPoint:
       b     HandlePrefetchAbort
@ 0x10: Data Access Memory Abort
DataAbortEntryPoint:
       b     HandleDataAbort
@ 0x14: Not used
NotUsedEntryPoint:
       b     HandleNotUsed
@ 0x18: IRQ(Interrupt Request) exception
IRQEntryPoint:
       b     HandleIRQ
@ 0x1c: FIQ(Fast Interrupt Request) exception
FIQEntryPoint:
       b     HandleFIQ
复位时关闭看门狗定时器、屏蔽所有中断:
Reset:
       @ disable watch dog timer
       mov r1, #0x53000000
       mov r2, #0x0
       str   r2, [r1]
 
       @ disable all interrupts
       mov r1, #INT_CTL_BASE
       mov r2, #0xffffffff
       str   r2, [r1, #oINTMSK]
       ldr   r2, =0x7ff
       str   r2, [r1, #oINTSUBMSK]
设置系统时钟:
    @init clk
    @ 1:2:4
    mov r1, #CLK_CTL_BASE
    mov r2, #0x3 
    str   r2, [r1, #oCLKDIVN]
    mrc p15, 0, r1, c1, c0, 0             @ read ctrl register
    orr   r1, r1, #0xc0000000             @ Asynchronous 
    mcr p15, 0, r1, c1, c0, 0             @ write ctrl register
    @ now, CPU clock is 200 Mhz
    mov r1, #CLK_CTL_BASE
    ldr   r2, mpll_200mhz
    str   r2, [r1, #oMPLLCON]
点亮所有的用户LED
       @ All LED on
       mov r1, #GPIO_CTL_BASE
       add  r1, r1, #oGPIO_F
       ldr   r2,=0x55aa
       str   r2, [r1, #oGPIO_CON]
       mov r2, #0xff
       str   r2, [r1, #oGPIO_UP]
       mov r2, #0x00
       str   r2, [r1, #oGPIO_DAT]
设置(初始化)内存映射:
ENTRY(memsetup)
       @ initialise the static memory
 
       @ set memory control registers
       mov r1, #MEM_CTL_BASE
       adrl  r2, mem_cfg_val
       add  r3, r1, #52
1:     ldr   r4, [r2], #4
       str   r4, [r1], #4
       cmp r1, r3
       bne  1b
 
       mov pc, lr
设置(初始化)UART
       @ set GPIO for UART
       mov r1, #GPIO_CTL_BASE
       add  r1, r1, #oGPIO_H
       ldr   r2, gpio_con_uart  
       str   r2, [r1, #oGPIO_CON]
       ldr   r2, gpio_up_uart
       str   r2, [r1, #oGPIO_UP]    
       bl     InitUART
 
@ Initialize UART
@
@ r0 = number of UART port
InitUART:
       ldr   r1, SerBase
       mov r2, #0x0
       str   r2, [r1, #oUFCON]
       str   r2, [r1, #oUMCON]
       mov r2, #0x3
       str   r2, [r1, #oULCON]
       ldr   r2, =0x245
       str   r2, [r1, #oUCON]
#define UART_BRD ((50000000 / (UART_BAUD_RATE * 16)) - 1)
       mov r2, #UART_BRD
       str   r2, [r1, #oUBRDIV]
       mov r3, #100
       mov r2, #0x0
1:     sub  r3, r3, #0x1
       tst    r2, r3
       bne  1b
 
#if 0
       mov r2, #'U'
       str   r2, [r1, #oUTXHL]
 
1:     ldr   r3, [r1, #oUTRSTAT]
       and  r3, r3, #UTRSTAT_TX_EMPTY
       tst    r3, #UTRSTAT_TX_EMPTY
       bne  1b   
 
       mov r2, #'0'
       str   r2, [r1, #oUTXHL]
 
1:     ldr   r3, [r1, #oUTRSTAT]
       and  r3, r3, #UTRSTAT_TX_EMPTY
       tst    r3, #UTRSTAT_TX_EMPTY
       bne  1b   
#endif
 
       mov pc, lr
此外,vivi还提供了几个汇编情况下通过串口打印字符的函数PrintCharPrintWordPrintHexWord
@ PrintChar : prints the character in R0
@   r0 contains the character
@   r1 contains base of serial port
@   writes ro with XXX, modifies r0,r1,r2
@   TODO : write ro with XXX reg to error handling
PrintChar:
TXBusy:
       ldr   r2, [r1, #oUTRSTAT]
       and  r2, r2, #UTRSTAT_TX_EMPTY
       tst    r2, #UTRSTAT_TX_EMPTY
       beq  TXBusy 
       str   r0, [r1, #oUTXHL]
       mov pc, lr
 
@ PrintWord : prints the 4 characters in R0
@   r0 contains the binary word
@   r1 contains the base of the serial port
@   writes ro with XXX, modifies r0,r1,r2
@   TODO : write ro with XXX reg to error handling
PrintWord:
       mov r3, r0
       mov r4, lr
       bl     PrintChar
 
       mov r0, r3, LSR #8              /* shift word right 8 bits */
       bl     PrintChar
 
       mov r0, r3, LSR #16             /* shift word right 16 bits */
       bl     PrintChar
      
       mov r0, r3, LSR #24             /* shift word right 24 bits */
       bl     PrintChar
 
       mov r0, #'\r'
       bl     PrintChar
 
       mov r0, #'\n'
       bl     PrintChar
 
       mov pc, r4
 
@ PrintHexWord : prints the 4 bytes in R0 as 8 hex ascii characters
@   followed by a newline
@   r0 contains the binary word
@   r1 contains the base of the serial port
@   writes ro with XXX, modifies r0,r1,r2
@   TODO : write ro with XXX reg to error handling
PrintHexWord:
       mov r4, lr
       mov r3, r0
       mov r0, r3, LSR #28
       bl     PrintHexNibble
       mov r0, r3, LSR #24
       bl     PrintHexNibble
       mov r0, r3, LSR #20
       bl     PrintHexNibble
       mov r0, r3, LSR #16
       bl     PrintHexNibble
       mov r0, r3, LSR #12
       bl     PrintHexNibble
       mov r0, r3, LSR #8
       bl     PrintHexNibble
       mov r0, r3, LSR #4
       bl     PrintHexNibble
       mov r0, r3
       bl     PrintHexNibble
 
       mov r0, #'\r'
       bl     PrintChar
 
       mov r0, #'\n'
       bl     PrintChar
 
       mov pc, r4
3.2Bootloader 拷贝
配置为从NAND FLASH启动,需要将NAND FLASH中的vivi代码copyRAM中:
#ifdef CONFIG_S3C2410_NAND_BOOT
       bl     copy_myself
 
       @ jump to ram
       ldr   r1, =on_the_ram
       add  pc, r1, #0
       nop
       nop
1:     b     1b           @ infinite loop
 
#ifdef CONFIG_S3C2410_NAND_BOOT
@
@ copy_myself: copy vivi to ram
@
copy_myself:
       mov r10, lr
 
       @ reset NAND
       mov r1, #NAND_CTL_BASE
       ldr   r2, =0xf830           @ initial value
       str   r2, [r1, #oNFCONF]
       ldr   r2, [r1, #oNFCONF]
       bic   r2, r2, #0x800        @ enable chip
       str   r2, [r1, #oNFCONF]
       mov r2, #0xff        @ RESET command
       strb  r2, [r1, #oNFCMD]
       mov r3, #0                    @ wait
1:     add  r3, r3, #0x1
       cmp r3, #0xa
       blt    1b
2:     ldr   r2, [r1, #oNFSTAT]       @ wait ready
       tst    r2, #0x1
       beq  2b
       ldr   r2, [r1, #oNFCONF]
       orr   r2, r2, #0x800        @ disable chip
       str   r2, [r1, #oNFCONF]
 
       @ get read to call C functions (for nand_read())
       ldr   sp, DW_STACK_START      @ setup stack pointer
       mov fp, #0                    @ no previous frame, so fp=0
 
       @ copy vivi to RAM
       ldr   r0, =VIVI_RAM_BASE
       mov     r1, #0x0
       mov r2, #0x20000
       bl     nand_read_ll
 
       tst    r0, #0x0
       beq  ok_nand_read
#ifdef CONFIG_DEBUG_LL
bad_nand_read:
       ldr   r0, STR_FAIL
       ldr   r1, SerBase
       bl     PrintWord
1:     b     1b           @ infinite loop
#endif
      
ok_nand_read:
#ifdef CONFIG_DEBUG_LL
       ldr   r0, STR_OK
       ldr   r1, SerBase
       bl     PrintWord
#endif
 
       @ verify
       mov r0, #0
       ldr   r1, =0x33f00000
       mov r2, #0x400      @ 4 bytes * 1024 = 4K-bytes
go_next:
       ldr   r3, [r0], #4
       ldr   r4, [r1], #4
       teq   r3, r4
       bne  notmatch
       subs r2, r2, #4
       beq  done_nand_read    
       bne  go_next
notmatch:
#ifdef CONFIG_DEBUG_LL
       sub  r0, r0, #4
       ldr   r1, SerBase
       bl     PrintHexWord
       ldr   r0, STR_FAIL
       ldr   r1, SerBase
       bl     PrintWord
#endif
1:     b     1b
done_nand_read:
 
#ifdef CONFIG_DEBUG_LL
       ldr   r0, STR_OK
       ldr   r1, SerBase
       bl     PrintWord
#endif
 
       mov pc, r10
 
@ clear memory
@ r0: start address
@ r1: length
mem_clear:
       mov r2, #0
       mov r3, r2
       mov r4, r2
       mov r5, r2
       mov r6, r2
       mov r7, r2
       mov r8, r2
       mov r9, r2
 
clear_loop:
       stmia       r0!, {r2-r9}
       subs r1, r1, #(8 * 4)
       bne  clear_loop
 
       mov pc, lr
 
#endif @ CONFIG_S3C2410_NAND_BOOT
3.3 进入 C 代码
首先要设置堆栈指针sp,堆栈指针的设置是为了执行C语言代码作好准备。设置好堆栈后,调用C语言的main函数:
@ get read to call C functions
ldr   sp, DW_STACK_START      @ setup stack pointer
mov fp, #0                    @ no previous frame, so fp=0
mov a2, #0                   @ set argv to NULL
 
bl     main                     @ call main
 
mov pc, #FLASH_BASE              @ otherwise, reboot
4. BootLoader 第二阶段
vivi Bootloader的第二阶段又分成了八个小阶段,在main函数中分别调用这几个小阶段的相关函数:
int main(int argc, char *argv[])
{
       int ret;
 
       /*
        * Step 1:
        */
       putstr("\r\n");
       putstr(vivi_banner);
 
       reset_handler();
 
       /*
        * Step 2:
        */
       ret = board_init();
       if (ret) {
              putstr("Failed a board_init() procedure\r\n");
              error();
       }
 
       /*
        * Step 3:
        */
       mem_map_init();
       mmu_init();
       putstr("Succeed memory mapping.\r\n");
 
       /*
        * Now, vivi is running on the ram. MMU is enabled.
        */
 
       /*
        * Step 4:
        */
       /* initialize the heap area*/
       ret = heap_init();
       if (ret) {
              putstr("Failed initailizing heap region\r\n");
              error();
       }
 
       /* Step 5:
        */
       ret = mtd_dev_init();
 
       /* Step 6:
        */
       init_priv_data();
 
       /* Step 7:
        */
       misc();
 
       init_builtin_cmds();
 
       /* Step 8:
        */
       boot_or_vivi();
 
       return 0;
}
STEP1putstr(vivi_banner)语句在串口输出一段字符说明vivi的版本、作者等信息,vivi_banner定义为:
const char *vivi_banner =
                       "VIVI version " VIVI_RELEASE " (" VIVI_COMPILE_BY "@"
                       VIVI_COMPILE_HOST ") (" VIVI_COMPILER ") " UTS_VERSION "\r\n";
reset_handler进行相应的复位处理:
void
reset_handler(void)
{
       int pressed;
 
       pressed = is_pressed_pw_btn();
 
       if (pressed == PWBT_PRESS_LEVEL) {
              DPRINTK("HARD RESET\r\n");
              hard_reset_handle();
       } else {
              DPRINTK("SOFT RESET\r\n");
              soft_reset_handle();
       }
}
hard_reset_handleclear内存,而软件复位处理则什么都不做:
static void
hard_reset_handle(void)
{
       clear_mem((unsigned long)USER_RAM_BASE, (unsigned long)USER_RAM_SIZE);
}
STEP2进行板初始化,设置时间和可编程I/O口:
int board_init(void)
{
       init_time();
       set_gpios();
 
       return 0;
}
STEP3进行内存映射及MMU初始化:
void mem_map_init(void)
{
#ifdef CONFIG_S3C2410_NAND_BOOT
       mem_map_nand_boot();
#else
       mem_map_nor();
#endif
       cache_clean_invalidate();
       tlb_invalidate();
}
S3C2410AMMU初始化只需要调用通用的arm920 MMU初始化函数:
static inline void arm920_setup(void)
{
       unsigned long ttb = MMU_TABLE_BASE;
 
__asm__(
       /* Invalidate caches */
       "mov       r0, #0\n"
       "mcr       p15, 0, r0, c7, c7, 0\n"  /* invalidate I,D caches on v4 */
       "mcr       p15, 0, r0, c7, c10, 4\n" /* drain write buffer on v4 */
       "mcr       p15, 0, r0, c8, c7, 0\n"  /* invalidate I,D TLBs on v4 */
       /* Load page table pointer */
       "mov       r4, %0\n"
       "mcr       p15, 0, r4, c2, c0, 0\n"  /* load page table pointer */
       /* Write domain id (cp15_r3) */
       "mvn       r0, #0\n"                /* Domains 0, 1 = client */
       "mcr       p15, 0, r0, c3, c0, 0\n"  /* load domain access register */
       /* Set control register v4 */
       "mrc       p15, 0, r0, c1, c0, 0\n"  /* get control register v4 */
       /* Clear out 'unwanted' bits (then put them in if we need them) */
                                          /* .RVI ..RS B... .CAM */
       "bic  r0, r0, #0x3000\n"         /* ..11 .... .... .... */
       "bic  r0, r0, #0x0300\n"         /* .... ..11 .... .... */
       "bic  r0, r0, #0x0087\n"         /* .... .... 1... .111 */
       /* Turn on what we want */
       /* Fault checking enabled */
       "orr  r0, r0, #0x0002\n"         /* .... .... .... ..1. */
#ifdef CONFIG_CPU_D_CACHE_ON
       "orr  r0, r0, #0x0004\n"         /* .... .... .... .1.. */
#endif 
#ifdef CONFIG_CPU_I_CACHE_ON
       "orr  r0, r0, #0x1000\n"         /* ...1 .... .... .... */
#endif 
       /* MMU enabled */
       "orr  r0, r0, #0x0001\n"         /* .... .... .... ...1 */
       "mcr       p15, 0, r0, c1, c0, 0\n"  /* write control register */
       : /* no outputs */
       : "r" (ttb) );
}
STEP4设置堆栈;STEP5进行mtd设备的初始化,记录MTD分区信息;STEP6设置私有数据;STEP7初始化内建命令。
STEP8启动一个SHELL,等待用户输出命令并进行相应处理。在SHELL退出的情况下,启动操作系统:
#define DEFAULT_BOOT_DELAY       0x30000000
void boot_or_vivi(void)
{
       char c;
       int ret;
       ulong boot_delay;
 
       boot_delay = get_param_value("boot_delay", &ret);
       if (ret) boot_delay = DEFAULT_BOOT_DELAY;
       /* If a value of boot_delay is zero,
        * unconditionally call vivi shell */
       if (boot_delay == 0) vivi_shell();
 
 
       /*
        * wait for a keystroke (or a button press if you want.)
        */
       printk("Press Return to start the LINUX now, any other key for vivi\n");
       c = awaitkey(boot_delay, NULL);
       if (((c != '\r') && (c != '\n') && (c != '\0'))) {
              printk("type \"help\" for help.\n");
              vivi_shell();
       }
       run_autoboot();
 
       return;
}
SHELL中读取用户从串口输出的命令字符串,执行该命令:
void
vivi_shell(void)
{
#ifdef CONFIG_SERIAL_TERM
       serial_term();
#else
#error there is no terminal.
#endif
}
void serial_term(void)
{
       char cmd_buf[MAX_CMDBUF_SIZE];
 
       for (;;) {
              printk("%s> ", prompt);
 
              getcmd(cmd_buf, MAX_CMDBUF_SIZE);
 
              /* execute a user command */
              if (cmd_buf[0])
                     exec_string(cmd_buf);
       }
}
5. 电路板调试
在电路板的调试过程中,我们首先要在ADT新建的工程中添加第一阶段的汇编代码head.S文件,修改Link脚本,将代码和数据映射到S3C2410A自带的0x40000000开始的4KB内存空间内:
SECTIONS
{
       . = 0x40000000;
       .text : { *(.text) }
       Image_RO_Limit = .;
       Image_RW_Base = .;
       .data : { *(.data) }
       .rodata : { *(.rodata) }
       Image_ZI_Base = .;
       .bss : { *(.bss) }
       Image_ZI_Limit = .;
       __bss_start__ = .;
       __bss_end__ = .;
       __EH_FRAME_BEGIN__ = .;
       __EH_FRAME_END__ = .;
PROVIDE (__stack = .);
       end = .;
       _end = .;
       .debug_info     0 : { *(.debug_info)  }
      .debug_line            0 : { *(.debug_line)  }
     .debug_abbrev   0 : { *(.debug_abbrev)}
     .debug_frame    0 : { *(.debug_frame) }
}
借助万用表、示波器等仪器仪表,调通SDRAM,并将vivi中自带的串口、NAND FLASH驱动添加到工程中,调试通过板上的串口和FLASH。如果板电路的原理与三星公司DEMO板有差距,则vivi中硬件的操作要进行相应的修改。全部调试通过后,修改vivi源代码,重新编译vivi,将其烧录入NAND FLASH就可以在复位后启动这个Bootloader了。
调试板上的新增硬件时,宜在ADT中添加相应的代码,在不加载操作系统的情况下,单纯地操作这些硬件。如果电路板设计有误,要进行飞线和割线等处理。
6. 小结
本章讲解了ARM汇编、Bootloader的功能,Bootloader的调试环境及ARM电路板的调试方法。




 本文转自 21cnbao 51CTO博客,原文链接:http://blog.51cto.com/21cnbao/120251,如需转载请自行联系原作者




相关文章
|
4月前
|
Ubuntu Linux
查看Linux系统架构的命令,查看linux系统是哪种架构:AMD、ARM、x86、x86_64、pcc 或 查看Ubuntu的版本号
查看Linux系统架构的命令,查看linux系统是哪种架构:AMD、ARM、x86、x86_64、pcc 或 查看Ubuntu的版本号
978 3
|
3天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
35 13
|
4月前
|
NoSQL Linux C语言
嵌入式GDB调试Linux C程序或交叉编译(开发板)
【8月更文挑战第24天】本文档介绍了如何在嵌入式环境下使用GDB调试Linux C程序及进行交叉编译。调试步骤包括:编译程序时加入`-g`选项以生成调试信息;启动GDB并加载程序;设置断点;运行程序至断点;单步执行代码;查看变量值;继续执行或退出GDB。对于交叉编译,需安装对应架构的交叉编译工具链,配置编译环境,使用工具链编译程序,并将程序传输到开发板进行调试。过程中可能遇到工具链不匹配等问题,需针对性解决。
128 3
|
4月前
|
Linux 网络安全 开发工具
内核实验(二):自定义一个迷你Linux ARM系统,基于Kernel v5.15.102, Busybox,Qemu
本文介绍了如何基于Linux Kernel 5.15.102版本和BusyBox创建一个自定义的迷你Linux ARM系统,并使用QEMU进行启动和调试,包括内核和BusyBox的编译配置、根文件系统的制作以及运行QEMU时的命令和参数设置。
367 0
内核实验(二):自定义一个迷你Linux ARM系统,基于Kernel v5.15.102, Busybox,Qemu
|
4月前
|
传感器 人工智能 网络协议
:嵌入式 Linux 及其用途
【8月更文挑战第24天】
217 0
|
5月前
|
Ubuntu 算法 Linux
嵌入式Linux的学习误区
**嵌入式Linux学习误区摘要** 1. **过度聚焦桌面Linux** - 许多学习者误将大量时间用于精通桌面Linux系统(如RedHat、Fedora、Ubuntu),认为这是嵌入式Linux开发的基石。 - 实际上,桌面Linux仅作为开发工具和环境,目标不应是成为Linux服务器专家,而应专注于嵌入式开发工具和流程。 2. **盲目阅读Linux内核源码** - 初学者在不了解Linux基本知识时试图直接研读内核源码,这往往导致困惑和挫败感。 - 在具备一定嵌入式Linux开发经验后再有针对性地阅读源码,才能有效提升技能。
|
22天前
|
机器学习/深度学习 弹性计算 人工智能
阿里云服务器架构有啥区别?X86计算、Arm、GPU异构、裸金属和高性能计算对比
阿里云ECS涵盖x86、ARM、GPU/FPGA/ASIC、弹性裸金属及高性能计算等多种架构。x86架构采用Intel/AMD处理器,适用于广泛企业级应用;ARM架构低功耗,适合容器与微服务;GPU/FPGA/ASIC专为AI、图形处理设计;弹性裸金属提供物理机性能;高性能计算则针对大规模并行计算优化。
|
2月前
|
编解码 弹性计算 应用服务中间件
阿里云服务器Arm计算架构解析:Arm计算架构云服务器租用收费标准价格参考
阿里云服务器架构分为X86计算、Arm计算、高性能计算等多种架构,其中Arm计算架构以其低功耗、高效率的特点受到广泛关注。本文将深入解析阿里云Arm计算架构云服务器的技术特点、适用场景以及包年包月与按量付费的收费标准与最新活动价格情况,以供选择参考。
|
2月前
|
机器学习/深度学习 弹性计算 编解码
阿里云服务器计算架构X86/ARM/GPU/FPGA/ASIC/裸金属/超级计算集群有啥区别?
阿里云服务器ECS提供了多种计算架构,包括X86、ARM、GPU/FPGA/ASIC、弹性裸金属服务器及超级计算集群。X86架构常见且通用,适合大多数应用场景;ARM架构具备低功耗优势,适用于长期运行环境;GPU/FPGA/ASIC则针对深度学习、科学计算、视频处理等高性能需求;弹性裸金属服务器与超级计算集群则分别提供物理机级别的性能和高速RDMA互联,满足高性能计算和大规模训练需求。
|
2月前
|
存储 Docker 容器
ARM架构鲲鹏主机BClinux离线安装docker步骤
下载并安装适用于ARM架构的Docker CE二进制文件,解压后移动至/usr/bin目录。创建docker组,配置systemd服务脚本(docker.service、docker.socket、containerd.service),重载systemd配置,启动并启用docker服务。编辑daemon.json配置存储驱动、镜像加速地址等,最后拉取所需镜像。
67 0
下一篇
DataWorks