我的SVC(Supervisor Call)代码不起作用,我也不清楚为什么
#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 #0 和 svc #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纯汇编驱动的更多示例,你可以在我的 第二个仓库 中找到。