前一小节通过了调用github上某位同道中人写好的库,实现了对GPIO的操作,这里从原理上分析如何操作 树莓派3B 的寄存器,也是从最简单的例子开始,点亮第二个LED灯。

所以我们今天的任务:通过操作寄存器的方式点亮第二个LED灯(板子上的第10脚,对应BCM.GPIO15,也是之前图中的RXD)

BCM.GPIO15.png

本文源码地址参见 github

一、分析下树莓派硬件寄存器

BCM2835数据手册

这里因为之前查阅了官网的说明,BCM2837 和 BCM2835 在外设这一块是没有变化的,所以我们可以直接参考 BCM2835 的数据手册。我们直接翻阅到 89 页左右,这里就是我们的目标了,GPIO 主要在第六章,这里找到下面这个表格:(图片部分截取)

寄存器地址.png

可以看到在芯片地址中 GPIO 主要分布在 0x7E200000 这个地址往后走的一部分,最大的地址是 0x7E200B0。我们接着查阅芯片手册中关于每个寄存器作用。最后确认下来,如果我们要点亮那个LED灯,需要将 BCM.GPIO15设置为输出,且输出一个高电平就可以了。通过找寻寄存器对应的区域,判断到需要将下面图2中15位设置为1(BCM.GPIO15设置为输出模式)。

GPIOAFSR1.png

进一步设置中需要对输出寄存器相关位进行赋值,可以将 BCM.GPIO15 设置为高,也就是下图3中寄存器相关位。

GPSET0.png

当时设置了输出位为高电平可以点亮LED灯,同时也需要输出位为低电平以关闭LED灯,也就是下面图4这个寄存器。

GPCLR0.png

了解清楚我们要操作的寄存器后,我们需要进一步确定这个地址到底是多少,通过手册第5页 图5 我们可以看出实际上ARM的MMU把上面的 0x7E200000 这种实际地址映射到了0x200000000x40000000 这个地址上去,但是具体地址是多少也不是很清楚,这里就去找到了文档 bcm2835 的c语言程序找寻蛛丝马迹。后来发现了可以通过读取 /proc/device-tree/soc/ranges 找到具体的外设地址和范围,按照实际的偏移进行映射就可以使用了。

地址映射图.png

二、实战

经过了上面一圈的查资料、分析,发现思路越来越明朗了,这里就开始这几实战了。Go 语言对于指针操作会比C语言要求更为严格一点,也没有宏定义可以使用,这里就直接定义到const中。

package main

import (
    "os"
    "fmt"
    "bytes"
    "encoding/binary"
    "syscall"
    "unsafe"
    "time"
)

const(

    // define the device tree range
    BCM2837_PRI3B_DT_FILENAME                 = "/proc/device-tree/soc/ranges"
    BCM2837_PRI3_DT_PERI_BASE_ADDRESS_OFFSET  =  0x4
    BCM2837_PRI3_DT_PERI_SIZE_OFFSET          =  0X8
    BCM2837_GPIO_BASE                         =  0x00200000

    /*!  灵感部分来自 bcm2835 demo包
    GPIO register offsets from BCM2835_GPIO_BASE.
    Offsets into the GPIO Peripheral block in bytes per 6.1 Register View
    */
    BCM2837_GPFSEL0                 =    0x0000 /*!< GPIO Function Select 0 */
    BCM2837_GPFSEL1                 =    0x0004 /*!< GPIO Function Select 1 */
    BCM2837_GPSET0                  =    0x001c /*!< GPIO Pin Output Set 0 */
    BCM2837_GPSET1                  =    0x0020 /*!< GPIO Pin Output Set 1 */
    BCM2837_GPCLR0                  =    0x0028 /*!< GPIO Pin Output Clear 0 */
    BCM2837_GPCLR1                  =    0x002c /*!< GPIO Pin Output Clear 1 */
   
)

var  Bcm2837_peripherals_base   uint32
var  Bcm2837_peripherals_size   uint32
var  Bcm2837_gpio               uint32

func main(){
    // find the io peripheral base and range
    f,err:= os.OpenFile(BCM2837_PRI3B_DT_FILENAME,os.O_RDONLY,0)

    if err != nil {
        fmt.Println("open range file err")
    }
    defer f.Close()
    //read value and change []byte to uint32
    var buf []byte = make([]byte,4)
    f.ReadAt(buf , BCM2837_PRI3_DT_PERI_BASE_ADDRESS_OFFSET )
    bytesBuffer := bytes.NewBuffer(buf)
    binary.Read(bytesBuffer , binary.BigEndian , &Bcm2837_peripherals_base )

    f.ReadAt(buf , BCM2837_PRI3_DT_PERI_SIZE_OFFSET )
    bytesBuffer  = bytes.NewBuffer(buf)
    binary.Read(bytesBuffer , binary.BigEndian , &Bcm2837_peripherals_size )

    fmt.Printf("get peripherals base:%x size:%x\n" , Bcm2837_peripherals_base , Bcm2837_peripherals_size )

    //need su execute
    if os.Geteuid() == 0 {

        /* open the master /dev/mem device */
        f, err := os.OpenFile("/dev/mem",os.O_RDWR,0)
        if err != nil {
            fmt.Println("Open mem error")
        }

        p,err := syscall.Mmap(int(f.Fd()),int64(Bcm2837_peripherals_base),int(Bcm2837_peripherals_size),syscall.PROT_READ|syscall.PROT_WRITE,syscall.MAP_SHARED)

        if err != nil {
            fmt.Println("mmap error")
        }

        //strat find the gpio register
        Bcm2837_gpio = *(*uint32)(unsafe.Pointer(&p)) + uint32( BCM2837_GPIO_BASE )

        var test uintptr = uintptr( Bcm2837_gpio + BCM2837_GPFSEL1 )
        *(*uint32)(unsafe.Pointer(test)) = ( 0x1 << 15 )

        test = uintptr( Bcm2837_gpio + BCM2837_GPSET0 )
        *(*uint32)(unsafe.Pointer(test)) = ( 0x1 << 15 )

        time.Sleep( time.Second  * 2 )

        test = uintptr( Bcm2837_gpio + BCM2837_GPCLR0 )
        *(*uint32)(unsafe.Pointer(test)) = ( 0x1 << 15 )

    }else {
        fmt.Println("please use root execute")
        panic(err)
    }
}

如有朋友感兴趣可以简信我。