前一小节通过了调用github上某位同道中人写好的库,实现了对GPIO的操作,这里从原理上分析如何操作 树莓派3B 的寄存器,也是从最简单的例子开始,点亮第二个LED灯。
所以我们今天的任务:通过操作寄存器的方式点亮第二个LED灯(板子上的第10脚,对应BCM.GPIO15,也是之前图中的RXD)。
本文源码地址参见 github
一、分析下树莓派硬件寄存器
这里因为之前查阅了官网的说明,BCM2837 和 BCM2835 在外设这一块是没有变化的,所以我们可以直接参考 BCM2835 的数据手册。我们直接翻阅到 89 页左右,这里就是我们的目标了,GPIO 主要在第六章,这里找到下面这个表格:(图片部分截取)
可以看到在芯片地址中 GPIO 主要分布在 0x7E200000 这个地址往后走的一部分,最大的地址是 0x7E200B0。我们接着查阅芯片手册中关于每个寄存器作用。最后确认下来,如果我们要点亮那个LED灯,需要将 BCM.GPIO15设置为输出,且输出一个高电平就可以了。通过找寻寄存器对应的区域,判断到需要将下面图2中15位设置为1(BCM.GPIO15设置为输出模式)。
进一步设置中需要对输出寄存器相关位进行赋值,可以将 BCM.GPIO15 设置为高,也就是下图3中寄存器相关位。
当时设置了输出位为高电平可以点亮LED灯,同时也需要输出位为低电平以关闭LED灯,也就是下面图4这个寄存器。
了解清楚我们要操作的寄存器后,我们需要进一步确定这个地址到底是多少,通过手册第5页 图5 我们可以看出实际上ARM的MMU把上面的 0x7E200000 这种实际地址映射到了0x20000000 到 0x40000000 这个地址上去,但是具体地址是多少也不是很清楚,这里就去找到了文档 bcm2835 的c语言程序找寻蛛丝马迹。后来发现了可以通过读取 /proc/device-tree/soc/ranges 找到具体的外设地址和范围,按照实际的偏移进行映射就可以使用了。
二、实战
经过了上面一圈的查资料、分析,发现思路越来越明朗了,这里就开始这几实战了。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)
}
}
如有朋友感兴趣可以简信我。