[英]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.