本文转自http://blog.csdn.net/xukai871105/article/details/17881433,这篇有关树莓派wiringPi库函数及使用的介绍写的蛮好的,虽然自己曾经也使用过该库使用过其中的部分函数,但是远未有这位博主理解的深刻。
1.前言
最近认真学习了树莓派,从浅到深认真分析了wiringPi实现代码,借助树莓派学习linux收获颇丰。深入学习linux一段时间后发现它非常有魅力,一个简单的IO口输出操作尽有那么多的“玩法”。wiringPi是一个简单易用的函数库,通过wiringPi可以扩展SPI和I2C等芯片,关于wiringPi的介绍和安装请参考我的另一篇【博文】。
2.BCM2835 GPIO相关寄存器
(该部分表述可能有误,正在确认修改中)
图1 BCM2835 物理地址和虚拟地址映射关系
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( )
{
// 初始化wiringPi
wiringPiSetup();
int i = 0;
// 设置IO口全部为输出状态
for( i = 0 ; i < 8 ; i++ )
pinMode(i, OUTPUT);
for (;;)
{
for( i = 0 ; i < 8 ; i++ )
{
// 点亮500ms 熄灭500ms
digitalWrite(i, HIGH); delay(500);
digitalWrite(i, LOW); delay(500);
}
}
return 0;
}
为了方便生成可执行文件,可编写以下makefile文件,cd进入该目录之后直接make即可。
blink:blink.o
gcc blink.c -o blink -lwiringPi
clean:
rm -f blink blink.o
图3 代码运行结果
4.代码详解
上面的代码非常简单,可以分为四个部分——wiringPiSetupi初始化、pinMode设置IO为输出方向、digitalWrite输出高电平或低电平和delay系统延时函数。
4.1wiringPiSetup
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 ;
}
int wiringPiSetupSys (void)
{
int boardRev ;
int pin ;
char fName [128] ;
// 获得树莓派版本编号,版本1或者版本2
boardRev = piBoardRev () ;
if (boardRev == 1)
{
pinToGpio = pinToGpioR1 ;
physToGpio = physToGpioR1 ;
}
else
{
pinToGpio = pinToGpioR2 ;
physToGpio = physToGpioR2 ;
}
// 查找/sys/class/gpio,并记录GPIOx操作文件fd
for (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的管脚编号必须小于64
if ((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 ;
}
}
static int pinToGpioR2 [64] =
{
17, 18, 27, 22, 23, 24, 25, 4, // GPIO 0 through 7: wpi 0 - 7
2, 3, // I2C - SDA0, SCL0 wpi 8 - 9
8, 7, // SPI - CE1, CE0 wpi 10 - 11
10, 9, 11, // SPI - MOSI, MISO, SCLK wpi 12 - 14
14, 15, // UART - Tx, Rx wpi 15 - 16
28, 29, 30, 31, // New GPIOs 8 though 11 wpi 17 - 20
} ;
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,
} ;
4.3 digitalWrite
void digitalWrite (int pin, int value)
{
struct wiringPiNodeStruct *node = wiringPiNodes ;
// 树莓派 板载GPIO设置,板载GPIO的管脚编号必须小于64
if ((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) ;
}
}
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时钟精度的影响无法实现纳秒级别的延时,但是微妙级别的延时也可以让人接受。