assembly - ARM 汇编的 Cortex-M3/M4 定时器中断
问题描述
我正在尝试在使用 Cortex-M3/M4 的 CC26x2 MCU 上获得定时器中断。如果我理解正确,那么为了获得中断,我需要将向量表中的一个条目更改为中断处理程序的地址,然后当相应的事件发生时,它会自动转到该地址。所以我在组装中执行以下步骤:
- 将向量表重定位到 SRAM(首先复制其每个条目,然后更改 VTOR 寄存器)
- 将事件处理程序地址写入其“GP Timer 0A Interrupt”条目
- 配置 GP Timer 0A 以产生中断
- 清除 NVIC_ICPR0 中的未决中断
- 在 NVIC_ISER0 中启用 GP Timer 0A 中断
- 使用 CPSIE I 启用中断
但是当我运行代码时,中断处理程序永远不会被调用,即使 GP Timer 0A 中断在 NVIC 寄存器中显示为挂起。显然,相应的中断线没有激活(可以在 NVIC_IABR0 寄存器中看到)。我究竟做错了什么?
解决方案
我为您提供了一个功能示例,它是基于 cortex-m4 的 TI 部件,但与您拥有的芯片/板不同,我手边没有那个板/芯片。并不意味着外围设备与您的 TI 部件相同,但 cortex-m4 处理应该相同。
我的是 MSP432P401R 启动板。您应该知道您需要启动板的数据表、MCU 的数据表、MCU 的技术参考手册、ARM cortex-m4 技术参考手册和 ARMv7-m 架构参考手册,然后再开始。
下面的代码是完全独立的,您需要添加的只是 ARM 过去 10 年左右的 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 样式)设置为类似于向量表但没有堆栈指针初始值,这将我们带入示例:
sram.s
.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);
}
sram.ld
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 有疑问 ORR 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 语言,并期望这些习惯/FAD 有一天会失败)。
对于这些部件(通常是 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)您的条目根据规则是正确的建筑学。
弄错其中任何一个,它都不会起作用。然后当然还有外围设置,通过门到内核启用中断,通过内核启用到处理器并在处理程序中处理清除中断,这样它就不会无限触发。
推荐阅读
- python - 我如何创建一个刽子手计时器,以便它在 python 中每 10 秒扣除一次用户的分数?
- javascript - 如何在expo react native中将图像上传到firebase
- c++ - 是否有任何 C++ 函数可以对哈希表进行排序?
- android - 启用 PRODUCT_FULL_TREBLE_OVERRIDE 标志会影响 SELINUX 策略吗?
- python - Xpath没有给出结果scrapy python
- php - soapclient 请求 https 服务器错误,“您正在对启用 SSL 的服务器端口使用纯 HTTP”
- javascript - 如何在 Yarn 工作区的一个 package.json 文件中安装包?
- java - Spring批处理JobExecutionListener不起作用
- azure-logic-apps - ISE 中的逻辑应用程序 - 事件触发器(存储)不会触发,而 ISE 外部的逻辑应用程序会针对同一事件触发
- c++ - 如何创建从另一个线程到 Qt5 GUI 线程的 qDebug 信号