树莓派底层编程-GPIO篇

0 前言

本来是准备写一篇用python控制GPIO的例子,结果发现太简单了,还是深入一些吧,多深入底层

接下来,我会按照下面的顺序来介绍:
1)GPIO控制器地址映射
2)GPIO主要控制寄存器介绍
3)GPIO操作流程
4)用C语言控制GPIO
6)用python来控制GPIO

1 概述

树莓派2B采用的CPU为BCM2836,外设部分和BCM2835完全一致,所以就可以参考BCM2835的寄存器手册
但是,这个寄存器手册笔误有点多,建议配合着wiki的勘误手册来看

CPU总共有54个IO,每个IO都可以复用,配置输入或者输出模式,单从GPIO部分来看,比较简单,无非是输入/输出,触法模式等,和一般的MCU没有太大差异

2 地址映射

BCM2835将BUS的地址空间,通过MMU映射到线性寻址空间,如下所示

地址空间
这里,我们从左到右看,IO Peripherals的基地址为0x7E000000,然后映射后的基地址为0x20000000
也就是说外设的地址空间为0x20000000~0x20FFFFFF

接下来,在手册中,找到GPIO章节,查看寄存器地址,如下所示

GPIO寄存器地址
比如GPFSEL0寄存器的BUS地址为0x7E200000,也就是说相对偏移为0x00200000,所以我们可以知道映射后的GPIO基地址为
0x20200000(0x20000000 + 0x00200000),其它寄存器的地址以此类推

3 寄存器介绍

解决了GPIO寄存器地址问题,下面就需要知道如何控制GPIO,大致浏览GPIO章节,发现相当简单,GPIO的主要功能有

  • 外设模式
  • GPIO模式
  • 输出/输出
  • 上拉/下拉
  • 事件监测(上升沿/下降沿/边缘变化/高电平/低电平)

下面简单介绍GPIO相关寄存器

  1. GPFSELn(n=0/1/2/3/4)
    用于选择GPIO管脚的模式是IO输出还是输入或者是复用
  2. GPSETn
    设置IO输出值,写1有效,表示置高对应管脚
  3. GPCLRn
    设置IO输出值,写1有效,表示置低对应管脚
  4. GPLEVn
    IO当前值,用于读入当前的电平值
  5. GPEDSn
    也就是所谓的事件标志位,可以通过该位来检查是否有事件发生,写1清零
  6. GPRENn/GPRENn/GPHENn/GPLENn
    用于设置IO的事件模式,比如上升沿/下降沿/跳变/高电平/低电平触发方式,触发事件后,会发送一个中断信号给CPU
    从而实现中断机制
  7. GPPUD/GPPUDCLKn
    上拉/下拉配置寄存器,这个比较特殊,CPU不会保存这个配置状态,需要用户用软件来配置并保存,配置掉电不丢失,同时,
    系统只提供两个相关寄存器,所以配置时需要采用复用模式,并遵循特定的配置方式和延时,具体参见手册介绍

总共就这么多寄存器,介绍完毕,下面说一下操作流程

4 操作流程

对于普通的IO来说,无非就是输入或者输出,输入模式下,可以轮询或者中断;输出模式下,往对应管脚寄存器里写1/0即可

输出配置

  1. 通过GPFSELn设置输出模式
  2. 往GPSETn寄存器写1,表示在对应管脚输出高电平
  3. 往GPCLRn寄存器写0,表示在对应管脚输出低电平
  4. 可以通过GPLEVn来读取当前输出值

输入配置

  1. 通过GPFSELn设置输入模式
  2. 通过GPPUD/GPPUDCLKn设置上拉或者下拉模式
  3. (可选)通过GPRENn/GPRENn/GPHENn/GPLEN设置事件模式
  4. 可以通过GPLEVn来读取当前输出值

5 用C语言控制IO

请参考 https://github.com/WiringPi/WiringPi/blob/master/wiringPi/wiringPi.c

6 用python语言控制IO

请参考 http://raspberrywebserver.com/gpio/

参考资料

  1. BCM2835 ARM Peripherals
  2. BCM2835_datasheet_errata
  3. wiringPi Code
  4. wiringPi HomePage