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

如何在FreeBSD14.2树莓派上用C直接访问BCM2835寄存器控制GPIO

树莓派FreeBSD下直接访问BCM2835 GPIO寄存器的问题解决

问题描述

在树莓派3/4的FreeBSD14.2系统上,尝试通过直接访问BCM2835寄存器控制GPIO引脚,代码基于Mike McCauley的C库。该代码在树莓派OS(Debian Bookworm)上运行正常,但在FreeBSD下可正常编译、运行,却无法实际控制GPIO引脚。

简化测试代码如下:

/*
Program to test direct access to bcm2835 registers to blink a LED
Build as follows: gcc -Wall -o gpio gpio.c
Run as root
*/

#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdint.h>
#include <time.h> // 补充FreeBSD需要的sleep头文件

// GPIO pin to which a LED is connected:
#define GPIO_PIN (23)

// 根据树莓派型号选择外设基地址:3用0x3F000000,4用0xFE000000
#define PERIPH_BASE 0x3F000000

int main() {   
    int  mem_fd;
    uint32_t *periph_base;
    uint32_t *periph_gpio;
    uint32_t *gpio_function_register;
    uint32_t gpio_function_register_pin_mask;
    uint32_t function_register_value;
    volatile uint32_t *gpio_set_lower_register;
    volatile uint32_t *gpio_clear_lower_register;
    uint32_t gpio_pin_mask;

    mem_fd = open("/dev/mem", O_RDWR|O_SYNC);
    if (mem_fd < 0) return 1;

    periph_base = (uint32_t *)mmap(
        NULL,                   // 自动分配地址
        0x01000000,             // 映射长度
        PROT_READ|PROT_WRITE|PROT_NOCACHE, // 增加PROT_NOCACHE确保MMIO非缓存访问
        MAP_SHARED,             // 共享映射
        mem_fd,                 // 映射的文件
        PERIPH_BASE             // 外设基地址偏移
    );

    close(mem_fd);

    if (periph_base == (void *)-1) return 2;

    /********* 计算所需寄存器地址 *********/

    // GPIO寄存器相对于外设基地址的偏移是0x200000,除以4是因为uint32_t指针步长为4字节
    periph_gpio = periph_base + 0x200000/4;

    // 引脚功能选择寄存器地址
    gpio_function_register = periph_gpio + GPIO_PIN/10;
    // 功能选择位掩码
    gpio_function_register_pin_mask = (7 << ((GPIO_PIN%10)*3));

    // GPIO置位/清零寄存器地址
    gpio_set_lower_register = periph_gpio + 7;
    gpio_clear_lower_register = periph_gpio + 10;
    gpio_pin_mask = (uint32_t)1 << GPIO_PIN;

    /********* 设置GPIO为输出模式 *********/

    function_register_value = *gpio_function_register;
    function_register_value &= ~gpio_function_register_pin_mask;    
    function_register_value |= (((uint32_t)1) << ((GPIO_PIN%10)*3));
    *gpio_function_register = function_register_value;

    /********* 循环闪烁LED *********/
    for (;;) {
        *gpio_set_lower_register = gpio_pin_mask;
        sleep(1);
        *gpio_clear_lower_register = gpio_pin_mask;
        sleep(1);
    }

    return 0;
}

解决方案

1. 启用非缓存内存映射

BCM2835的GPIO寄存器属于内存映射IO(MMIO),必须绕过CPU缓存直接访问硬件。FreeBSD默认不会为/dev/mem映射启用非缓存,因此需要在mmap的保护标志中添加PROT_NOCACHE,确保写操作直接同步到硬件而非停留在缓存中。

2. 禁用内核GPIO驱动

FreeBSD默认加载了bcmgpio内核模块,该模块会接管GPIO引脚的控制权,导致直接寄存器访问失效。执行以下命令卸载模块:

kldunload bcmgpio

若需永久禁用,可在/boot/loader.conf中添加:

bcmgpio_load="NO"

3. 确认外设基地址

  • 树莓派3的外设基地址为0x3F000000
  • 树莓派4的外设基地址为0xFE000000
    确保代码中PERIPH_BASE宏与你的树莓派型号匹配。

4. 补充必要头文件

FreeBSD中sleep函数需要显式包含<time.h>头文件,否则可能出现隐式声明导致的运行时异常。

5. 以Root权限运行

直接访问/dev/mem需要root权限,运行程序时使用:

sudo ./gpio

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 17:40:09