我的SVC(Supervisor Call)代码不起作用,我也不清楚为什么

编程语言 2026-07-11
#include "hardware/regs/addressmap.h"
#include "hardware/regs/m0plus.h"

.syntax unified             @ Specify unified assembly syntax
.cpu cortex-m0plus          @ Specify CPU type is Cortex M0+
.thumb                      @ Specify thumb assembly for RP2040
.global main_asm            @ Provide program starting address to the linker
.align 4                    @ Specify code alignment
.equ SLEEP_TIME, 500        @ Specify the sleep time (in ms)
.equ LED_GPIO_PIN, 25       @ Specify the pin that the LED is connected to
.equ LED_GPIO_OUT, 1        @ Specify the direction of the GPIO pin
.equ LED_VALUE_ON, 1        @ Specify the value that turns the LED "on"
.equ LED_VALUE_OFF, 0       @ Specify the value that turns the LED "off"
.equ SVC_ISR_OFFSET, 0x2C   @ The SVC is entry 11 in the vector table
.equ SVC_MAX_INSTRS, 0x01   @ Maximum allowed SVC subroutines

@ Entry point to the ASM portion of the program
main_asm:
    bl init_gpio_led        @ Initialise the GPIO LED pin
    bl install_svc_isr      @ Install the SVC interrupt service routine
loop:
    svc #0                  @ Call the SVC ISR with value 0 (turns on LED)
    nop                     @ Add a no-op instruction for alignment after SVC
    bl do_sleep             @ Short pause before proceeding
    svc #1                  @ Call the SVC ISR with value 1 (turns off LED)
    nop                     @ Add a no-op instruction for alignment after SVC
    bl  do_sleep            @ Add a short pause before proceeding
    b   loop                @ Always jump back to the start of the loop

@ Subroutine used to introduce a short delay in the application
do_sleep:
    ldr     r0, =SLEEP_TIME             @ Set the value of SLEEP_TIME we want to wait for
    bl      sleep_ms                    @ Sleep until SLEEP_TIME has elapsed then toggle the LED GPIO pin
    bx lr

@ Subroutine used to initialise the PI Pico built-in LED
init_gpio_led:
    movs    r0, #LED_GPIO_PIN           @ This value is the GPIO LED pin on the PI PICO board
    bl      asm_gpio_init               @ Call the subroutine to initialise the GPIO pin specified by r0
    movs    r0, #LED_GPIO_PIN           @ This value is the GPIO LED pin on the PI PICO board
    movs    r1, #LED_GPIO_OUT           @ We want this GPIO pin to be setup as an output pin
    bl      asm_gpio_set_dir            @ Call the subroutine to set the GPIO pin specified by r0 to state specified by r1
    bx lr

@ Subroutine used to install the SVC interrupt service handler
install_svc_isr:
    ldr r2, =(PPB_BASE + M0PLUS_VTOR_OFFSET)    @ Loads the Address of the vector table offset register
    ldr r1, [r2]                                @ Load the base address of the interrupt vector table
    movs r2, #SVC_ISR_OFFSET                    @ Load the offset of the SVC entry within the vector table
    add r2, r1                                  @ Calculate the address of the SVC vector entry
    ldr r0, =svc_isr                            @ Load the address of the SVC interrupt service routine
    str r0, [r2]                                @ Store the ISR address into the SVC vector table entry
    bx lr                                       @ Return from subroutine

@ SVC interrupt service handler routine
.thumb_func @ Required for all interrupt service routines
svc_isr:
    push {lr}                                   @ Save the return address
    ldr r0, [sp, #0x1C]                         @ Load ther stacked PC values
    subs r0, #0x2                               @ Move back 2 bytes to get the address of the SVC instruction
    ldrb r0, [r0]                                @ Load the address of the SVC instruction
    ldr r1, =#0xFF                              @ Load mask value to extract the SVC immediate field
    ands r0, r1                                 @ Mask upper bits, leaving only the SVC number (lower 8 bits)
    cmp r0, #SVC_MAX_INSTRS                     @ Compare SVC number with maximum allowed SVC index
    bhi svc_done                                @ If SVC number is invalid, branch to exit handler
    adr r1, svc_jmptbl                          @ Load the address of the SVC jump table
    lsls r0, #2                                 @ Multiply SVC number by 4 (each table entry is 4 bytes)
    ldr r1, [r1, r0]                            @ Load the address of the corresponding SVC subroutine
    mov pc, r1                                  @ Jump to the selected SVC subroutine

svc_done:
    pop {pc}                                    @ Restore the return address and return from the interrupt service routine

@ First function of SVC subroutine - turn on the LED
.thumb_func
svc_num0:
    movs r0, #LED_GPIO_PIN
    movs r1, #LED_VALUE_ON
    bl asm_gpio_put
    b svc_done                                  @ Branch back to the main ISR when done

@ Second function of SVC subroutine - turn off the LED
.thumb_func
svc_num1:
    movs r0, #LED_GPIO_PIN
    movs r1, #LED_VALUE_OFF
    bl asm_gpio_put
    b svc_done                                  @ Branch back to the main ISR when done

@ SVC function entry jump table.
.align 2
svc_jmptbl:
    .word svc_num0                              @ Entry zero goes to SVC function #0.
    .word svc_num1                              @ Entry one goes to SVC function #1.
    .word 0                                     @ Null termination of the jump table.

@ Set data alignment
.data
    .align 4

我正在尝试在RP2040(Cortex-M0+)上用汇编实现一个SVC。目标是使用 svc #0svc #1 通过在SVC中断服务例程内的跳转表来切换板载LED。

程序编译通过,但当我点击运行时似乎没起作用,因为树莓派Pico板上的LED似乎没有闪烁。因此,我不知道问题出在哪里。我已经研究了好几个小时,似乎把SVC ISR搞砸了,或者跳转表没有起作用

预期行为

  • svc #0 应该调用 svc_num0 并将LED点亮。
  • svc #1 应该调用 svc_num1 并将LED关闭。

实际行为

在执行 svc 指令时,LED未按预期切换。

如果有任何建议,帮助我让代码能够正常工作,将不胜感激。

解决方案

你的代码中存在一些错误的假设。不过,为了回答你的问题,我会提供我对你函数的实现,因为你没有给出全部代码。 其中一个问题,已在注释中提到,是在 do_sleep 函数中忘记把链接寄存器推入栈。

这一切都归结于ARM汇编中函数的调用方式。当你调用一个函数时,链接寄存器(lr)会保存被调用之后的一条指令的地址。例如:

    mov     r0, #0
    bl      foo
    mov     r1, #1  @ <— address of this instruction is stored in lr

现在,ARM汇编中函数返回基本上有两种方式。第一种,在你没有继续调用后续函数的情况下,就是简单地跳回到存储在 lr 中的地址。然而,当你调用后续函数时,你必须把链接寄存器压入栈以便返回到它,这也是在开始处使用 push {lr}、在结尾使用 pop {pc} 的原因。

你代码的第二个问题可能是假设向量表已经放置正确。重启后,VTOR寄存器中没有值。因此,尝试读取当前向量表偏移并在那里写入一个值可能导致未定义行为。你必须手动准备向量表,将其放在RAM的某个位置(地址必须能被256整除!!!),然后再执行你已经完成的操作。

还有一些小问题,比如不必要的指令,或把 movs 错误地当成 ldr 的指令,反之亦然。

总之,我给出你代码的最小可工作示例,并做了一些必要的调整。我还删除了向向量表写入的函数,改为直接放置svc_isr处理程序。

.syntax unified
.cpu cortex-m0plus
.thumb

.equ LED_GPIO_PIN, 25
.equ SVC_ISR_OFFSET, 0x2C   @ The SVC is entry 11 in the vector table
.equ SVC_MAX_INSTRS, 0x01   @ Maximum allowed SVC subroutines

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
.section .vectors, "ax"
.align 2
.global vector_table
.thumb_func
vector_table:
.word 0x20040000        @
.word reset             @
.word 0                 @ isr_nmi
.word 0                 @ isr_hardfault
.word 0                 @ Reserved, should never fire
.word 0                 @ Reserved, should never fire
.word 0                 @ Reserved, should never fire
.word 0                 @ Reserved, should never fire
.word 0                 @ Reserved, should never fire
.word 0                 @ Reserved, should never fire
.word 0                 @ Reserved, should never fire
.word svc_isr           @
.word 0                 @ Reserved, should never fire
.word 0                 @ Reserved, should never fire
.word 0
.word 0
irq_1_31: .fill 32, 4, 0

.section .reset, "ax"

.thumb_func
.global reset
.align 4
reset:
        @ 1) Init vector table
        ldr     r1, =0xe000ed08         @ (PPB_BASE + M0PLUS_VTOR_OFFSET)
        ldr     r0, =vector_table
        str     r0, [r1]

        @ 2) init stack pointer to the end of available RAM
        ldr     r0, =0x20040000
        mov     sp, r0

platform_entry:
        ldr  r1, =main
        blx  r1
        nop
        bkpt #0     @ should not return

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

.thumb_func
.global main
.align 4
main:
        @ Reset the hardware
        movs    r0, #32                 @ Bit 5 - IO_BANK0
        bl      hw_reset

        @ Reset PADS
        ldr     r0, =0x100              @ Bit 8 - PADS_BANK0
        bl      hw_reset

        bl      init_gpio_led

        loop:
                svc     #0
                bl      do_sleep
                svc     #1
                bl      do_sleep
                b       loop


.thumb_func
.align 4
hw_reset:
        ldr     r2, =0x4000c000    @ RESETS_BASE
        ldr     r1, =0x3000        @ Atomic register access
        add     r1, r1, r2
        str     r0, [r1]

        adds    r2, r2, 0x08       @ RESET_DONE_OFFSET

        .rst_loop:
                ldr     r1, [r2]
                tst     r1, r0
                beq     .rst_loop

        bx      lr


.thumb_func
.align 4
do_sleep:
        ldr     r0, =0x100000
        .wait:
                subs    r0, r0, #1
                cmp     r0, #0
                bne     .wait

        bx      lr


@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

.thumb_func
.align 4
init_gpio_led:
        movs    r0, LED_GPIO_PIN

        @ 1) Funcsel
        movs    r1, #5                  @ function 5 = SIO
        ldr     r2, =0x40014000         @ IO_BANK0_BASE

        movs    r3, #8
        muls    r3, r3, r0              @ calculate offset for GPIO_N_CTRL (minus 0x04)
        adds    r3, #0x04               @ GPIO0_CTRL offset

        str     r1, [r2, r3]            @ write specfied function

        @ 2) Output enable
        ldr     r1, =0xd0000000         @ SIO_BASE
        movs    r2, #1
        lsls    r2, r2, r0              @ set enable on n-th pin
        str     r2, [r1, 0x24]          @ GPIO_OE_SET

        bx      lr


.thumb_func
.align 4
svc_isr:
        push    {lr}

        mov     r0, sp
        ldr     r0, [r0, #0x1c]         @ return address - i.e. address of instruction following `svc #n`
        subs    r0, r0, #2              @ svc instruction starts 2 bytes before that address,
                                        @   argument for svc is first byte in order (little endian)

        ldrb    r0, [r0]                @ load svc argument

        cmp     r0, SVC_MAX_INSTRS
        bhi     svc_done
        adr     r1, svc_jmptbl
        lsls    r0, #2
        ldr     r1, [r1, r0]
        mov     pc, r1

svc_done:
        pop     {pc}


.thumb_func
.align 4
svc_num0:
        movs    r0, LED_GPIO_PIN

        ldr     r2, =0xd0000000         @ SIO_BASE
        movs    r1, #1
        lsls    r1, r1, r0              @ set output on n-th pin
        str     r1, [r2, #0x14]         @ GPIO_OUT_SET

        b       svc_done


.thumb_func
.align 4
svc_num1:
        movs    r0, LED_GPIO_PIN

        ldr     r2, =0xd0000000         @ SIO_BASE
        movs    r1, #1
        lsls    r1, r1, r0              @ set output on n-th pin
        str     r1, [r2, #0x18]         @ GPIO_OUT_CLR

        b       svc_done


.align 2
svc_jmptbl:
        .word svc_num0
        .word svc_num1
        .word 0

此外,你需要使用这个链接脚本进行链接

MEMORY
{
    sram_code (rx) : ORIGIN = 0x20000000, LENGTH = 256k     /*four larger banks*/
    sram_data (rw) : ORIGIN = 0x20040000, LENGTH = 4k       /*smaller data bank*/
}

SECTIONS
{
    .text : {
        *(.reset*)
        *(.text*)
    } > sram_code

    .vectors : {
        . = ALIGN(256);
        *(.vectors*)
    } > sram_code
}

我建议用RPi Pico Probe进行开发,因为你可以直接从RAM运行代码并逐步观察所有寄存器。

我还建议进一步学习Cortex-M0+架构,其中一个非常好的资源是Joseph Yiu的《ARM Cortex-M0与 Cortex-M0+处理器权威指南(第二版)》。

此外,我个人也为RP2040和 RP2350编写了大量ARM汇编代码。你可以从我的项目中得到灵感,例如在 interrupt subroutines 中的实现。在这个仓库里,你可以找到多个示例,特别是针对RP2350的,但其中一些同样适用于RP2040。关于为RP2040纯汇编驱动的更多示例,你可以在我的 第二个仓库 中找到。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章