GPIO口是一种非常简单的IO操作,也非常常用接口,一般用于设备控制外部接入或输出开关;
通常来说,控制GPIO可以通过主板上原生IO口,也可以通过I2C芯片扩展IO两种方式。原生IO比较好理解,一般来说,通过输出1或0就可以实现拉高拉低操作。I2C芯片扩展呢?主板这侧通过I2c通讯协议,操作芯片(例如PCA9555、PCA9534之类)寄存器的值,由芯片控制其外接IO,这种芯片逻辑结构非常简单,有扩展8路IO、也有16路的,像某些主板或开发板都是通过类似信息扩展然后支持触摸屏等外设,内核挂载个驱动即可,因此,如果我们自己要控制的话,就需要了解I2C通讯协议以及相关知识。
不管原生还是扩展的,都需要硬件原理图支持,可能还需要内核的支持,对于应用开发者,这需要多种角色配合或者你自己搞得定。
1、如何控制原生GPIO?
很简单,首先要确认IO对应number。一般来说,硬件会告诉你,xx外设接在GPIOx_Yz (例如:GPIO4_D2),那么用户态怎么read or write?
- 先计算number:
number = x * 32 + (Y - ‘A’) * 8 + z
GPIO4_D2 = 4 * 32 + 3 * 8 + 2 = 154
2. 查看该io是否被扩展:
ls /sys/class/gpio/gpio154
若不存在,需要export下
echo 154 > /sys/class/gpio/export
3. 然后查看IO direction(输入or输出)
cat /sys/class/gpio/gpio154/direction
若与实际不符
需要配置下:
//配置成 输出
echo out > /sys/class/gpio/gpio154/direction
//配置成 输入
echo in > /sys/class/gpio/gpio154/direction
4. 操作value
//当io为 in时,获取IO值
cat /sys/class/gpio/gpio154/value
//当Io为out,设置1,或设置0
echo 1 > /sys/class/gpio/gpio154/value
2. 扩展芯片访问IO口
如果成熟点或厉害点,可以自己在dts整个驱动,将芯片IO映射成主板的IO,这个也是Linux下非常通用操作,这样之后,我们访问扩展芯片的IO就像访问本地IO一样,方法如上;
但这个需要知道number,这个时候,我们如何查询io number呢?
2.1 首先,要知道该芯片挂载到哪个dev下面
一般硬件人员也会提供一个i2c的号,
ls /dev/i2c-1 i2c-1 就是第一路i2c
也可以通过
/sys/class/gpio# ls -l
total 0
--w------- 1 root root 4096 Apr 14 09:36 export
gpio11 -> ../../devices/platform/fdd60000.gpio/gpiochip0/gpio/gpio11
gpio154 -> ../../devices/platform/fe770000.gpio/gpiochip4/gpio/gpio154
gpiochip0 -> ../../devices/platform/fdd60000.gpio/gpio/gpiochip0
gpiochip128 -> ../../devices/platform/fe770000.gpio/gpio/gpiochip128
gpiochip32 -> ../../devices/platform/fe740000.gpio/gpio/gpiochip32
gpiochip479 -> ../../devices/platform/fe5a0000.i2c/i2c-1/1-0022/gpio/gpiochip479
devices/platform/fe5a0000.i2c/i2c-1/1-0022/gpio/gpiochip479
“gpiochip”是指扩展芯片的,一般是一组
此处可知:i2c-1/1-0022 挂载地址0x22
2.2 查看寄存器内容:
/sys/bus/i2c/devices/i2c-1# i2cdetect -r -y 1
也可以看哪些地址被使用(挂载)
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- UU UU -- -- -- -- -- -- -- -- -- -- -- -- -- //此时,0x21 0x22 以及 0x51 都是链接i2c
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- UU -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
查看寄存器内容:
/sys/class/gpio# i2cdump -y -f 1 0x22
No size specified (using byte-data access)
0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
00: f5 fa ff ff 00 00 6b f5 XX XX XX XX XX XX XX XX ??....k?XXXXXXXX
10: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XXXXXXXXXXXXXXXX
20: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XXXXXXXXXXXXXXXX
30: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XXXXXXXXXXXXXXXX
结合芯片手册,就可知道具体含义:
一般是 in out ... config 这样
in out是value的值,config是IO的方向
这些值都按位考虑的,即每个bit表示一个IO口
2.3 i2c与gpioxxx映射关系
对于芯片扩展的,我们需要知道base 以及 count
/sys/class/gpio/gpiochip479# cat base
479
/sys/class/gpio/gpiochip479# cat ngpio
16
那么此时,对应的芯片IO(0)是479这个号
通过(参考步骤1 就可完成每个IO定义,访问)
echo 479 > /sys/class/gpio/export
类似480是指第2个IO,类推
2.4 用户态代码操作IO口
直接上代码,前提要知道 I2C地址,寄存器地址(一般0x00开始)
#include "I2cDevice.h"
#include <stdio.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <stdlib.h>
#include "Base/Logger/Define.h"
using namespace std;
I2cDevice::I2cDevice() : mFile(-1)
{
}
I2cDevice::~I2cDevice()
{
if(mFile != -1)
{
Close();
}
}
bool I2cDevice::Open(const char *node)
{
if(mFile != -1)
{
Close();
}
mFile = open(node, O_RDWR);
if(mFile < 0)
{
mFile = -1;
errorf("I2C device open [%s] failed.\n",node);
return false;
}
return true;
}
bool I2cDevice::Close()
{
if(mFile != -1)
{
close(mFile);
mFile = -1;
}
return true;
}
int I2cDevice::Read(const uint8_t addr, I2cReg* info)
{
if(mFile == -1)
{
return -1;
}
struct i2c_rdwr_ioctl_data data;
struct i2c_msg messages[2];
uint8_t reg = info->reg;
messages[0].addr = addr; /*device address*/
messages[0].flags = 0; /*write*/
messages[0].len = sizeof(reg);
messages[0].buf = ® /*data address*/
messages[1].addr = addr; /*device address*/
messages[1].flags = I2C_M_RD; /*read*/
messages[1].len = sizeof(info->val);
messages[1].buf = &(info->val);
data.msgs = messages;
data.nmsgs = 2;
/*设置从机模式*/
ioctl(mFile, I2C_SLAVE_FORCE, addr);
if(ioctl(mFile, I2C_RDWR, &data) < 0)
{
debugf("I2C read failed. addr=%02X, reg=%d, val=%d\n",addr,info->reg,info->val);
return -2;
}
return 0;
}
int I2cDevice::Write(const uint8_t addr, const I2cReg &info)
{
if(mFile == -1)
{
return -1;
}
uint8_t buf[2];
struct i2c_rdwr_ioctl_data data;
struct i2c_msg messages;
buf[0] = info.reg;
buf[1] = info.val;
messages.addr = addr; /*device address*/
messages.flags = 0; /*write*/
messages.len = 2;
messages.buf = buf; /*data address*/
data.msgs = &messages;
data.nmsgs = 1;
/*设置从机模式*/ 必须设置,否则会失败
ioctl(mFile, I2C_SLAVE_FORCE, addr);
if(ioctl(mFile, I2C_RDWR, &data) < 0)
{
debugf("I2C write failed. addr=%02X, reg=%d, val=%d\n",addr,info.reg,info.val);
return -2;
}
usleep(1000);
return 0;
}