当前位置: 首页 > news >正文

控制LED灯设备

本章分别使用C库和系统调用的文件操作方式控制开发板的LED灯,展示如何在应用层通过系统提供的设备文件控制相关硬件。

本章的示例代码目录为:base_code/linux_app/led/sys_class_leds。

9.1. LED子系统

在Linux系统中,绝大多数硬件设备都有非常成熟的驱动框架,驱动工程师使用这些框架添加与板子相关的硬件支持,建立硬件与Linux内核的联系,内核再通过统一文件系统接口呈现给用户,用户通过对应的设备文件控制硬件。

对于LED设备,Linux提供了LED子系统驱动框架,在Linux内核源码中的“Documentation/leds/leds-class.txt”有相关的描述,它实现了一个leds类,用户层通过sysfs文件系统对LED进行控制。

9.1.1. LED设备目录

使用了LED子系统驱动的设备,会被展现在/sys/class/leds目录下,可在主机和开发板使用如下命令查看,命令的输出可能会因为硬件环境不同而不一样:

 123456789
10
11
#在主机或ARM板的终端上执行如下命令:
ls /sys/class/leds/#根据具体的目录内容继续查看:#在主机上有input2::capslock目录,可在主机执行如下命令查看
ls /sys/class/leds/input2::capslock#在开发板上有cpu目录,可在开发板上执行如下命令查看ls /sys/class/leds/cpu

如下图

未找到图片02|

未找到图片03|

上图可看到,示例中的Ubuntu主机和开发板/sys/class/leds下包含了以LED设备名 字命名的目录,如“input2::capslock”、“input2::numlock”和“blue”、“cpu”等LED灯,这 些目录对应的具体LED灯如下表所示。

表 /sys/class/leds下目录对应的设备

/sys/class/leds下的目录

对应的LED灯设备

input2::capslock

键盘大写锁定指示灯(input后的数字编号可能不同)

input2::numlock

键盘数字键盘指示灯(input后的数字编号可能不同)

input2::scrolllock

键盘ScrollLock指示灯(input后的数字编号可能不同)

cpu

开发板的心跳灯

red

Pro开发板RGB灯的红色,Mini开发板的用户灯

green

Pro开发板RGB灯的绿色,Mini开发板的用户灯

blue

Pro开发板RGB灯的蓝色,Mini开发板的用户灯

mmc0:

SD卡指示灯(出厂镜像默认没有启用)

9.1.2. LED设备属性

上图中,在具体的LED目录下又包含brightness、max_brightness、trigger等文件,这些文件包含了LED设备的属性和控制接口。

  • max_brightness文件:表示LED灯的最大亮度值。

  • brightness文件:表示当前LED灯的亮度值,它的可取 值范围为[0~max_brightness],一些LED设备不支持多级亮度,直接以非0值来 表示LED为点亮状态,0值表示灭状态。

  • trigger文件:则指示了LED灯的触发方式,查看该文件的内容时,该文件会 列出它的所有可用触方式,而当前使用的触发方式会以“[]”符号括起。常见的触 发方式如下表所示。

表 trigger常见的触发方式

触发方式

说明

none

无触发方式

disk-activity

硬盘活动

nand-disk

nand flash活动

mtd

mtd设备活动

timer

定时器

heartbeat

系统心跳

9.2. 控制LED实验(C库函数)

在《命令行点灯和检测按键》章节中,我们演示了使用echo命令修改设备文件,实际上也可以使用gedit、Vim等编辑器进 行修改,修改时注意用户权限即可。既然设备是以文件形式提供的,那么自然也可以使用C库函数 或系统调用的方式读写文件,达到控制设备的目的。

9.2.1. 实验代码分析

本小节的示例代码目录为:led/sys_class_leds/c_stdio。

本小节先演示使用C库函数控制LED,具体如下所示。

通过C库函数控制LED(base_code/linux_app/led/sys_class_leds/c_stdio/sources/main.c文件)

 123456789
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>//ARM 开发板LED设备的路径
#define RLED_DEV_PATH "/sys/class/leds/red/brightness"
#define GLED_DEV_PATH "/sys/class/leds/green/brightness"
#define BLED_DEV_PATH "/sys/class/leds/blue/brightness"//Ubuntu主机LED设备的路径,具体请根据自己的主机LED设备修改
// #define RLED_DEV_PATH "/sys/class/leds/input2::capslock/brightness"
// #define GLED_DEV_PATH "/sys/class/leds/input2::numlock/brightness"
// #define BLED_DEV_PATH "/sys/class/leds/input2::scrolllock/brightness"int main(int argc, char *argv[])
{FILE *r_fd, *g_fd, *b_fd;printf("This is the led demo\n");//获取红灯的设备文件描述符r_fd = fopen(RLED_DEV_PATH, "w");if(r_fd < 0){printf("Fail to Open %s device\n", RLED_DEV_PATH);exit(1);}//获取绿灯的设备文件描述符g_fd = fopen(GLED_DEV_PATH, "w");if(g_fd < 0){printf("Fail to Open %s device\n", GLED_DEV_PATH);exit(1);}//获取蓝灯的设备文件描述符b_fd = fopen(BLED_DEV_PATH, "w");if(b_fd < 0){printf("Fail to Open %s device\n", BLED_DEV_PATH);exit(1);}while(1){//红灯亮fwrite("255",3,1,r_fd);fflush(r_fd);//延时1ssleep(1);//红灯灭fwrite("0",1,1,r_fd);fflush(r_fd);//绿灯亮fwrite("255",3,1,g_fd);fflush(g_fd);//延时1ssleep(1);//绿灯灭fwrite("0",1,1,g_fd);fflush(g_fd);//蓝灯亮fwrite("255",3,1,b_fd);fflush(b_fd);//延时1ssleep(1);//蓝灯亮fwrite("0",1,1,b_fd);fflush(b_fd);}
}

可以发现,这个控制LED灯的过程就是一个普通的文件写入流程:

  • 第5~13行:定义了三盏LED灯的brightness文件路径。配套的程序默认使用 开发板RGB灯的路径,如果要在Ubuntu主机上测试请根据自己主机上的设备文件修改10~13行的内容。

  • 第18~41行:使用fopen库函数,以“w”的写模式打开了三盏LED的brightness文件,并获得文件描述符。

  • 第43~70行:在循环中分别对三盏灯写入“255”和“0”的字符串来控制LED灯的亮 度,写入后调用了fflush库函数要求立刻把缓冲区的内容写入到文件上。

本代码有两处值得注意的地方:

如果是普通文件,按代码while循环的执行流程,运行一段时间后,由于多次 写入,文件中的内容应该为“255025502550255”这样的字符串,但对于此 处的brightness设备文件,它的最终内容只是“255”或“0”,而不是像普通 文件那样记录了一连串前面输入的字符。这是因为在LED的设备驱动层中 ,brightness文件就相当于一个函数的参数接口,每次对文件执行写入操 作时,会触发驱动代码以这次写入的内容作为参数,修改LED灯的亮度;而每次读 取操作时,则触发驱动代码更新当前LED灯亮度值到brightness文件,所以brightness始终 是一个0~255的亮度值,而不是“255025502550255”这样的字符串。特别地, 如果在一次写入操作中,直接写入“0255025502550”这样的 字符串,驱动层会把它当成数字255025502550,而该数字大于最大亮度值,所以它最终会以255的 亮度控制LED灯,若此时读取brightness文件,也会发现它的值确实是255。关于这些细节, 在学习了LED子系统框架后查看驱动源码可更好地了解。

另一处要注意的是代码中调用fwrite函数写入内容时,它可能只是把内容保存 到了C库的缓冲区,并没有执行真正的系统调用write函数把内容写入到设备文件,这种情况下LED灯 的状态是不会被改变的,代码中在fwrite函数后调用了fflush要求立刻把缓冲区的内容写入到文件,确保 执行了相应的操作。在实验时可以尝试把代码中的fflush都注释掉, 这种情况下有极大的几率是无法正常改变LED灯状态的。

如果不考虑操作的时间开销,其实控制硬件更推荐的做法是,每次控制LED灯都使用fopen—fwrite—fclose的 流程,这样就不需要考虑flseek、fflush的问题了。当然,我们最推崇的还是下一小节直接通过 系统调用来控制硬件的方式。

9.2.2. 编译及测试

本实验使用的Makefile由上一章节修改而来,修改了最终的可执行文件名为led_demo,以及C源 文件目录改为了main.c文件所在的sources,其它方面没有差异。

9.2.2.1. x86架构

本工程的main.c实验代码使用的设备文件默认是开发板 上的RGB灯,在Ubuntu主机上并没有这样的设备,如果想尝试在主机上使用, 可以根据自己Ubuntu主机上存的LED设备修改代码中的LED路径,然后使用make直接编译测试。

1
2
3
4
5
6
7
#在主机的实验代码Makefile目录下编译
#默认编译x86平台的程序
make
#运行需要root权限,要使用sudo运行
#运行需要root权限,要使用sudo运行
sudo ./build_x86/led_demo
#程序运行后终端会输出提示,相应的LED灯设备状态会改变
9.2.2.2. ARM架构

对于ARM架构的程序,可使用如下步骤进行编译:

1
2
3
#在主机的实验代码Makefile目录下编译
#编译arm平台的程序
make ARCH=arm

编译后生成的ARM平台程序为build_arm/led_demo,使用网络文件系统共享至开 发板,在开发板的终端上测试即可。

如下图:

未找到图片09|

程序执行后终端会有输出,开发板上的三盏用户LED灯也会轮流闪烁。

9.3. 控制LED实验(系统调用)

由于使用C库的文件操作函数存在缓冲机制,使用它来控制硬件时存在不 确定性,所以我们更喜欢直接以系统调用来控制硬件设备。

9.3.1. 实验代码分析

本小节的示例代码目录为:led/sys_class_leds/c_systemcall。

本小节通过系统调用的文件操作方式控制LED,具体如下所示。

通过系统调用控制LED(base_code/linux_app/led/sys_class_leds/c_systemcall/sources/main.c文件)

 123456789
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>//ARM 开发板LED设备的路径
#define RLED_DEV_PATH "/sys/class/leds/red/brightness"
#define GLED_DEV_PATH "/sys/class/leds/green/brightness"
#define BLED_DEV_PATH "/sys/class/leds/blue/brightness"//Ubuntu主机LED设备的路径,具体请根据自己的主机LED设备修改
// #define RLED_DEV_PATH "/sys/class/leds/input2::capslock/brightness"
// #define GLED_DEV_PATH "/sys/class/leds/input2::numlock/brightness"
// #define BLED_DEV_PATH "/sys/class/leds/input2::scrolllock/brightness"int main(int argc, char *argv[])
{int res = 0;int r_fd, g_fd, b_fd;printf("This is the led demo\n");//获取红灯的设备文件描述符r_fd = open(RLED_DEV_PATH, O_WRONLY);if(r_fd < 0){printf("Fail to Open %s device\n", RLED_DEV_PATH);exit(1);}//获取绿灯的设备文件描述符g_fd = open(GLED_DEV_PATH, O_WRONLY);if(g_fd < 0){printf("Fail to Open %s device\n", GLED_DEV_PATH);exit(1);}//获取蓝灯的设备文件描述符b_fd = open(BLED_DEV_PATH, O_WRONLY);if(b_fd < 0){printf("Fail to Open %s device\n", BLED_DEV_PATH);exit(1);}while(1){//红灯亮write(r_fd, "255", 3);//延时1ssleep(1);//红灯灭write(r_fd, "0", 1);//绿灯亮write(g_fd, "255", 3);//延时1ssleep(1);//绿灯灭write(g_fd, "0", 1);//蓝灯亮write(b_fd, "255", 3);//延时1ssleep(1);//蓝灯亮write(b_fd, "0", 1);}
}

本实验代码与上一小节使用C库函数操作的控制流程完全一样,只是把C库的文件操作 替换成了系统调用的文件操作方式,特别之处在于这种方式不需要调用fflush之类的 函数确保缓冲区的内容被写出,而且系统调用也不存在类似这样操作的函数。

相对C库函数的操作方式,通过系统调用更加简单直接,而且这种与设备文件联系比较 紧密的应用,C库函数兼容性好的优点也没有用武之地,所以在编写这类应用通常直接使用系统调用的方式。

9.3.2. 编译及测试

本实验使用的Makefile与上一小节的完全一样,不再分析。

本实验的x86和arm架构的编译、测试步骤也与上一小节完全一样,注意切换到对应的工程路径即可。

对于ARM架构的程序,可使用如下步骤进行编译:

1
2
3
#在主机的实验代码Makefile目录下编译
#编译arm平台的程序
make ARCH=arm

编译后生成的ARM平台程序为build_arm/led_demo,使用网络文件系统共享至开发 板,在开发板的终端上测试即可。

如下图:

未找到图片10|

程序执行后终端会有输出,开发板上的三盏用户LED灯也会轮流闪烁,实验现象 与使用C库函数操作方式是一样的。

Next  Previous

相关文章:

  • 专题一:汉诺塔问题:递归算法的精妙解析
  • Spring框架(一)
  • OpenResty反向代理
  • 在Java项目中实现本地语音识别与热点检测,并集成阿里云智能语音服务(优化版)
  • 【Part 2安卓原生360°VR播放器开发实战】第四节|安卓VR播放器性能优化与设备适配
  • Redis设计与实现——单机Redis实现
  • iVX 平台技术解析:图形化与组件化的融合创新
  • 信息系统项目管理师-软考高级(软考高项)​​​​​​​​​​​2025最新(十五)
  • 深入剖析缓存与数据库一致性:Java技术视角下的解决方案与实践
  • java的Stream流处理
  • MySql(进阶)
  • macOS 15 (Sequoia) 解除Gatekeeper限制
  • wget、curl 命令使用场景与命令实践
  • 第八讲 | stack和queue的使用及其模拟实现
  • MySQL 数据库故障排查指南
  • 浏览器的B/S架构和C/S架构
  • 什么是卷积神经网络
  • QtGUI模块功能详细说明,事件与输入处理(五)
  • 无人机飞控算法开发实战:从零到一构建企业级飞控系统
  • JDS-算法开发工程师-第9批
  • 体坛联播|C罗儿子完成国家队首秀,德约结束与穆雷合作
  • 中保协发布《保险机构适老服务规范》,全面规范保险机构面向老年人提供服务的统一标准
  • 王毅集体会见加勒比建交国外长及代表
  • 扶桑谈|从石破茂“越菲行”看日本周边外交布局战略新动向
  • 言短意长|西湖大学首次“走出西湖”
  • 来伊份深夜回应“粽子中吃出疑似创可贴”:拿到实物后会查明原因