assembly Cortex-M3/M4定时器中断(带ARM组件)

yhqotfr8  于 2022-11-13  发布在  其他
关注(0)|答案(1)|浏览(202)

我正在尝试在使用Cortex-M3/M4的CC 26 x2 MCU上获取定时器中断。如果我理解正确的话,那么为了获取中断,我需要将向量表中的一个条目更改为中断处理程序的地址,然后当相应的事件发生时,它会自动转到该地址。因此,我在汇编中执行以下步骤:
1.将向量表重定位到SRAM(首先复制其每个条目,然后更改VTOR寄存器)
1.将事件处理程序地址写入其“通用定时器0A中断”条目
1.配置通用定时器0A以生成中断
1.清除NVIC_ICPR0中的挂起中断
1.使能NVIC_ISER0中的通用定时器0A中断
1.通过CPSIE I使能中断
但是,当我运行代码时,中断处理程序从未被调用,即使NVIC寄存器中显示GP Timer 0A中断挂起。显然,相应的中断线未激活(从NVIC_IABR0寄存器中可以看出)。我做错了什么?

j8ag8udp

j8ag8udp1#

我为您提供了一个功能示例,它是一个TI器件,基于cortex-m4,但与您的芯片/电路板不同。我手边没有该电路板/芯片。这并不意味着外设与您的TI器件相同,但cortex-m4处理应该相同。
我的产品是MSP 432 P401 R Launchpad。您应该知道,在开始之前,您需要Launchpad的数据手册、MCU的数据手册、MCU的技术参考手册、ARM cortex-m4技术参考手册和ARMv7-m架构参考手册。
下面的代码是完全独立的,你需要添加的是一个过去10年左右ARM的gnu工具链。完全消除了其他代码的干扰。你添加的每一行代码都增加了风险。如果你能让它在这个级别上工作,那么你就知道你对CPU和外设足够了解,可以继续前进。然后将其添加到更大的项目中,或者使用库将其添加到某个项目中,则会增加其他代码的风险,至少可以有一个温暖的模糊的感觉,你知道这个外围设备和你是如何使用它,所以如果事情不工作,然后你要么移植的立场-单独的实验错误或更大程序中的某个东西正在干扰。
我使用openocd与该部分进行通信,在我第一次得到这个董事会的时候,然而很多年前(你甚至可以得到这个董事会任何更多?)闪烁没有他们的沙盒涉及我使我自己的程序来做到这一点。如果(用户应用程序)闪存被擦除,然后内置的引导加载程序运行,这改变了时钟和其他事情。所以我把flash编程为一个基本上是无限循环的程序,它关闭WDT并处于无限循环中。
复位停止load_image不是main.sram.elf恢复0x 01000000
每次我想做另一个实验时重复这三行。
我倾向于从一个LED闪烁灯开始,使用带有LED的系统棒或计时器来确定/确认内部时钟频率,然后转到UART,在那里我有一个简单的例程,打印十六进制数字它有十几行左右的代码,不像printf那样可怕的庞大,做我需要的一切。当深入到中断,无论你有多少几十年的经验是一个先进的主题。理想情况下,你需要一种方法来可视化正在发生的事情。LED在紧要关头,但UART要好得多。如果可能的话,你想从独立的外设开始,在本例中,我使用的是TIMER 32编号1。TI的风格是在数据手册中提供存储器空间地址,然后在参考手册中说明如何使用它们。TI具有原始中断状态寄存器和屏蔽中断状态寄存器。
从禁用屏蔽开始,学习定时器和中断,以及如何通过轮询RIS寄存器来清除它。
一旦您掌握了该功能,然后启用中断,确保您没有以任何方式将其启用到处理器内核中,并看到屏蔽的中断状态(在我的示例中)以及ICSR ISRPENDING中的第22位均已置位。确认您已从芯片供应商逻辑将中断启用到ARM内核中。
TI的风格是在数据手册中也有中断表列表。对于我使用的定时器,我看到:
INTISR[25]定时器32_INT1
因此,接下来我发送垃圾邮件给NVIC_ISER0,打开所有位(这是一个有针对性的测试,芯片中不应该有其他操作)。我已经执行了cpsid I,以防止中断进入内核。
然后,我在中断后检查ICSR,在我的情况下,VECTPENDING字段为0x 29或41,即16+15。这与数据表匹配。如果我现在将NVID_ISER0仅更改为1〈〈25并重复,则相同的答案VECTPENDING为0x 29。现在可以继续。
这里是你有选择的地方,你必须掌握你的工具。我继续,并跳过使用0x 00000000的VTOR和闪存中的向量表,并转移到sram,这是你的愿望,也是我正在开发的方式。首先,从手臂文档,你看到VTOR必须对齐。我继续,并设置为sram 0x 01000000的开始。并设置我的入口代码(SRAM样式而非闪存样式)以类似于向量表,但没有堆栈指针init值,这将我们带入示例:
静态存储器

.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-whatever-linux风格,这段代码不受差异的影响。

体系结构参考手册显示,向量表中的第一个条目是堆栈指针初始化值,您可以选择是否使用该值,但它是偏移量0x 0000。然后,异常开始异常1复位,2是NMI,依此类推。异常16是外部(ARM内核)中断0开始的位置,并沿线路向下,因此中断25到达此处
.word定时器32_处理程序/* 0x 00 A4 41外部中断25 */
如果你很绝望或者芯片没有很好的记录,那么要么在挂起状态之间,要么简单地用指向处理程序的所有条目来垃圾发送向量表,你可以缩小偏移/中断号。(当中断到来时点亮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>

所有条目都是处理程序的地址,根据需要与1进行或运算。
在gnu汇编程序中,注意要使get循环正常工作,需要在标签前面加上.thumb_func,以告诉工具下一个标签是函数(因此,当我询问它的地址时,设置lsbit)

.thumb_func
loop:   b .

如果没有.thumb_func,则地址将是错误的,处理程序将不会被调用,另一个异常将再次发生,如果处理程序地址是错误的,则游戏将真正结束。
如果您想手动构建该表,请了解在编写此答案时,gnu中存在一个未决错误,表明ADR无法正常工作,它是一个伪指令,在架构参考手册中的记录很差,因此它取决于定义汇编语言的汇编程序(汇编是由工具定义的,而不是目标或体系结构,机器码是由体系结构定义的,在gnu汇编程序的情况下,文档声称当设置了互配时,它将提供一个设置了lsbit的地址,以便可以使用bxrd,但对于前向引用标签,这是假的。其他汇编程序可以使用ADR,但他们希望,你应该检查他们的定义。当不确定ORR的lsbit,如果你觉得需要使用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只是反汇编程序试图理解值010000 fd,这是你应该关注的,工具为我做了工作,提供了我需要的正确形式的地址。仍然依赖于工具,知道/希望它们工作,但使用的东西至少可以与gas/binutils一起工作。
请注意,为了安全起见,我的 Boot 程序从禁用中断开始。设置堆栈指针并启动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)根据该体系结构的规则,您的输入正确。
当然,还有外设设置,使能通过门到内核的中断,使能通过内核到处理器的中断,以及处理清除处理程序中的中断,这样它就不会无限触发。

相关问题