0. 前言
这是基于正点imx6ull开发板的linux应用编程开发,正如所言,其中开发板的驱动编程已经被编写好了。
那么应用编程和驱动编程之间的关系和驱动是什么呢?对于一个系统,普通用户的程序肯定是不愿意随便一个程序直接影响到硬件的,其需要一个中介来联系应用层和硬件,而操作系统便是这个中介。而驱动程序根据硬件编写好了各种设备驱动文件,充当了硬件与应用软件中间的桥梁。而应用程序呢,则是调用已经编译好的设备驱动文件,去实现一个控制。应用编程学习主要是学习arm的linux应用层的应用开发。
1. imx6ull的GPIO
连接开发板后(步骤可以看我的上一篇文章),进入/sys/class/gpio文件夹
可以看到,其拥有以上文件夹。关于gpiochip0~gpiochip128这中间五个文件夹(后面统称为gpiochioX,X为该控制器最小引脚编号)分别代表了imx6ull的gpio1~gpio5五个控制器。注意,写引脚编号是连续的,每个gpio控制器均有32个引脚,GPIO1为0-31,GPIO2为32-63,GPIO3为64-95,GPIO4为96-127,GPIO128-159。
进入gpiochip0这个文件夹,可得知其有以下驱动文件:
其中的含义为:
1.base:与 gpiochipX 中的 X 相同,表示该控制器所管理的这组 GPIO 引脚中最小的编号
2.label: 该组 GPIO 对应的标签,也就是名字。
3.ngpio: 该控制器所管理的 GPIO 引脚的数量(所以引脚编号范围是: base ~ base+ngpio-1)
可以用cat命令看到这些信息。
回到gpio文件夹,其中还有export和unexport未进行解释
1.export:用于将指定编号的GPIO引脚导出。需要对其导出,才能成功使用GPIO引脚。例:
echo 0 > export # 导出编号为 0 的 GPIO 引脚(对于 I.MX6UL/I.MX6ULL 来说,也就是
GPIO1_IO0)
因为编号是0,所以就是GPIO1_IO0。可以思考一下,如果是echo 34 >export呢?其导出哪个引脚?
答,34大于32小于64,显然是GPIO2,GPIO2是32到63,所以是GPIO2_IO2。
2.unexport:将导出的GPIO删除,当使用完GPIO引脚后,需要将导出的引脚删除
echo 0 > unexport # 删除导出的编号为 0 的 GPIO 引脚
删除后,export语句生成的gpio0文件就会消失。
3.gpioX:这是第一条语句生成的文件,第一条生成的应该是gpio0。将指定的编号写入到 export 文件中,可以导出指定编号的 GPIO 引脚,导出成功之后会在/sys/class/gpio目录下生成对应的 gpioX(X 表示 GPIO 的编号)文件夹。
我们利用在终端执行第一条语句,可以发现生成了gpio0文件,打开gpio0,可以发现有以下文件,我们来依次解读。
1.direction: 配置 GPIO 引脚为输入或输出模式,in为输入,out为输出,设置为输出的语句为:
ech0 "out" > direction
2.value:
若 GPIO 配置为输出,,向 value 文件写入"0"控制 GPIO 引脚输出低电平;写入"1"则控制 GPIO 引脚输出高电平。
在输入模式下,读取 value 文件获取 GPIO 引脚当前的输入电平状态。
echo "1" > value
3.active_low: 这个属性文件用于控制极性, 可读可写,默认情况下为 0,控制极性是什么意思呢?在正常情况下,默认为0,那就是正常的,value输入0的时候就是控制低电平,但是如果将极性取反,active_low输入1,那么value的输入会取反,value输入1就是控制高电平了。
# active_low 等于 0 时
echo "0" > active_low
echo "out" > direction
echo "1" > value #输出高
echo "0" > value #输出低
# active_low 等于 1 时
$ echo "1" > active_low
$ echo "out" > direction
$ echo "1" > value #输出低
$ echo "0" > value #输出高
4.edge: 控制中断的触发模式,在配置 GPIO 引脚的中断触发模式之前,需将其设置为输入模式
非中断引脚: echo "none" > edge
上升沿触发: echo "rising" > edge
下降沿触发: echo "falling" > edge
边沿触发: echo "both" > edg
学习了这些概念,之后便开始代码测试部分。
2.代码
1.GPIO输出
在源码上,加入了些自己的注释,有错误欢迎指出~
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
// 全局变量用于存储GPIO路径
static char gpio_path[100];
// 配置GPIO属性的函数
static int gpio_config(const char *attr, const char *val)
{
char file_path[100]; // 用于存储文件路径
int len; // 字符串长度
int fd; // 文件描述符
// 生成属性文件的完整路径
sprintf(file_path, "%s/%s", gpio_path, attr);
// 打开属性文件
if (0 > (fd = open(file_path, O_WRONLY))) {
perror("open error"); // 打开文件出错时输出错误信息
return fd;
}
len = strlen(val); // 获取值字符串的长度
// 写入值到属性文件
if (len != write(fd, val, len)) {
perror("write error"); // 写入出错时输出错误信息
close(fd); // 关闭文件
return -1;
}
close(fd); // 关闭文件
return 0;
}
int main(int argc, char *argv[])
{
// 校验传参个数,必须是3个
if (3 != argc) {
fprintf(stderr, "usage: %s <gpio> <value>\n", argv[0]); // 提示正确用法
exit(-1);
}
// 生成GPIO路径
sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]);
// 检查GPIO目录是否存在
if (access(gpio_path, F_OK)) { // 如果目录不存在,则需要导出
int fd; // 文件描述符
int len; // 字符串长度
// 打开导出文件
if (0 > (fd = open("/sys/class/gpio/export", O_WRONLY))) {
perror("open error"); // 打开文件出错时输出错误信息
exit(-1);
}
len = strlen(argv[1]); // 获取GPIO编号字符串的长度
// 写入GPIO编号到导出文件
if (len != write(fd, argv[1], len)) { // 导出GPIO
perror("write error"); // 写入出错时输出错误信息
close(fd); // 关闭文件
exit(-1);
}
close(fd); // 关闭文件
}
// 配置GPIO为输出模式
if (gpio_config("direction", "out"))
exit(-1);
// 配置GPIO极性为默认(不反转)
if (gpio_config("active_low", "0"))
exit(-1);
// 设置GPIO的值(高电平或低电平)
if (gpio_config("value", argv[2]))
exit(-1);
// 正常退出程序
exit(0);
}
关于这段程序:
1.sprintf(file_path, "%s/%s", gpio_path, attr);这句作用是
构造属性文件的完整路径。‘/’
是普通字符,用于拼接路径
gpio_path
:第一个字符串参数,对应第一个%s
,表示GPIO的基路径,例如/sys/class/gpio/gpio20
。attr
:第二个字符串参数,对应第二个%s
,表示GPIO的属性名,例如direction
。
最后拼接路径为:/sys/class/gpio/gpio20/direction。说白了就是将gpio_path+attr写入file_path.
2.
static int gpio_config(const char *attr, const char *val)是配置GPIO属性函数,具体怎么配置呢?
当你在括号中写入attr和val后,attr则是补全路径,val则是将值写入文件描述符fd指向的文件,写完后关闭描述符,方便下次使用。
3.sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]);,输入的路径和/sys/class/gpio/gpio合并写入gpio_path,这个值和第一条解释对应。注意,这里有俩个gpio,所以终端输入数字就行,
例如./xxx 1 1。所以argv[1]是1,所以就是路径/sys/class/gpio/gpio1,同时后面 write(fd, argv[1], len),将argv[1]的值写入fd描述符指向的文件路径,就是/sys/class/gpio/export。就是设置了gpio_path路径的同时,将gpiox的引脚引出。
4.access(gpio_path, F_OK)
这行代码的作用是检查指定路径 gpio_path
是否存在。F_OK
是一个宏,用于测试文件是否存在。
5.
// 配置GPIO为输出模式
if (gpio_config("direction", "out"))
exit(-1);
可得在gpio_config()中的第一个参数attr为direction,所以file_path路径为/sys/class/gpio/gpio(*argv[1])/direction。argv[1]需要在终端输入。第二个参数val为out,赋入文件描述符fd,而fd指向direction。写入成功
之后进行上机测试:
ubuntu上命令 :${CC} -o gpio_out gpio_out.c。
如果${CC} 环境不会配置,可以看我另外一篇文章:
后连接开发板,在mobaxterm上对开发板输入命令。
利用scp指令将编译好的文件传入开发板:
scp -r book@192.168.5.12:/home/book/project/APP/app/16_gpio/gpio_out /home/root/app
scp -r 【用户名】@【ubuntu的ip地址】:【unbuntu上要传入文件的路径】 【开发板要存入的路径】
传入成功。
我这是mini板引脚图,可知gpio1对应开发板引脚的7,gpio2为9,gpio4为10
./gpio_out 【引脚编号】【高低电平】
所以我设置了1、4脚为高,2为低。
根据led,测试成功