【妙算使用笔记】Linux学习笔记—— 浅谈关于妙算GPIO的使用

时间:2021-03-16 17:52:51

Linux学习笔记——

                 浅谈关于妙算GPIO的使用

        青岛科技大学 信息科学技术学院 集成162 Listen C

一.背景

去大疆ROBOMASTERS的比赛,队长让我负责视觉,于是乎把妙算放在我这了。由于其他方面(机械、嵌入式)处理的不是很好,最后视觉也没用上,所以妙算俨然被我私藏了……当然不能让这小电脑真的变成铁壳,好歹性能甩树莓派好几条街呢,不能让它瞎在我这,由此入了Linux的坑。

作为一名资深的单片机小白,接触最多的便是GPIO的使用。无论是51arduino,还是stm32,点灯无疑成为了单片机学习中的“HELLO WORLD”,虽然嵌入式Linux与单片机还是有相当大的区别,但是点个灯这种事大都会用得上嘛~。加之老师最近想用妙算的GPIO产生1msPWM,虽然考虑到跑操作系统的话速度可能会有问题换了方案,但是作为学(gao)习(shi),技多不压身,嘻嘻,那就试一下呗!

二.准备工作

首先,要对妙算的GPIO进行操作,要先了解妙算的GPIO(废话……)

关于妙算GPIO,最直接的资料便是官方的使用手册。

【妙算使用笔记】Linux学习笔记—— 浅谈关于妙算GPIO的使用

从官方的手册上得知,背面那26IO大概是这样的。作为用户操作GPIO只有1in1out。当然点个灯的话已经足够了。

而关于LinuxGPIO操作,首先我们需要建立一种思想——一切皆文件。在Linux里,为了方便操作,GPIO被虚拟为文件,也就是说我们控制GPIO,实际上是对文件进行读写操作。

在官方的用户手册中提到了GPIO的文件位置,当然不提根据Linux的体系也都在那里。

【妙算使用笔记】Linux学习笔记—— 浅谈关于妙算GPIO的使用

        除此之外,我们做的工作是点灯,关于LED的电路,想必这里应该不用再专门画个原理图了吧~。我们采用灌电流模式,即正极接vcc,负极接GPIO158,串联一个限流电阻,应该是最简单的电路了吧。这里不再累述。

三.Linux文件操作解释

关于Linux的文件操作,鄙人不才,从书中了解了一下,按自己的理解放在这里Mark一下,如果有不对的地方欢迎大家指点!

首先是Linux所包含的文件类型大概包括以下几种:

普通文件                           regular

目录文件                           directory

管道文件                           pipe

套接字文件                      socket

链接文件                           link

字符设备文件                  character

块设备文件                      block

        其中,除了目录文件和套接字文件以外,其余的都可以open。目录文件是我们常用的cddir等命令操作的,而套接字文件跟网卡相关。

        而对于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文件操作

LinuxGPIO操作,大致分为如下三个步骤:

(1)      通过/sys/class/gpio/export文件先创建用户接口,让相应的gpio出现在我们可以操作的地方。

(2)      更改/sys/class/gpio/gpioxx为引脚编号,如妙算的158接口就是gpio158/direction文件的值改为inout更改模式。

(3)      out模式下,通过更改/sys/class/gpio/gpiox/value的值,0为低电平,1为高电平,来实现对gpio的操作。

当然gpio的功能还有许许多多,而且通过观察gpiox目录下的文件我们也能看到很多操作,这里不一一介绍,无论是怎样的操作都回归到文件读写。只要了解了各文件的功能就不难办了。

【妙算使用笔记】Linux学习笔记—— 浅谈关于妙算GPIO的使用

五.点灯

(一)控制台命令

用控制台命令是最简单直接的方法。

首先先让我们在root用户下定位到gpio的文件位置:

 

cd /sys/class/gpio/gpio158

 

这里有人会问,不应该先创建用户接口吗?没错,理论上是的,但是妙算已经给用户创建好了,不需要我们再去重复操作了。

接下来,就是按我们想的,点灯!点灯前先让其灭掉,灌电流模式高电平为灭。

 

echo 1 > value

 

点灯

 

echo 0 > value

 

这时再观察我们的小灯泡,如果亮了,那就证明成功了。

如果出现没有权限等的问题,很有可能是没进入root模式,作为小白的我还是热心的提醒一下大家在操作GPIO之前不要忘了

 

su root

 

        当然这里又有人会问,既然是文件,我可不可以直接用geditvim直接打开更改呢?这里我亲自试了下,无论是哪种方式,都可以打得开,但是无法保存。这其实还是回到了Linux文件的理解上了。GPIO属于虚拟的文件系统,其文件内容是以输入输出流直接写在内存上的。而像这些文本编辑器,其原理是先将内容放进缓冲区,更改之后再保存。在硬盘上没有多大问题,而在内存中可能在你改完之后,这个文件就已经不是以前的该文件了,它是动态变化的。故只能通过文件流的方式操作。

(二)C语言实现

大家可能发现了,在控制台下操作固然容易了,但是如果想做一些程序化的操作反而不方便了,比如尝试产生PWM。所以用PythonC等语言会相对便利点。这里以C语言为例来看看怎么实现对GPIO的控制

这里我用的codeblocksroot用户下实现。

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)输出错误信息,使得探索的过程中方便了很多。


参考资料:http://blog.csdn.net/junllee/article/details/8900372