初始目的是為了通過匯編編寫CRC功能。
但是基礎為0,所以目前從搭建工程開始記錄。
大佬繞路。
(一)創建項目
1. 新建項目
- 打開 Keil uVision。
- 選擇 Project -> New uVision Project 創建一個新項目。
- 選擇你的目標設備(如 ARM Cortex-M 系列處理器),我這里一開始選擇的M0,后面因為報錯改為了M3。
2. 新建匯編文件
- 在項目中,右鍵點擊 Source Group 1,選擇 Add New Item to Group ‘Source Group 1’。
- 選擇 Assembler Source File,命名文件并點擊 Add。
3. 編寫匯編代碼
```asmAREA MyCode, CODE, READONLYENTRYEXPORT __main__main MOV R0, #0x10 ; 將立即數 0x10 加載到 R0 寄存器MOV R1, #0x20 ; 將立即數 0x20 加載到 R1 寄存器ADD R2, R0, R1 ; 將 R0 和 R1 相加,結果存入 R2B . ; 無限循環END
```
4. 配置啟動文件
確保項目中包含正確的啟動文件(startup file),這對于 ARM Cortex-M 項目來說非常重要。你可以在目標設備的 Startup 文件夾中找到適合的啟動文件并將其添加到項目中。
5. 編譯項目
1.點擊 Project -> Build Target 或按快捷鍵 F7 進行編譯。
2.如果有編譯錯誤,修復后重新編譯。
6. 加載并調試代碼
-
配置模擬器:
- 選擇 Options for Target ‘Target 1’(點擊工具欄中的扳手圖標)。
- 在 Debug 選項卡中,選擇 Use Simulator。
- 點擊 OK 保存配置。
-
進入調試模式:
- 點擊 Debug -> Start/Stop Debug Session 或按快捷鍵 Ctrl+F5 進入調試模式。
- Keil 將啟動模擬器,你可以看到一個新的調試窗口。
-
調試代碼:
- 使用調試工具欄中的按鈕進行代碼的單步執行(Step Over/F10, Step Into/F11)、設置斷點(在代碼行左側點擊)和觀察寄存器、內存等。
- 在調試窗口中,可以使用 Watch 窗口查看變量和寄存器的值。
- 使用 Memory 窗口查看特定內存地址的內容。
- 使用 Register 窗口查看和修改 CPU 寄存器的值。
(二)Disassembly窗口解釋
-
地址列(Address):
這一列顯示了每條匯編指令在內存中的地址。地址是十六進制格式,表示當前指令在程序內存中的位置。這有助于你了解代碼在內存中的分布以及調試時的具體位置。 -
機器碼列(Machine Code / Opcode):
這一列顯示了每條匯編指令對應的機器碼(十六進制格式)。它展示了 CPU 執行指令時實際處理的字節碼。對于調試和分析底層行為非常有用。 -
匯編指令列(Disassembly / Instruction):
這一列顯示了指令的匯編語言格式。它展示了 CPU 執行的指令以及它們的操作數。你可以看到每條指令的操作碼和操作數,例如 MOV R0, #1 或 ADD R1, R2。 -
源代碼列(Source Code)(如果有):
如果你的工程配置了源代碼文件,并且源代碼與匯編指令有映射關系,Disassembly 窗口可能會顯示源代碼行號和代碼內容。這列幫助你將匯編代碼與對應的高層源代碼關聯起來。 -
符號列(Symbol)(如果啟用):
這列可能顯示符號信息,例如函數名或變量名。在某些設置中,你可以看到每條匯編指令的符號名稱,這對于理解代碼的功能和調試非常有幫助。
Address Machine Code Disassembly Source Code
08000000 E3A00001 MOV R0, #1 // int x = 1;
08000004 E0801003 ADD R1, R0, R3
- Address:08000000 是 MOV R0, #1 指令的內存地址。
- Machine Code:E3A00001 是 MOV R0, #1 指令的機器碼。
- Disassembly:MOV R0, #1 是指令的匯編語言表示。
- Source Code:如果有源代碼文件,這一列顯示了相關的源代碼行(如 int x = 1;)。
(三)寄存器窗口
在寄存器窗口中,通常會顯示以下信息:
-
寄存器名稱(Register Name):
顯示寄存器的名稱。常見的寄存器名稱包括 R0、R1、PC(程序計數器)、SP(棧指針)、LR(鏈接寄存器)等。不同的微控制器架構可能有不同的寄存器集合。 -
寄存器值(Value):
顯示每個寄存器當前的值,通常以十六進制格式表示。這些值是 CPU 當前使用的寄存器內容。
使用寄存器窗口
-
查看寄存器值:
在寄存器窗口中,可以看到所有寄存器的當前值。 -
修改寄存器值:
有些調試器允許手動修改寄存器的值。可以在寄存器值欄中直接輸入新的值并按 Enter 鍵來修改,例如可以修改程序計數器(PC)以改變程序的執行流。 -
同步查看:
寄存器窗口通常會自動更新顯示寄存器的最新值。當程序執行或暫停時,可以看到寄存器值的變化。
Register Value
---------------------
R0 0x00000010
R1 0x00000020
R2 0x00000030
PC 0x08000000
SP 0x20001000
LR 0x08000010
R0:寄存器 R0 的值是 0x00000010。
PC:程序計數器 PC 的值是 0x08000000,表示當前正在執行的指令地址。
SP:棧指針 SP 的值是 0x20001000,表示當前棧的頂部地址。
(四)Program Size
Program Size: Code=544 RO-data=268 RW-data=4 ZI-data=3445
Program Size 是指編譯和鏈接后的程序代碼和數據在目標微控制器(如 ARM Cortex-M)中所占用的存儲空間。它主要由以下幾個部分組成:
1. Code Size (代碼大小)
這是指程序中所有的指令(即代碼段 .text)在目標設備閃存中所占用的空間。它包括用戶編寫的代碼、庫函數、以及編譯器生成的代碼。
影響因素:代碼優化級別、函數的復雜度、庫函數的使用情況、內聯函數(inline functions)等。
2. RO Data Size (只讀數據大小)
這是指所有只讀數據(即 .rodata 段)在閃存中所占用的空間。這些數據包括常量字符串、只讀變量、編譯時確定的初始化數據等。
影響因素:程序中定義的常量、字符串以及所有使用 const 修飾的變量。
3. RW Data Size (讀寫數據大小)
這是指可讀寫數據段(即 .data 段)在閃存和 SRAM 中所占用的空間。它包括全局變量和靜態變量(靜態分配的),這些變量在程序啟動時由閃存中的初始化數據拷貝到 SRAM 中。
影響因素:全局變量、靜態變量的數量和類型,以及這些變量的初始值。
4. ZI Data Size (零初始化數據大小)
這是指未初始化的全局變量和靜態變量(即 .bss 段)在 SRAM 中所占用的空間。在程序啟動時,這些變量會被初始化為零,因此在閃存中不占用空間。
影響因素:未初始化的全局變量和靜態變量的數量和類型。
5. Stack Size (堆棧大小)
堆棧用于存儲函數調用時的局部變量、返回地址、中斷處理器上下文等。它位于 SRAM 中,但在 Program Size 報告中通常不會直接顯示。
影響因素:遞歸調用深度、函數中局部變量的數量和大小、中斷服務程序的嵌套層次等。
6. Heap Size (堆大小)
堆用于動態內存分配(如 malloc),位于 SRAM 中。在 Program Size 報告中通常也不會直接顯示,但它的大小會影響 SRAM 的總體使用情況。
影響因素:動態內存分配的需求,程序運行時的內存分配策略。
7. 總大小 (Total Size)
這是代碼、只讀數據、讀寫數據和零初始化數據段的總和,通常是需要在閃存和 SRAM 中分配的總空間。對于嵌入式系統,優化代碼大小以符合設備的存儲限制是非常重要的。
(五)map文件
1. 總空間大小分配
Code (inc. data) RO Data RW Data ZI Data Debug 11720 1356 1268 108 1388 267457 Grand Totals11720 1356 1268 108 1388 267457 ELF Image Totals11720 1356 1268 108 0 0 ROM Totals==========================================================================Total RO Size (Code + RO Data) 12988 ( 12.68kB)
Total RW Size (RW Data + ZI Data) 1496 ( 1.46kB)
Total ROM Size (Code + RO Data + RW Data) 13096 ( 12.79kB)
KEIL編譯器會在每次編譯和鏈接后生成一個 map 文件,其中詳細列出了程序中每個段的大小,以及每個段在閃存和 SRAM 中的具體分布情況。
RO Size (Code + RO Data) ,表示程序占用Flash空間的大小。RW Size (RW Data + ZI Data),表示運行時占用的RAM的大小。ROM Size (Code + RO Data + RW Data) ,表示燒寫程序占用的Flash空間的大小。
2. Memory Map of the image
打開.map文件,找到Memory Map of the image即可查看對應的內存分配。
1. Exec Addr (執行地址)
- 定義:這是程序代碼或數據在運行時的執行地址,即程序運行時,該部分代碼或數據會被映射到這個地址。
- 用途:確定程序在內存中實際運行的地址,通常用于區分代碼的執行位置(如在閃存中)和數據的運行位置(如在 SRAM 中)。
2. Load Addr (加載地址)
- 定義:這是程序或數據在加載時的地址,即當程序加載到內存中時,該段最初被存儲的地址。
- 用途:加載地址通常用于初始化數據時。對于代碼段,加載地址和執行地址通常是相同的;但對于初始化的數據段,加載地址通常在閃存中,而執行地址在 SRAM 中。
3. Size (大小)
- 定義:表示該段占用的內存大小,通常以字節為單位。
- 用途:用于確定每個段占用了多少內存空間,便于內存布局優化。
4. Type (類型)
- 定義:標識該段的類型,通常包括 Code、Data 等類型。
用途: - Code:表示這部分是代碼段。
- Data:表示這部分是數據段(包括初始化數據和未初始化數據)。
5. Attr (屬性)
- 定義:標識該段的屬性,通常是一個或多個字符組合,如 RO(只讀),RW(讀寫),ZI(零初始化)。
用途: - RO:只讀數據,如常量或代碼。
- RW:可讀寫數據,如初始化的全局變量。
- ZI:零初始化數據段,通常是未初始化的全局變量。
6. Idx (索引)
- 定義:這是該段在 MAP 文件中的索引號,用于區分和標識不同的段。
- 用途:可以幫助開發者快速定位和參考特定的段信息。
7. Section Name (段名稱)
- 定義:表示該段在源文件中的名稱,如 .text(代碼段)、.data(數據段)、.bss(未初始化數據段)等。
- 用途:段名稱幫助識別該段屬于哪個類型和功能,便于調試和優化。
8. Object (對象)
- 定義:表示該段所屬的目標文件(通常是 .o 文件),即該段是由哪個源文件生成的。
- 用途:幫助開發者了解代碼和數據是從哪個源文件或庫生成的,以便進行進一步的分析和優化。
示例解釋
假設 MAP 文件中有如下條目:
Exec Addr Load Addr Size Type Attr Idx E Section Name Object
0x08000000 0x08000000 0x00000400 Code RO 1 .text main.o
0x20000000 0x08000400 0x00000020 Data RW 2 .data main.o
0x20000020 0x20000020 0x00000100 Data ZI 3 .bss main.o
-
第1行:
- Exec Addr 和 Load Addr:0x08000000,表示代碼段 .text 在加載和執行時都在這個地址(通常是閃存)。
- Size:0x400 字節,即 1024 字節。
- Type:Code,表示這是代碼段。
- Attr:RO,表示該段是只讀的。
- Section Name:.text,表示這是代碼段。
- Object:main.o,表示這是從 main.o 文件中生成的段。
-
第2行:
- Exec Addr:0x20000000,表示這個數據段在執行時位于 SRAM 中。
- Load Addr:0x08000400,表示數據的初始值存儲在閃存中(通常在程序啟動時會從閃存加載到 SRAM)。
- Size:0x20 字節,即 32 字節。
- Type:Data,表示這是數據段。
- Attr:RW,表示該段是可讀寫的。
- Section Name:.data,表示這是初始化數據段。
- Object:main.o,表示這是從 main.o 文件中生成的段。
-
第3行:
- Exec Addr 和 Load Addr:0x20000020,表示這個段在 SRAM 中,且未初始化(因此不占用閃存)。
- Size:0x100 字節,即 256 字節。
- Type:Data,表示這是數據段。
- Attr:ZI,表示這是零初始化段。
- Section Name:.bss,表示這是未初始化數據段。
- Object:main.o,表示這是從 main.o 文件中生成的段。
(六)keil的memory中顏色的區別
KEIL IDE 中,Memory 視圖使用顏色來表示不同類型的內存區域或數據。
1. 黃色 (Yellow)
- 含義:黃色通常表示 已分配的代碼段 或 只讀數據。
- 用途:在 Flash 中,這通常是代碼 (.text 段) 或者是只讀常量數據 (.rodata 段),這些內容在程序運行時不會被修改。
- 例子:程序的指令集、常量字符串、const 變量等。
2. 綠色 (Green)
- 含義:綠色通常表示 已分配的可讀寫數據段。
- 用途:在 SRAM 中,這通常是已初始化的全局變量和靜態變量 (.data 段),以及動態分配的內存(如堆或棧)。
- 例子:全局變量、靜態變量、初始化的數組或結構體等。
3. 黑色 (Black):
- 含義:表示未使用或未分配的內存空間。
- 用途:在這種區域內存并未被當前的程序占用,可能在某些情況下可用于動態分配(如堆的擴展)或供其他用途。
- 位置:這種顏色通常出現在內存映射中未被分配的區域,包括 Flash、SRAM 等。
總結
- 黃色:表示代碼或只讀數據,通常位于 Flash 中,不會在運行時改變。
- 綠色:表示可讀寫的數據段,通常位于 SRAM 中,可以在運行時修改。
- 黑色:表示未分配的內存區域,內存視圖中這些部分是空閑的,沒有被程序使用或初始化。
顏色變化
在 KEIL 的內存視圖中,黃色 和 綠色 的變化通常與內存的使用狀態和訪問權限有關。雖然黃色通常表示只讀內容,如代碼段或只讀數據段,但有時候這些段會在程序運行時被修改,導致它們的狀態發生變化。這種變化可能反映為顏色從黃色變為綠色。
可能的原因包括:
- 內存重定位或重寫:
某些情況下,代碼或只讀數據段在運行時可能會被重新加載或重寫。例如,某些嵌入式系統中,可能會使用自編程技術動態修改程序的某些部分,這可能導致這些段的狀態從只讀變為可寫。 - 調試器的視角:
在調試過程中,調試器可能會修改某些內存區域,特別是在使用斷點或修改內存時。調試器可能將某些內存段標記為可寫(綠色),即使它們在正常情況下是只讀的(黃色)。這種情況尤其常見于調試初始化數據或調試代碼時。 - 運行時狀態改變:
一些系統在啟動時會將某些內存段從只讀狀態轉換為可寫狀態。例如,啟動時初始化數據段可能會從閃存(只讀,黃色)復制到 SRAM(可寫,綠色),以便程序運行時修改這些數據。
調試工具的內存可視化更新:
在調試過程中,內存視圖會動態更新,顯示當前內存的狀態。如果調試器檢測到某段內存的訪問權限發生了變化(例如從只讀變為讀寫),顏色也會相應更新。
問題記錄
1. error: A1859E: Flag preserving form of this instruction not available
解決方法
(1)MOV等命令加上s
(2)更改為M3(一開始使用的M0)
2.Error: L6218E: Undefined symbol Image$$ARM_LIB_STACK$$ZI$$Limit (referred form startup_armcm3.o).
因為 armclang 默認不勾選 Use Memory Layout from Target Dialog 選項,因此必須要手動添加鏈接腳本,即分散加載文件。通過下拉框,直接使用默認的鏈接腳本即可。
這里記得選擇M3對應的。圖片是借鑒的其他人的。
3.不同group見互相include文件失敗
選擇 Options for Target。
配置包含路徑:
- 在彈出的對話框中,選擇 C/C++ 選項卡。
- 在 Include Paths 輸入框中添加包含頭文件的路徑。
3. error 65: access violation at xxx : no 'read' permission
通常表示匯編代碼中嘗試訪問未授權或不存在的內存區域。這個錯誤常見于訪問無效的地址或指令地址不正確。
檢查內存訪問
-
訪問的地址是否有效:
確保你讀取和寫入的內存地址在有效范圍內。特別是訪問寄存器或數據結構時,確保地址正確。 -
棧操作:
如果你從棧中讀取參數,確保棧指針 SP 的位置正確。訪問棧空間時,地址應該是相對于棧指針的正確偏移量。
4.Note: source file ‘..\xx\cxx\xx.c‘ -object file renamed from “xx.o“ to “xx_1.o“
原因
使用keil添加文件時,在不同的group文件夾里添加了兩個相同的.c文件,會導致編譯出現如上的提示,即同一個c源文件,在不同的文件夾下,被添加了多次。
解決辦法
(1)反復rebuild
- 刪除重復文件
- 在對應重復的文件上右鍵“Options for File ‘xxx.c’…”,取消"include in Target Build"處的勾選,點擊“OK”后,rebuild
這一步不管報錯與否都不用管 - 再次勾選上,rebuild
雖然這個方法沒有解決我的問題,但是很多人好像可以解決,用以參考
(2)重命名
我的group下,.c,.h.s都是一個名字,更改.s名字后不報錯
5.關于寄存器讀取字節順序問題
我做到cr64時,需要分別讀取內存中的高 32 位和低 32 位的 CRC 多項式。然而,讀取的順序可能會受到系統的字節序影響。
字節序 (Endianness) 是決定多字節數據在內存中存儲順序的系統特性,有兩種常見的字節序:
- 大端序 (Big-endian): 高字節存儲在低地址,低字節存儲在高地址。
- 小端序 (Little-endian): 低字節存儲在低地址,高字節存儲在高地址。
ARM 處理器可以支持這兩種字節序,但默認情況下大多數 ARM 設備使用小端序。這意味著在內存中存儲的數據順序可能與我們在代碼中預期的順序相反。
- 假設你的系統是小端序
例如,如果內存中存儲的 64 位數0x42F0E1EBA9EA3693
是按小端序排列的,它在內存中的布局將是:
內存地址: 低地址 --> 高地址
數據: 93 36 EA A9 EB E1 F0 42
6.不能用M3的核,必須用M0的,接下來記錄改為M0的一些修改
6.1 Error: 1874E: Specified register list cannot be loaded or stored in target instruction setxxx
這個問題由于我PUSH和POP超過了R4-R7,改為PUSH{R4-R7, LR}
即可
pop的時候也要注意,不能直接popLR
需要
POP {R4-47}
POP {R3}
MOV PC, R3
6.2其他寄存器使用問題
用M0的核的話,大部分的指令只能訪問R4-R7,少部分指令如MOV可訪問高位寄存器R8-R12.這里整個重新規劃寄存器的使用
6.3指令變體
Cortex-M0架構中,某些指令的變體(如設置條件標志的變體)可能不可用,例如ADD指令在設置條件標志時可能需要用ADDS。
ADDS、MOVS、SUBS、LSLS、ANDS和ORRS指令:這些指令不僅執行算術或邏輯操作,還會根據結果自動更新條件標志位。使用這些指令可以替代標準的ADD、MOV、SUB、LSL、AND和ORR指令,以確保符合Cortex-M0架構的指令集。
如MOV立即數需要用MOVS,而寄存器則仍然可以使用MOV
6.4 LDRB自增
如LDRB R4, [R5], #1
數據讀取與地址更新分離:
將LDRB R4, [R5], #1
指令分成兩步:
- 先用
LDRB R4, [R5]
讀取字節 - 再用
ADDs R5, R5, #1
更新指針R5。
這樣可以避免在Cortex-M0架構中出現的尋址模式不支持問題。
6.5 BIC R0, R0, #0x100
清零問題
Cortex-M0架構不支持BIC指令直接處理立即數超過8位。
對于BIC R0, R0, #0x100
這行代碼,可以通過邏輯與的方式替換為:
ANDS R0, R0, #0xFF ; 保留低8位,將高位清零
這行指令會將R0的高位清零,僅保留低8位。這樣做可以達到與BIC R0, R0, #0x100相同的效果,同時適用于Cortex-M0架構。
6.6 CRC16的最高位清零問題
immediate 0x0000ffff out of range for this operation,permintted values are 0x00000000 to 0x000000FF
在ARM的Thumb指令集中(比如Cortex-M0),ANDS指令只允許使用8位以內的立即數(0x00到0xFF)。但是嘗試使用的立即數是0xFFFF,超出了這個范圍,所以會報錯。
可以通過先用MOVS指令將0xFFFF加載到一個寄存器,然后再使用ANDS指令來解決這個問題。
MOVS R2, #0xFF ; 將0xFF加載到R2
LSLS R2, #8 ; 將R2左移8位,得到0xFF00
ADDS R2, R2, #0xFF ; 加上0xFF,得到0xFFFF
ANDS R0, R2 ; 將R0和R2進行按位與運算
6.7 LDRB R5, [R4, #48],m0核報錯,data transfer offset 0x00000030out of range,permitter values are 0x00000000 to 0x0000001F
在 ARM Cortex-M0 架構中,數據傳輸指令(如 LDRB)的偏移量限制在 5 位以內,也就是 0x00 到 0x1F(十進制 0 到 31)。當偏移量超過這個范圍時,會報錯。
如果需要使用更大的偏移量,可以通過多次累加的方式來實現。比如,可以先將偏移量分成多步,或者先將基址寄存器加上偏移量,再執行加載指令。
解決方法
1. 分段加偏移:
可以先對寄存器進行加法運算,然后再使用 LDRB 指令。
ADDS R4, R4, #32 ; 先加32,使偏移量小于32
LDRB R5, [R4, #16] ; 現在的偏移量是16,總偏移量是48
2. 直接調整基址寄存器:
可以將偏移量直接添加到基址寄存器,然后進行加載操作。
ADDS R4, R4, #48 ; 將R4加上48,偏移基址
LDRB R5, [R4, #0] ; 使用偏移量為0的LDRB指令
6.8 復合語句拆分
ORR R0, R0, R1, LSR #31
這種形式的操作可能會報錯,因為Cortex-M0處理器在Thumb指令集中不支持使用立即數位移的形式。可以通過分步操作來實現這一功能。具體實現如下:
LSRS R1, R1, #31 ; 將R1邏輯右移31位,將結果存入R1
ORRS R0, R0, R1 ; 將R0和R1按位或,結果存入R0
7. 無法查看匯編返回結果
如果result是局部變量,有時調試器可能不容易顯示其值。可以將result聲明為全局變量來確保它在調試器中容易被訪問。
volatile int result; // 全局變量,確保在調試器中可見int main() {result = add(3, 4); // 調用匯編函數while(1); // 用于暫停程序在此處,方便調試器檢查變量值
}
參考鏈接
以上很多圖片由于keil不在當前電腦,圖片大部分借用的其他人的。
參考鏈接1
參考鏈接2
參考鏈接3
參考鏈接4
參考鏈接5