树莓派学习笔记——wiringPi GPIO使用详解

article/2025/10/28 5:32:32

1.前言

    最近认真学习了树莓派,从浅到深认真分析了wiringPi实现代码,借助树莓派学习linux收获颇丰。深入学习linux一段时间后发现它非常有魅力,一个简单的IO口输出操作尽有那么多的“玩法”。wiringPi是一个简单易用的函数库,通过wiringPi可以扩展SPI和I2C等芯片,关于wiringPi的介绍和安装请参考我的另一篇【博文】。
    本篇博文将通过一个简单的例子呈现wiringPi的使用,虽然例子简单但会深入分析wiringPi内部实现代码。
     【树莓派学习笔记——索引博文】

2.BCM2835 GPIO相关寄存器

    (该部分表述可能有误,正在确认修改中)
    树莓派平台的GPIO驱动,例如RPi.GPIO和WiringPi均采用直接操作GPIO寄存器的方式,树莓派的CPU采用博通的BCM2835,想要更好的了解树莓派的GPIO驱动实现就必须阅读BCM2835的数据手册。在BCM2835数据手册中需要认真关注两个内容:
    外设寄存器物理地址和外设虚拟地址的映射关系。在linux操作系统中,借助ARM内部的MMU,CPU外设物理地址映射成了虚拟地址,外设的物理起始地址为0x7E00 0000,被MMU虚拟之后的起始地址为0x2000 0000。以此类推,GPIO外设物理起始地址为0x7E20 0000 = 0x7E00 0000+0x0020 0000,被MMU虚拟之后的GPIO外设地址为0x2000 0000+0x0020 0000。那么对于Linux系统而言,GPIO相关操作的起始地址为0x2020 0000。BCM2835的内部映射关系如下图所示。

图1 BCM2835 物理地址和虚拟地址映射关系
    GPFSELx、GPSETx、GPCLRx和GPLEVn寄存器。简单来说,GPFSELx为IO口方向或复用寄存器,负责IO口方向例如输入或输出;GPSETx为IO口输出寄存器,负责IO口输出逻辑高电平;GPCLRx寄存器同为IO口输出寄存器,不过和GPSETx相反,负责输出逻辑低电平。GPLEVx为IO口输入寄存器,负责IO口输入状态。
亲爱的网友们,如果您不理解这些寄存器也不理解MMU机制,也不会影响您使用wiringPi。请放心大胆地使用wiringPi,它已经帮你完成了很多基础性的工作

3.简单测试代码

下面通过一个简单的代码实现树莓派流水灯,在树莓派(树莓派版本2)中可以直接利用的IO口共有8个, 在wiringPi中的编号为GPIO0到GPIO7,对于BCM2835而言编号分别为17, 18, 27, 22, 23, 24, 25, 4。具体对应关系见下图。

图2 wiringPi GPIO 和 BCM2835 GPIO映射关系
#include <wiringPi.h>
int main( )
{// 初始化wiringPiwiringPiSetup();int i = 0;// 设置IO口全部为输出状态for( i = 0 ; i < 8 ; i++ )pinMode(i, OUTPUT);for (;;){for( i = 0 ; i < 8 ; i++ ){// 点亮500ms 熄灭500msdigitalWrite(i, HIGH); delay(500);digitalWrite(i, LOW); delay(500);}}return 0;
}
为了方便生成可执行文件,可编写以下makefile文件,CD进入该目录之后直接make即可。
blink:blink.ogcc blink.c -o blink -lwiringPi
clean:rm -f blink blink.o

图3 代码运行结果

4.代码详解

上面的代码非常简单,可以分为四个部分—— wiringPiSetupi初始化、pinMode设置IO为输出方向、digitalWrite输出高电平或低电平和delay系统延时函数。

4.1 wiringPiSetup

int wiringPiSetup (void)
{int fd ;int boardRev ;// 第一步,获得树莓派的版本编号,并根据版本编号映射IO口boardRev = piBoardRev () ;if (boardRev == 1){pinToGpio = pinToGpioR1 ;physToGpio = physToGpioR1 ;}else{pinToGpio = pinToGpioR2 ;physToGpio = physToGpioR2 ;}// 第二步,打开/dev/mem设备,使得在用户空间可以直接操作内存地址if ((fd = open ("/dev/mem", O_RDWR | O_SYNC | O_CLOEXEC) ) < 0)return wiringPiFailure (WPI_ALMOST, "wiringPiSetup: Unable to open /dev/mem: %s\n", strerror (errno)) ;gpio = (uint32_t *)mmap(0, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, GPIO_BASE) ;if ((int32_t)gpio == -1)return wiringPiFailure (WPI_ALMOST, "wiringPiSetup: mmap (GPIO) failed: %s\n", strerror (errno)) ;// 第三步,设定wiringPi GPIO外设的操作模式wiringPiMode = WPI_MODE_PINS ;return 0 ;
}

该部分代码的实现可以分为三步(注意该部分并不是wiringPiSetup的完整代码,为了说明问题对代码进行简化)
第一步,获得树莓派的版本编号,并根据版本编号映射IO口。 pinToGpioR2为树莓派版本2的GPIO映射关系,不但包括GPIO,还包括SPI、I2C和UART等。此处physToGpioRx存在疑问。
第二步,打开/dev/mem设备,使得在用户空间可以直接操作内存地址。 /dev/mem是物理内存的全映像,可以用来访问物理内存(能够访问物理内存当然也包括MCU外设),一般用法是open("/dev/mem",O_RDWR|O_SYNC),接着可以用mmap的地址来访问物理内存(此处为GPIO_BASE),这是实现用户空间驱动的一种方法【 参考博文】。(该部分需要深入,请关注后期博文)
第三步,设定wiringPi GPIO外设的操作模式。此处也存在若干疑惑,默认情况便是使用WPI_MODE_PINS 模式,wiringPi的IO管脚编号和BCM IO管脚编号存在一个固定映射关系,但是wiringPi其他代码中还存在wiringPiSetupSys函数,该函数操作GPIO端口时通过/sys/class/gpio中的驱动文件实现,这也是实现树莓派GPIO操作的另一个途径。这种方法便是应用Sysfs——Sysfs 是 Linux 2.6 所提供的一种虚拟文件系统,这个文件系统不仅可以把设备(devices)和驱动程序(drivers) 的信息从内核输出到 用户空间,也可以用来对设备和驱动程序做设置【 wiki百科】。(该部分需要深入,请关注后期博文)。
int wiringPiSetupSys (void)
{int boardRev ;int pin ;char fName [128] ;// 获得树莓派版本编号,版本1或者版本2boardRev = piBoardRev () ;if (boardRev == 1){pinToGpio = pinToGpioR1 ;physToGpio = physToGpioR1 ;}else{pinToGpio = pinToGpioR2 ;physToGpio = physToGpioR2 ;}// 查找/sys/class/gpio,并记录GPIOx操作文件fdfor (pin = 0 ; pin < 64 ; ++pin){sprintf (fName, "/sys/class/gpio/gpio%d/value", pin) ;sysFds [pin] = open (fName, O_RDWR) ;}// 设置操作模式为 sysfs模式 文件方式驱动GPIO而非寄存器方式wiringPiMode = WPI_MODE_GPIO_SYS ;return 0 ;
}

4.2 pinMode

void pinMode (int pin, int mode)
{int fSel, shift, alt ;struct wiringPiNodeStruct *node = wiringPiNodes ;// 树莓派 板载GPIO设置,板载GPIO的管脚编号必须小于64if ((pin & PI_GPIO_MASK) == 0){// 第一步 确定BCM GPIO引脚编号if (wiringPiMode == WPI_MODE_PINS)pin = pinToGpio [pin] ;// 第二步,确定该管脚对应的fsel寄存器fSel = gpioToGPFSEL [pin] ;shift = gpioToShift [pin] ;// 第三步,根据输入和输出状态设置fsel寄存器if (mode == INPUT)*(gpio + fSel) = (*(gpio + fSel) & ~(7 << shift)) ; else if (mode == OUTPUT)*(gpio + fSel) = (*(gpio + fSel) & ~(7 << shift)) | (1 << shift) ;}// 树莓派 外扩GPIO设置else{if ((node = wiringPiFindNode (pin)) != NULL)node->pinMode (node, pin, mode) ;return ;}
}

该部分代码的实现可以分为三步(注意该部分并不是pinMode 的完整代码,为了说明问题对代码进行简化)
【注意】在wiringPi中,pin编号小于64认为是板载GPIO,如果编号大于64则认为是外扩GPIO,例如使用外扩的MCP23017或者PCF8574,同时外扩的AD和DA芯片的相应pin也应该大于64。
第一步,确定BCM GPIO引脚编号。如果是树莓派2版本,那么映射关系由数组pinToGpioR2决定
static int pinToGpioR2 [64] =
{17, 18, 27, 22, 23, 24, 25, 4, // GPIO 0 through 7: wpi 0 - 72, 3, // I2C - SDA0, SCL0 wpi 8 - 98, 7, // SPI - CE1, CE0 wpi 10 - 1110, 9, 11, // SPI - MOSI, MISO, SCLK wpi 12 - 1414, 15, // UART - Tx, Rx wpi 15 - 1628, 29, 30, 31, // New GPIOs 8 though 11 wpi 17 - 20
} ;

第二步,根据输入和输出状态设置fsel寄存器 。作者采用简单明了的查表法,在一个FSEL寄存器中共可设置10个GPIO管脚。具体的含义可查看数据手册和gpioToGPFSEL、gpioToShift的具体定义
static uint8_t gpioToGPFSEL [] =
{0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,
} ;
static uint8_t gpioToShift [] =
{0,3,6,9,12,15,18,21,24,27,0,3,6,9,12,15,18,21,24,27,0,3,6,9,12,15,18,21,24,27,0,3,6,9,12,15,18,21,24,27,0,3,6,9,12,15,18,21,24,27,
} ;

 第三步,根据输入和输出状态设置FSEL寄存器。结合第二步便可发现其中的设置技巧。例如操作wringPi的GPIO0对应BCM GPIO17;那么查找gpioToGPFSEL表,应操作第1个(从0开始计数)FSELl寄存器;*(gpio + fSel)中gpio指GPIO外设的虚拟起始地址,此处为0x2200000,第二个FSEL寄存器在此基础上地址偏移1。 shift决定置1或者置0的具体位,例如此时的GPIO17,对应该fsel寄存器的21位;如果是输入状态21-23位全部为0,如果是输出状态21位为1,具体代码如下:

*(gpio + fSel) = (*(gpio + fSel) & ~(7 << shift)) ;                      ——设置为输入IO
*(gpio + fSel) = (*(gpio + fSel) & ~(7 << shift)) | (1 << shift) ;  ——设置为输出IO


图4 BCM2835 FSEL寄存器含义

4.3 digitalWrite

void digitalWrite (int pin, int value)
{struct wiringPiNodeStruct *node = wiringPiNodes ;// 树莓派 板载GPIO设置,板载GPIO的管脚编号必须小于64if ((pin & PI_GPIO_MASK) == 0){// 第一步 确定BCM GPIO引脚编号if (wiringPiMode == WPI_MODE_PINS)pin = pinToGpio [pin] ;// 第二步 设置高电平和低电平if (value == LOW)*(gpio + gpioToGPCLR [pin]) = 1 << (pin & 31) ;else*(gpio + gpioToGPSET [pin]) = 1 << (pin & 31) ;}else{if ((node = wiringPiFindNode (pin)) != NULL)node->digitalWrite (node, pin, value) ;}
}

该部分代码的实现可以分为两步(注意该部分并不是digitalWrite  的完整代码,为了说明问题对代码进行简化)
第一步,确定BCM GPIO引脚编号。
第二步,设置高电平和低电平。该步骤用于设置GPCLR寄存器和GPSET寄存器。BCM GPIO0到GPIO31 位于GPIO Output Set Register 0 ,相对于GPIO_BASE的偏移量为7,而BCM GPIO32到GPIO53 位于GPIO Output Set Register 1,相对于GPIO_BASE的偏移量为8。例如操作wringPi的GPIO0,对应BCM GPIO17;查找gpioToGPSET表可获得GPIO17位于GPIO Output Set Register 0寄存器,该寄存器的偏移量(相对于GPIO_BASE)为7。通过*(gpio + gpioToGPSET [pin]) = 1 << (pin & 31) ,便可设置GPIO17为输出高电平。

图5 BCM2835 SET寄存器含义 

4.4 delay

void delay (unsigned int howLong)
{struct timespec sleeper, dummy ;sleeper.tv_sec = (time_t)(howLong / 1000) ;sleeper.tv_nsec = (long)(howLong % 1000) * 1000000 ;nanosleep (&sleeper, &dummy) ;
}
delay是wiringPi提供的一个毫秒级别的延时函数,该函数通过nanosleep实现。nanosleep的声明如下:
#include <time.h> 
int nanosleep(const struct timespec *req, struct timespec *rem);
调用nanosleep使得进程挂起,直到req所设定的时间耗尽。在该函数中,req至进程最终休眠的时间而rem只剩余的休眠时间,struct timespec结构体的定义如下,

struct timespec {time_t tv_sec;      /* 秒 */long tv_nsec;      /* 纳秒 */
};
从结构体的成员来说,nanosleep似乎可以实现纳秒级别的延时,但是受到linux时钟精度的影响无法实现纳秒级别的延时,但是微妙级别的延时也可以让人接受。

5.总结

深入分析wiringPi之后收获颇丰。wiringPi可通过两种方式实现GPIO的驱动——第一,在用户空间直接操作寄存器(RAM),在用户空间操作寄存器(RAM)需要借助 /dev/mem;第二,利用/sys/class/gpio,通过操作文件的方式控制GPIO。在wiringPi中pin编号小于64为板载设备,例如GPIO0到GPIO7为板载设备,pin编号大于64为扩展设备,例如扩展的AD和DA芯片。最后可以使用nanosleep实现定时休眠。
未来将利用wiringPi实现SPI和I2C设备。

6.参考资料和博文链接

6.1 【 elinux 树莓派外设驱动开发指南】
6.2 【 树莓派 GPIO入门指南】




http://chatgpt.dhexx.cn/article/Z28VmQmq.shtml

相关文章

树莓派在下载Wiringpi时遇到的问题1.软件包 wiringpi 没有可安装候选2.失败:域名解析暂时失败。 wget: 无法解析主机地址 “project-downloads.drogon.ne

项目场景&#xff1a; 最近无聊,在玩实验室的树莓派,在装Wringpi时遇到了一些问题,记录一下 错误1 我用的是树莓派4B,在sudo apt-get install wiringpi时报了如下错误: 正在读取软件包列表… 完成 正在分析软件包的依赖关系树… 完成 正在读取状态信息… 完成 没有可用的软件…

树莓派学习笔记——wiringPi简介、安装和管脚说明

1.WiringPi简介 WiringPi是应用于树莓派平台的GPIO控制库函数&#xff0c;WiringPi遵守GUN Lv3。wiringPi使用C或者C开发并且可以被其他语言包转&#xff0c;例如python、ruby或者PHP等。WiringPi中的函数类似于Arduino的wiring系统&#xff0c;这使得熟悉arduino的用户使用wri…

WiringPi介绍及安装方法

WiringPi介绍及安装方法 1.WiringPi简介 WiringPi是应用于树莓派平台的GPIO控制库函数&#xff0c;WiringPi遵守GUN Lv3。wiringPi使用C或者C开发并且可以被其他语言包转&#xff0c;例如python、ruby或者PHP等。WiringPi中的函数类似于Arduino的wiring系统&#xff0c;这使得…

树莓派wiringPi库详解

达者为先 师者之意 树莓派wiringPi库详解 1 WiringPi安装2 wiringPi库编译和运行3 wiringPi库API大全3.1 硬件初始化函数3.2 通用GPIO控制函数3.3 时间控制函数3.4 中断3.5 多线程3.6 softPwm软件实现的PWM3.7 串口通信3.8 shift移位寄存器芯片API3.9 树莓派硬件平台特有的API…

第七课:树莓派WiringPi库

目录 一、WiringPi库介绍 二、WiringPi安装 方法一&#xff1a; 安装git工具&#xff08;已安装跳过&#xff09;&#xff08;前提已更换国内下载源&#xff09; ①&#xff1a;输入一下指令 ​②&#xff1a; 输入指令--在线获得 WiringPi 的源代码&#xff08;2019后不…

树莓派——wiringPi库详解

文章目录 查看树莓派引脚编号wiringPi库API大全硬件初始化函数通用GPIO控制函数时间控制函数串口通信串口通信配置测试代码串口通信实例 中断多线程其他 wiringPi&#xff08;特定平台&#xff0c;特定功能接口&#xff09;库是基于c语言开发的&#xff0c;提供了丰富的接口&am…

【树莓派】了解wiringPi库、控制继电器

目录 一、wiringPi库二、继电器1、继电器介绍及接线说明2、树莓派控制继电器 一、wiringPi库 wiringPi是一个很棒的树莓派IO控制库&#xff0c;使用C语言开发&#xff0c;提供了丰富的接口&#xff1a;GPIO控制&#xff0c;中断&#xff0c;多线程等。 在树莓派命令行输入gpio…

树莓派开发—— wiringPi 库的使用

一、wiringPi 的安装 参考文献&#xff1a; https://www.cnblogs.com/lulipro/p/5992172.html 进入 wiringPi的github (https://git.drogon.net/?pwiringPi;asummary) 下载安装包。点击页面的第一个链接的右边的snapshot,下载安装压缩包。 然后进入安装包所在的目录执行以下…

WiringPi安装及使用详解

这个文章先不要看&#xff0c;现在写的跟屎一样&#xff0c;我会断断续续的对本文章进行修改和优化&#xff0c;第一部分没问题&#xff0c;可以参考 WiringPi安装及使用详解 1、WiringPi的安装 2、Wiring常用命令详解 3、Python调用WiringPi库进行开发 1、安装WiringPi 登…

【wrodpress】wordpress发表文章后无法显示

搭建完wordpress&#xff0c;发表第一篇博客测试。文章发布后&#xff0c;首页已经能显示出文章的标题&#xff0c;但是点进去后却提示该页无法显示。 而且可以看到 因为我写的标题是文字&#xff0c;所以标题默认就是文字&#xff0c;而wordpress生成的链接中包含中文字符是…

如何在Hexo博客发布文章

1、markdown格式上传&#xff1a;Hexo默认使用的格式是markdown格式的文件&#xff0c;所以上传markdown文件可以自动转化为HTML格式的文章&#xff1b; 2、将.md文件复制到你hexo博客文件夹 中 source 下的 _posts 中&#xff0c;默认只有一个 hello-world.md&#xff1b; 3、…

微信公众号申请及文章发送详细流程介绍

文章目录 准备工作一、注册订阅号1.打开微信公众平台网址2.点击右上角立即注册&#xff0c;选择订阅号3.填写信息 二、登录微信公众号发表文章1.登录刚注册的公众号2.选择发表文章类型发表文章3.编辑好文章后可以选择阅览查看内容 三、管理微信公众号1.设置自己公众号内容2.管理…

使用wxjava实现发表内容、预览信息以及推送文章

大家好,我是雄雄。 文章目录 前言保存草稿的方法获取草稿箱列表根据media_id获取草稿箱信息给指定人发送预览文章推送文章(按照标签推送)前言 今天分享的内容有如下几个: 保存草稿根绝media_id会哦去草稿箱的信息发表内容,并不是发发布(已发表的内容不会推送给用户,不占…

CSDN 文章审核中的有趣现象

简 介&#xff1a; 对于今天CSDN记录实验博文在审核过程中遇到的“审核未通过”进行查询&#xff0c;通过“二分法” 逐步定位到影响审核通过的具体词语&#xff0c;经过修改之后&#xff0c;便完成了 CSDN 中的文章审核。莫名其妙的审核不通过&#xff0c;来自于输入文字中的 …

csdn发布不了文章的解决

插件的问题 问题 csdn发布不了文章&#xff0c;点击“发布后”&#xff0c;没有任何反应&#xff1b;编辑框也出现了莫名其妙的框图&#xff08;附图1&#xff09; 本以为是csdn在更新&#xff0c;经咨询客服4006600108&#xff08;可能是浏览器插件或者缓存的问题&#xff0…

在CSDN写文章是一种什么体验?

大家好&#xff0c;我是记得诚。 不知不觉&#xff0c;距离我第一篇博客&#xff0c;已快三年了&#xff0c;时间过得真快。 今天简单写一篇文章&#xff0c;记录自己在CSDN的成长进步。 开始 写博客是受杨秀章老师的启发&#xff0c;看到他充满烟火气的文字&#xff0c;给…

27.blog前端-发布文章

点击写文章&#xff0c;会跳转到该页面 点击右上角的发布&#xff0c;会弹出发布的对话框因此&#xff0c;我们要调用文章分类列表和文章标签列表的接口 以及如果是点击文字的编辑按钮进入该页面的话&#xff0c;还需要通过调用文章id获取文章详情的接口 还需要调用发布文章的…

Python爬取网上文章并发表到微信公众号

前言 话说懒惰是人类进步的原动力&#xff0c;古人诚不欺我。最近在折腾一个微信公众号&#xff0c;开始的时候在网上找一些资源然后进行二次创作然后发表到微信公众号&#xff0c;但是这就要自己先把里面的图片下载下来然后文字也复制过来然后再慢慢的上传到微信公众号&#…

如何用CSDN发布文章

CSDN怎么写博客&#xff1f; 1、首先打开CSDN官网&#xff0c;进行注册或者登录 2、登录后点击右上角的创作中心 3、进入以后点击左上角的发布 4、可以开始写入你要写的内容&#xff0c;先写入标题 5、然后再写你要写的内容&#xff0c;左侧写内容&#xff0c;右侧可以预览&a…

Hexo博客发表文章、草稿、添加分类和标签

写在前面 本文主要写了hexo的配置&#xff0c;关于博客标题这些配置HEXO已经有了很详细的介绍了&#xff0c;这里不再赘述&#xff0c;本文主要记录一些我个人认为小白比较容易有疑问的地方&#xff0c;也就是博客的主要功能&#xff0c;发表文章、添加文章分类和标签。文章可…