【树莓派学习笔记】九、C语言寄存器操作控制GPIO

平台:树莓派3B
版本: 2021-05-07-raspios-buster-armhf


CPU型号确定

pinout

命令可知,所用的板子Soc型号为BCM2837
在这里插入图片描述

寄存器的地址问题

本节内容修改自虚拟地址/物理地址——virtual address(memory)/physical address: 树莓派 mmap example —— 风竹夜
由于官方只公开了BCM2835的芯片手册(Raspberry Pi Documentation),而我们用的板子Soc为BCM2837,因此由手册得到的地址是不准确的。但两个芯片外设上区别不大,手册仍有一定的参考价值。
手册第5页描绘了BCM2835的地址映射情况
在这里插入图片描述
其中要区分文档中的三个地址,Bus Address、Physical AddressVirtual Address
在树莓派中,借助ARM内部的MMU,CPU外设物理地址映射成了虚拟地址。
这里的 Virtual AddressPhysical Address 是通过 ARM MMU 来实现映射的(先不管cache等其他因素)。主板上外设的实际地址是 Physical Address,所以要访问 GPIO 寄存器,也就是访问 Physical Address 中从 0x20000000 开始的某处的地址, 那么就需要在代码中访问 Virtual Address 中从 0xF2000000 开始的某处的地址,由于该虚拟地址在高地址内存,因此只能在内核的代码中才能够访问到。
在这里插入图片描述
而在树莓派3B中我们可以通过

sudo cat /proc/iomem

命令获取物理地址分配情况
在这里插入图片描述
由图可知,树莓派3B GPIO 的物理起始地址为0x3f200000

GPIO寄存器

从手册第89页开始详细地描述了GPIO外设。
在这里插入图片描述

第90-91页的表格标明了和GPIO相关的寄存器的地址。
其中GPFESLn(选择引脚功能)、GPSETn(设置引脚输出高电平)和GPCLRn(设置引脚输出低电平)是控制引脚输出电平需要用到的寄存器。
虽然树莓派3B的Soc换成了BCM2837,GPIO外设的基地址有变,但手册上的偏移地址还是有参考价值的。
其中GPFESL0的偏移地址为0x00GPSET0的偏移地址为0x1CGPCLRn的偏移地址为0x28
在这里插入图片描述在这里插入图片描述

GPFESLn

由手册知
GPFESL0控制GPIO Pin 0~9;
GPFESL1控制GPIO Pin 10~19;
GPFESL2控制GPIO Pin 20~29;
GPFESL3控制GPIO Pin 30~39;
GPFESL4控制GPIO Pin 40~49;
GPFESL5控制GPIO Pin 50~59;
3位为间隔,000 为输入,001为输出
在这里插入图片描述
在这里插入图片描述

GPSETn

由手册知
GPSET0控制GPIO pin 0~31
GPSET1控制GPIO pin 32~53
在这里插入图片描述

GPCLRn

由手册知
GPCLR0控制GPIO pin 0~31
GPCLR1控制GPIO pin 32~53
在这里插入图片描述

重要函数

mmap函数

mmap将一个文件或者其它对象映射进内存。mmap操作提供了一种机制,让用户程序直接访问设备内存,这种机制,相比较在用户空间和内核空间互相拷贝数据,效率更高。

void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
start:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址。
length:映射区的长度。//长度单位是 以字节为单位,不足一内存页按一内存页处理。
prot:期望的内存保护标志,不能与文件的打开模式冲突。
PROT_EXEC //页内容可以被执行;
PROT_READ //页内容可以被读取;
PROT_WRITE //页可以被写入;
PROT_NONE //页不可访问
flags:指定映射对象的类型,映射选项和映射页是否可以共享。
fd:有效的文件描述词。一般是由open()函数返回。
offset:被映射对象内容的起点。
返回值:成功:被映射区的指针。失败:MAP_FAILED[其值为(void *)-1]。

munmap函数

munmap()用来取消参数start所指的映射内存起始地址

int munmap(void *start,size_t length);
start:映射区的开始地址
length:欲取消的内存大小
返回值:成功:0;失败:-1。

点灯程序

在合适的地方编写main.c文件

nano main.c
#include <stdio.h>
#include <sys/mman.h>   //mmap、munmap函数的定义
#include <fcntl.h>      //open函数的定义
#include <unistd.h>     //close函数的定义
#include <stdint.h>     //uint8_t、uint32_t等类型的定义
#include <unistd.h>     //sleep函数的定义

#define GPIO_BASE_Physical_Address  0x3f200000

#define GPFSEL0_Offs    0x00  
#define GPSET0_Offs     0x1C
#define GPCLR0_Offs     0x28 

int main(int argc, char *argv[])
{
    
    
    int fd;
    uint8_t Pin = 3;

    fd = open("/dev/gpiomem", O_RDWR);
    if (fd == -1)   //只有root用户才能读取/dev/mem, 故本实验选择读取/dev/gpiomem,以实现普通用户控制GPIO
    {
    
    
        printf("open Error!\n");
        return -1;
    }

    void *GPIO_BASE = mmap(0, sysconf(_SC_PAGESIZE), PROT_READ | PROT_WRITE, MAP_SHARED , fd, GPIO_BASE_Physical_Address);
    close(fd);
    if(GPIO_BASE == MAP_FAILED)
    {
    
    
        printf("mmap Error!\n");
        return -1;
    }

    volatile uint32_t * GPFSEL0 = (uint32_t *)(GPIO_BASE + GPFSEL0_Offs);
    volatile uint32_t * GPSET0 = (uint32_t *)(GPIO_BASE + GPSET0_Offs);
    volatile uint32_t * GPCLR0 = (uint32_t *)(GPIO_BASE + GPCLR0_Offs);

    *GPFSEL0 = (*GPFSEL0 & ~((uint32_t)7 << (3 * Pin))) | ((uint32_t)1) << (3 * Pin); //设置Pin 3为输出模式

    for(uint8_t i = 0; i < 10; ++i)             //反转Pin 3 i次
    {
    
    
        *GPCLR0 = ((uint32_t)1) << Pin;   
        sleep(1);
        *GPSET0 = ((uint32_t)1) << Pin;     
        sleep(1);
    }

    *GPFSEL0 = *GPFSEL0 & ~((uint32_t)7 << (3 * Pin));  //设置Pin 3为输入模式

    if(munmap(GPIO_BASE, sysconf(_SC_PAGESIZE)) == -1)
    {
    
    
        printf("munmap Error!\n");
        return -1;
    }
    GPIO_BASE = MAP_FAILED;
    GPFSEL0 = MAP_FAILED;
    GPSET0 = MAP_FAILED;
    GPCLR0 = MAP_FAILED;
    return 0;
}

其中sysconf(_SC_PAGESIZE)为页的长度,相关知识见yaaangmin大佬的视频:深入理解计算机系统20:内存 - 多级页表、虚拟地址、物理地址

编写Makefile文件

nano Makefile

注意Makefile里的缩进为Tab而不是空格

main: main.o
    gcc -o main main.o
main.o: main.c
    gcc -c main.c
clean:
    rm *.o
    rm main
clear:
    rm *.o
    rm main

在这里插入图片描述
编译并测试

make
./main

可见Pin 3 脚所连LED闪烁(Pin 3脚已事先接好LED和限流电阻下拉至GND)

此编号对应

gpio readall

命令下的BCM编号
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44457994/article/details/120610413