栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

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

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

目录
  • CPU型号确定
  • 寄存器的地址问题
  • GPIO寄存器
    • GPFESLn
    • GPSETn
    • GPCLRn
  • 重要函数
    • mmap函数
    • munmap函数
  • 点灯程序

平台:树莓派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 Address、Virtual Address
在树莓派中,借助ARM内部的MMU,CPU外设物理地址映射成了虚拟地址。
这里的 Virtual Address 和 Physical 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的偏移地址为0x00;GPSET0的偏移地址为0x1C;GPCLRn的偏移地址为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 
#include    //mmap、munmap函数的定义
#include       //open函数的定义
#include      //close函数的定义
#include      //uint8_t、uint32_t等类型的定义
#include      //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编号

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/302926.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号