今天继续点灯,当然与前两次方法(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_#defineLED_GPIO_INDEX"85"externintled_init(void);externintled_deinit(void);externintled_on(void);externintled_off(void);#endif
3.源文件led.c
#include#include#include#include#include"includes/led.h"intled_init(void){intfd; fd =open("/sys/class/gpio/export", O_WRONLY);if(fd <0)return1;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)return2;write(fd,"out",strlen("out"));close(fd);return0; }intled_deinit(void){intfd; fd =open("/sys/class/gpio/unexport", O_WRONLY);if(fd <0)return1;write(fd, LED_GPIO_INDEX,strlen(LED_GPIO_INDEX));close(fd);return0; }intled_on(void){intfd; fd =open("/sys/class/gpio/gpio"LED_GPIO_INDEX"/value", O_WRONLY);if(fd <0)return1;write(fd,"1",1);close(fd);return0; }intled_off(void){intfd; fd =open("/sys/class/gpio/gpio"LED_GPIO_INDEX"/value", O_WRONLY);if(fd <0)return1;write(fd,"0",1);close(fd);return0; }
- main.c
#include"includes/led.h"intmain(intargc,char*argv[]){charbuf[10];intres;printf("This is third led demo\\n"); res =led_init();if(res){printf("led init error,code = %d",res);return0; }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");return0;default:break; } } }
- makefile文件
Target = led_demo ARCH = loongarch CC = loongarch64-linux-gnu-gcc build_dir = build_$(ARCH)src_dir = sources inc_dir = includes . sources =$(foreachdir,$(src_dir),$(wildcard$(dir)/*.c)) objects =$(patsubst%.c,$(build_dir)/%.o,$(notdir$(sources))) includes =$(foreachdir,$(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_buildclean:rm -rf$(build_dir)cleanall:rm -rf build_x86 build_armcheck:[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子系统方式:相对内核模块方式要简单些,因为是在用户空间内编程,所以可以像裸机编程方式一样进行操作,其他子系统也可以像上述过程一样操作。