上一章,我们为读者展示了如何像操作单片机一样操作树莓派GPIO。但仅仅只是操作而已,上一章并未告诉读者Linux GPIO的控制机制到底是如何运作的。BCM2835函数库已经把所有与GPIO相关的细节封装起来了,读者调用一些相关的函数控制GPIO。在这一章,我们抛开BCM2835函数库,自行实现几个GPIO控制的小实验,初探GPIO控制的内幕。
在Linux用户态(内核态、用户态和接口?)控制GPIO,通常有三种方式
是的,借助sysfs接口,现在我们可以仅仅依靠几条Linux终端命令,点亮一盏LED。这盏LED连接在P1-11管脚,GPIO编号17,高电平点亮。我们进入 /sys/class/gpio 目录,这里是GPIO的控制接口所在地。
pi@raspberrypi ~ $ cd /sys/class/gpio/
pi@raspberrypi ~ $ ls
export gpiochip0 unexport
目录下有3个文件(夹)
我们往export写入“17”,导出GPIO17控制接口————gpio17文件夹
pi@raspberrypi /sys/class/gpio $ sudo echo 17 > export
pi@raspberrypi /sys/class/gpio $ ls
export gpio17 gpiochip0 unexport
pi@raspberrypi /sys/class/gpio $ ls gpio17
active_low direction edge power subsystem uevent value
gpio17文件夹下,我们用到direction和value两个接口
配置GPIO为输入或输出。写入“out”为输出,“in”为输入,默认输出。
GPIO电平,任何非零的值都被认为是高电平。
输入指令
pi@raspberrypi /sys/class/gpio $ sudo echo out > gpio17/direction
pi@raspberrypi /sys/class/gpio $ sudo echo 1 > gpio17/value
此时,可以看到LED已经被点亮了。熄灭LED,并取消GPIO17的导出,则输入指令
pi@raspberrypi /sys/class/gpio $ sudo echo 0 > gpio17/value
pi@raspberrypi /sys/class/gpio $ sudo echo 17 > unexport
pi@raspberrypi /sys/class/gpio $ ls
export gpiochip0 unexport
现在gpio17文件夹消失了,GPIO17的导出已经被取消。硬件资源是宝贵的,使用完之后,都应该及时释放。
这个例程的C语言版本位于 https://github.com/openRPi/gpio/tree/master/sysfs/blink
小结:
?
树莓派的CPU是ARM架构的(什么是ARM架构?)。这种架构的机器把GPIO的控制寄存器和运行RAM看成一体。也是就说,在ARM架构的CPU看来,GPIO控制寄存器同样位于运行RAM的地址空间,CPU可以像访问RAM一样访问GPIO控制寄存器。
在树莓派的/dev目录下,存在一个特殊的设备接口———— /dev/mem,它是树莓派物理内存的全映像,可以用来从用户空间访问物理内存。BCM2835函数库就是利用了 /dev/mem 设备接口,实现从用户空间控制GPIO。
当我们需要将某GPIO置位/复位时,通常经历了以下几个步骤:
BCM2835函数库封装了上述过程。了解它的详细工作原理,可以阅读函数库源码 https://github.com/openRPi/gpio/tree/master/lib
现在,我们利用/dev/mem设备接口,再次点亮一盏LED。实验例程位于 https://github.com/openRPi/gpio/tree/master/dev_mem/blink ,它展示了通过/dev/mem设备接口控制硬件的一般步骤。
实验涉及的LED连接在P1-11管脚,GPIO编号17。
查阅[BCM2835芯片datasheet](https://github.com/openRPi/gpio/blob/master/resource/BCM2835-ARM-Peripherals.pdf)得知
我们用 open 函数打开 /dev/mem
int mem_fd=0;
mem_fd = open("/dev/mem", O_RDWR | O_SYNC))
并将GPIO控制寄存器簇基址(物理地址)映射到用户空间能访问的虚拟地址。映射工作由 mmap 函数完成。
volatile int * gpio=MAP_FAILED;
gpio = mmap(NULL,4096,PROT_READ|PROT_WRITE, MAP_SHARED, mem_fd, GPIO_BASE);
mmap 函数执行成功后,我们可以通过 gpio 指针访问到真实的物理地址。
设置GPIO17为输出:将 GPFSEL1 的23到21位设为 001
*(gpio+GPFSEL1/4) &= ~0xE00000;
*(gpio+GPFSEL1/4) |= 1<<21;
置位GPIO17:置位GPSET0的第17位。这段代码执行成功后,LED被点亮了。
*(gpio+GPSET0/4) = 1<<17;
复位GPIO17:置位 GPCLR0 的第17位。LED熄灭。
*(gpio+GPCLR0/4) = 1<<17;
解除映射并关闭 /dev/mem
munmap(&gpio,4096);
close(mem_fd);