[英]Cortex-M3/M4 Timer Interrupts with ARM Assembly
我正在嘗試在使用 Cortex-M3/M4 的 CC26x2 MCU 上獲取定時器中斷。 如果我理解正確,那么為了獲得中斷,我需要將向量表中的一個條目更改為中斷處理程序的地址,然后當相應的事件發生時,它會自動轉到該地址。 所以我在組裝中執行以下步驟:
但是當我運行代碼時,中斷處理程序永遠不會被調用,即使 GP Timer 0A 中斷顯示為在 NVIC 寄存器中掛起。 顯然,相應的中斷線沒有激活(在 NVIC_IABR0 寄存器中可以看到)。 我究竟做錯了什么?
我為您列出了一個功能示例,它是基於 cortex-m4 的 TI 部件,但與您擁有的芯片/板不同,我手頭沒有那個板/芯片。 並不意味着外圍設備與您的 TI 部件相同,但 cortex-m4 處理應該相同。
我的是 MSP432P401R 啟動板。 在開始之前,您應該知道您需要啟動板的數據表、MCU 的數據表、MCU 的技術參考手冊、ARM cortex-m4 技術參考手冊和 ARMv7-m 架構參考手冊。
下面的代碼是完全獨立的,您需要添加的是過去 10 年左右的 ARM 的 gnu 工具鏈。 完全消除其他代碼的任何其他干擾。 您添加的每一行代碼都會增加風險。 如果你能讓它在這個級別上工作,那么你就知道你對 CPU 和外圍設備的了解足以向前推進,然后將它添加到一個更大的項目或使用一個庫將它添加到某些東西中,這會增加其他代碼的風險,並且可以在至少有一種溫暖的模糊感覺,你知道這個外圍設備以及你是如何使用它的,所以如果事情不起作用,那么你要么錯誤地移植了獨立的實驗,要么在更大的程序中出現了干擾。
我使用 openocd 來討論這部分,在我第一次得到這個板的時候,但是很多年前(你還能再得到這個板嗎?)沒有他們的沙箱的閃爍涉及我制作我自己的程序來做到這一點。 如果(用戶應用程序)閃存被擦除,則內置引導加載程序會運行,從而更改時鍾和其他內容。 所以我對閃存進行了編程,基本上是一個無限循環程序,它關閉 WDT 並處於無限循環中。 所以現在我可以很容易地使用 openocd 在 sram 中進行開發
重置暫停 load_image notmain.sram.elf 恢復 0x01000000
每次我想嘗試另一個實驗時重復這三行。
我傾向於從 LED 閃光燈開始,使用 systick 或帶 LED 的計時器來確定/確認內部時鍾速率,然后轉到 uart,在那里我有一個簡單的例程,可以打印大約十多行的十六進制數字代碼,不像 printf 那樣龐大,可以做我需要的一切。 當深入研究中斷時,無論您擁有多少年的經驗,這都是一個高級主題。 理想情況下,您需要一種方法來可視化正在發生的事情。 LED 在緊要關頭,但 UART 好得多。 如果可能,您希望從外圍設備獨立開始,輪詢。 在這種情況下,我使用 TIMER32 編號 1。TI 的風格是在數據表中包含內存空間地址,然后在參考手冊中如何使用它們。 TI 既有原始中斷狀態寄存器,也有屏蔽中斷狀態寄存器。
從禁用屏蔽開始學習定時器和中斷以及如何通過輪詢 RIS 寄存器清除它。
一旦你掌握了,然后啟用中斷,確保你沒有以任何方式啟用它進入處理器的核心,並查看我的情況下的屏蔽中斷狀態以及 ICSR ISRPENDING 中的第 22 位被設置。 確認您已啟用從芯片供應商邏輯進入 ARM 內核的中斷。
TI 的風格是在數據表中也有中斷表列表。 對於我使用的計時器,我看到:
INTISR[25] Timer32_INT1
所以接下來我向 NVIC_ISER0 發送垃圾郵件,打開所有位(這是一個有針對性的測試,芯片中不應該進行任何其他操作)。 我已執行 cpsid I 以將中斷排除在核心之外。
然后我在中斷后檢查 ICSR,在我的情況下,VECTPENDING 字段是 0x29 或 41,即 16+15。 這與數據表相符。 如果我現在僅將 NVID_ISER0 更改為 1<<25 並重復,則相同的答案 VECTPENDING 為 0x29。 現在可以前進了。
在這里,您可以選擇並掌握您的工具。 我繼續使用 0x00000000 的 VTOR 上的電源和閃存中的向量表並移至 sram,這是您的願望,這也是我正在開發的方式。 首先從 arm 文檔中您可以看到 VTOR 必須對齊。 我繼續將它設置為 sram 0x01000000 的開頭,並設置我的入口代碼(sram 樣式不是 flash 樣式)以類似於向量表但沒有堆棧指針 init 值,這將我們帶入示例:
SRAM
.thumb
.thumb_func
.global _start
_start:
b reset
nop
.word loop /*0x0004 1 Reset */
.word loop /*0x0008 2 NMI */
.word loop /*0x000C 3 HardFault */
.word loop /*0x0010 4 MemManage */
.word loop /*0x0014 5 BusFault */
.word loop /*0x0018 6 UsageFault */
.word loop /*0x001C 7 Reserved */
.word loop /*0x0020 8 Reserved */
.word loop /*0x0024 9 Reserved */
.word loop /*0x0028 10 Reserved */
.word loop /*0x002C 11 SVCall */
.word loop /*0x0030 12 DebugMonitor */
.word loop /*0x0034 13 Reserved */
.word loop /*0x0038 14 PendSV */
.word loop /*0x003C 15 SysTick */
.word loop /*0x0040 16 External interrupt 0 */
.word loop /*0x0044 17 External interrupt 1 */
.word loop /*0x0048 18 External interrupt 2 */
.word loop /*0x004C 19 External interrupt 3 */
.word loop /*0x0050 20 External interrupt 4 */
.word loop /*0x0054 21 External interrupt 5 */
.word loop /*0x0058 22 External interrupt 6 */
.word loop /*0x005C 23 External interrupt 7 */
.word loop /*0x0060 24 External interrupt 8 */
.word loop /*0x0064 25 External interrupt 9 */
.word loop /*0x0068 26 External interrupt 10 */
.word loop /*0x006C 27 External interrupt 11 */
.word loop /*0x0070 28 External interrupt 12 */
.word loop /*0x0074 29 External interrupt 13 */
.word loop /*0x0078 30 External interrupt 14 */
.word loop /*0x007C 31 External interrupt 15 */
.word loop /*0x0080 32 External interrupt 16 */
.word loop /*0x0084 33 External interrupt 17 */
.word loop /*0x0088 34 External interrupt 18 */
.word loop /*0x008C 35 External interrupt 19 */
.word loop /*0x0090 36 External interrupt 20 */
.word loop /*0x0094 37 External interrupt 21 */
.word loop /*0x0098 38 External interrupt 22 */
.word loop /*0x009C 39 External interrupt 23 */
.word loop /*0x00A0 40 External interrupt 24 */
.word timer32_handler /*0x00A4 41 External interrupt 25 */
.word loop /*0x00A8 42 External interrupt 26 */
.word loop /*0x00AC 43 External interrupt 27 */
.word loop /*0x00B0 44 External interrupt 28 */
.word loop /*0x00B4 45 External interrupt 29 */
.word loop /*0x00B8 46 External interrupt 30 */
.word loop /*0x00BC 47 External interrupt 31 */
.word loop /*0x00C0 48 External interrupt 32 */
reset:
cpsid i
ldr r0,stacktop
mov sp,r0
bl notmain
b loop
.thumb_func
loop: b .
.align
stacktop: .word 0x20008000
.thumb_func
.globl ienable
ienable:
cpsie i
bx lr
.thumb_func
.globl PUT8
PUT8:
strb r1,[r0]
bx lr
.thumb_func
.globl GET8
GET8:
ldrb r0,[r0]
bx lr
.thumb_func
.globl PUT16
PUT16:
strh r1,[r0]
bx lr
.thumb_func
.globl GET16
GET16:
ldrh r0,[r0]
bx lr
.thumb_func
.globl PUT32
PUT32:
str r1,[r0]
bx lr
.thumb_func
.globl GET32
GET32:
ldr r0,[r0]
bx lr
.thumb_func
.globl get_addr
get_addr:
ldr r0,=timer32_handler
bx lr
您的標題問題說程序集我使用混合 C/asm 以使其更易於閱讀/使用。 如果你願意,你當然可以在 asm 中完成你的所有工作,我的不是一個圖書館,而是一個參考,看看你是否在做同樣的事情。
不是main.c
void PUT32 ( unsigned int, unsigned int );
unsigned int GET32 ( unsigned int );
void PUT8 ( unsigned int, unsigned int );
unsigned int GET8 ( unsigned int );
void PUT16 ( unsigned int, unsigned int );
unsigned int GET16 ( unsigned int );
void ienable ( void );
#define PORT_BASE 0x40004C00
#define PAOUT_L (PORT_BASE+0x02)
#define PADIR_L (PORT_BASE+0x04)
#define WDTCTL 0x4000480C
#define TIMER32_BASE 0x4000C000
#define ICSR 0xE000ED04
#define SCR 0xE000ED10
#define VTOR 0xE000ED08
#define NVIC_ISER0 0xE000E100
#define NVIC_IABR0 0xE000E300
#define NVIC_ICPR0 0xE000E280
volatile unsigned int ticks;
void timer32_handler ( void )
{
ticks^=1;
PUT8(PAOUT_L,ticks);
PUT32(TIMER32_BASE+0x0C,0);
PUT32(NVIC_ICPR0,1<<25);
}
void notmain ( void )
{
PUT16(WDTCTL,0x5A84);
PUT8(PADIR_L,GET8(PADIR_L)|0x01);
ticks=0;
PUT32(VTOR,0x01000000);
PUT32(NVIC_ISER0,1<<25);
ienable();
PUT32(TIMER32_BASE+0x08,0xA4);
}
文件
MEMORY
{
ram : ORIGIN = 0x01000000, LENGTH = 0x3000
}
SECTIONS
{
.text : { *(.text*) } > ram
.rodata : { *(.rodata*) } > ram
.bss : { *(.bss*) } > ram
}
這就是本示例 100% 的源代碼,您需要做的就是構建它:
arm-none-eabi-as --warn sram.s -o sram.o
arm-none-eabi-gcc -Wall -O2 -nostdlib -nostartfiles -ffreestanding -mcpu=cortex-m4 -mthumb -c notmain.c -o notmain.o
arm-none-eabi-ld -T sram.ld sram.o notmain.o -o notmain.sram.elf
arm-none-eabi-objdump -D notmain.sram.elf > notmain.sram.list
arm-none-eabi-objcopy notmain.sram.elf notmain.sram.bin -O binary
過去十年左右的任何 gnu gcc/binutils 交叉編譯器都應該可以工作,arm-none-eabi 風格以及 arm-whatever-linux 風格,此代碼不受差異的影響。
架構參考手冊顯示向量表中的第一個條目是堆棧指針初始化值,您可以選擇使用或不使用它,但它是偏移量 0x0000。 然后異常啟動異常 1 被重置,2 是 NMI,依此類推。 異常 16 是外部(到 arm 核心)中斷 0 開始和下線的地方,所以中斷 25 在這里
.word timer32_handler /*0x00A4 41 外部中斷 25 */
在向量表中的偏移量 0xA4 處。 如果你很絕望或者芯片沒有很好的記錄,那么要么在掛起狀態之間,要么只是簡單地向向量表發送垃圾郵件,所有條目都指向處理程序,你可以縮小偏移/中斷號。 (當中斷到來時點亮 LED 或其他東西,然后進入無限循環,對於現實世界的東西來說是一個可怕的處理程序,但對於逆向工程一個記錄不佳的部分來說很好)。
在您執行任何操作之前確認您構建正確,入口點應該是您期望的代碼,在這種情況下是 sram 我有入口點作為指令(當我更改 VTOR 時,它會跳過我即將成為向量表)
Disassembly of section .text:
01000000 <_start>:
1000000: e060 b.n 10000c4 <reset>
1000002: 46c0 nop ; (mov r8, r8)
1000004: 010000d1 ldrdeq r0, [r0, -r1]
1000008: 010000d1 ldrdeq r0, [r0, -r1]
100000c: 010000d1 ldrdeq r0, [r0, -r1]
1000010: 010000d1 ldrdeq r0, [r0, -r1]
1000014: 010000d1 ldrdeq r0, [r0, -r1]
1000018: 010000d1 ldrdeq r0, [r0, -r1]
100001c: 010000d1 ldrdeq r0, [r0, -r1]
1000020: 010000d1 ldrdeq r0, [r0, -r1]
1000024: 010000d1 ldrdeq r0, [r0, -r1]
1000028: 010000d1 ldrdeq r0, [r0, -r1]
100002c: 010000d1 ldrdeq r0, [r0, -r1]
1000030: 010000d1 ldrdeq r0, [r0, -r1]
1000034: 010000d1 ldrdeq r0, [r0, -r1]
1000038: 010000d1 ldrdeq r0, [r0, -r1]
100003c: 010000d1 ldrdeq r0, [r0, -r1]
1000040: 010000d1 ldrdeq r0, [r0, -r1]
1000044: 010000d1 ldrdeq r0, [r0, -r1]
1000048: 010000d1 ldrdeq r0, [r0, -r1]
100004c: 010000d1 ldrdeq r0, [r0, -r1]
1000050: 010000d1 ldrdeq r0, [r0, -r1]
1000054: 010000d1 ldrdeq r0, [r0, -r1]
1000058: 010000d1 ldrdeq r0, [r0, -r1]
100005c: 010000d1 ldrdeq r0, [r0, -r1]
1000060: 010000d1 ldrdeq r0, [r0, -r1]
1000064: 010000d1 ldrdeq r0, [r0, -r1]
1000068: 010000d1 ldrdeq r0, [r0, -r1]
100006c: 010000d1 ldrdeq r0, [r0, -r1]
1000070: 010000d1 ldrdeq r0, [r0, -r1]
1000074: 010000d1 ldrdeq r0, [r0, -r1]
1000078: 010000d1 ldrdeq r0, [r0, -r1]
100007c: 010000d1 ldrdeq r0, [r0, -r1]
1000080: 010000d1 ldrdeq r0, [r0, -r1]
1000084: 010000d1 ldrdeq r0, [r0, -r1]
1000088: 010000d1 ldrdeq r0, [r0, -r1]
100008c: 010000d1 ldrdeq r0, [r0, -r1]
1000090: 010000d1 ldrdeq r0, [r0, -r1]
1000094: 010000d1 ldrdeq r0, [r0, -r1]
1000098: 010000d1 ldrdeq r0, [r0, -r1]
100009c: 010000d1 ldrdeq r0, [r0, -r1]
10000a0: 010000d1 ldrdeq r0, [r0, -r1]
10000a4: 010000fd strdeq r0, [r0, -sp]
10000a8: 010000d1 ldrdeq r0, [r0, -r1]
10000ac: 010000d1 ldrdeq r0, [r0, -r1]
10000b0: 010000d1 ldrdeq r0, [r0, -r1]
10000b4: 010000d1 ldrdeq r0, [r0, -r1]
10000b8: 010000d1 ldrdeq r0, [r0, -r1]
10000bc: 010000d1 ldrdeq r0, [r0, -r1]
10000c0: 010000d1 ldrdeq r0, [r0, -r1]
010000c4 <reset>:
10000c4: b672 cpsid i
10000c6: 4803 ldr r0, [pc, #12] ; (10000d4 <stacktop>)
10000c8: 4685 mov sp, r0
10000ca: f000 f835 bl 1000138 <notmain>
10000ce: e7ff b.n 10000d0 <loop>
010000d0 <loop>:
10000d0: e7fe b.n 10000d0 <loop>
所有條目都是處理程序的地址 ORRed,根據需要為 1。
在 gnu 匯編程序通知中,要使循環正常工作,您需要在標簽之前加上 .thumb_func 以告訴工具下一個標簽是一個函數(因此在我詢問其地址時設置 lsbit)
.thumb_func
loop: b .
如果沒有 .thumb_func 那里的地址將是錯誤的,並且處理程序不會被調用,另一個異常將再次發生,如果該處理程序地址錯誤,則游戲結束。
如果您想手動構建表,請了解在編寫此答案時,gnu 中存在一個未決錯誤,表明 ADR 無法正常工作,這是一條偽指令,並且在架構參考手冊中的文檔記錄不足,因此已啟動對於定義匯編語言的匯編程序(匯編由工具定義,而不是目標或架構,機器代碼由架構定義,匯編語言對所有人都是免費的)。 在 gnu 匯編程序的情況下,文檔聲稱當設置互通時,它將提供一個帶有 lsbit 集的地址,以便可以使用 bx rd,但對於前向引用的標簽來說這是錯誤的。 其他匯編器可以隨意使用 ADR,您應該檢查它們的定義。 如果您覺得需要使用 ADR(不要添加或),如果對 lsbit 有疑問,我當然會避免使用該指令,例如:
.thumb_func
.globl get_addr
get_addr:
ldr r0,=timer32_handler
bx lr
010000f4 <get_addr>:
10000f4: 4800 ldr r0, [pc, #0] ; (10000f8 <get_addr+0x4>)
10000f6: 4770 bx lr
10000f8: 010000fd strdeq r0, [r0, -sp]
效果很好(注意這是反匯編,strdeq 只是試圖理解值 010000fd 的反匯編器,這是您應該關注的內容,工具為我完成了工作,以我需要的正確形式提供地址。仍然依靠工具並知道/希望它們起作用,但使用至少與 gas/binutils 有/確實起作用的東西。
為了安全起見,我的引導帶從禁用中斷開始。 設置堆棧指針並啟動 C 入口點。 由於我沒有 .data 也不需要 .bss 歸零,因此鏈接器腳本和引導程序是微不足道的。 我有多種抽象讀/寫訪問的原因,您可以按照自己的方式進行(請注意,流行的方式不一定符合 C,並希望這些習慣/時尚有一天會失敗)。
對於這些部件(通常是 TI 似乎),您希望盡早禁用看門狗定時器,否則重置該部件會讓您發瘋,試圖弄清楚發生了什么。
我的板上有一個 LED,我將該端口引腳設置為輸出。
我有一個用於跟蹤中斷的變量,因此我可以在每次中斷時打開/關閉 LED。
由於我讓工具完成工作,因此我將 VTOR 設置為 sram 的開頭,這是一個正確對齊的地址。
我在 NVIC 中啟用中斷
我啟用對核心的中斷
我設置了外圍設備並啟用了它的中斷。
由於我編寫了引導程序並且知道它在 C 入口點函數返回時只是進入無限循環,因此我可以返回並將處理器留在該無限循環中等待中斷和中斷處理程序來完成其余的工作。
在處理程序中,我從外圍開始到核心,YMMV 如果你以另一種方式這樣做,清除中斷(在切換 LED 之后)。
就是這樣。 聽起來您正在執行這些步驟,但是由於您沒有提供查看您真正在做什么所需的信息,因此只能猜測哪個步驟丟失或值錯誤或位置錯誤。
我不能強調,在任何可能的芯片/處理器中,盡可能多地使用輪詢來嘗試使用目標測試來找出外設並跟蹤中斷,無論中斷門有多少層,只有在中斷門之后才允許中斷進入核心您已經掌握了盡可能多的內容,而不會實際導致處理器中斷。 一次完成這一切會使開發平均花費多倍的時間,並且通常會更加痛苦。
我希望這個長答案可以觸發對您的代碼進行簡單的三秒修復,如果沒有,您至少可以嘗試從中開發對您的芯片的測試。 我還沒有發布我用來發現這部分工作原理的啟用 uart 的版本,但是使用該路徑很容易找出外圍設備,然后將中斷推向核心,准備好創建並清除中斷,然后最后啟用中斷進入核心並且它第一次工作(那里有點運氣,並不總是那樣發生)。
編輯
但是,如果我不將向量表重新分配到 SRAM 中,如何將相應的中斷路由到其處理程序?
您只需將標簽添加到向量表
.thumb
.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hello
.word world
.thumb_func
reset: b .
.thumb_func
hello: b .
.thumb_func
world: b .
arm-none-eabi-as flash.s -o flash.o
arm-none-eabi-ld -Ttext=0 flash.o -o flash.elf
arm-none-eabi-objdump -D flash.elf
flash.elf: file format elf32-littlearm
Disassembly of section .text:
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000011 andeq r0, r0, r1, lsl r0
8: 00000013 andeq r0, r0, r3, lsl r0
c: 00000015 andeq r0, r0, r5, lsl r0
00000010 <reset>:
10: e7fe b.n 10 <reset>
00000012 <hello>:
12: e7fe b.n 12 <hello>
00000014 <world>:
14: e7fe b.n 14 <world>
無需復制和修改向量表一切都在閃存中。
我想知道為什么你不知道你的處理程序在構建時是什么,而必須在運行時添加東西,這是一個 MCU。 也許你有一個通用的引導程序? 但在這種情況下,您不需要保留任何先前的處理程序。 如果您必須將表移動到 sram 並在運行時添加一個很好的條目,但您必須確保 1) VTOR 由您正在使用的內核和該內核的實現支持 2) 根據此規則,您的條目是正確的建築學。
弄錯任何一個,它就行不通。 然后當然還有外圍設置,通過門到內核的中斷啟用,通過內核到處理器的啟用和處理清除處理程序中的中斷,因此它不會無限地觸發。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.