新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > 基于ARM的嵌入式Linux移植真實體驗(2)――BootLoader

        基于ARM的嵌入式Linux移植真實體驗(2)――BootLoader

        作者: 時間:2016-11-09 來源:網絡 收藏
        BootLoader指系統啟動后,在操作系統內核運行之前運行的一段小程序。通過BootLoader,我們可以初始化硬件設備、建立內存空間的映射圖,從而將系統的軟硬件環境帶到一個合適的狀態,以便為最終調用操作系統內核準備好正確的環境。通常,BootLoader是嚴重地依賴于硬件而實現的,特別是在嵌入式世界。因此,在嵌入式世界里建立一個通用的 BootLoader 幾乎是不可能的。盡管如此,我們仍然可以對BootLoader歸納出一些通用的概念來,以指導用戶特定的BootLoader設計與實現。
        BootLoader 的實現依賴于CPU的體系結構,因此大多數 BootLoader 都分為stage1 和stage2 兩大部分。依賴于CPU體系結構的代碼,比如設備初始化代碼等,通常都放在 stage1中,而且通常都用匯編語言來實現,以達到短小精悍的目的。而stage2 則通常用C 語言來實現,這樣可以實現更復雜的功能,而且代碼會具有更好的可讀性和可移植性。
        BootLoader 的 stage1 通常包括以下步驟:
        Ø 硬件設備初始化;
        Ø 為加載Boot Loader的stage2準備 RAM 空間;
        Ø 拷貝Boot Loader的stage2 到RAM空間中;
        Ø 設置好堆棧;
        Ø 跳轉到 stage2 的 C 入口點。
        Boot Loader的stage2通常包括以下步驟:
        Ø 初始化本階段要使用到的硬件設備;
        Ø 檢測系統內存映射(memory map);
        Ø 將kernel 映像和根文件系統映像從flash上讀到 RAM 空間中;
        Ø 為內核設置啟動參數;
        Ø 調用內核。
        本系統中的BootLoader參照韓國mizi公司的vivi進行修改。
        1.開發環境
        我們購買了武漢創維特信息技術有限公司開發的具有自主知識產權的應用于嵌入式軟件開發的集成軟、硬件開發平臺ADT(ARM 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還提供了幾個匯編情況下通過串口打印字符的函數PrintChar、PrintWord和PrintHexWord:
        @ 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代碼copy到RAM中:
        #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("rn");
        putstr(vivi_banner);
        reset_handler();
        /*
        * Step 2:
        */
        ret = board_init();
        if (ret) {
        putstr("Failed a board_init() procedurern");
        error();
        }
        /*
        * Step 3:
        */
        mem_map_init();
        mmu_init();
        putstr("Succeed memory mapping.rn");
        /*
        * 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 regionrn");
        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;
        }
        STEP1的putstr(vivi_banner)語句在串口輸出一段字符說明vivi的版本、作者等信息,vivi_banner定義為:
        const char *vivi_banner =
        "VIVI version " VIVI_RELEASE " (" VIVI_COMPILE_BY "@"
        VIVI_COMPILE_HOST ") (" VIVI_COMPILER ") " UTS_VERSION "rn";
        reset_handler進行相應的復位處理:
        void
        reset_handler(void)
        {
        int pressed;
        pressed = is_pressed_pw_btn();
        if (pressed == PWBT_PRESS_LEVEL) {
        DPRINTK("HARD RESETrn");
        hard_reset_handle();
        } else {
        DPRINTK("SOFT RESETrn");
        soft_reset_handle();
        }
        }
        hard_reset_handle會clear內存,而軟件復位處理則什么都不做:
        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();
        }
        S3C2410A的MMU初始化只需要調用通用的arm920 MMU初始化函數:
        static inline void arm920_setup(void)
        {
        unsigned long ttb = MMU_TABLE_BASE;
        __asm__(
        /* Invalidate caches */
        "mov r0, #0n"
        "mcr p15, 0, r0, c7, c7, 0n" /* invalidate I,D caches on v4 */
        "mcr p15, 0, r0, c7, c10, 4n" /* drain write buffer on v4 */
        "mcr p15, 0, r0, c8, c7, 0n" /* invalidate I,D TLBs on v4 */
        /* Load page table pointer */
        "mov r4, %0n"
        "mcr p15, 0, r4, c2, c0, 0n" /* load page table pointer */
        /* Write domain id (cp15_r3) */
        "mvn r0, #0n" /* Domains 0, 1 = client */
        "mcr p15, 0, r0, c3, c0, 0n" /* load domain access register */
        /* Set control register v4 */
        "mrc p15, 0, r0, c1, c0, 0n" /* get control register v4 */
        /* Clear out unwanted bits (then put them in if we need them) */
        /* .RVI ..RS B... .CAM */
        "bic r0, r0, #0x3000n" /* ..11 .... .... .... */
        "bic r0, r0, #0x0300n" /* .... ..11 .... .... */
        "bic r0, r0, #0x0087n" /* .... .... 1... .111 */
        /* Turn on what we want */
        /* Fault checking enabled */
        "orr r0, r0, #0x0002n" /* .... .... .... ..1. */
        #ifdef CONFIG_CPU_D_CACHE_ON
        "orr r0, r0, #0x0004n" /* .... .... .... .1.. */
        #endif
        #ifdef CONFIG_CPU_I_CACHE_ON
        "orr r0, r0, #0x1000n" /* ...1 .... .... .... */
        #endif
        /* MMU enabled */
        "orr r0, r0, #0x0001n" /* .... .... .... ...1 */
        "mcr p15, 0, r0, c1, c0, 0n" /* 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 vivin");
        c = awaitkey(boot_delay, NULL);
        if (((c != r) && (c != n) && (c != ))) {
        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電路板的調試方法。



        評論


        技術專區

        關閉
        主站蜘蛛池模板: 万载县| 哈尔滨市| 东辽县| 建瓯市| 乌拉特中旗| 荆州市| 青铜峡市| 章丘市| 新余市| 梓潼县| 津市市| 远安县| 芜湖市| 吉林市| 塘沽区| 城固县| 青海省| 哈密市| 成都市| 庐江县| 铁力市| 玛纳斯县| 汕头市| 静乐县| 炉霍县| 梧州市| 临汾市| 吉安市| 普安县| 武清区| 崇义县| 雷山县| 策勒县| 新沂市| 老河口市| 东兴市| 南宁市| 搜索| 马公市| 海原县| 陇南市|