You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

树莓派3B在RaspbianOS下用汇编点亮GPIO12 LED遇段错误求助

问题:Raspberry Pi汇编代码点亮GPIO12 LED出现段错误

我正在完成课程作业,需要用C和汇编实现MasterMind游戏,先尝试用汇编点亮连接到GPIO12的LED,但运行时出现段错误。以下是我的代码和编译步骤:

汇编代码(testled.s)

.section .init
.globl _start
_start:
ldr r0,=0x3F200000 // Base adress from GPIO
 
// Set the GPIO pin 12 as output
mov r1,#1
lsl r1,#6
str r1,[r0,#0x4] // 0x3F200004 is the GPSEL1 Register, where you can choose the function for the pin
 
// Prepare the number used for turning the LED off and on
mov r1,#1
lsl r1,#12
 
// Main loop, loops all the letters (bl means branch with saving the current physical adress in the link register (lr) so you can jump back to it)
loop$:
 bl morseS
 bl morseO
 bl morseS
b loop$
 
morseS:
 mov r6,lr // Move the lr adress to r6 so you dont override it (would be better if implemented with stack)
 bl dotBlink
 bl dotBlink
 bl dotBlink
 bl delayMedium
 mov lr,r6 // Move the adress back into the link register
 bx lr
 
morseO:
 mov r6,lr
 bl dashBlink
 bl dashBlink
 bl dashBlink
 bl delayMedium
 mov lr,r6
 bx lr
 
dotBlink:
 mov r7,lr
 str r1,[r0,#0x1C] // This turns the led on (According to the BCM2835 documentation the Register found at 0x3F20001C is GPSET0, so you can turn on the GPIO Pin 12 there
 bl delayShort
 str r1,[r0,#0x28] // This is GPCLR0, so used for turning the pin off again
 bl delayShort
 mov lr,r7
 bx lr
 
dashBlink:
 mov r7,lr
 str r1,[r0,#0x1C]
 bl delayLong
 str r1,[r0,#0x28]
 bl delayShort
 mov lr,r7
 bx lr
 
delayShort: 
 ldr r4,=0x3f003004 // Address for the timer of the Raspberry Pi
 ldr r4,[r4] // Load the value from the timer
 ldr r5,=0x30D40 // 200'000 micro seconds in hexadecimal
 delayLoop1$:
  ldr r3,=0x3F003004 
  ldr r3,[r3] // Load new timer value
  sub r3,r3,r4 // Save time passed since the beginning of subroutine in r3
  cmp r3,r5 // Compare 
  blt delayLoop1$ // If difference is smaller than 200ms go to the delay loop again
 bx lr // If time has passed return from function
 
delayMedium:
 ldr r4,=0x3f003004
 ldr r4,[r4]
 ldr r5,=0x61A80
 delayLoop3$:
  ldr r3,=0x3F003004
  ldr r3,[r3]
  sub r3,r3,r4
  cmp r3,r5
  blt delayLoop3$
 bx lr
 
delayLong:
 ldr r4,=0x3f003004
 ldr r4,[r4]
 ldr r5,=0x927C0
 delayLoop2$:
  ldr r3,=0x3F003004
  ldr r3,[r3]
  sub r3,r3,r4
  cmp r3,r5
  blt delayLoop2$
 bx lr

编译运行命令

  • 汇编:as -o testled.o testled.s
  • 链接:ld -o testled testled.o
  • 运行:sudo ./testled

运行后出现段错误,求解决方法。


解决方案

段错误的核心原因是用户空间直接访问物理地址:Raspberry Pi的Linux系统启用了内存地址虚拟化,用户程序无法直接读写硬件物理地址,必须通过内存映射或裸机运行方式绕过限制。以下是两种可行修复方案:

方案1:用户空间映射物理内存(C+汇编结合)

用C代码完成物理内存到用户空间的映射,再调用汇编函数操作GPIO,避免直接访问物理地址触发内存保护:

  1. 编写C启动文件(main.c):
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

#define GPIO_BASE 0x3F200000
#define TIMER_BASE 0x3F003004
#define BLOCK_SIZE (4*1024)

volatile unsigned int *gpio;
volatile unsigned int *timer;

void led_blink();

int main() {
    int mem_fd = open("/dev/mem", O_RDWR | O_SYNC);
    if (mem_fd < 0) {
        perror("Failed to open /dev/mem");
        exit(1);
    }

    // 映射GPIO寄存器到用户空间
    void *gpio_map = mmap(NULL, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, mem_fd, GPIO_BASE);
    if (gpio_map == MAP_FAILED) {
        perror("Failed to mmap GPIO");
        close(mem_fd);
        exit(1);
    }
    gpio = (volatile unsigned int *)gpio_map;

    // 映射定时器寄存器到用户空间
    void *timer_map = mmap(NULL, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, mem_fd, TIMER_BASE & ~(BLOCK_SIZE-1));
    if (timer_map == MAP_FAILED) {
        perror("Failed to mmap Timer");
        munmap(gpio_map, BLOCK_SIZE);
        close(mem_fd);
        exit(1);
    }
    timer = (volatile unsigned int *)((char*)timer_map + (TIMER_BASE & (BLOCK_SIZE-1)));

    led_blink();

    // 释放映射资源
    munmap(gpio_map, BLOCK_SIZE);
    munmap(timer_map, BLOCK_SIZE);
    close(mem_fd);
    return 0;
}
  1. 修改汇编代码(testled.s),去掉_start入口,改为led_blink函数,通过全局变量访问寄存器:
.globl led_blink
led_blink:
    // 将GPIO基地址存入r0
    ldr r0, =gpio
    ldr r0, [r0]

    // 设置GPIO12为输出模式
    mov r1, #1
    lsl r1, #6
    str r1, [r0, #0x4]

    mov r1, #1
    lsl r1, #12

loop$:
    bl morseS
    bl morseO
    bl morseS
    b loop$

morseS:
    mov r6, lr
    bl dotBlink
    bl dotBlink
    bl dotBlink
    bl delayMedium
    mov lr, r6
    bx lr

morseO:
    mov r6, lr
    bl dashBlink
    bl dashBlink
    bl dashBlink
    bl delayMedium
    mov lr, r6
    bx lr

dotBlink:
    mov r7, lr
    str r1, [r0, #0x1C]
    bl delayShort
    str r1, [r0, #0x28]
    bl delayShort
    mov lr, r7
    bx lr

dashBlink:
    mov r7, lr
    str r1, [r0, #0x1C]
    bl delayLong
    str r1, [r0, #0x28]
    bl delayShort
    mov lr, r7
    bx lr

delayShort:
    ldr r4, =timer
    ldr r4, [r4]
    ldr r5, =0x30D40  // 200ms
delayLoop1$:
    ldr r3, =timer
    ldr r3, [r3]
    sub r3, r3, r4
    cmp r3, r5
    blt delayLoop1$
    bx lr

delayMedium:
    ldr r4, =timer
    ldr r4, [r4]
    ldr r5, =0x61A80  // 400ms
delayLoop3$:
    ldr r3, =timer
    ldr r3, [r3]
    sub r3, r3, r4
    cmp r3, r5
    blt delayLoop3$
    bx lr

delayLong:
    ldr r4, =timer
    ldr r4, [r4]
    ldr r5, =0x927C0  // 600ms
delayLoop2$:
    ldr r3, =timer
    ldr r3, [r3]
    sub r3, r3, r4
    cmp r3, r5
    blt delayLoop2$
    bx lr
  1. 编译链接命令:
gcc -c main.c -o main.o
as -o testled.o testled.s
gcc -o testled main.o testled.o
sudo ./testled

方案2:裸机运行汇编代码

如果想纯汇编运行,需要编译成裸机镜像,通过SD卡引导Raspberry Pi脱离Linux系统运行:

  1. 编写链接脚本(linker.ld):
SECTIONS {
    .init 0x8000 : { *(.init) }
    .text : { *(.text) }
}
  1. 编译链接成裸机镜像:
as -o testled.o testled.s
ld -T linker.ld -o testled.elf testled.o
objcopy testled.elf -O binary testled.bin
  1. 将testled.bin重命名为kernel.img,替换SD卡中的对应文件,重启Raspberry Pi即可运行。

内容的提问来源于stack exchange,提问作者Aditya Shibu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 21:39:50