《APM32芯得》系列內(nèi)容為用戶使用APM32系列產(chǎn)品的經(jīng)驗總結(jié),均轉(zhuǎn)載自21ic論壇極海半導體專區(qū),全文未作任何修改,未經(jīng)原文作者授權(quán)禁止轉(zhuǎn)載。
在有些情況下,我們想要把代碼放到SDRAM運行。下面介紹在APM32的MCU中,如何把代碼重定位到SDRAM運行。對于不同APM32系列的MCU,方法都是一樣的。
1、APM32啟動模式
熟悉MCU都知道,可以通過配置 BOOT0/1 兩個引腳的高低電平,選擇不同的啟動方式。對于APM32來說也是一樣的,可以配置 BOOT0/1 引腳電平選擇不同的啟動模式,下表是我從APM32的用戶手冊截圖的啟動模式配置表:

有內(nèi)置SRAM、Flash、系統(tǒng)存儲區(qū)啟動3種模式。對于讓程序重定位到SDRAM運行,我們選擇Flash啟動即可。
不同的啟動模式,本質(zhì)其實就是MCU上電時,從哪個地址處讀取出數(shù)據(jù)然后賦值給PC寄存器,以及SP寄存器。比如選擇從Flash啟動,MCU上電時,0x08000000處起始的4個字節(jié)的數(shù)據(jù),賦值給SP寄存器,08000004處起始的4個字節(jié)的數(shù)據(jù)賦值給PC寄存器。設(shè)置了SP和PC寄存器,程序就可以正常運行了。
MCU上電,PC寄存器從哪里開始取值,和后面要講的把代碼搬運到SDRAM運行,是有關(guān)聯(lián)的,所以這里提一下APM32的啟動模式。
2、程序段概念的引入
一個程序的源碼被編譯之后,鏈接器會根據(jù)代碼中的不同屬性,把他們劃分為一個個不同的段,比如 .text段、.rodata段、.data段、.bss/.zi段等等,還有用戶也可以自定義一些段,比如把所有初始化的代碼,自定義一個初始化段。
.text段:代碼段或者文本段。我們編寫的代碼,鏈接器都是歸類在這個段的。對于MCU來說就是存放在內(nèi)部的Flash中。
.rodata段:只讀數(shù)據(jù)段。比如我們使用const定義的變量,或者定義的字符串這些,都被鏈接到只讀數(shù)據(jù)段。只讀數(shù)據(jù)段和代碼段,都是只能讀不能寫,所以都是存放在Flash中的。
.data段:可讀可寫的數(shù)據(jù)段。我們定義的初始化為非0的全局變量、非0的靜態(tài)局部變量,都是存放在這個段的。
.bss/.zi段:.bss段或者.zi段,都是同一個段,只是叫法不一樣。.bss段和.data段是一樣的,都是可讀可寫的數(shù)據(jù)段的一種。.bss段存放的就是初始值為0的全局變量或者初始值為0的靜態(tài)局部變量。
既然.data段和.bss段存放的內(nèi)容基本一樣,為什么要把這兩個段分開存放?這是因為.bss段的初值是0,不需要燒錄到Flash里面存放,在程序使用之前,我們把.bss段的對應(yīng)區(qū)域給清0就行了,這樣不需要浪費Flash空間。
堆:一段空閑的內(nèi)存空間,可以給程序員自由使用??梢酝ㄟ^一些內(nèi)存管理接口函數(shù)進行申請和釋放。
棧:也是一塊內(nèi)存空間,不過程序自行管理。C語言的運行需要棧,MCU上電時就需要把SP(棧)寄存器指向一片正??捎玫腞AM作為棧來使用。
用戶如果有需要,也可以自定義自己的段,然后鏈接器會根據(jù)用戶的要求,把指定的代碼編譯到自定義的段。
我們要把代碼重定位到SDRAM運行,本質(zhì)就是復(fù)制這些段的數(shù)據(jù),把這些數(shù)據(jù)復(fù)制到它們應(yīng)該位于的地方(代碼應(yīng)該要位于鏈接地址處運行)。
3. keil散列文件語法分析
前面提到,代碼的重定位,本質(zhì)就是數(shù)據(jù)的復(fù)制。數(shù)據(jù)的復(fù)制有3個要點要知道:從那里復(fù)制(源地址)、復(fù)制到哪里去(目的地址)、復(fù)制多長(長度)。
源地址:我們要從哪里開始復(fù)制數(shù)據(jù)呢?其實就是加載地址,我們程序燒錄到哪個位置,那就是加載地址。比如程序燒寫到內(nèi)部的Flash中,那么加載地址就是0x08000000 。程序存放到外部的SPI Flash中,加載地址就是存放在外部SPI Flash的地址。
目的地址:要把程序復(fù)制到這里的地址,就是程序的鏈接地址,程序的運行就是要位于它的鏈接地址處運行(當然如果寫的所有代碼都是位置無關(guān)碼,那么可以不在鏈接地址處運行)。
長度:復(fù)制多長。
上面提到的這些復(fù)制數(shù)據(jù)所需的信息,所有的這一切都可以從鏈接腳本中獲取到,它是用來指導鏈接器如何進行鏈接的一種規(guī)則文件。對于Keil來說,鏈接腳本指的就是散列文件。
3.1 Keil默認的散列文件示例
下面的代碼是keil自動生成的APM32F407ZG型號的散列文件:
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x08000000 0x00100000 { ; load region size_region
ER_IROM1 0x08000000 0x00100000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x20000000 0x00020000 { ; RW data
.ANY (+RW +ZI)
}
}
一個散列文件,是由多個加載域、執(zhí)行域和輸入段所組成??梢酝ㄟ^Keil幫助文檔獲得這些內(nèi)容的講解。
3.2 散列文件語法
上面的示例,每個數(shù)據(jù)、符號代表什么意義?這些內(nèi)容我們可以通過Keil的幫助文檔學習到,安裝了Keil軟件,就可以獲取到幫助文檔信息了。

打開鏈接相關(guān)的文檔,找到散列文件的語法介紹。
下圖就是幫助文檔關(guān)于散列文件的組成結(jié)構(gòu):

根據(jù)上圖:一個散列文件可以一個或多個加載域,每個加載域又可以包含多個可執(zhí)行域,而可執(zhí)行域是由各個段(如代碼段、數(shù)據(jù)段等)組成的。
3.2.1加載域語法
load_region_description ::=
load_region_name ( base_address | ("+" offset )) [ attribute_list ] [ max_size ]
"{"
execution_region_description +
"}"
load_region_name就是加載域的名稱,base_address加載域的基地址,加載域的長度(就是整個程序燒錄的大?。?。然后加載域里面包含一個或多個可執(zhí)行域。
以上面的示例為例:
LR_IROM1 0x08000000 0x00100000 { ; load region size_region
}
加載域名稱就是 LR_IROM1,起始地址0x08000000,長度0x00100000。
3.2.2 可執(zhí)行域語法
execution_region_description ::=exec_region_name ( base_address | "+" offset ) [ attribute_list ] [ max_size | length ]"{"input_section_description *"}"
和加載域的描述也是類似的。然后可執(zhí)行域里面就是由各個段組成的。
3.2.3 輸入段

前面是選擇代碼的哪些區(qū)域空間鏈接進這個段,后面是段的名字。
比如:main.o(+RO) 就是說main.o文件鏈接到RO段。
常見段類型的解析:
*.o (RESET, +First) :指的是所有的.o文件的RESET段,+First就是要求RESET段要鏈接到程序最開始的地方。
*(InRoot$$Sections):鏈接器去鏈接Keil自帶的一部分代碼。這部分代碼的作用主要是數(shù)據(jù)段的重定位和清除bss段
.ANY (+RO):.ANY作用和 * 一樣,指的是所有的意思,但是優(yōu)先級比 * 低。這里說是把所有文件的RO段(Read Only段)放到這里。
.ANY (+XO):所有的 execute-only 段。
3.3如何通過散列文件獲取源、目的、長度
我們學習散列文件的目的就是為了得到代碼重定位的源(加載地址)、目的(鏈接地址)、和長度。那么我們?nèi)绾瓮ㄟ^散列文件獲取這些信息?
Keil的鏈接器定義了各種符號,通過這些符號我們可以獲取到這些信息。
3.3.1 可執(zhí)行域區(qū)域信息

通過可執(zhí)行域的這些符號,我們就知道把代碼復(fù)制到哪里去了。
3.3.2 加載域區(qū)域信息

通過加載域的這些信息,可以獲取到各個段(代碼段、數(shù)據(jù)段、bss段)的起始地址,和長度信息。
4. 代碼重定位到SDRAM的方法
我們?yōu)槭裁匆汛a重定位到SDRAM運行?
一般有兩種情況:
內(nèi)部Flash空間不足,不能存放下所有的代碼。這個時候我們就只能把編譯得到的bin文件燒錄到外部的存儲設(shè)備了,比如SPI Flash等。但是SPI Flash根本就不能運行代碼,所以MCU上電后就需要把SPI Flash的bin文件,搬運到SDRAM或者其他可運行代碼的存儲設(shè)備。
為了得到更快的執(zhí)行速率,這個時候我們可以把代碼搬運到SRAM或者SDRAM執(zhí)行。(但是對于MCU來說,我不確定是內(nèi)部的Flash執(zhí)行代碼更快還是SDRAM更快)
對于第一種情況,是很常用的。比如嵌入式Linux的設(shè)備,就是這種啟動方式,內(nèi)核鏡像存儲在外部EMMC、SD卡等這種大容量設(shè)備中,但是他們都無法執(zhí)行代碼,所以上電后會把內(nèi)核鏡像搬運到內(nèi)存中運行。
根據(jù)這兩種情況,我們可以有兩種方法把代碼搬運到SDRAM運行。
應(yīng)用程序自己復(fù)制自己
通過Bootloader程序,復(fù)制應(yīng)用程序
4.1 程序自己復(fù)制自己
當整個應(yīng)用程序都燒寫在MCU的內(nèi)部Flash時,這個時候我們可以使用這種方法,讓應(yīng)用程序自己復(fù)制自己到內(nèi)存中,比如SDRAM運行。
當然在這種情況中,我好像想不到把程序復(fù)制到SDRAM運行的意義。是為了獲得更快的代碼運行速度?但是我也不確定程序在內(nèi)部Flash運行更快還是SDRAM運行更快?
可能唯一的意義可以就是可以學習到代碼重定位相關(guān)的知識了吧......
廢話少說,程序它為什么可以自己復(fù)制自己?
我們前面介紹過,程序運行應(yīng)該位于它的鏈接地址上,但是當這個程序的所有代碼都是位置無關(guān)碼的時候,它就可以不在鏈接地址上運行。位置無關(guān)碼就是這段代碼可以在任何地址上正常運行,它與執(zhí)行的位置無關(guān)。
位置無關(guān)碼編寫要求:
匯編指令,不能使用絕對跳轉(zhuǎn)。比如對PC指針賦值跳轉(zhuǎn),或者使用跳轉(zhuǎn)指令,跳轉(zhuǎn)到某個地址值。比如下面這些就是絕對跳轉(zhuǎn)
; 下面這種跳轉(zhuǎn)方式就是絕對跳轉(zhuǎn)
ldr PC, =main
LDR R0, =SystemInit
BLX R0
; BL指令是相對跳轉(zhuǎn)指令
BL main
C言語,不能使用函數(shù)指針調(diào)用函數(shù)。因為函數(shù)指針調(diào)用方式就是指向一個確定的地址值,而這個地址是鏈接時分配的地址,代碼沒有在鏈接地址處運行的時候,跳轉(zhuǎn)過去程序只能崩潰
對于訪問數(shù)據(jù)的話,不要去訪問全局變量、靜態(tài)局部變量、字符串。
我們把應(yīng)用程序燒寫到MCU內(nèi)部的Flash時,可以在應(yīng)用程序的最前面一部分代碼, 放置重定位相關(guān)的代碼,這重定位相關(guān)的代碼編寫要求就是必須使用位置無關(guān)碼編寫。
因為當我們要把程序放到SDRAM運行時,就需要修改程序的鏈接地址指向SDRAM的內(nèi)存空間,而MCU剛上電,是從Flash的空間開始取指令運行的,所以Flash最開始的那一部分代碼必須是位置無關(guān)碼,否則就無法正常運行。
4.2 Bootloader復(fù)制應(yīng)用程序
當應(yīng)用程序太大,無法燒錄到內(nèi)部Flash時,就只能燒寫到外部Flash。
這個時候,就可以編寫一個簡單的程序(當然你也可以做得很復(fù)雜,做到適配各種外部存儲設(shè)備,各種協(xié)議什么的),它的主要作用就是復(fù)制應(yīng)用程序到鏈接地址處運行。
然后把這個程序燒寫到內(nèi)部的Flash上,MCU上電,可以先運行這段代碼,接著把外部的應(yīng)用程序復(fù)制到內(nèi)存(SDRAM)運行,復(fù)制完成之后再跳轉(zhuǎn)到內(nèi)存運行即可。這個程序通常被叫做Bootloader。
嵌入式Linux設(shè)備就是使用這種方式啟動的,當然啟動過程比這里說的還要復(fù)雜,但是總體啟動過程類似。
5. 代碼重定位到SDRAM運行的過程
前面講了很多內(nèi)容,大家可以不用看,只看最后這章,看看代碼怎么寫就行了。重定位的代碼其實也很簡單,本質(zhì)就是復(fù)制數(shù)據(jù),而復(fù)制數(shù)據(jù)調(diào)用一個memcpy函數(shù)就足夠了。
上面介紹了兩種代碼重定位到SDRAM運行的方法,下面我只講第一種方法。其實大家也可以把前面這部分重定位的代碼看作是Bootloader程序,只不過它比較簡單,和應(yīng)用程序鏈接在一起了。
重定位的這部分代碼編寫流程:
初始化系統(tǒng)時鐘
初始化SDRAM,因為訪問SDRAM需要設(shè)置SDRAM的時序參數(shù)
.text段重定位
.data段重定位
.bss/zi段清零
重新設(shè)置中斷向量表寄存器的基地址
絕對跳轉(zhuǎn)到SDRAM運行
然后我的開發(fā)板使用的是APM32F407ZG型號,下面的代碼是適配這個型號的。當然APM32系列的其他型號,重定位的思路方法都是一樣的,參考著來就行。
5.1 修改散列文件
我們的目的是要把程序放到SDRAM運行,所以我們必須修改鏈接地址在SDRAM的內(nèi)存空間,讓鏈接器根據(jù)這段內(nèi)存空間分配地址。而修改鏈接地址,對于Keil來說就要修改散列文件。
我使用的芯片型號是APM32F407ZG,而且外面接的SDRAM的起始地址是0x60000000,大小一共是2MB。所以修改出來的散列文件如下:
LR_IROM1 0x08000000 0x00100000 { ; load region size_region
ER_IROM1 0x60000000 0x00200000 { ; load address = execution address
*.o (RESET, +First)
;*(InRoot$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x20000000 0x00020000 { ; RW data
.ANY (+RW +ZI)
}
ARM_LIB_HEAP +0 EMPTY 0x0200 { ; Heap region growing up
}
ARM_LIB_STACK +0 EMPTY 0x0400 { ; Stack region growing down
}
}
加載域的地址不變,因為我們還是要下載到Flash中存儲程序的。但是執(zhí)行域修改為了SDRAM的起始地址和大小。然后可讀可寫的數(shù)據(jù)域地址和大小沒改,依舊是使用MCU內(nèi)部的SRAM作為數(shù)據(jù)存儲區(qū)。
但是下面我新增了兩個執(zhí)行域,那就是堆和棧,這兩個域的地址是緊接著 RW_IRAM1域進行編排地址的,大小分別是 0x200 和 0x400。為什么要加上這兩個域下面講解。
5.2 初始化系統(tǒng)時鐘和SDRAM
初始化時鐘相關(guān)的代碼,直接調(diào)用APM32 SDK提供的SystemInit函數(shù)即可,但是要更改一下調(diào)用方式為相對跳轉(zhuǎn)。
我們是要把代碼從MCU內(nèi)部的Flash復(fù)制到SDRAM運行,那么復(fù)制之前,必須能正確的讀寫SDRAM才能正常復(fù)制,而SDRAM的讀寫需要先初始化它的時序參數(shù)才行。
我的板子使用的SDRAM型號是:EM638165TS-7IG,然后關(guān)于這個SDRAM型號的初始化代碼,可以從官方的 APM32F4xx_EVAL_SDK_V1.0 這個SDK中,稍微修改下就可以拿過來使用了。
然后我們在匯編代碼的 Reset_Handler 函數(shù)(標號)中調(diào)用這幾個函數(shù):
BL SystemInit
BL SDRAM_GPIOConfig
BL SDRAM_Init
5.3 .text段重定位
各個段的重定位,就是數(shù)據(jù)的復(fù)制。我們必須要知道,源、目的、長度。而這些信息,都在散列文件中獲取到。前面花了很大篇幅講了散列文件作用就在這里。
代碼如下:
IMPORT |Image$ER_IROM1$Base|
IMPORT |Image$ER_IROM1$Length|
IMPORT |Load$ER_IROM1$Base|
; relocate text section
LDR R0, = |Image$ER_IROM1$Base| ; destination
LDR R1, = |Load$ER_IROM1$Base| ; source
LDR R2, = |Image$ER_IROM1$Length| ; lenth
BL mymemcpy
ER_IROM1 就段名,這個段就是 .text 代碼段的意思。
|Load$$ER_IROM1$$Base| :.text段加載地址,就是復(fù)制數(shù)據(jù)的源地址
|Image$$ER_IROM1$$Base| :.text段執(zhí)行域地址,也就是鏈接地址,就是復(fù)制數(shù)據(jù)的目的地址
|Image$$ER_IROM1$$Length| :.text段的長度
我們通過上面那幾個奇奇怪怪的符號,就可以獲得.text段的加載地址,鏈接地址,和長度了。知道這些信息,然后復(fù)制數(shù)據(jù)調(diào)用一個memcpy就可以把Flash的.text段復(fù)制到SDRAM了。
5.4 .data段重定位
.data段,存放的就是各種初值不是0的全局變量、靜態(tài)局部變量,我們也需要把這部分數(shù)據(jù)的初始值,從Flash空間中復(fù)制到對應(yīng)的內(nèi)存中去。
我們在散列文件,數(shù)據(jù)段的鏈接地址是設(shè)置在0x20000000地址處的。
重定位代碼如下:
IMPORT |Image$RW_IRAM1$Base|
IMPORT |Image$RW_IRAM1$Length|
IMPORT |Load$RW_IRAM1$Base|
; relocate data section
LDR R0, = |Image$RW_IRAM1$Base| ; destination
LDR R1, = |Load$RW_IRAM1$Base| ; source
LDR R2, = |Image$RW_IRAM1$Length| ; lenth
BL mymemcpy
代碼基本和.text段重定位差不多的,就是那幾個符號不一樣,是獲取.data段的加載地址、鏈接地址和長度的符號。
5.5 .bss/zi段清零
.bss/zi段 是初始值為0的全局變量和靜態(tài)局部變量存放的地址,但是這個段的內(nèi)容不會存放在Flash中,因為初值為0,我們在使用之前把這個段的內(nèi)存空間清0即可,無需浪費空間把編譯進bin文件里面。
IMPORT |Image$RW_IRAM1$ZI$Base|
IMPORT |Image$RW_IRAM1$ZI$Length|
; clear bss/zi
LDR R0, = |Image$RW_IRAM1$ZI$Base| ; destination
MOV R1, #0 ; Value
LDR R2, = |Image$RW_IRAM1$ZI$Length| ; lenth
BL bss_section_clear
|Image$$RW_IRAM1$$ZI$$Base| : 是bss/zi段的鏈接地址
|Image$$RW_IRAM1$$ZI$$Length| : bss/zi段的長度
我們調(diào)用一個memset函數(shù)就可以把對應(yīng)的內(nèi)存段清0了。
5.6 重新設(shè)置中斷向量表寄存器基地址
中斷向量表的基地址默認是0x08000000處的,當中斷發(fā)生時,MCU會自動到這個地址處找到對應(yīng)的中斷處理函數(shù),然后跳轉(zhuǎn)過去。
但是我們需要把代碼搬運到了SDRAM運行,如果還想要正常使用中斷處理函數(shù)的話,就必須修改中斷向量表的基地址到鏈接的起始地址,也就是0x60000000。
修改方法也很簡單,SCB->VTOR 修改這個寄存器的值就行了,這個寄存器是內(nèi)核相關(guān)的寄存器,它的地址是0xE000ED08.
代碼如下:
; set interrupt vector base address to 0x60000000
ldr r0, =__Vectors
ldr r1, =0xE000ED08 ; SCB->VTOR register address is 0xE000ED08
str r0, [r1]
ldr指令,把__Vectors的鏈接地址(其實就是0x60000000)加載到r0寄存器中,然后再使用str指令,把這個值寫到0xE000ED08地址處。
5.7 一些問題
做完前面的過程,基本就完成了把代碼重定位到SDRAM了,這個時候就可以使用絕對跳轉(zhuǎn)指令,跳轉(zhuǎn)到用戶應(yīng)用程序運行代碼了。
LDR R0, =mymain
BX R0
當執(zhí)行了上面兩條指令,那么就已經(jīng)是跳轉(zhuǎn)到SDRAM運行代碼了,因為mymain的函數(shù)鏈接地址,就是位于SDRAM那邊的。
但是我在這個過程中遇到了一些問題。
5.7.1 Reset_Handler 中斷問題

file://E:/%E5%8D%9A%E5%AE%A2%E6%96%87%E7%AB%A0/picture/image-20230808233404427.png?lastModify=1691512138
在中斷向量表的第二個位置,會放這個 Reset_Handler 的函數(shù)在那里。這個是MCU上電的時候,就會從這個位置,然后把這個 Reset_Handler 的函數(shù)地址賦值給PC指針,然后讓MCU從 Reset_Handler 開始不斷的執(zhí)行代碼。
程序被燒錄到內(nèi)部的Flash中,然后0x08000004地址開始存放的,就是 Reset_Handler 的地址了。
但是問題是 Reset_Handler 這個函數(shù)名,鏈接器是使用鏈接地址給它分配地址值的,也就是它的地址是 0x60000000 開始的某個地址。然后MCU上電,就把這個 0x60000000 開始的某個地址,賦值給了PC指針,這個時候會怎么樣?
因為這時還沒有把代碼復(fù)制到SDRAM,那MCU從那里取不到正確的指令,只能是無法執(zhí)行下去。
所以我們不能使用 Reset_Handler 函數(shù)名放在那個位置,而是要把 0x08000000 開始的某個地址放在那里,因為我們把程序燒錄到了 0x08000000 處。
然后這個數(shù)值怎么得到?通過反匯編文件。

file://E:/%E5%8D%9A%E5%AE%A2%E6%96%87%E7%AB%A0/picture/image-20230808234433096.png?lastModify=1691512138
這里的 +1 是因為ARM公司有兩種指令集,ARM 指令集和 Thumb 指令集,其中指令的 bit0 位為1,那就代表是Thumb指令。而Cortex-M3/M4 內(nèi)核使用的就是Thumb指令集,所以會 +1.
所以才會看到中斷向量表的第二個位置放了個奇怪的數(shù)據(jù): 0x080003f5,就是這么來的。
5.7.2 清除bss/zi段時把堆棧也給清除了
在運行bss段清0的代碼時,我發(fā)現(xiàn)代碼就死掉了。通過 .map 文件分析,發(fā)現(xiàn)Keil把堆和棧的大小,也歸類為了bss里面了,這可能是 .s 文件定義堆棧的方式導致的,我沒有去深究。
如果把堆棧都歸類了bss段了,那么清除bss段時,就把堆棧給干掉了,堆可能關(guān)系還不大,因為還沒有用到堆內(nèi)存。但是把棧清0了,肯定不行,因我們前面的代碼有C語言寫的代碼,C函數(shù)的調(diào)用,局部變量都用到了棧。
解決方式其實很簡單,既然把堆棧都歸類為bss段,那么我們想辦法不讓Keil把堆棧歸類到bss段即可。
我是直接在散列文件那里定義堆棧的大小了,然后通過鏈接器的符號:|Image$$ARM_LIB_STACK$$ZI$$Limit|獲取到棧頂指針的地址值,然后在中斷向量表的第一個位置填這個符號即可。

file://E:/%E5%8D%9A%E5%AE%A2%E6%96%87%E7%AB%A0/picture/image-20230809000650638.png?lastModify=1691512138
當然,不使用散列文件的話,也有很多其他取巧的解決辦法,比如:
調(diào)用 bss 段清0函數(shù)之前,重新設(shè)置 SP 的值
bss 段清0函數(shù)內(nèi)部,長度信息減去堆棧的空間長度
以上就是程序自己復(fù)制自己,把自己重定位到SDRAM運行的介紹。
整個demo程序也上傳這里了,以供大家學習參考。
注:文章作者在原帖中提供了例程文件,有需要請至原文21ic論壇下載
原文地址:https://bbs.21ic.com/icview-3319862-1-1.html
-
mcu
+關(guān)注
關(guān)注
147文章
18881瀏覽量
396664 -
SDRAM
+關(guān)注
關(guān)注
7文章
457瀏覽量
57588 -
寄存器
+關(guān)注
關(guān)注
31文章
5607瀏覽量
129833 -
FlaSh
+關(guān)注
關(guān)注
10文章
1742瀏覽量
155405 -
引腳
+關(guān)注
關(guān)注
16文章
2110瀏覽量
55605
原文標題:APM32芯得 EP.64 | APM32代碼重定位--如何讓整個程序在SDRAM運行
文章出處:【微信號:geehysemi,微信公眾號:Geehy極海半導體】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
新品發(fā)布丨極海半導體推出工業(yè)級標準型APM32S103系列MCU
極海推出APM32A系列車規(guī)級MCU芯片
APM32工業(yè)級硬核抗寒體質(zhì),無懼-40℃寒潮!
極海半導體新品上市—工業(yè)增強型APM32F091xC系列MCU
基于APM32 MCU的電動車BMS及電機控制應(yīng)用方案
嵌入式開發(fā)工具服務(wù)商IAR Systems工具鏈全面支持極海半導體APM32系列MCU
在極海APM32系列MCU中如何把代碼重定位到SDRAM運行
評論