我正在尝试在使用Cortex-M3/M4的CC26x2 MCU上获取计时器中断。如果我理解正确,那么为了获得中断,我需要将向量表中的一个条目更改为中断处理程序的地址,然后当相应的事件发生时,它将自动转到该地址。因此,我在组装中执行以下步骤:
- 将矢量表重新定位到SRAM(首先复制其每个条目,然后更改VTOR寄存器)
- 将事件处理程序地址写入其"GP Timer 0A Interrupt"条目
- 配置GP定时器0A以生成中断
- 清除NVIC_ICPR0中的挂起中断
- 启用NVIC_ISER0中的GP定时器0A中断
- 使用CPSIE I启用中断
但是当我运行代码时,中断处理程序永远不会被调用,即使GP Timer 0A中断在NVIC寄存器中显示为挂起。显然,相应的中断线没有激活(可以在NVIC_IABR0寄存器中看到)。我做错了什么?
我给你举了一个函数的例子,它是一个TI部分,它是基于cortex-m4的,但不是你的芯片/板,我手头没有那个板/芯片。这并不意味着外围设备与TI部分相同,但cortex-m4的处理应该相同。
我的是MSP432P401R发射台。您应该知道,在启动之前,您需要启动板的数据表、MCU的数据表,MCU的技术参考手册、ARM cortex-m4技术参考手册和ARMv7-m体系结构参考手册。
下面的代码是完全独立的,您只需要为ARM添加过去10年左右的gnu工具链。完全消除其他代码中的任何其他干扰。您添加的每一行代码都会增加风险。如果你能让它在这个级别上工作,那么你就知道你对CPU和外围设备有足够的了解,可以继续前进,然后把它添加到一个更大的项目中,或者使用一个库将它添加到某个东西中,这会增加其他代码的风险,并且至少可以有一种温暖而模糊的感觉,你知道这个外设以及你是如何使用它的,所以如果事情不起作用,那么你要么把独立的实验移植错了,要么更大的程序中有什么东西在干扰。
我用openocd来谈论这一部分,但多年前我第一次得到这个板的时候(你还能再得到这个板吗?)在没有沙盒的情况下闪烁,这涉及到我自己制作程序来做到这一点。如果(用户应用程序)闪存被擦除,则运行内置的引导加载程序,从而更改时钟和其他内容。因此,我已经将闪存编程为基本上有一个无限循环程序,它关闭WDT并处于无限循环中。所以现在我可以使用openocd在sram中进行开发,非常容易
重置暂停加载图像notmain.sram.elf恢复0x01000000
每次我想尝试另一个实验时,重复这三行。
我倾向于从led闪光灯开始,使用systick或带led的计时器来确定/确认内部时钟频率,然后转到uart,在那里我有一个简单的例程,可以打印十六进制数字,它有十几行左右的代码,不像printf那样庞大,可以完成我需要的一切。当深入到中断中时,无论你有多少年的经验,这都是一个高级话题。理想情况下,你需要一种方法来可视化正在发生的事情。LED在紧要关头,但uart要好得多。如果可能的话,您希望从外围设备独立开始,轮询。在这种情况下,我使用的是TIMER32数字1。TI的风格是在数据表中列出内存空间地址,然后在参考手册中说明如何使用这些地址。TI同时具有原始中断状态寄存器和屏蔽中断状态寄存器。
从禁用掩码开始,学习定时器和中断,以及如何通过轮询RIS寄存器来清除它。
一旦你掌握了这一点,那么就启用中断,确保你没有以任何方式将其启用到处理器的核心,并查看我的情况下的屏蔽中断状态以及ICSR ISRPENDING中的位22。确认您已经从芯片供应商的逻辑中启用了进入ARM内核的中断。
TI的风格是在数据表中也有中断表列表。对于我正在使用的计时器,我看到:
INTISR[25]时间r32_INT1
所以接下来我垃圾邮件NVIC_ISER0,打开所有位(这是一个有针对性的测试,芯片中不应该发生任何其他事情)。我已经执行了cpsid I,以将中断排除在核心之外。
然后我检查中断后的ICSR,在我的情况下,VECTPENDING字段是0x29或41,即16+15。与数据表匹配。如果我现在将NVID_ISER0改变为1<lt;25,重复,相同的答案VECTPENDING为0x29。现在可以继续前进了。
这里是你可以选择的地方,你必须掌握你的工具。我继续使用0x00000000的VTOR和flash中的矢量表,并转移到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中完成你的全部,我的不是一个图书馆,而是一个参考,看看你是否在做同样的事情。
notmain.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-and-what-linux风格,这些代码不受差异的影响。
体系结构参考手册显示,向量表中的第一个条目是堆栈指针初始化值,您可以选择是否使用该值,但偏移量为0x0000。然后异常开始异常1被重置,2是NMI,依此类推。异常16是外部(到臂核心)中断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汇编程序中,为了让循环正常工作,你需要在标签前面加上.tumb_func,告诉工具下一个标签是一个函数(所以当我询问lsbit的地址时设置它)
.thumb_func
loop: b .
如果没有.tumb_func,地址将是错误的,处理程序将不会被调用。另一个异常将再次发生,如果处理程序地址是错误的话,那就真的结束了。
如果您想手动构建表,请理解在编写此答案时,gnu有一个未决的错误,表明ADR工作不正常,它是一条伪指令,在体系结构参考手册中记录得很差,因此由汇编程序来定义汇编语言(汇编是由工具定义的,而不是目标或体系结构,机器代码是由体系结构定义的,汇编语言是免费的)。在gnu汇编程序的情况下,文档声称,当设置interwork时,它将提供一个带有lsbit集的地址,以便可以使用bx-rd,但对于前向引用的标签,这是错误的。其他汇编程序可以按照自己的意愿使用ADR,您应该检查它们的定义。当对lsbit的ORR有疑问时,如果你觉得有必要使用ADR(不要添加,或者),我肯定会避免使用该指令,例如:
.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)根据该体系结构的规则,您的条目是正确的。
如果其中任何一个错了,它都不会起作用。当然还有外围设置,通过门到核心启用中断,通过核心到处理器启用中断,并在处理程序中处理清除中断,使其不会无限触发。