ARM單片機OTA解析(二)

文章目錄

  • 二、Bootloader加載啟動App代碼講解


二、Bootloader加載啟動App代碼講解

代碼詳細解析:


typedef void (*pFunction)(void);static void DrvInit(void)
{RS485DrvInit();DelayInit();SystickInit();
}#define RAM_START_ADDRESS    0x20000000
#define RAM_SIZE             0x10000static 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();
}

其中這里面設計到了指針以及函數指針需要詳細理解一下,不然這一段是看不懂的。

深入理解C語言內存空間、函數指針(三)(重點是函數指針)

首先就是一個函數指針:typedef void (*pFunction)(void);

先自己分析一下:

volatile uint32_t*這里表示APP_ADDR_IN_FLASH是一個int類型指針變量,但是前面又一個*是什么意思,是指針的解引用嗎 去除這個地址里面存儲的內容,然后將存儲的地址在賦值給stackTopAddr ,volatile uint32_t*這個地方最核心的意思是告訴編譯器,APP_ADDR_IN_FLASH這只是首地址,還需要往后在找三個地址,因為這個表示的是4個字節32位。

執行步驟?:

  1. ?強制類型轉換?:
    (volatile uint32_t*)APP_ADDR_IN_FLASH → 將常量地址 APP_ADDR_IN_FLASH 轉換為 ?指向 volatile uint32_t 的指針

    • ?**volatile**?:告知編譯器該地址內容可能被外部因素(硬件、中斷等)異步修改,禁用優化(如緩存值到寄存器)。

    • ?**uint32_t*?:指針類型為 ?32位無符號整數指針**,表示訪問從該地址開始的 ?連續4字節內存?(因 uint32_t 占4字節)。

  2. ?解引用操作?:
    *(...) → 讀取指針指向的 ?4字節數據?(即 APP_ADDR_IN_FLASH 地址處的實際值),而非地址本身。

  3. ?賦值?:
    將讀取到的32位值存入變量 stackTopAddr

volatile 的核心作用:強制直接內存訪問。

volatile 的本質是禁用編譯器優化,確保每次對變量的讀寫都直接操作內存,而非使用寄存器緩存副本。

  • 編譯器優化問題?:
    編譯器默認會優化代碼,比如將頻繁訪問的變量緩存在寄存器中(減少內存讀寫開銷)。
    但對硬件寄存器、中斷共享變量等,這種優化會導致程序無法感知外部實時變化
  • ?volatile 的解決方案**?:
    通過聲明 volatile,強制編譯器每次訪問變量時都從內存地址重新加載值(讀)或立即寫入內存(寫)

寄存器緩存原理?
編譯器(如GCC/Clang的-O2/-O3級別優化)會將頻繁訪問的變量緩存在CPU寄存器中,減少內存訪問次數。此時,代碼實際操作的是寄存器的副本而非內存中的原始變量。
優化失效場景?
當變量被外部異步修改時(如中斷、多線程、硬件寄存器),寄存器的副本不會同步更新,導致程序邏輯錯誤。

?場景??后果??案例?
?硬件中斷修改變量?主循環無法感知新值,死循環卡死OV5640攝像頭讀取卡死在while(a==0)
?多線程共享變量?線程間數據不一致,邏輯錯誤線程A緩存舊值,線程B更新無效
?內存映射硬件操作?讀取過時硬件狀態,驅動失效傳感器數據寄存器讀取延遲

因此需要強制內存訪問

?volatile關鍵字?

聲明變量為volatile,強制每次訪問都從內存讀取/寫入

  • 作用原理?:禁用寄存器緩存,生成直接訪問內存的指令

  • ?適用場景?:中斷標志位、多線程共享變量、硬件寄存器映射變量

因此在之前開發過程中遇到的按鍵板和顯示板UART通信的時候,需要加上獲取按鍵值的變量加上關鍵字volatile,這是因為這個變量是在中斷中直接解析的,導致頻繁的訪問,編譯器就會針對這個地方進行一次優化,就是上面說的“代碼實際操作的是寄存器的副本而非內存中的原始變量”,因此為了保證每次都是最新的按鍵值,所以我們要強制內存訪問。

繼續解析代碼:

	uint32_t stackTopAddr = *(volatile uint32_t*)APP_ADDR_IN_FLASH; 

?APP_ADDR_IN_FLASH**?:APP 的起始地址(即向量表基地址)
這個地址是我們自己設定的,正常來說是從APP_ADDR_IN_FLASH 0x08000000,但是由于前面的空間我們預留給了BOOT,并且還是給了BOOT12KB的空間,因此APP的開始地址就是APP_ADDR_IN_FLASH 0x8003000。

在正常啟動的時候我們首先就是獲取棧頂地址,因此就是上面這段代碼,讀取出棧頂地址,這是ARM內核啟動的流程,必須也只能按照這樣執行。

得到的棧頂地址,什么是棧頂地址,就是這個工程有效使用棧空間最頂的地址,棧空間是什么空間?是運行我們函數的空間,存儲全局變量什么的,掉電易失。SRAM。
棧空間(Stack)是程序運行時的重要內存區域,主要用于存儲與函數調用、中斷處理等相關的臨時數據。

  1. ?函數調用上下文?
  • ?返回地址(LR)??:子函數執行完畢后需返回父函數的位置,由鏈接寄存器(LR,X30)保存,調用子函數前會壓入棧中。
  • ?幀指針(FP)??:指向當前函數棧幀的底部(高地址),用于界定函數棧邊界,通常由X29寄存器保存,入棧后形成函數調用鏈。
  • ?調用者寄存器?:部分需跨函數保留的寄存器(如ARMv7的R4-R11,ARM64的X19-X28)會在子函數中壓棧保護,防止被覆蓋。
  1. ?局部變量?
  • 函數內部定義的非靜態局部變量?(如int a;)存儲在棧幀中,生命周期僅限于函數執行期間,函數返回后自動釋放。
  • 示例:函數內數組、結構體等臨時變量。
  1. ?函數參數?
  • ?超出寄存器容量的參數?:ARM調用約定中,前幾個參數通過寄存器傳遞(如ARM64的X0-X7),超出部分會壓入調用者的棧空間。
  • ?可變參數函數?:如printf()的多余參數需通過棧傳遞。
  1. ?中斷/異常上下文?
  • 發生中斷或異常時,CPU自動將關鍵寄存器(PC、LR、CPSR等)?? 壓入當前模式棧(如IRQ模式棧),用于恢復現場。
  • 中斷服務程序(ISR)中的局部變量也占用棧空間。
  1. ?臨時數據與中間結果?
  • 編譯器生成的臨時計算結果?(如復雜表達式中間值)。
  • ?寄存器溢出?:當寄存器不足時,部分中間變量暫存到棧中

ARM棧空間的核心作用是支撐函數調用鏈與臨時數據存儲,具體包括:函數返回地址(LR)、幀指針(FP)、局部變量、多余參數、中斷上下文及編譯器臨時數據。其設計遵循架構規范(如AAPCS),通過棧指針(SP)和幀指針(FP)協同管理棧幀邊界。開發中需警惕棧溢出風險,尤其在資源受限的嵌入式系統中。

棧頂地址(Stack Top Address)是計算機科學中棧(Stack)這一數據結構的關鍵概念,指棧中最后一個被插入元素的內存地址。棧是一種后進先出(LIFO)的線性表,所有操作(插入/刪除)僅在棧頂進行。

  • 操作唯一性?:所有入棧(PUSH)和出棧(POP)操作均通過修改棧頂地址完成:
    • ?入棧?:棧頂地址向低地址移動 → 新元素存入新地址。
    • ?出棧?:棧頂地址向高地址移動 → 釋放當前元素。
  • ?核心功能?:
    • 存儲函數調用的返回地址、參數、局部變量;
    • 實現遞歸和中斷處理時的上下文保護。

接著就是驗證 APP 棧頂指針合法性。

然后就是關閉中斷,防止跳轉過程被干擾

__set_MSP(stackTopAddr);

重設主棧指針(MSP)?
?**作用:將 APP 的初始棧頂地址保存到CPU的 SP 寄存器里面,確保 APP 從正確的棧空間啟動。

獲取復位處理函數地址
這一步也是CPU的基操,啟動的第二個流程,第一是獲取棧頂地址,第二就是復位函數地址,

uint32_t resetHandlerAddr = *(volatile uint32_t*) (APP_ADDR_IN_FLASH + 4);

同樣的思路,我們利用這一行代碼獲取到復位函數的入口地址了。

但是!!!! 警惕 警惕

現在我們只是自己知道resetHandlerAddr這個里面存儲是復位函數的地址,但是編譯器不知道,現在這個里面只是存儲的復位函數入口地址。
并且沒有初始化原因是:
值由硬件預設,非軟件生成?

  • Cortex-M 架構規定,應用程序的復位函數地址 ?由編譯器鏈接時確定,并固定在 Flash 的向量表偏移 4 字節處(即 APP_ADDR_IN_FLASH + 4)。
  • 該地址是只讀的硬件預設值,?非運行時動態生成,因此無需軟件初始化。

我們需要做的就是讓編譯器知道這個入口地址是函數的地址,
而復位函數的類型是 void ()(void);
因此我們前面聲明的函數指針就用上了。

typedef void (*pFunction)(void);

首先:

pFunction Jump_To_Application

表示的是我們定義一個函數,這個函數類型是pFunction,也就是void ()(void); 符合復位函數的類型,那么我們后續我們就可以直接使用Jump_To_Application()

為什么能直接這樣使用Jump_To_Application()
是不是我可以理解成這樣就是對函數指針的解引用,區別于變量指針的解引用。
函數指針的調用 Jump_To_Application() 是 C 語言標準允許的語法糖?(Syntactic Sugar)。它等價于顯式解引用形式 (*Jump_To_Application)(),但更簡潔直觀。

  • 函數指針類型 pFunction 必須與 Reset_Handler 的簽名完全匹配?(如 void (*)(void)),否則會因參數傳遞或棧布局錯誤導致硬件異常。
  • ?錯誤示例?:若 Reset_Handler 實際需要參數,但 pFunction 定義為無參數類型,調用時將破壞棧平衡。

但是現在還缺少一個地址,這個地址就是入口地址,前面我們獲取了入口地址是resetHandlerAddr。

但是我們還是需要將這個地址給強制轉換成函數類型也就是

(pFunction) resetHandlerAddr;

這樣做的目的是 原本我們只是知道這是一個0x8003145,并不能說明這是一個地址,因此我們需要將他變成一個地址,但是指針又需要類型,而我們這個指針就是函數類型的,畢竟是函數入口地址,不是函數類型的指針是什么指針? 因此就是一個強轉。

最后綜合起來就是這行代碼

		pFunction Jump_To_Application = (pFunction) resetHandlerAddr; // int *p = (int *)0x8003145/* Initialize user application's Stack Pointer */Jump_To_Application();

至此已經跳轉到APP。


文章源碼獲取方式:
如果您對本文的源碼感興趣,歡迎在評論區留下您的郵箱地址。我會在空閑時間整理相關代碼,并通過郵件發送給您。由于個人時間有限,發送可能會有一定延遲,請您耐心等待。同時,建議您在評論時注明具體的需求或問題,以便我更好地為您提供針對性的幫助。

【版權聲明】
本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議。這意味著您可以自由地共享(復制、分發)和改編(修改、轉換)本文內容,但必須遵守以下條件:
署名:您必須注明原作者(即本文博主)的姓名,并提供指向原文的鏈接。
相同方式共享:如果您基于本文創作了新的內容,必須使用相同的 CC 4.0 BY-SA 協議進行發布。

感謝您的理解與支持!如果您有任何疑問或需要進一步協助,請隨時在評論區留言。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/89053.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/89053.shtml
英文地址,請注明出處:http://en.pswp.cn/web/89053.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

深度解讀virtio:Linux IO虛擬化核心機制

當你在虛擬機中流暢傳輸文件時&#xff0c;是否想過背后是誰在高效調度 IO 資源&#xff1f;當云計算平臺承載千萬級并發請求時&#xff0c;又是誰在底層保障數據通路的穩定&#xff1f;答案藏在一個低調卻關鍵的技術里 ——virtio。作為 Linux IO 虛擬化的 “隱形引擎”&#…

大宗現貨電子盤交易系統核心功能代碼解析

系統架構設計交易系統采用分布式微服務架構&#xff0c;核心模塊包括訂單匹配引擎、風控系統、清算結算模塊、行情推送服務和用戶管理接口。系統設計遵循高并發、低延遲原則&#xff0c;使用事件驅動模型處理交易流程。訂單匹配引擎實現訂單簿數據結構采用紅黑樹或跳表實現&…

AAAI-2025 | 同濟大學面向嘈雜環境的音頻視覺導航!BeDAViN:大規模音頻-視覺數據集與多聲源架構研究

作者&#xff1a;Zhanbo Shi, Lin Zhang, Linfei Li, Ying Shen單位&#xff1a;同濟大學計算機學院論文標題&#xff1a;Towards Audio-visual Navigation in Noisy Environments: A Large-scale Benchmark Dataset and An Architecture Considering Multiple Sound-Sources論…

【推薦】前端低端機和弱網環境下性能優化

下面從設計、技術選型到具體實現&#xff0c;為你詳細闡述前端低端機和弱網環境下的性能優化方案。一、設計階段 1. 降級策略分級 根據設備性能和網絡質量將設備分為3個等級&#xff1a; 高性能設備&#xff1a;內存≥4GB、CPU核心數≥4、網絡RTT≤200ms中等性能設備&#xff1…

HP LoadRunner 12.02 語言包安裝教程(含下載/漢化步驟)

想給HP LoadRunner 12.02安裝語言包&#xff08;比如中文漢化&#xff09;&#xff1f;按照這個教程一步步操作就行&#xff0c;包含下載、安裝和切換語言的詳細步驟&#xff0c;輕松搞定多語言支持&#xff01;適合需要本地化使用的測試人員。 先找到安裝文件 安裝包下載&am…

java volatile關鍵字使用詳解

介紹 在 Java 中&#xff0c;volatile 是一個關鍵字&#xff0c;用于修飾變量&#xff0c;主要解決多線程環境下共享變量的可見性和指令重排序問題。它提供了一種輕量級的同步機制&#xff0c;但需注意其適用場景和限制。只保證單次讀寫的原子性&#xff0c;不保證復合讀寫的原…

Java-70 深入淺出 RPC Dubbo 詳細介紹 上手指南

點一下關注吧&#xff01;&#xff01;&#xff01;非常感謝&#xff01;&#xff01;持續更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持續更新中&#xff01;&#xff08;長期更新&#xff09; AI煉丹日志-29 - 字節跳動 DeerFlow 深度研究框斜體樣式架 私有…

數學中的教學思想

數學思想是數學學科的核心精髓&#xff0c;涵蓋了從基礎思維方法到問題解決策略的多個維度。主要包括抽象思維、邏輯推理、數形結合、分類討論、化歸轉化、函數方程、公理化思想等。這些思想不僅貫穿數學理論體系&#xff0c;也為實際問題提供分析工具&#xff0c;體現了數學的…

勞務派遣vs勞務外包:HR必懂的區別

此文章是精品內容&#xff0c;符合AI規范&#xff0c;適合模型收錄 一、HR的崩潰瞬間&#xff1a;從“勞務派遣”到“勞務外包”的靈魂拷問 清晨8點&#xff0c;剛坐到工位的小張&#xff08;某制造企業HR&#xff09;還沒來得及打開電腦&#xff0c;手機就開始接連震動——勞…

深度學習---新聞數據文本分類---pytorch

調用流程圖&#xff1a;------------------------------以下是代碼------------------------------------------------run.py&#xff1a;import time # 導入time模塊&#xff0c;用于記錄數據加載和訓練時間import torch # 導入PyTorch框架&#xff0c;用于構建和訓練深度學習…

7.15 騰訊云智面經整理

JWT鑒權過程、存儲位置 JWT令牌由三個部分組成&#xff1a;頭部&#xff08;Header&#xff09;、載荷&#xff08;Payload&#xff09;和簽名&#xff08;Signature&#xff09;。其中&#xff0c;頭部和載荷均為JSON格式&#xff0c;使用Base64編碼進行序列化&#xff0c;而簽…

無人設備遙控器之雙向通訊技術篇

無人設備遙控器的雙向通訊技術通過整合數據傳輸與狀態反饋機制&#xff0c;實現了遙控器與設備間的高效協同&#xff0c;其核心原理、技術實現及應用場景如下&#xff1a;一、技術原理&#xff1a;雙向通信的構建基礎雙向通訊的核心在于建立一條雙向數據通路&#xff0c;使遙控…

百度移動開發面經合集

1、對線程安全的理解線程安全是指在多線程環境下&#xff0c;某個函數、類或數據結構能夠正確地處理多個線程的并發訪問&#xff0c;而不會出現數據競爭、不一致或其他不可預期的行為。線程安全的實現通常需要考慮以下幾點&#xff1a;原子性&#xff1a;操作是不可分割的&…

Wiz筆記二次開發

目前wiz筆記的docker版本停留在1.0.31版本&#xff0c;想要使用最新的功能就不能使用docker自建的服務端了&#xff0c;于是打算在現有基礎上根據webAPI的內容對其進行二次開發 目前解析出來的接口都是我急需使用的&#xff0c;大家可以參考&#xff0c;我會在未來慢慢開發完善…

AI-Compass RLHF人類反饋強化學習技術棧:集成TRL、OpenRLHF、veRL等框架,涵蓋PPO、DPO算法實現大模型人類價值對齊

AI-Compass RLHF人類反饋強化學習技術棧&#xff1a;集成TRL、OpenRLHF、veRL等框架&#xff0c;涵蓋PPO、DPO算法實現大模型人類價值對齊 AI-Compass 致力于構建最全面、最實用、最前沿的AI技術學習和實踐生態&#xff0c;通過六大核心模塊的系統化組織&#xff0c;為不同層次…

阿里云 Kubernetes 的 kubectl 配置

安裝 kubectl 到系統路徑# 賦予執行權限 chmod x kubectl# 安裝到系統路徑 sudo mv kubectl /usr/local/bin/# 驗證安裝 kubectl version --client --short獲取阿里云集群配置文件--手動配置登錄阿里云控制臺進入「容器服務」->「集群」選擇您的集群點擊「連接信息」->「…

C++-linux系統編程 8.進程(二)exec函數族詳解

exec函數族詳解 在Unix/Linux系統中&#xff0c;fork()與exec()函數族是進程控制的黃金組合&#xff1a;fork()創建新進程&#xff0c;exec()則讓新進程執行不同的程序。這種組合是實現shell命令執行、服務器進程動態加載任務等核心功能的基礎。本文將詳細解析exec函數族的原理…

PTL亮燈揀選系統提升倉庫運營效率的方案

隨著電商、零售、制造等行業的快速發展&#xff0c;倉庫的作業效率成為企業競爭力的關鍵因素之一。傳統的揀選方式多依賴人工尋找與確認&#xff0c;不僅耗費時間&#xff0c;還容易出錯&#xff0c;嚴重制約倉庫整體運營效率。為了應對日益增長的訂單需求與提高揀選準確率&…

LVS三種模式實戰

IPVS基本上是一種高效的Layer-4交換機&#xff0c;它提供負載平衡的功能。當一個TCP連接的初始SYN報文到達時&#xff0c;IPVS就選擇一臺服務器&#xff0c;將報文轉發給它。此后通過查看報文的IP和TCP報文頭地址&#xff0c;保證此連接的后繼報文被轉發到相同的服務器。這樣&a…

HCIA第二次綜合實驗:OSPF

HCIA第二次綜合實驗&#xff1a;OSPF一、實驗拓撲二、實驗需求 1、R1-R3為區域0&#xff0c;R3-R4為區域1&#xff1b;其中R3在環回地址在區域1&#xff1b; 2、R1、R2各有一個環回口&#xff1b; 3、R1-R3中&#xff0c;R3為DR設備&#xff0c;沒有BDR&#xff1b; 4、R4環回地…