Linux学习笔记——
浅谈关于妙算GPIO的使用
青岛科技大学 信息科学技术学院 集成162 Listen C
一.背景
去大疆ROBOMASTERS的比赛,队长让我负责视觉,于是乎把妙算放在我这了。由于其他方面(机械、嵌入式)处理的不是很好,最后视觉也没用上,所以妙算俨然被我私藏了……当然不能让这小电脑真的变成铁壳,好歹性能甩树莓派好几条街呢,不能让它瞎在我这,由此入了Linux的坑。
作为一名资深的单片机小白,接触最多的便是GPIO的使用。无论是51,arduino,还是stm32,点灯无疑成为了单片机学习中的“HELLO WORLD”,虽然嵌入式Linux与单片机还是有相当大的区别,但是点个灯这种事大都会用得上嘛~。加之老师最近想用妙算的GPIO产生1ms的PWM,虽然考虑到跑操作系统的话速度可能会有问题换了方案,但是作为学(gao)习(shi),技多不压身,嘻嘻,那就试一下呗!
二.准备工作
首先,要对妙算的GPIO进行操作,要先了解妙算的GPIO(废话……)
关于妙算GPIO,最直接的资料便是官方的使用手册。
从官方的手册上得知,背面那26个IO大概是这样的。作为用户操作GPIO只有1个in1个out。当然点个灯的话已经足够了。
而关于Linux的GPIO操作,首先我们需要建立一种思想——一切皆文件。在Linux里,为了方便操作,GPIO被虚拟为文件,也就是说我们控制GPIO,实际上是对文件进行读写操作。
在官方的用户手册中提到了GPIO的文件位置,当然不提根据Linux的体系也都在那里。
除此之外,我们做的工作是点灯,关于LED的电路,想必这里应该不用再专门画个原理图了吧~。我们采用灌电流模式,即正极接vcc,负极接GPIO158,串联一个限流电阻,应该是最简单的电路了吧。这里不再累述。
三.Linux文件操作解释
关于Linux的文件操作,鄙人不才,从书中了解了一下,按自己的理解放在这里Mark一下,如果有不对的地方欢迎大家指点!
首先是Linux所包含的文件类型大概包括以下几种:
普通文件 regular
目录文件 directory
管道文件 pipe
套接字文件 socket
链接文件 link
字符设备文件 character
块设备文件 block
其中,除了目录文件和套接字文件以外,其余的都可以open。目录文件是我们常用的cd、dir等命令操作的,而套接字文件跟网卡相关。
而对于IO,又分为两大类:系统IO和标准IO。
系统IO是系统调用接口,相当于是最直接最原始的操作,在c语言中可以对其open(),read(),write(),lseek(),roctl(),close()等操作。
而标准IO是标准C库接口,是一种有缓冲可以多样化操作的方式,一般我们在学C语言时都是用的标准C库。由于其多样性的特点,后文只用一种方案实现不去深究。对它的操作可以用fopen(),fread(),fwrite(),fgets(),fputs(),fseek(),fprintf(),fscanf(),fclose()等函数。
需要注意的是,标准IO也可以按照系统IO的方法操作。
注意,网络接口只能用系统IO!
关于其他的注意事项,这里不再多谈,感兴趣的可以去看一下《Linux环境编程图文指南》这本书,写的很详细。
关于各函数的用法这里也不再一一解释,总之对文件的操作思路即打开文件,写数据,关闭文件。无论是控制台命令,还是各种语言的编程,思路都是一样的,不过是实现方法不同而已。
四.GPIO文件操作
对Linux的GPIO操作,大致分为如下三个步骤:
(1) 通过/sys/class/gpio/export文件先创建用户接口,让相应的gpio出现在我们可以操作的地方。
(2) 更改/sys/class/gpio/gpiox(x为引脚编号,如妙算的158接口就是gpio158)/direction文件的值改为in或out更改模式。
(3) 在out模式下,通过更改/sys/class/gpio/gpiox/value的值,0为低电平,1为高电平,来实现对gpio的操作。
当然gpio的功能还有许许多多,而且通过观察gpiox目录下的文件我们也能看到很多操作,这里不一一介绍,无论是怎样的操作都回归到文件读写。只要了解了各文件的功能就不难办了。
五.点灯
(一)控制台命令
用控制台命令是最简单直接的方法。
首先先让我们在root用户下定位到gpio的文件位置:
cd /sys/class/gpio/gpio158
这里有人会问,不应该先创建用户接口吗?没错,理论上是的,但是妙算已经给用户创建好了,不需要我们再去重复操作了。
接下来,就是按我们想的,点灯!点灯前先让其灭掉,灌电流模式高电平为灭。
echo 1 > value
点灯
echo 0 > value
这时再观察我们的小灯泡,如果亮了,那就证明成功了。
如果出现没有权限等的问题,很有可能是没进入root模式,作为小白的我还是热心的提醒一下大家在操作GPIO之前不要忘了
su root
当然这里又有人会问,既然是文件,我可不可以直接用gedit或vim直接打开更改呢?这里我亲自试了下,无论是哪种方式,都可以打得开,但是无法保存。这其实还是回到了Linux文件的理解上了。GPIO属于虚拟的文件系统,其文件内容是以输入输出流直接写在内存上的。而像这些文本编辑器,其原理是先将内容放进缓冲区,更改之后再保存。在硬盘上没有多大问题,而在内存中可能在你改完之后,这个文件就已经不是以前的该文件了,它是动态变化的。故只能通过文件流的方式操作。
(二)C语言实现
大家可能发现了,在控制台下操作固然容易了,但是如果想做一些程序化的操作反而不方便了,比如尝试产生PWM。所以用Python、C等语言会相对便利点。这里以C语言为例来看看怎么实现对GPIO的控制
这里我用的codeblocks在root用户下实现。
1. 系统IO方式点灯
/* GPIO应用 妙算的GPIO2_OUT为158 方便调试,用unistd中的usleep延时微秒,sleep延时毫秒 */ #include <stdio.h> #include <unistd.h> //符号常量,在VC环境下不包含它可能会报错 #include <sys/ioctl.h> //GPIO Control #include <sys/stat.h> //获取文件属性 #include <linux/fs.h> //与设备驱动程序有关 #include <fcntl.h> //改变已打开的文件性质 #include <string.h> #include <termios.h> //参数设置 #include <errno.h> int gpio_fd = -1; //document int ret; //fail or success char gpio[]="158";//gpio number char dir[]="out"; //direction char off[]="1"; //high char on[]="0"; //low int set_gpio_init()//create gpio document { //open gpio_fd = open("/sys/class/gpio/export",O_WRONLY); if(gpio_fd < 0) { printf("open gpio/export failed\:%s\n",strerror(errno)); return -1; } //write ret = write(gpio_fd,gpio,strlen(gpio)); if(ret < 0) { printf("write to gpio/export failed:%s\n",strerror(errno)); return -1; } close(gpio_fd); return 0; } int set_gpio158_dir()//set gpio direction { //open gpio_fd = open("/sys/class/gpio/gpio158/direction",O_RDWR); if(gpio_fd < 0) { printf("open /gpio158/direction failed:%s\n",strerror(errno)); return -1; } //write ret = write(gpio_fd,dir,strlen(dir)); if(ret < 0) { printf("write to /gpio157/direction failed:%s\n",strerror(errno)); return -1; } close(gpio_fd); return 0; } int set_gpio158_open()//open value { gpio_fd = open("/sys/class/gpio/gpio158/value",O_RDWR); if(gpio_fd < 0) { printf("open /gpio158/value failed:%s\n",strerror(errno)); return -1; } } int set_gpio158_close()//close value { close(gpio_fd); return 0; } int set_gpio158_high()//echo 1 > value { ret = write(gpio_fd,off,strlen(off)); if(ret < 0) { printf("write to /gpio158/value failed:%s\n",strerror(errno)); return -1; } return 0; } int set_gpio158_low()//echo 0 >value { ret = write(gpio_fd,on,strlen(on)); if(ret < 0) { printf("write to /gpio158/value failed:%s\n",strerror(errno)); return -1; } return 0; } int main() { set_gpio158_open(); while(1) { printf("LED off\n"); set_gpio158_high(); sleep(5); printf("LED on\n"); set_gpio158_low(); sleep(5); } set_gpio158_close(); return 0; }
2. 标准IO方式点灯
/* GPIO应用 妙算的GPIO2_OUT为158 方便调试,用unistd中的usleep延时微秒,sleep延时毫秒 */ #include <stdio.h> #include <unistd.h> //符号常量,在VC环境下不包含它可能会报错 #include <sys/ioctl.h> //GPIO Control #include <sys/stat.h> //获取文件属性 #include <linux/fs.h> //与设备驱动程序有关 #include <fcntl.h> //改变已打开的文件性质 #include <string.h> #include <termios.h> //参数设置 #include <errno.h> FILE *gpio_fd; //document int ret; //fail or success char gpio[]="158";//gpio number char dir[]="out"; //direction int off=1; //high int on=0; //low int set_gpio_init()//create gpio document { //open gpio_fd = fopen("/sys/class/gpio/export","w"); if(gpio_fd == NULL) { printf("open gpio/export failed:%s\n",strerror(errno)); return -1; } //write ret = fprintf(gpio_fd,"%s",gpio); if(ret < 0) { printf("write to gpio/export failed:%s\n",strerror(errno)); return -1; } fclose(gpio_fd); return 0; } int set_gpio158_dir()//set gpio direction { //open gpio_fd = fopen("/sys/class/gpio/gpio158/direction","w"); if(gpio_fd < 0) { printf("open /gpio158/direction failed:%s\n",strerror(errno)); return -1; } //write ret = fprintf(gpio_fd,"%s",dir); if(ret < 0) { printf("write to /gpio157/direction failed:%s\n",strerror(errno)); return -1; } fclose(gpio_fd); return 0; } int set_gpio158_open()//open value { gpio_fd = fopen("/sys/class/gpio/gpio158/value","r+"); if(gpio_fd < 0) { printf("open /gpio158/value failed:%s\n",strerror(errno)); return -1; } } int set_gpio158_close()//close value { fclose(gpio_fd); return 0; } int set_gpio158_high()//echo 1 > value { set_gpio158_open(); ret = fprintf(gpio_fd,"%d",off); set_gpio158_close(); if(ret < 0) { printf("write to /gpio158/value failed:%s\n",strerror(errno)); return -1; } return 0; } int set_gpio158_low()//echo 0 >value { set_gpio158_open(); ret = fprintf(gpio_fd,"%d",on); set_gpio158_close(); if(ret < 0) { printf("write to /gpio158/value failed:%s\n",strerror(errno)); return -1; } return 0; } int main() { while(1) { printf("LED off\n"); set_gpio158_high(); sleep(5); printf("LED on\n"); set_gpio158_low(); sleep(5); } return 0; }
3. 模拟echo方式调节PWM
/* GPIO应用 妙算的GPIO2_OUT为158 方便调试,用unistd中的usleep延时微秒,sleep延时毫秒 */ #include <stdio.h> #include <unistd.h> //符号常量,在VC环境下不包含它可能会报错 #include <sys/ioctl.h> //GPIO Control #include <sys/stat.h> //获取文件属性 #include <linux/fs.h> //与设备驱动程序有关 #include <fcntl.h> //改变已打开的文件性质 #include <string.h> #include <termios.h> //参数设置 #include <stdlib.h> #define set_gpio158_Init() system("echo 157 > /sys/class/gpio/export") #define set_gpio158_dir() system("echo out > /sys/class/gpio/gpio157/direction"); #define set_gpio158_low() system("echo 0 > /sys/class/gpio/gpio158/value") #define set_gpio158_high() system("echo 1 > /sys/class/gpio/gpio158/value") /* PWM Mode START 0 ->first low 1->first high rat 0 - 100 rat% cycle a cycle time Ttype 1 -> um 1000 -> ms 1000000 -> s Total time = cycle*Ttype us */ void Set_PWM(int START,int rat,int cycle,int Ttype) { if(START) set_gpio158_high(); else set_gpio158_low(); usleep(cycle*rat/100*Ttype); if(START) set_gpio158_low(); else set_gpio158_high(); usleep(cycle*(100-rat)/100*Ttype); } int main() { int i,j=0; printf("PWM start!\n"); while(1) { for(i=100;i>=0;i--) Set_PWM(j,i,100,1000); usleep(100*1000); j = ~j; } return 0; }
六.总结
在前两种方式下点灯效果正常,需要注意Linux版本不同包含的头文件会有差异,具体查询Linux内核版本请在控制台下输入一下命令:
uname –a
在PWM调节呼吸灯时,虽然可以看到有很粗略的“渐变”效果,但是很差。这也是我们担心的问题。的确可以让妙算进行毫秒级延时,但是因为跑操作系统有许许多多工作,导致波形非常不稳定,用示波器观察惨不忍睹。所以在这种情况下选择单片机做通信比直接控制要效果好很多。
这次的学习遇到不少问题,也有很多粗心大意导致结果不对。通过strerror(errno)输出错误信息,使得探索的过程中方便了很多。