0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

如何開發(fā)與存儲位置無關(guān)的STM32應(yīng)用?

jf_pJlTbmA9 ? 來源:STM32單片機(jī) ? 作者:STM32單片機(jī) ? 2023-10-18 16:46 ? 次閱讀

1、前言

最近有客戶詢問,能否使用 STM32CubeIDE 在編譯時(shí)通過設(shè)置某個(gè)編譯選項(xiàng),讓STM32 應(yīng)用與存儲位置無關(guān)。這樣的優(yōu)勢是能使同一個(gè)固件被燒在 STM32 Flash 里的不同位置, 而在系統(tǒng) Bootloader 里只需要跳到相應(yīng)的位置就可以正常執(zhí)行固件代碼。客戶希望STM32 代碼從 Flash 里執(zhí)行,不復(fù)制到 RAM 里;客戶希望是一個(gè)完整的映像,而不僅僅是其中某個(gè)函數(shù)做到了位置無關(guān)。

2、分析

嵌入式場景下,不一定有操作系統(tǒng)。即使有操作系統(tǒng),一般也是 RTOS。一般 RTOS沒有一個(gè)通用的程序加載器。因此,存儲位置無關(guān)的需求,在這時(shí)可以說無關(guān)緊要。但是,如果客戶需要進(jìn)行在線固件更新,例如 IoT 應(yīng)用的固件升級,那么位置無關(guān)就存在價(jià)值了。位置無關(guān)之后,對于不同的軟件版本,不需要頻繁的為燒寫位置的反復(fù)改變而修改編譯鏈接腳本。也不需要在代碼里顯式的在兩個(gè) Bank 之間進(jìn)行切換。

最簡單的情況是所有的代碼都復(fù)制到內(nèi)存執(zhí)行。因?yàn)?Flash 的功能只是進(jìn)行存儲,自然對 Flash 的位置沒有任何要求。但大部分 MCU 用戶面臨的真實(shí)案例都是 Flash 比較大,例如 ,1M 字節(jié) ;RAM 比較小,例如,128K 字節(jié)。在這種情況下,代碼在 Flash 原地執(zhí)行就是一個(gè)必須的選擇。Flash 位置改變,會(huì)影響從 Bootloader 跳轉(zhuǎn)之后的固件執(zhí)行時(shí)的 PC 指針,也就是 PC指針值會(huì)發(fā)生相應(yīng)的變化。位置無關(guān)的原理,是讓應(yīng)用程序經(jīng)過編譯后所生成的映像,其中的代碼和數(shù)據(jù),都是基于相對代碼的位置進(jìn)行引用。那么,當(dāng)應(yīng)用被搬到不同位置時(shí),他們的相對位置不變,從而執(zhí)行不受影響。

代碼和數(shù)據(jù)基于絕對地址還是基于相對地址,是由編譯器所決定。以客戶要求的

STM32CubeIDE 編譯工具為例,我們可以看到在[Project]->[Properties]->[C/C++ Build]->[Settings]->[Tool Settings]->[MCU GCC Compiler]->[Miscellaneous]已經(jīng)有一項(xiàng)[Position Independent Code (-fPIC)]。

是否只要選一下-fPIC 選項(xiàng)就大功告成了呢?答案是沒有那么簡單。

wKgaomUD3lSADVZtAAHhWs-yoBo379.png

事實(shí)上,對于完整應(yīng)用程序工程,用戶應(yīng)該經(jīng)過這些步驟將其變成位置無關(guān):? 選擇正確的編譯器選項(xiàng)

? 去掉或者替換掉那些包含絕對位置的庫文件

? 修改代碼中的 Flash 絕對地址(這里以 STM32H7 的 CRC_Example 例程為例,其他情況下有可能要修改更多)

? 在 startup_xxx.s 匯編代碼里的 sidata

? 在 system_xxx.c 里的 SCB->VTOR 以及中斷向量表內(nèi)容

? GOT

對于完整工程,要正確的跳轉(zhuǎn)到應(yīng)用程序進(jìn)行執(zhí)行,還需要由 Bootloader 向應(yīng)用程序提供或者由應(yīng)用程序在鏈接時(shí)自身解析計(jì)算,得到以下信息

? Flash 偏移量

? 中斷向量表的開始以及結(jié)束地址

? GOT 的開始以及結(jié)束地址

我們接下來就舉例說明這些步驟。

3、步驟

3.1. 選擇正確的編譯器選項(xiàng)

如果我們不使用任何編譯選項(xiàng),編出來的代碼會(huì)怎么樣?我們可以通過.list 文件進(jìn)行查看。.list 文件在 STM32 例程中默認(rèn)生成,如果沒有請勾選如下選項(xiàng), 在 [Project]->[Properties]->[C/C++ Build]->[Settings]->[Tool Settings]->[MCU Post Build outputs]->[Generate list file],可參考下圖。

wKgaomUD3lWAWakXAAKEtQtlrCQ438.png

wKgZomUD3laACDnvAAGBeiK3aVk710.png

wKgaomUD3lqAXWz1AAKAs0Qsbt0691.png

我們看到代碼中直接使用了變量的絕對地址,例如 0x2000 0028。我們不要被 literal pool 文字池的使用所迷惑,那個(gè)基于 PC 的操作只是為了取變量的絕對地址,例如, 0x2000 0028,并沒有將絕對地址變成相對地址。

當(dāng)然大家說這里是 RAM 地址,沒有關(guān)系。我們選擇這個(gè)函數(shù)來說明,是因?yàn)槲恢脽o關(guān)的編譯器選項(xiàng)是不區(qū)分 RAM 還是 Flash 里的變量,而這個(gè)函數(shù)最簡單容易理解。如果我們查看另外一個(gè)復(fù)雜一點(diǎn)的函數(shù),例如,HAL_RCC_ClockConfig,我們可以看到以下對Flash 里變量的直接使用。這就不妙了,因?yàn)橐坏└淖兞?Flash 下載的位置,在絕對地址處就取不出變量的真實(shí)內(nèi)容了。

wKgZomUD3luAAlWbAAD3u1Xo1rk587.png

我們沒有辦法一個(gè)一個(gè)查找修改所有的變量。當(dāng)然這里的變量是指全局變量。如果要修改,我們希望編譯器能把他們集中在一起。對于此,編譯器提供了多個(gè)編譯選項(xiàng)。例如,PIC 是位置無關(guān)代碼, PIE 是位置無關(guān)執(zhí)行。PIC 和 PIE 這兩者類似,但是存在一個(gè)顯著的差異是 PIE 會(huì)對部分全局變量優(yōu)化。我們可以觀察到用兩種不同編譯選項(xiàng)的效果。

wKgZomUD3l2AV-3wAAIVOBzXdrI619.png

其中 80004C0 地址處包含的是 GOT 自身的偏移量,存在 r2 里,要在兩次取全局變量 uwTickFreq 和 uwTick 時(shí)引用。GCC 編譯器引入 GOT 全局偏移量表來解決全局變量的絕對地址的問題。在之前對絕對地址的直接使用,現(xiàn)在被轉(zhuǎn)化成先取得 GOT 入口相對于 PC 的偏移,再獲得實(shí)際變量相對于 GOT 入口的偏移,從而得到實(shí)際變量的地址。計(jì)算公式如下:

實(shí)際變量的絕對地址=PC + GOT 相對于 PC 的偏移 + 變量地址相對于 GOT 的偏移

GOT 只有一個(gè),如果代碼放在不同的位置,代碼自身就可以根據(jù) Bootloader 傳遞過來的信息,或者自行計(jì)算來對 GOT 進(jìn)行更新。這樣變量的地址就和新的 Flash 偏移相匹配。

wKgaomUD3l6ASaVqAAGsxHxWOhE588.png

這里可以看到 80004c0 對應(yīng)的 uwTick(可以從 str 指令結(jié)合 C 語言源代碼快速知道它對應(yīng)于 uwTick)不再使用 GOT 偏移,而是相對于 PC 的偏移(與前文相比,多了一條指令 “add r3,pc”)。換句話說,PIE 對局部的全局變量做了優(yōu)化。這個(gè)優(yōu)化顯然不是我們所需要的。因?yàn)槿绱艘詠?,RAM 變量的地址就會(huì)隨著 PC 的不同而不同。而我們則希望所有對RAM 的用法不發(fā)生變化。

為了能夠修改 GOT 內(nèi)容,我們選擇將 GOT 最終存放在 RAM 中,導(dǎo)致代碼中對 GOT的尋址也是使用了相對于 PC 的偏移。而因?yàn)?RAM 有限,或者因?yàn)闆]有虛擬內(nèi)存的緣故,我們不希望 RAM 的用法有所不同,否則,可能代價(jià)很大。這時(shí),一旦 Flash 代碼位置發(fā)生變化引起 PC 指針變化,GOT 就無法找到。因此,即使我們不使用 PIE,PIC 也沒有辦法單獨(dú)使用。為了確保沒有任何存放在 RAM 里的變量的位置是相對于 PC 的偏移。我們應(yīng)該使用如下所有編譯選項(xiàng),single-pic-base 讓系統(tǒng)只使用一個(gè) PIC 基址,就是下文反匯編中看到r9;no-pic-data-is-text-relative 則讓編譯器不要讓任何變量相對于 PC 尋址。

wKgZomUD3l-AcZmMAABNbCX6skw395.jpg

wKgaomUD3mGAJas8AAJQ-dqeDu0708.png

這樣實(shí)際變量的絕對地址,就變成實(shí)際變量的絕對地址=PIC 基址 + GOT 相對于 PIC 基址的偏移 + 變量地址相對于 GOT的偏移使用以上編譯選項(xiàng),這樣我們看到 HAL_IncTick 就如下所示:

wKgZomUD3mKANupCAAJjO9rL3s0694.png

這樣所有在 RAM 里的全局變量都是相對于 GOT 的偏移。注意,這個(gè)時(shí)候你編譯出來的代碼現(xiàn)在沒有辦法進(jìn)行測試,盡管你只是改了編譯選項(xiàng)。這是因?yàn)?PIC 的基址需要你通過寄存器 r9 顯式指定。在本例中,我們在鏈接腳本里如下定義 GOT 的位置:

wKgaomUD3mOAaHmpAADFAKrZWr4610.png

因此,我們可以很容易的從.map 文件中獲得 GOT_START 的 RAM 地址,0x2000 0000,它就是 PIC 的基址。如果想測試編譯器選項(xiàng)是否如我們所期望,我們可以在Reset_Handler 開始部分加上如下語句(參考后文內(nèi)存布局的代碼):

wKgaomUD3miAJtIpAAAGgchzc-E288.jpg

經(jīng)過測試,我們可以確信,編譯器選項(xiàng)的改動(dòng)對我們最終執(zhí)行結(jié)果沒有影響。

值得注意的是,STM32 用戶的代碼,例如 RTOS 的移植, 也可能使用寄存器 r9。在這種情況,用戶應(yīng)當(dāng)解決沖突。一般情況寄存器 r9 對應(yīng)用程序并不是必要的。

3.2. 去掉或者替換掉那些包含絕對位置的庫文件

我們要將位置無關(guān)的庫去掉或者替換掉。在 STM32 參考代碼里,我們需要

startup_xxx.s 里 C 庫調(diào)用去掉。示例如下:

wKgZomUD3mmAeTqiAAAkL5LfISg206.png

3.3. 修改 Flash 絕對地址

3.3.1. 內(nèi)存布局

如果要對代碼中的 Flash 絕對地址進(jìn)行修改,我們需要知道存放 Flash 絕對地址的 RAM起始和結(jié)束地址,以及需要增加或減少的 Flash 偏移量。存放 Flash 絕對地址的 RAM 起始和結(jié)束地址,在編譯時(shí)可以讓應(yīng)用代碼本身借助自身鏈接腳本在鏈接時(shí)導(dǎo)出的變量得到,然后由應(yīng)用程序在運(yùn)行時(shí)存放在 RAM 中的固定位置;也可以在編譯后從.map 文件或使用工具解析 elf 文件獲得,然后作為應(yīng)用程序一部分的元信息,例如,給應(yīng)用程序加個(gè)頭部存放元信息,由 Bootloader 下載并解析,將其放入到 RAM 固定位置。

我們規(guī)劃在一段 RAM 里按如下順序存放如下元信息,它可以是應(yīng)用程序本身在最初階段自我存放在這里,也可以簡單的由 Bootloader 解析元信息后,跳轉(zhuǎn)到應(yīng)用程序之前就存放在這里。

wKgZomUD3muAZJo4AABGEtGo0Lc439.png

我們在前文已經(jīng)在鏈接腳本中定義了 GOT_START 和 GOT_END,我們還需要在鏈接腳本中定義 VT_START 和 VT_END。如下圖所示:

wKgaomUD3myAdpvZAAAg2hsiboM270.jpg

如果我們希望 Bootloader 僅僅是做簡單的跳轉(zhuǎn),我們可以將規(guī)劃這段內(nèi)存的工作,交給應(yīng)用程序的初始化部分(在 “l(fā)dr sp, =_estack”之前)。假定 0x0 處對應(yīng)為 0x2400 0000,參考代碼如下:

wKgZomUD3m6AWAK7AAKiHtOdcJw677.png

3.3.2. 匯編代碼

3.3.2.1. _sidata

在默認(rèn)的 STM32 工程中,還有一些對變量絕對地址的使用。在 startup_xxx.s 有許多地方使用絕對地址,它們不能被編譯器收集到 GOT 中。其中,默認(rèn)在鏈接腳本里的_sidata,標(biāo)志 flash 里 RAM 數(shù)據(jù)區(qū)的 Flash 位置,需要修改。

wKgaomUD3nCATtZ7AALWnRds6WE206.png

注意,變量絕對地址本身不是個(gè)問題,而對它解應(yīng)用,取它的內(nèi)容才會(huì)發(fā)生錯(cuò)誤。而這里的 _sidata 是要被初始化代碼使用,目的是將 Flash 的內(nèi)容搬移到 RAM 里。我們顯然要對_sidata 進(jìn)行修改,否則無法取得正確的內(nèi)容到 RAM 里。

根據(jù)前文的內(nèi)存布局,我們可以把 Flash 的偏移量從內(nèi)存中放置在寄存器 r8 里,例如:

wKgZomUD3nGAHLD8AAAXuYIqLds005.png

則我們只需要一行簡單的代碼 “add r3,r8” 就可以修正_sidata 的地址。

wKgaomUD3nKAbBoXAABP5WZRdYA366.png

3.3.3. C 代碼

3.3.3.1. 公共函數(shù)

如果一段內(nèi)存的數(shù)據(jù)都是硬編碼,我們只需要一個(gè)公共函數(shù)就可以對其循環(huán)進(jìn)行修正。我們需要知道什么樣的地址之外不是 Flash 地址,那么就對這樣的值不做修改。例如,我們定義 0x1fff ffff 之外的就不是 Falsh 地址,相應(yīng)的宏定義如下:

wKgZomUD3nWAUmcjAAD3Ze_GJ-E268.png

3.3.3.2. SCB->VTOR

在 C 語言中如果使用賦值語句進(jìn)行硬編碼,編譯器也無法進(jìn)行收集。例如在

system_stm32xxxx.c 中的 SystemInit 有如下語句:

wKgaomUD3neANBzbAAFwARvWczI524.png

中斷向量表相關(guān)的內(nèi)容需要修改,包括兩部分:

? 中斷向量表的內(nèi)存位置

? 中斷向量表的內(nèi)容

我們應(yīng)該將中斷向量表復(fù)制到 RAM 里,通過 UpdateOffset 函數(shù)修正其中包含的所有Flash 絕對地址的值,同時(shí)通過對 SCB->VTOR 賦值來將中斷向量表的位置指向我們修改過內(nèi)容的 RAM 地址。注意,VTOR 所指向的地址 VT_RAM_START 要按照 ARM 要求,根據(jù)中斷總大小向上進(jìn)行 2 的冪次對齊,例如,37 個(gè)字大小要使用 64 個(gè)字對齊。另外,中斷向量表的內(nèi)容,也包含有 RAM 地址,對此,我們并不需要修改。當(dāng)然,UpdateOffset 函數(shù)已

經(jīng)考慮到這一點(diǎn),所以我們可以直接使用它。更新中斷向量表以及 VTOR 的參考代碼如下:

wKgZomUD3niASpV2AAISQHDL-XM141.png

3.3.3.3. GOT

編譯器已經(jīng)將 C 語言中所有全局變量的地址都收集到 GOT 中,因此我們很容易對其Flash 地址的內(nèi)容進(jìn)行修正,參考代碼如下:

wKgaomUD3nqAdnHjAAEhMu-UQ4M197.png

4、總結(jié)

除非你僅僅是運(yùn)行一小塊代碼,否則開發(fā)位置無關(guān)的 STM32 完整工程,不僅僅要設(shè)置正確的編譯器選項(xiàng),還要保證它所鏈接的預(yù)編譯的庫不含有絕對地址引用,要保證所有源代碼里沒有對絕對地址的硬編碼,包括修改 data 區(qū)的 Flash 起始地址,中斷向量表的內(nèi)容與位置,以及 GOT 的內(nèi)容。

來源:STM32單片機(jī)
免責(zé)聲明:本文為轉(zhuǎn)載文章,轉(zhuǎn)載此文目的在于傳遞更多信息,版權(quán)歸原作者所有。本文所用視頻、圖片、文字如涉及作品版權(quán)問題,請聯(lián)系小編進(jìn)行處理

審核編輯 黃宇

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • 存儲
    +關(guān)注

    關(guān)注

    13

    文章

    4226

    瀏覽量

    85577
  • RAM
    RAM
    +關(guān)注

    關(guān)注

    8

    文章

    1354

    瀏覽量

    114444
  • STM32
    +關(guān)注

    關(guān)注

    2264

    文章

    10854

    瀏覽量

    354297
收藏 人收藏

    評論

    相關(guān)推薦

    STM32應(yīng)用與存儲位置無關(guān)

    最近有客戶詢問,能否使用 STM32CubeIDE 在編譯時(shí)通過設(shè)置某個(gè)編譯選項(xiàng),讓STM32 應(yīng)用與存儲位置無關(guān)。這樣的優(yōu)勢是能使同一個(gè)固件被燒在
    發(fā)表于 09-05 11:43 ?780次閱讀

    win10 IE瀏覽器無法更改臨時(shí)文件夾存儲位置

    臨時(shí)文件修改不了存儲位置的問題。這是怎么回事呢?接下來,小編就給大家介紹下win10系統(tǒng)下無法更改ie臨時(shí)文件位置的解決方案。具體方法如下: 1、每次用ie修改,注銷后還是沒有改變。2、可以通過修改注冊表
    發(fā)表于 03-08 13:46

    Keil C51 使用C語言編寫程序,怎么設(shè)置程序的起始存儲位置從0x1000開始

    使用STC 的IAP系列單片機(jī),打算自己寫一段更新程序。求教C語言編寫的話要如何設(shè)置程序的起始存儲位置,該段程序準(zhǔn)備存儲在以0x1000起始的連續(xù)的地址上。匯編中使用ORG命令就可以了,用C語言編寫的話沒有頭緒了,求助。
    發(fā)表于 02-09 17:27

    位置無關(guān)的代碼

    ) }--------------------------------------------這里把代碼段等鏈接到外存地址,那么確實(shí)在引導(dǎo)代碼里要注意“位置無關(guān)的代碼”問題,個(gè)人查了一些資料,只是提到bl,adr等相對pc的一些指令用法 .rodata ALIGN(4
    發(fā)表于 06-17 05:45

    【求助】指定變量在各個(gè)片上存儲區(qū)域的存儲位置時(shí)出現(xiàn)...

    和L2中,解算結(jié)果就是正確的。 請問這種情況是因?yàn)樵赾md文件中已經(jīng)指定了已初始化/未初始化全局變量的存儲位置在DDR2中導(dǎo)致的嗎?或者是因?yàn)槎嘧兞靠?b class='flag-5'>存儲區(qū)域讀寫導(dǎo)致出現(xiàn)不可控的問題?如果不是,可能是哪里的問題? 請工程師撥冗解答,謝謝!
    發(fā)表于 05-13 06:40

    C語言中局部變量的存儲位置是如何分配的?

    ADS下C語言中局部變量的存儲位置是如何分配的?
    發(fā)表于 04-26 06:31

    請問存儲位置的內(nèi)容如何固話到emmc中呢?

    接下來我該將該存儲位置的內(nèi)容如何固話到emmc中呢?應(yīng)該會(huì)有個(gè)mmc write的操作,但是我在文檔中沒找到,所以不知道是要寫到那個(gè)地址下,看到uboot env環(huán)境參數(shù)中有一段操作
    發(fā)表于 01-11 07:52

    存儲位元與存儲單元是什么含義

    存儲位元與存儲單元是什么含義?數(shù)據(jù)通信的方式可以分為哪幾種呢?
    發(fā)表于 01-21 07:17

    STM32+cubeMX點(diǎn)亮LED的方法

    STM32+cubeMX第一個(gè)工程,點(diǎn)亮LED打開cubeMX選擇new project創(chuàng)建一個(gè)stm32工程,芯片選擇stm32f103c8t6設(shè)置HSE時(shí)鐘來源為外部晶振設(shè)置系統(tǒng)Debug接口為串口設(shè)置時(shí)鐘樹設(shè)置工程名,
    發(fā)表于 01-27 08:26

    ARM的位置無關(guān)程序設(shè)計(jì)在Bootloader中的應(yīng)用

    ARM的位置無關(guān)程序設(shè)計(jì)在Bootloader中的應(yīng)用 ARM處理器支持位置無關(guān)的程序設(shè)計(jì),這種程序加載到存儲器的任意地址空間都可以正常運(yùn)
    發(fā)表于 03-29 15:12 ?1224次閱讀

    ARM處理器的位置無關(guān)程序設(shè)計(jì)

    ARM處理器支持位置無關(guān)的程序設(shè)計(jì),這種程序加載到存儲器的任意地址空間都可以正常運(yùn)行,其設(shè)計(jì)方法在嵌入式應(yīng)用系統(tǒng)開發(fā)中具有重要的作用。尤其在裸機(jī)狀態(tài)下
    發(fā)表于 09-22 17:03 ?1027次閱讀

    ARM處理器位置無關(guān)的程序設(shè)計(jì)方案解析

    ARM處理器支持位置無關(guān)的程序設(shè)計(jì),這種程序加載到存儲器的任意地址空間都可以正常運(yùn)行,其設(shè)計(jì)方法在嵌入式應(yīng)用系統(tǒng)開發(fā)中具有重要的作用。尤其在裸機(jī)狀態(tài)下
    發(fā)表于 10-27 13:00 ?4次下載

    ARM處理器的位置無關(guān)程序設(shè)計(jì)

    ARM處理器支持位置無關(guān)的程序設(shè)計(jì),這種程序加載到存儲器的任意地址空間都可以正常運(yùn)行,其設(shè)計(jì)方法在嵌入式應(yīng)用系統(tǒng)開發(fā)中具有重要的作用。尤其在裸機(jī)狀態(tài)下
    發(fā)表于 12-01 01:16 ?547次閱讀

    STM32 內(nèi)存分配解析及變量的存儲位置

    單元的。因此在一些嵌入式系統(tǒng)中,比如常用的STM32來講,內(nèi)存映射被劃分為閃存段(也被稱為Flash,用于存儲代碼和只讀數(shù)據(jù))和RAM段,用于存儲讀寫數(shù)據(jù)。STM32 的 Flash
    發(fā)表于 11-26 18:51 ?49次下載
    <b class='flag-5'>STM32</b> 內(nèi)存分配解析及變量的<b class='flag-5'>存儲位置</b>

    如何開發(fā)位置無關(guān)STM32 完整工程

    最近有客戶詢問,能否使用 STM32CubeIDE 在編譯時(shí)通過設(shè)置某個(gè)編譯選項(xiàng),讓STM32 應(yīng)用與存儲位置無關(guān)。
    的頭像 發(fā)表于 09-15 09:59 ?1582次閱讀