|
本帖最后由 lonerzf 于 2014-1-18 21:25 编辑
有了上次 BB Black 入门基础之初识GPIO 的基础,今天的内容就轻松多了。咱们使用poll()来轮询设备,配置GPIO中断,接着控制LED灯。
参考资料是 derek molly 在YouTube上的视频介绍(不方便发链接,自己搜吧),以及BridgeRun的GPIO实现程序。
同样地,为了减少管理员的工作量,链接以截图形式给出。
前期准备
不管是面包板还是万用板还是印制电路板还是别的,只要有类似功能的都可以。
今天要做的就是读入按键部分的高低电平信号,并控制LED灯亮灭。
GPIO操作步骤大致总结如下:
1 在文件系统中配置内核GPIO支持。这点我们不需要做了。直接在/sys/class/gpio 可用。
如果你非知道怎么在sysfs中配置,我们再进一步学习交流。
elinux.org/GPIO上写着是这么配置的
Symbol: GPIO_SYSFS [=y]
Prompt: /sys/class/gpio/... (sysfs interface)
Defined at drivers/gpio/Kconfig:51
Depends on: GPIOLIB && SYSFS && EXPERIMENTAL
Location:
-> Kernel configuration
-> Device Drivers
-> GPIO Support (GPIOLIB [=y])
当然,这部分现就不做展开了。别忘了咱们的目的,先入门再说吧。
2 导出我们需要操作并且可用的GPIO口。
3 配置GPIO的direction 为in 或 out。
4 配置GPIO作为中断源(如果有需要) 。需要配置上下沿触发rising, falling,或者both,即两者都触发中断。
注意:如果配置了GPIO作为中断源,那么程序对于该GPIO的value的读取会一直被阻塞,直到有相应的中断发生。
好了,下面介绍程序部分。
咱们的核心程序是poll()。这里就简单介绍下吧,深入的我也不会了。
//下面的都是个人总结,难免有误。如有错误请指正。
poll函数原型如下:
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
返回值:
-1 表示有错误.一种可能是调用被中断。
0 表示在文件描述符就绪之前调用超时。
>0 表示一个或多个文件描述符就绪,在revents域中可以读取返回值.
参数:
参数fds 文件描述符数组。
它是一个如下结构体
struct pollfd
{
int fd; /* File descriptor */
short events; /* Requested events bit mask */
short revents; /* Returned events bit mask */
};
参数nfds_t unsigned int类型数据, 表示poll()中共需要轮询处理的文件描述符的个数。
参数timeout 超时时间, 单位毫秒。timeout = -1,则持续等待; timeout=0,直接运行而不等待;timeout>0,则等待timeout时间。
关于pollfd 结构体,也简单介绍下。
events 和revents这两个域都是 bit masks 操作,下表列举了每个域中的各种值。
第一组的几位和输入事件相关
第二组的几位和输出事件相关
第三组的几位返回的是文件描述符的附加信息。
最后一个位保留
这里可能对POLLIN和POLLPRI疑惑比较大,我当时也是,找了好多资料都没怎么讲到。
就近似这么理解吧:
POLLPRI用于高优先级数据输入,也就是紧急数据的读物,POLLIN处理的是普通数据。
补充:
blog.csdn.net/jnu_simba/article/details/8806654
这篇博客讲得不错,关于文件描述符的。下面摘录其中一部分:
用户程序不能直接访问内核中的文件描述符表,而只能使用文件描述符表的索引 (即0 1 2 3 ...),这些索引就称为文件描述符(File Descriptor),用int型变量保存。当调用open打开一个文件或创建一个新文件时,内核分配一个文件描述符并返回给用户程序,该文件描述符表项中的指针指向新打开的文件。当读写文件时,用户程序把文件描述符传给read 或write ,内核根据文件描述符找到相应的表项,再通过表项中的指针找到相应的文件。
默认情况下(没有重定向),每个进程的标准输入(stdin)、标准输出(stdout)和标准错误输出(stderr)都指向控制终端,因为在程序启动时(在main函数还没开始执行之前)会自动把控制终端打开三次,分别赋给三个FILE*指针stdin、stdout和stderr,这三个文件指针是libc中定义的全局变量,这三个文件的描述符分别是0、1、2,保存在相应的FILE结构体中。进程从标准输入读也就是读用户的键盘输入,进程往标准输出或标准错误输出写也就是输出到显示器上。头文件unistd.h 中有如下的宏定义来表示这三个文件描述符:
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
好了,有了前面的铺垫,上代码。
改编自BridgeRun的源程序。
源程序在这里
Gpio-int-test.rar
(2.03 KB, 下载次数: 40)
以下是改过的程序,多少有点差别。但是人家的思想很好,很有借鉴意义,是吧?嘿嘿。
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define POLL_TIMEOUT (3 * 1000) //超时时间定为3s
#define MAX_BUF 64
typedef unsigned char uint8_t;
//注意以下的几个函数,都做成了独立可复用的函数模块。这个思想很值得我们学习借鉴的。
//gpio export function
int gpioExport(uint8_t gpioPin)
{
int fd, len;
char buf[MAX_BUF];
fd = open( "/sys/class/gpio/export", O_WRONLY);
if (fd < 0)
{
perror("gpio/export");
return fd;
}
len = snprintf(buf, sizeof(buf), "%d", gpioPin);
write(fd, buf, len);
close(fd);
return 0;
}
// gpio unexport function
int gpioUnexport(uint8_t gpioPin)
{
int fd, len;
char buf[MAX_BUF];
fd = open( "/sys/class/gpio/unexport", O_WRONLY);
if (fd < 0)
{
perror("gpio/export");
return fd;
}
len = snprintf(buf, sizeof(buf), "%d", gpioPin);
write(fd, buf, len);
close(fd);
return 0;
}
//gpio set direction function
int gpioSetDirection(uint8_t gpioPin, uint8_t outdir)
{
int fd, len;
char buf[MAX_BUF];
len = snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d/direction", gpioPin);
fd = open(buf, O_WRONLY);
if (fd < 0)
{
perror("gpio/direction");
return fd;
}
if (outdir)
{
strcpy(buf, "out");
}
else
{
strcpy(buf, "in");
}
len = strlen(buf);
write(fd, buf, len);
close(fd);
return 0;
}
// gpio set value function
int gpioSetValue(uint8_t gpioPin, uint8_t value)
{
int fd, len;
char buf[MAX_BUF];
len = snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d/value", gpioPin);
fd = open(buf, O_WRONLY);
if (fd < 0)
{
perror("gpio/set-value");
return fd;
}
if (value)
{
strcpy(buf, "1");
}
else
{
strcpy(buf, "0");
}
len = strlen(buf);
write(fd, buf, len);
close(fd);
return 0;
}
//gpio get value function
int gpioGetValue(uint8_t gpioPin, uint8_t *value)
{
int fd;
char buf[MAX_BUF];
char ch;
snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d/value", gpioPin);
fd = open(buf, O_RDONLY);
if (fd < 0)
{
perror("gpio/get-value");
return fd;
}
read(fd, &ch, 1);
if (ch != '0')
{
*value = 1;
}
else
{
*value = 0;
}
close(fd);
return 0;
}
//gpio set edge function
int gpioSetEdge(uint8_t gpioPin, const char *edge)
{
int fd;
char buf[MAX_BUF];
snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d/edge", gpioPin);
fd = open(buf, O_WRONLY);
if (fd < 0)
{
perror("gpio/set-edge");
return fd;
}
write(fd, edge, strlen(edge) + 1);
close(fd);
return 0;
}
// gpio fd open function
int gpioFdOpen(uint8_t gpioPin)
{
int fd;
char buf[MAX_BUF];
snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d/value", gpioPin);
fd = open(buf, O_RDONLY | O_NONBLOCK );
if (fd < 0)
{
perror("gpio/fd_open");
}
return fd;
}
// close a gpio with the file descriptor we have known.
int gpioFdClose(int fd)
{
return close(fd);
}
// 到此为止就是几个函数模块的声明与实现。
//下面就是主函数。最重要的当然要看poll()了。
int main(int argc, char **argv, char **envp)
{
struct pollfd fdset[2];
int nfds = 2;
int gpio_fd, timeout, rc;
char *buf[MAX_BUF];
uint8_t gpioPin, ledPin;
bool isLEDFlashing = false;
if (argc < 3)
{
printf("Usage: HelloBBB \n");
printf("Waits for a change in the GPIO pin voltage level or input on stdin\n");
exit(-1);
}
gpioPin = atoi(argv[1]); // string to int
ledPin = atoi(argv[2]);
gpioExport(gpioPin);
gpioSetDirection(gpioPin, 0); //input
gpioSetEdge(gpioPin, "rising");
gpio_fd = gpioFdOpen(gpioPin); //the open file descriptor for input gpio.
gpioExport(ledPin);
gpioSetDirection(ledPin, 1); //led output
timeout = POLL_TIMEOUT;
while(1)
{
memset((void *)fdset, 0, sizeof(fdset) ); //fill with 0, clear the memory.
fdset[0].fd = STDIN_FILENO;
fdset[0].events = POLLIN; //Data other than high-priority data can be read
fdset[1].fd = gpio_fd;
fdset[1].events = POLLPRI; //High-priority data can be read
rc = poll(fdset, nfds, timeout);
if (rc < 0) // if poll is unsuccessful, returns a negative value.
{
printf("\npoll() failed!\n");
return -1;
}
if (rc == 0) //timeout, loop again.
{
printf("#");
}
if (fdset[1].revents & POLLPRI) // if the button was pressed, high-priority value
{
read(fdset[1].fd, buf, MAX_BUF);
printf("\n poll() GPIO %d interrupt occurred\n", gpioPin);
if(isLEDFlashing)
{
gpioSetValue(ledPin, 1);
}
else
{
gpioSetValue(ledPin, 0);
}
isLEDFlashing = !isLEDFlashing;
}
if (fdset[0].revents & POLLIN)
{
(void)read(fdset[0].fd, buf, 1);
printf("\n poll() stdin read 0x%2.2X\n", (unsigned int) buf[0]);
}
fflush(stdout);
}
gpioFdClose(gpio_fd);
return 0;
}
看看运行是什么下效果吧。这里的参数参数 GPIO口为什么是26 和44就不用再解释了吧。
结果还算满意。怎么样,很有成就感吧?
运行视频见附件
VID_LED_INT.rar
(11.67 MB, 下载次数: 142)
好啦。今天就到这里吧,程序部分个人感觉还算好,就是开始接触Linux系统接口会有那么一个过程。
其他的暂时没什么要注意的了吧。今天的知识点不多,但足够消化一会儿啦。
|
|