文章目錄
- 一、單片機燒寫程序的幾種方法
- 二、Bootloader如何加載啟動App
一、單片機燒寫程序的幾種方法
在線應用編程,由開發者實現Bootloader功能,比如ARM單片機的Code分區中的Flash本是存儲用戶應用程序的區間(上電從此處執行用戶代碼),開發者可以將自己實現的Bootloader和應用程序都存放到Flash區間MCU上電啟動先執行用戶的Bootloader代碼,該代碼可為用戶應用程序的下載、校驗、升級、啟動等提供支持,進而實現OTA遠程升級功能。
一般用于給用戶遠程升級,或者是燒寫程序不方便的時候。
復位以后進入Bootloader程序,
需要說明的是這個地方就是我們程序燒寫的其實地址,
這是因為我們要把前面的空間流出來給BOOT程序。
ARM單片機啟動流程(一)(詳細解析)-CSDN博客 在本人的這篇文章里面可以看到我們的Main Flash的物理其實地址就是0x08000000
,因此我需要把BOOT程序燒寫到這里,因為CPU執行程序最先
CPU都是從0地址開始訪問的,根據被引導到的地方,有可能直接跳轉到Main Flash 0x0800 0000的原始存儲空間;也有可能跳轉到MCU廠商預置的bootloader開始于0x1FFF F000的原始存儲空間。至于怎么跳轉這兩個以及默認是什么,連接文章在這一塊進行了詳細分析。
一般我們默認就是下面順序:我們就是直接跳轉到Main Flash 0x0800 0000的原始存儲空間;
(CPU通過程序計數器(PC)獲取下一條指令的地址。在無分支的情況下,PC自動增加(如 PC_new = PC_old + 指令長度
),實現物理地址的順序執行)
int main(void)
{ InitIrqAfterBoot();DrvInit();AppInit();while (1){TaskHandler();}
}void InitIrqAfterBoot(void)
{nvic_vector_table_set(NVIC_VECTTAB_FLASH, 0x3000);__enable_irq();
}#define NVIC_VECTTAB_FLASH ((uint32_t)0x08000000) /*!< Flash base address */
值得注意的是在APP程序里面,需要配置中斷向量表里面的初始地址,這是因為原本我們默認的程序初始地址是0x08000000
,但是由于BOOT程序的作用導致我們的初始地址變成了((uint32_t)0x08000000)+偏移地址0x3000
,中斷向量表的初始位置。
如果我們常規的程序,是CPU直接加載的,就不需要我們初始化,就是內核會默認幫我們搞好,從0x08000000
開始。
但是我們現在的程序是從BOOT里面啟動的,那就意味著我們需要自己配置一個中斷向量表初始地址,所以我們需要在應用程序里面做一些CPU需要做的事情,并且在BOOT程序里面一樣需要做一些CPU需要做的事情,說白了就是手動干一些CPU的事情。
同樣需要爛熟于心的還有0x3000
表示12KB,0x1000
表示4KB,也就是4096個字節(Byte)。
另外需要一點注意的:
.property = R | W,.address = 0x0004, // 觸發系統復位 01 06 00 04 00 01.minValue = 0,.maxValue = 1,.WriteCb = ModbusResetSystem,static void ModbusResetSystem(uint16_t value)
{ResetToBoot();
}void ResetToBoot(void)
{__disable_irq(); //關閉所有中斷NVIC_SystemReset(); //復位函數,需要一些執行的時間
}
在APP程序里面,觸發復位的時候,我們一定要先關閉所有中斷,如果不關閉中斷可能會導致我們的BOOT程序會出現錯誤,因為BOOT里面也需要中斷。
此外需要聯系之前的知識在做一次對比:
代碼段1:
__Vectors0x08000000: 20000428 (.. DCD 5368719760x08000004: 08000145 E... DCD 1342180530x08000008: 0800014d M... DCD 1342180610x0800000c: 0800014f O... DCD 1342180630x08000010: 08000151 Q... DCD 1342180650x08000014: 08000153 S... DCD 1342180670x08000018: 08000155 U... DCD 1342180690x0800001c: 00000000 .... DCD 00x08000020: 00000000 .... DCD 00x08000024: 00000000 .... DCD 00x08000028: 00000000 .... DCD 00x0800002c: 08000157 W... DCD 1342180710x08000030: 08000159 Y... DCD 1342180730x08000034: 00000000 .... DCD 00x08000038: 0800015b [... DCD 1342180750x0800003c: 0800015d ]... DCD 1342180770x08000040: 0800015f _... DCD 1342180790x08000044: 0800015f _... DCD 1342180790x08000048: 0800015f _... DCD 1342180790x0800004c: 0800015f _... DCD 1342180790x08000050: 0800015f _... DCD 1342180790x08000054: 0800015f _... DCD 1342180790x08000058: 0800015f _... DCD 134218079
代碼段2:
__Vectors0x08003000: 20000738 8.. DCD 5368727600x08003004: 08003145 E1.. DCD 1342303410x08003008: 08003d21 !=.. DCD 1342333770x0800300c: 08003965 e9.. DCD 1342324210x08003010: 08003c69 i<.. DCD 1342331930x08003014: 0800356b k5.. DCD 1342314030x08003018: 08004531 1E.. DCD 1342354410x0800301c: 00000000 .... DCD 00x08003020: 00000000 .... DCD 00x08003024: 00000000 .... DCD 00x08003028: 00000000 .... DCD 00x0800302c: 0800402d -@.. DCD 1342341570x08003030: 0800365b [6.. DCD 1342316430x08003040: 0800315f _1.. DCD 1342303670x08003044: 0800315f _1.. DCD 1342303670x08003048: 0800315f _1.. DCD 1342303670x0800304c: 0800315f _1.. DCD 1342303670x08003050: 0800315f _1.. DCD 1342303670x08003054: 0800315f _1.. DCD 134230367
可以明顯看出初始的中斷向量地址就是0x08003000:
開始的,跟之前的0x08000000:
有明顯的地址偏移量,而這個地址偏移量就是我們自己設計的0x3000
。
二、Bootloader如何加載啟動App
首先我們看一下ROM空間的分布,
相當于前面12KB劃分給了BOOT,后面的500KB空間給了APP。并且本例程使用的GD32這個單片機一共的FLASH空間就是512KB。
首先是讀取 0x08000000:
獲取棧頂地址,
接著是讀取 0x08000004:
獲取復位函數的地址
然后跳轉到復位函數地址 0x08000004:
執行復位函數的代碼指令,這些是CPU自動完成的。
這里需要說明的為什么CPU需要執行復位函數?
建立可預測的初始狀態,消除不確定性,確保系統行為可預測。
CPU上電或復位時,寄存器、程序計數器(PC)、狀態標志等內部狀態是隨機的或殘留前次運行的錯誤值。復位函數會強制將其清零或設為預設值(如PC指向復位向量地址0xFFFF0
),使CPU從已知起點開始執行。
程序計數器(PC)清零?:復位后PC指向固定的啟動地址(如ARM的0x00000000
或x86的0xFFFF0
),加載第一條指令
?寄存器初始化?:通用寄存器、狀態寄存器(如EFLAGS)恢復默認值,避免殘留數據干擾新程序。
此外當系統遇到致命錯誤時,復位是恢復運行的終極手段:
- ?軟件錯誤?:如堆棧溢出、死循環、空指針訪問等,通過看門狗定時器觸發復位。看門狗超時未清零則強制復位,脫離卡死狀態。
- ?硬件故障?:內存錯誤、總線沖突、電源欠壓等觸發復位以保護硬件。例如,STM32的欠壓檢測(VBOR)會直接復位CPU。
- ?中斷與死鎖?:多核系統中核心間死鎖可通過內核復位(如ARM的
VECTRESET
)局部恢復,避免全系統重啟。
那如何啟動APP?
我們使用BOOT啟動APP的時候也是需要干這些動作,只不過這些動作不是CPU幫助我們自動完成了,而是需要我們自己手動完成。
代碼詳細解析:
static void BootToApp(void)
{uint32_t stackTopAddr = *(volatile uint32_t*)APP_ADDR_IN_FLASH; if (stackTopAddr > RAM_START_ADDRESS && stackTopAddr < (RAM_START_ADDRESS + RAM_SIZE)) //判斷棧頂地址是否在合法范圍內{__disable_irq();__set_MSP(stackTopAddr);uint32_t resetHandlerAddr = *(volatile uint32_t*) (APP_ADDR_IN_FLASH + 4);/* Jump to user application */pFunction Jump_To_Application = (pFunction) resetHandlerAddr; // int *p = (int *)0x8003145/* Initialize user application's Stack Pointer */Jump_To_Application();}NVIC_SystemReset();
}
文章源碼獲取方式:
如果您對本文的源碼感興趣,歡迎在評論區留下您的郵箱地址。我會在空閑時間整理相關代碼,并通過郵件發送給您。由于個人時間有限,發送可能會有一定延遲,請您耐心等待。同時,建議您在評論時注明具體的需求或問題,以便我更好地為您提供針對性的幫助。
【版權聲明】
本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議。這意味著您可以自由地共享(復制、分發)和改編(修改、轉換)本文內容,但必須遵守以下條件:
署名:您必須注明原作者(即本文博主)的姓名,并提供指向原文的鏈接。
相同方式共享:如果您基于本文創作了新的內容,必須使用相同的 CC 4.0 BY-SA 協議進行發布。
感謝您的理解與支持!如果您有任何疑問或需要進一步協助,請隨時在評論區留言。