第49章 在SRAM中調試代碼
全套200集視頻教程和1000頁PDF教程請到秉火論壇下載:www.firebbs.cn
野火視頻教程優酷觀看網址:http://i.youku.com/firege
本章參考資料:《STM32F4xx 中文參考手冊》、《STM32F4xx規格書》、《Cortex-M3權威指南》、《Cortex-M4 Technical Reference Manual》(跟M3大部分是相同的,讀英文不習慣可先參考《Cortex-M3權威指南》)。
學習本章時,配合《STM32F4xx 中文參考手冊》"存儲器和總線結構"及"嵌入式FLASH接口"章節一起閱讀,效果會更佳,特別是涉及到寄存器說明的部分。
49.1 在RAM中調試代碼
一般情況下,我們在MDK中編寫工程應用后,調試時都是把程序下載到芯片的內部FLASH運行測試的,代碼的CODE及RW-data的內容被寫入到內部FLASH中存儲。但在某些應用場合下卻不希望或不能修改內部FLASH的內容,這時就可以使用RAM調試功能了,它的本質是把原來存儲在內部FLASH的代碼(CODE及RW-data的內容)改為存儲到SRAM中(內部SRAM或外部SDRAM均可),芯片復位后從SRAM中加載代碼並運行。
把代碼下載到RAM中調試有如下優點:
下載程序非常快。RAM存儲器的寫入速度比在內部FLASH中要快得多,且沒有擦除過程,因此在RAM上調試程序時程序幾乎是秒下的,對於需要頻繁改動代碼的調試過程,能節約很多時間,省去了煩人的擦除與寫入FLASH過程。另外,STM32的內部FLASH可擦除次數為1萬次,雖然一般的調試過程都不會擦除這么多次導致FLASH失效,但這確實也是一個考慮使用RAM的因素。
不改寫內部FLASH的原有程序。
對於內部FLASH被鎖定的芯片,可以把解鎖程序下載到RAM上,進行解鎖。
相對地,把代碼下載到RAM中調試有如下缺點:
存儲在RAM上的程序掉電后會丟失,不能像FLASH那樣保存。
若使用STM32的內部SRAM存儲程序,程序的執行速度與在FLASH上執行速度無異,但SRAM空間較小。
若使用外部擴展的SDRAM存儲程序,程序空間非常大,但STM32讀取SDRAM的速度比讀取內部FLASH慢,這會導致程序總執行時間增加,因此在SDRAM中調試的程序無法完美仿真在內部FLASH運行時的環境。另外,由於STM32無法直接從SDRAM中啟動且應用程序復制到SDRAM的過程比較復雜(下載程序前需要使STM32能正常控制SDRAM),所以在很少會在STM32的SDRAM中調試程序。
49.2 STM32的啟動方式
在前面講解的STM32啟動代碼章節了解到CM-4內核在離開復位狀態后的工作過程如下,見圖 491:
(1) 從地址0x00000000處取出棧指針MSP的初始值,該值就是棧頂的地址。
(2) 從地址0x00000004處取出程序指針PC的初始值,該值指向復位后應執行的第一條指令。
圖 491 復位序列
上述過程由內核自動設置運行環境並執行主體程序,因此它被稱為自舉過程。
雖然內核是固定訪問0x00000000和0x00000004地址的,但實際上這兩個地址可以被重映射到其它地址空間。以STM32F429為例,根據芯片引出的BOOT0及BOOT1引腳的電平情況,這兩個地址可以被映射到內部FLASH、內部SRAM以及系統存儲器中,不同的映射配置見表 491。
表 491 BOOT引腳的不同設置對0地址的映射
BOOT1 |
BOOT0 |
映射到的存儲器 |
0x00000000 地址映射到 |
0x00000004 地址映射到 |
x |
0 |
內部FLASH |
0x08000000 |
0x08000004 |
1 |
1 |
內部SRAM |
0x20000000 |
0x20000004 |
0 |
1 |
系統存儲器 |
0x1FFF0000 |
0x1FFF0004 |
內核在離開復位狀態后會從映射的地址中取值給棧指針MSP及程序指針PC,然后執行指令,我們一般以存儲器的類型來區分自舉過程,例如內部FLASH啟動方式、內部SRAM啟動方式以及系統存儲器啟動方式。
(1) 內部FLASH啟動方式
當芯片上電后采樣到BOOT0引腳為低電平時, 0x00000000和0x00000004地址被映射到內部FLASH的首地址0x08000000和0x08000004。因此,內核離開復位狀態后,讀取內部FLASH的0x08000000地址空間存儲的內容,賦值給棧指針MSP,作為棧頂地址,再讀取內部FLASH的0x08000004地址空間存儲的內容,賦值給程序指針PC,作為將要執行的第一條指令所在的地址。具備這兩個條件后,內核就可以開始從PC指向的地址中讀取指令執行了。
(2) 內部SRAM啟動方式
類似地,當芯片上電后采樣到BOOT0和BOOT1引腳均為高電平時,0x00000000和0x00000004地址被映射到內部SRAM的首地址0x20000000和0x20000004,內核從SRAM空間獲取內容進行自舉。
在實際應用中,由啟動文件starttup_stm32f429_439xx.s決定了0x00000000和0x00000004地址存儲什么內容,鏈接時,由分散加載文件(sct)決定這些內容的絕對地址,即分配到內部FLASH還是內部SRAM。(下一小節將以實例講解)
(3) 系統存儲器啟動方式
當芯片上電后采樣到BOOT0引腳為高電平,BOOT1為低電平時,內核將從系統存儲器的0x1FFF0000及0x1FFF0004獲取MSP及PC值進行自舉。系統存儲器是一段特殊的空間,用戶不能訪問,ST公司在芯片出廠前就在系統存儲器中固化了一段代碼。因而使用系統存儲器啟動方式時,內核會執行該代碼,該代碼運行時,會為ISP提供支持(In System Program),如檢測USART1/3、CAN2及USB通訊接口傳輸過來的信息,並根據這些信息更新自己內部FLASH的內容,達到升級產品應用程序的目的,因此這種啟動方式也稱為ISP啟動方式。
49.3 內部FLASH的啟動過程
下面我們以最常規的內部FLASH啟動方式來分析自舉過程,主要理解MSP和PC內容是怎樣被存儲到0x08000000和0x08000004這兩個地址的。
見圖 492 ,這是STM32F4默認的啟動文件的代碼,啟動文件的開頭定義了一個大小為0x400的棧空間,且棧頂的地址使用標號"__initial_sp"來表示;在圖下方定義了一個名為"Reset_Handler"的子程序,它就是我們總是提到的在芯片啟動后第一個執行的代碼。在匯編語法中,程序的名字和標號都包含它所在的地址,因此,我們的目標是把"__initial_sp"和"Reset_Handler"賦值到0x08000000和0x08000004地址空間存儲,這樣內核自舉的時候就可以獲得棧頂地址以及第一條要執行的指令了。在啟動代碼的中間部分,使用了匯編關鍵字"DCD"把"__initial_sp"和"Reset_Handler"定義到了最前面的地址空間。
圖 492 啟動代碼中存儲的MSP及PC指針內容
在啟動文件中把設置棧頂及首條指令地址到了最前面的地址空間,但這並沒有指定絕對地址,各種內容的絕對地址是由鏈接器根據分散加載文件(*.sct)分配的,STM32F429IGT6型號的默認分散加載文件配置見代碼清單 491。
代碼清單 491 默認分散加載文件的空間配置
1 ; *************************************************************
2 ; *** Scatter-Loading Description File generated by uVision ***
3 ; *************************************************************
4
5 LR_IROM1 0x08000000 0x00100000 { ; load region size_region
6 ER_IROM1 0x08000000 0x00100000 { ; load address = execution address
7 *.o (RESET, +First)
8 *(InRoot$$Sections)
9 .ANY (+RO)
10 }
11 RW_IRAM1 0x20000000 UNINIT 0x00030000 { ; RW data
12 .ANY (+RW +ZI)
13 }
14 }
15
分散加載文件把加載區和執行區的首地址都設置為0x08000000,正好是內部FLASH的首地址,因此匯編文件中定義的棧頂及首條指令地址會被存儲到0x08000000和0x08000004的地址空間。
類似地,如果我們修改分散加載文件,把加載區和執行區的首地址設置為內部SRAM的首地址0x20000000,那么棧頂和首條指令地址將會被存儲到0x20000000和0x20000004的地址空間了。
為了進一步消除疑慮,我們可以查看反匯編代碼及map文件信息來了解各個地址空間存儲的內容,見圖 493,這是多彩流水燈工程編譯后的信息,它的啟動文件及分散加載文件都按默認配置。其中反匯編代碼是使用fromelf工具從axf文件生成的,具體過程可參考前面的章節了解。
圖 493 從反匯編代碼及map文件查看存儲器的內容
從反匯編代碼可了解到,這個工程的0x08000000地址存儲的值為0x20000400,0x08000004地址存儲的值為0x080001C1,查看map文件,這兩個值正好是棧頂地址__initial_sp以及首條指令Reset_Handler的地址。下載器會根據axf文件(bin、hex類似)存儲相應的內容到內部FLASH中。
由此可知,BOOT0為低電平時,內核復位后,從0x08000000讀取到棧頂地址為0x20000400,了解到子程序的棧空間范圍,再從0x08000004讀取到第一條指令的存儲地址為0x080001C1,於是跳轉到該地址執行代碼,即從ResetHandler開始運行,運行SystemInit、__main(包含分散加載代碼),最后跳轉到C語言的main函數。
對比在內部FLASH中運行代碼的過程,可了解到若希望在內部SRAM中調試代碼,需要設置啟動方式為從內部SRAM啟動,修改分散加載文件控制代碼空間到內部SRAM地址以及把生成程序下載到芯片的內部SRAM中。
49.4 實驗:在內部SRAM中調試代碼
本實驗將演示如何設置工程選項實現在內部SRAM中調試代碼,實驗的示例代碼名為"RAM調試—多彩流水燈",學習以下內容時請打開該工程來理解,它是從普通的多彩流水燈例程改造而來的。
49.4.1 硬件設計
本小節中使用到的流水燈硬件不再介紹,主要講解與SRAM調試相關的硬件配置。在SRAM上調試程序,需要修改STM32芯片的啟動方式,見圖 494。
圖 494 實驗板的boot引腳配置
在我們的實驗板左側有引出STM32芯片的BOOT0和BOOT1引腳,可使用跳線帽設置它們的電平從而控制芯片的啟動方式,它支持從內部FLASH啟動、系統存儲器啟動以及內部SRAM啟動方式。
本實驗在SRAM中調試代碼,因此把BOOT0和BOOT1引腳都使用跳線帽連接到3.3V,使芯片從SRAM中啟動。
49.4.2 軟件設計
本實驗的工程從普通的多彩流水燈工程改寫而來,主要修改了分散加載文件及一些程序的下載選項。
1. 主要步驟
(1) 在原工程的基礎上創建一個調試版本;
(2) 修改分散加載文件,使鏈接器把代碼分配到內部SRAM空間;
(3) 添加宏修改STM32的向量表地址;
(4) 修改仿真器和下載器的配置,使程序能通過下載器存儲到內部SRAM;
(5) 根據使用情況選擇是否需要使用仿真器命令腳本文件*.ini;
(6) 嘗試給SRAM下載程序或仿真調試。
2. 創建工程的調試版本
由於在SRAM中運行的代碼一般只是用於調試,調試完畢后,在實際生產環境中仍然使用在內部FLASH中運行的代碼,因此我們希望能夠便捷地在調試版和發布版代碼之間切換。MDK的"Manage Project Items"可實現這樣的功能,使用它可管理多個不同配置的工程,見圖 495,點擊"Manage Project Items"按鈕,在彈出對話框左側的"Project Target"一欄包含了原工程的名字,如圖中的原工程名為"多彩流水燈",右側是該工程包含的文件。為了便於調試,我們在左側的"Project Target"一欄添加一個工程名,如圖中輸入"SRAM_調試",輸入后點擊OK即可,這個"SRAM_調試"版本的工程會復制原"多彩流水燈"工程的配置,后面我們再進行修改。
圖 495 使用Manage Project Items添加一個工程配置
當需要切換工程版本時,點擊MDK工程名的下拉菜單可選擇目標工程,在不同的工程中,所有配置都是獨立的,例如芯片型號、下載配置等等,但如果兩個工程共用了同一個文件,對該文件的修改會同時影響兩個工程,例如這兩個工程都使用同一個main文件,我們在main文件修改代碼,兩個工程都會被修改。
圖 496 切換工程
在下面的教程中我們將切換到"SRAM_調試"版本的工程,配置出一個代碼會被存儲到SRAM的多彩流水燈工程。
3. 配置分散加載文件
為方便講解,本工程的分散加載只使用手動編輯的sct文件配置,不使用MDK的對話框選項配置,在"Options for Target->linker"的選項見圖 497。
圖 497 使用新建的"SRAM_調試.sct"文件
為了防止"多彩流水燈"工程的分散加載文件被影響,我們在工程的Output路徑下新建了一個名為"SRAM_調試.sct"的文件,並在上圖中把它配置為"SRAM_調試"工程專用的分散加載文件,該文件的內容見代碼清單 492,若不了解分散加載文件的使用,請參考前面的章節。
代碼清單 492 分散加載文件配置(SRAM_調試.sct)
1 ; *************************************************************
2 ; *** Scatter-Loading Description File generated by uVision ***
3 ; *************************************************************
4
5 LR_IROM1 0x20000000 0x00010000 { ; load region size_region
6 ER_IROM1 0x20000000 0x00010000 { ; load address = execution address
7 *.o (RESET, +First)
8 *(InRoot$$Sections)
9 .ANY (+RO)
10 }
11 RW_IRAM1 0x20010000 0x00020000 { ; RW data
12 .ANY (+RW +ZI)
13 }
14 }
15
在這個分散加載文件配置中,把原本分配到內部FLASH空間的加載域和執行域改到了以地址0x20000000開始的64KB(0x00010000)空間,而RW data空間改到了以地址0x20010000開始的128KB空間 (0x00020000)。也就是說,它把STM32的內部SRAM分成了虛擬ROM區域以及RW data數據區域,鏈接器會根據它的配置給工程中的各種內容分配到SRAM地址。
在具體的應用中,虛擬ROM及RW區域的大小可根據自己的程序定制,配置完畢編譯工程后可在map文件中查看具體的空間地址分配。
4. 配置中斷向量表
由於startup_stm32f429_439xx.s文件中的啟動代碼不是指定到絕對地址的,經過它由鏈接器決定應存儲到內部FLASH還是SRAM,所以SRAM版本工程中的啟動文件不需要作任何修改。
重點在於啟動文件定義的中斷向量表被存儲到內部FLASH和內部SRAM時,這兩種情況對內核的影響是不同的,內核會根據它的"向量表偏移寄存器VTOR"配置來獲取向量表,即中斷服務函數的入口。VTOR寄存器是由啟動文件中Reset_Handle中調用的庫函數SystemInit配置的,見代碼清單 493。
代碼清單 493 SystemInit函數(system_stm32f4xx.c文件)
1 /**
2 * @brief Setup the microcontroller system
3 * Initialize the Embedded Flash Interface, the PLL and update the
4 * SystemFrequency variable.
5 * @param None
6 * @retval None
7 */
8 void SystemInit(void)
9 {
10 /* ..其它代碼部分省略 */
11
12 /* Configure the Vector Table location add offset address ----*/
13 #ifdef VECT_TAB_SRAM
14 SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* 向量表存儲在SRAM */
15 #else
16 SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* 向量表存儲在內部FLASH */
17 #endif
18 }
代碼中根據是否存儲宏定義VECT_TAB_SRAM來決定VTOR的配置,默認情況下代碼中沒有定義宏VECT_TAB_SRAM,所以VTOR默認情況下指示向量表是存儲在內部FLASH空間的。
由於本工程的分散加載文件配置,在啟動文件中定義的中斷向量表會被分配到SRAM空間,所以我們要定義這個宏,使得SystemInit函數修改VTOR寄存器,向內核指示向量表被存儲到內部SRAM空間了,見圖 498,在"Options for Target-> c/c++ ->Define"框中輸入宏VECT_TAB_SRAM,注意它與其它宏之間要使用英文逗號分隔開。
圖 498 在c/c++編譯選項中加入宏VECT_TAB_SRAM
配置完成后重新編譯工程,即可生成存儲到SRAM空間地址的代碼指令。
5. 修改FLASH下載配置
得到SRAM版本的代碼指令后,為了把它下載到芯片的SRAM中,還需要修改下載器的配置,見圖 499,"Options for Target->Utilities->Settings"中的選項。
圖 499 下載配置
這個配置對話框原本是用於設置芯片內部FLASH信息的,當我們點擊MDK的(下載、LOAD)按鈕時,它會從此處加載配置然后下載程序到FLASH中,而在上圖中我們把它的配置修改成下載到內部SRAM了,各個配置的解釋如下:
把"Download Function"中的擦除選項配置為"Do not Erase"。這是因為數據寫入到內部SRAM中不需要像FLASH那樣先擦除后寫入。在本工程中,如果我們不選擇"Do not Erase"的話,會因為擦除過程導致下載出錯。
"RAM for Algorithm"一欄是指"編程算法"(Programming Algorithm)可使用的RAM空間,下載程序到FLASH時運行的編程算法需要使用RAM空間,在默認配置中它的首地址為0x20000000,即內部SRAM的首地址,但由於我們的分散加載文件配置,0x20000000地址開始的64KB實際為虛擬ROM空間,實際的RAM空間是從地址0x20010000開始的,所以這里把算法RAM首地址更改為本工程中實際作為RAM使用的地址。若編程算法使用的RAM地址與虛擬ROM空間地址重合的話,會導致下載出錯。
"Programming Algorithm"一欄中是設置內部FLASH的編程算法,編程算法主要描述了FLASH的地址、大小以及扇區等信息,MDK根據這些信息把程序下載到芯片的FLASH中,不同的控制器芯片一般會有不同的編程算法。由於MDK沒有內置SRAM的編程算法,所以我們直接在原來的基礎上修改它的基地址和空間大小,把它改成虛擬ROM的空間信息。
從這個例子可了解到,這里的配置是跟我們的分散加載文件的實際RAM空間和虛擬ROM空間信息是一致的,若您的分散加載文件采用不同的配置,這個下載選項也要作出相應的修改,不能照抄本例子的空間信息。
這個配置是針對程序下載的,配置完成后點擊MDK的按鈕(下載、LOAD),程序會被下載到STM32的內部SRAM中,復位后程序會正常運行 (前提是BOOT0和BOOT要被設置為SRAM啟動) 。芯片掉電后這個存儲在SRAM的程序會丟失,想恢復的話必須要重新下載程序。
6. 仿真器的配置
上面的下載配置使得程序能夠加載到SRAM中全速運行,但作為SRAM版本的程序,其功能更着重於調試,也就是說我們希望它能支持平時使用按鈕(調試、debug)時進行的硬件在線調試、單步運行等功能。
要實現調試功能,還要在"Options for Target->Debug->Settings"中進行配置,見圖 4910。
圖 4910 設置仿真前檢查代碼並下載程序到FLASH中
在圖中我們需要勾選"Verify Code Download"及"Download to FLASH"選項,也就是說點擊調試按鈕后,本工程的程序會被下載到內部SRAM中,只有勾選了這兩個選項才能正常仿真。(至於為什么FLASH版本的程序不需要勾選,不太清楚)
經過這樣的配置后,硬件仿真時與平時內部FLASH版本的程序無異,支持軟件復位、單步運行、全速運行以及查看各種變量值等 (同樣地,前提是BOOT0和BOOT要被設置為SRAM啟動) 。
7. 不需要修改BOOT引腳的仿真配置
假如您使用的硬件平台中BOOT0和BOOT1引腳電平已被固定,設置為內部FLASH啟動,不方便改成SRAM方式,可以使用如下方法配置調試選項實現在SRAM調試:
(1) 與上述步驟一樣,勾選"Verify Code Download"及"Download to FLASH"選項;
(2) 見圖 4911,在"Options for Target->Debug"對話框中取消勾選"Load Application at startup"選項。點擊"Initialization File"文本框右側的文件瀏覽按鈕,在彈出的對話框中新建一個名為"Debug_RAM.ini"的文件;
圖 4911 新建一個ini文件
(3) 在Debug_RAM.ini文件中輸入如代碼清單 494中的內容。
代碼清單 494 Debug_RAM.ini文件內容
1 /***********************************************************/
2 /* Debug_RAM.ini: Initialization File for Debugging from Internal RAM */
3 /******************************************************/
4 /* This file is part of the uVision/ARM development tools. */
5 /* Copyright (c) 2005-2014 Keil Software. All rights reserved. */
6 /* This software may only be used under the terms of a valid, current, */
7 /* end user licence from KEIL for a compatible version of KEIL software */
8 /*development tools. Nothing else gives you the right to use this software */
9 /***************************************************/
10
11 FUNC void Setup (void) {
12 SP = _RDWORD(0x20000000); // 設置棧指針SP,把0x20000000地址中的內容賦值到SP。
13 PC = _RDWORD(0x20000004); // 設置程序指針PC,把0x20000004地址中的內容賦值到PC。
14 XPSR = 0x01000000; // 設置狀態寄存器指針xPSR
15 _WDWORD(0xE000ED08, 0x20000000); // Setup Vector Table Offset Register
16 }
17
18 LOAD %L INCREMENTAL // 下載axf文件到RAM
19 Setup(); //調用上面定義的setup函數設置運行環境
20
21 //g, main //跳轉到main函數,本示例調試時不需要從main函數執行,注釋掉了,程序從啟動代碼開始執行
上述配置過程是控制MDK執行仿真器的腳本文件Debug_RAM.ini,而該腳本文件在下載了程序到SRAM后,初始化了SP指針(即MSP)和PC指針分別指向了0x20000000和0x20000004,這樣的操作等效於從SRAM復位。
有了這樣的配置,即使BOOT0和BOOT1引腳不設置為SRAM啟動也能正常仿真了,但點擊下載按鈕把程序下載到SRAM然后按復位是不能全速運行的(這種運行方式脫離了仿真器的控制,SP和PC指針無法被初始化指向SRAM)。
上述Debug_RAM.ini文件是從STM32F4的MDK芯片包里復制過來的,若您感興趣可到MDK安裝目錄搜索該文件名,該文件的語法可以從MDK的幫助手冊的"µVision User's Guide->Debug Commands"章節學習。
49.5 每課一問
4. 在內部FLASH運行的程序與在SRAM運行的程序主要差異有哪些?