Cortex-M3/M4定时器中断与ARM组件



我正在尝试在使用Cortex-M3/M4的CC26x2 MCU上获取计时器中断。如果我理解正确,那么为了获得中断,我需要将向量表中的一个条目更改为中断处理程序的地址,然后当相应的事件发生时,它将自动转到该地址。因此,我在组装中执行以下步骤:

  1. 将矢量表重新定位到SRAM(首先复制其每个条目,然后更改VTOR寄存器)
  2. 将事件处理程序地址写入其"GP Timer 0A Interrupt"条目
  3. 配置GP定时器0A以生成中断
  4. 清除NVIC_ICPR0中的挂起中断
  5. 启用NVIC_ISER0中的GP定时器0A中断
  6. 使用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)根据该体系结构的规则,您的条目是正确的。

如果其中任何一个错了,它都不会起作用。当然还有外围设置,通过门到核心启用中断,通过核心到处理器启用中断,并在处理程序中处理清除中断,使其不会无限触发。

最新更新