美文网首页程序员联盟玩转树莓派程序员
【原创】树莓派3B开发Go语言(三)-寄存器版本GPIO

【原创】树莓派3B开发Go语言(三)-寄存器版本GPIO

作者: 爪爪熊 | 来源:发表于2017-08-27 20:04 被阅读836次

    前一小节通过了调用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)
        }
    }
    
    

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

    相关文章

      网友评论

      • 瘦肉粥不要瘦肉不要粥:请问一下用c的ioremap进行映射的时候应该把那个地址映射过去(以GPIO17为例)
        爪爪熊:@瘦肉粥不要瘦肉不要粥 我用GO这样子写是可以的,我实际测试过的.实际地址根据树莓派的不同可以根据文件 /proc/device-tree/soc/ranges 进行确定
        瘦肉粥不要瘦肉不要粥:@爪爪熊 你用Go写的有效果吗?我用c的gpio驱动不知道该把什么地址用ioremap映射,是不是把89页上面的地址直接映射就行
        爪爪熊:个人觉得同样是映射 Bcm2837_peripherals_base 这个的地址上去,操作不同IO口的时候注意好偏移就可以了.
      • 四月份平民:你好,我最近打算玩一下rassberry pi,嵌入式相关的知识0基础,但对程序开发比较熟悉,平时
        开发中golang用得多些,找了几本书,感觉没摸到门,特别是gpio,i2c相关的知识,在网上搜了一堆资料,也不明所以,能麻烦介绍一些资料(书,blog)之类的,以方便入门么?
        爪爪熊:@四月份平民 说实话,我觉得用golang操作树莓派和c操作树莓派没多大区别,主要还是要看芯片数据手册。按着填写寄存器就可以了,你可以先按照我前几篇文章中操作gpio的思路来实现一下。之后的i2c主要还是阅读数据手册进行操作。最近因为在搞机器人就没有更新这边的了。
      • 0859068e38e8:确实厉害~:kissing_heart::kissing_heart::kissing_heart:
        爪爪熊: @木木翻滚吧蛋炒饭 mua~
      • pyrene:牛逼
        爪爪熊: @pyrene 惭愧

      本文标题:【原创】树莓派3B开发Go语言(三)-寄存器版本GPIO

      本文链接:https://www.haomeiwen.com/subject/iojidxtx.html