今天继续点灯,当然与前两次方法(python和内核模块)不一样,今天采用类似裸机编程的方式。
一.GPIO子系统
第三种方式其实就是通过访问Linux操作系统提供的GPIO子系统驱动框架来实现的。该驱动框架把CPU的GPIO引脚导出到用户空间,然后用户通过访问/sys文件系统进行该引脚的控制访问。
GPIO子系统支持基本的输入输出功能(其实就是GPIO的定义),包括支持中断输入的检测功能。这样通过GPIO子系统便可以控制类似LED、BEEP、KEY、红外发射对管等这类硬件模块。
通常在各个linux开发板装载的默认镜像中,LED灯会使用LED子系统驱动控制(同理,KEY使用了INPUT输入子系统驱动控制),此时所分配的引脚便与某个驱动模块绑定了,该引脚功能在用户空间是无法再被修改的。即,该引脚是无法在用户空间中使用GPIO子系统进行相关操作的。当然,我们可以提前修改内核的设备树(注释掉关于LED部分代码,可以参考我之前的第一个帖子),然后编译重新下载即可,这样该引脚与特定的驱动模块解绑了,我们便可以在用户空间下使用GPIO子系统灵活配置引脚功能(比如输入、输出或中断输入检测)。
二. GPIO子系统的命令行方式点灯
根据原理图的定义,LED连接的是GPIO 85引脚。此时,可以通过将GPIO子系统导出到用户空间下进行点灯操作,操作命令如下图1所示。
图1 GPIO子系统的命令行方式点灯
- export文件:内核申请将该编号的GPIO导出到用户空间,如果内核没有把该GPIO用于其它功能,那么在/sys/class/gpio目录下会新增一个对应编号的gpioX目录,如上图1导出了gpio85和gpio10。
- unexport文件:export的相反操作,取消导出的gpio。
- direction文件:表示GPIO引脚的方向,in(输入)/out(输出)。
- value文件:表示GPIO的电平,1(高电平)/0(低电平)。
三. 继续点灯
有了上面的介绍,我们便可以在用户空间下,以文件的方式进行点灯了(区别与python和内核模块方式)。
1.工程目录如下图2所示。
图2 工程组织目录
2.头文件led.h
#ifndef _LED_H_
#define _LED_H_
#define LED_GPIO_INDEX "85"
extern int led_init(void);
extern int led_deinit(void);
extern int led_on(void);
extern int led_off(void);
#endif
3.源文件led.c
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include "includes/led.h"
int led_init(void)
{
int fd;
fd = open("/sys/class/gpio/export", O_WRONLY);
if(fd < 0)
return 1 ;
write(fd, LED_GPIO_INDEX, strlen(LED_GPIO_INDEX));
close(fd);
fd = open("/sys/class/gpio/gpio" LED_GPIO_INDEX "/direction", O_WRONLY);
if(fd < 0)
return 2;
write(fd, "out", strlen("out"));
close(fd);
return 0;
}
int led_deinit(void)
{
int fd;
fd = open("/sys/class/gpio/unexport", O_WRONLY);
if(fd < 0)
return 1;
write(fd, LED_GPIO_INDEX, strlen(LED_GPIO_INDEX));
close(fd);
return 0;
}
int led_on(void)
{
int fd;
fd = open("/sys/class/gpio/gpio" LED_GPIO_INDEX "/value", O_WRONLY);
if(fd < 0)
return 1;
write(fd, "1", 1);
close(fd);
return 0;
}
int led_off(void)
{
int fd;
fd = open("/sys/class/gpio/gpio" LED_GPIO_INDEX "/value", O_WRONLY);
if(fd < 0)
return 1;
write(fd, "0", 1);
close(fd);
return 0;
}
- main.c
#include "includes/led.h"
int main(int argc, char *argv[])
{
char buf[10];
int res;
printf("This is third led demo\\n");
res = led_init();
if(res){
printf("led init error,code = %d",res);
return 0;
}
while(1){
printf("Please input the value : 0--off 1--on 2--blink q--exit\\n");
scanf("%10s", buf);
switch (buf[0]){
case '0':
led_off();
break;
case '1':
led_on();
break;
case '2':
for(res = 0; res < 5; res++)
{
led_on();
usleep(500000);
led_off();
usleep(500000);
}
break;
case 'q':
led_deinit();
printf("Exit\\n");
return 0;
default:
break;
}
}
}
- makefile文件
Target = led_demo
ARCH = loongarch
CC = loongarch64-linux-gnu-gcc
build_dir = build_$(ARCH)
src_dir = sources
inc_dir = includes .
sources = $(foreach dir,$(src_dir),$(wildcard $(dir)/*.c))
objects = $(patsubst %.c,$(build_dir)/%.o,$(notdir $(sources)))
includes = $(foreach dir,$(inc_dir),$(wildcard $(dir)/*.h))
CFLAGS = $(patsubst %, -I%, $(inc_dir))
$(build_dir)/$(Target) : $(objects) | create_build
$(CC) $^ -o $@
$(build_dir)/%.o : $(src_dir)/%.c $(includes) | create_build
$(CC) -c $(CFLAGS) $< -o $@
.PHONY:clean cleanall check create_build
clean:
rm -rf $(build_dir)
cleanall:
rm -rf build_x86 build_arm
check:
[url=home.php?mod=space&uid=70594]@echo[/url] $(CFLAGS)
@echo $(CURDIR)
@echo $(src_dir)
@echo $(sources)
@echo $(objects)
create_build:
[url=home.php?mod=space&uid=2293869]@MKDIR[/url] -p $(build_dir)
四.小结
通过程序方式点灯总共有三种方式(我个人理解的命令行方式,因为没有组织成语句框架结构且不能随意操作,所以不算在内)。
- python方式,最简单且易操作,可以说所有的实体东西都是透明的,极大的迎合了不懂计算机的人士需求(虽然说得不好听,但是python的初衷就是让不懂计算机体系知识的人士也能玩转计算机系统,和ardunio设计的初衷类似。)。
- 内核模块方式:具有挑战性,难度高,但是对于理解计算机体系知识有帮助,是玩转嵌入式linux系统应用设计的必备技能。
- GPIO子系统方式:相对内核模块方式要简单些,因为是在用户空间内编程,所以可以像裸机编程方式一样进行操作,其他子系统也可以像上述过程一样操作。