初心
从事嵌入式开发已经是第七个年头了,想想还从来没有网上发布过一点东西。可能也是由于工作环境没有方便的互联网的原因,更大的原因是我这个人比较懒;但是和公司新人的技术分享,我可从来没有吝啬过,公司内部的共享资料库我的提交量也是第一。找个平台记录一下,至少证明我也在这个行业混过些年。虽然现在也是菜鸟一枚,特别是当前人工智能的功能越来越强大,还不知道还能在这个行业走多久。有段时间啃一些开源的代码,都是找人工智能软件帮我读,或者把需求提明确,直接就能把代码给我写出来了。想想真是可怕呀;
想想当年初入开发岗位,公司的基础库可以说是没有,从点亮一个LED灯都是从头开始,经过这么些年的积累,新人加入也不会束手无策了。
既然开始写东西,按照行规还是从最基础的入门吧,毕竟所有的教程第一课都是“点亮一个LED”!
妖娆多姿的LED指示灯
LED作为一个最基础的元器件。不管是STM32,CPU,FPGA,硬件工程师必然要画一些指示灯上去;可以说是存在与整个板子的开发,交付、维护周期。所有相关人员通过指示灯就能够一目了然的判断设备的基础状态。
那么,一个有内核洁癖的linux工程师是怎么点亮一个LED指示灯的呢?当然是用内核现成的驱动来搞了,能不用自己写代码的干嘛非要动手写呢!而且内核提供的丰富操作接口,可以让一个简单的led指示灯变的丰富多彩,除了可以实现指示灯秒闪、心跳闪烁,还可以用来指示MTD设备的读写、GPIO状态、最新的内核还支持指示网卡的link状态,流量指示等,还可以用户自定义很多指示状态。通过此文分享一些linux内核的leds应用,希望能够抛砖引玉,能够让led在各个领域发挥它的价值。
在此,也希望和同行们一起分享linux内核带给我们的便利,现在的项目我都尽可能去适配内核驱动,不管是一个散热风扇,一个LCD屏幕,或者是一个eeprom,库仑计等等,都是先去内核源码里面去翻一下,看看有没有适配的驱动,可能开发量并不比自己写一个接口或者写一个驱动小。但是能从驱动适配的过程中,去体会到高手们写的去驱动,理解到设计者的设计思想,对我的启发也是非常的大。
分享第一个点就是多去看看内核Documentation目录下的帮助文档,学习怎么配置设备数,怎么使用驱动。自己写的一些驱动,也最好按照内核标准来设计,然后完善相关的devicetree/bingding文档。
内核配置
通常来说,我们的LED指示灯一般都接到CPU的GPIO或者PWM脚,linux内核提供了gpio-led驱动和pwm-led驱动,驱动源码在drivers/led/s目录下: leds-gpio.c leds-pwm.c,内核配置选项中把这两个驱动都选中。
关于GPIO 和 PWM 控制器的驱动,一般都是由芯片厂家适配完成,所以gpio和pwm层的驱动我们一般是不需要关心的,只需要了解怎么在设备树中调用。以上驱动只提供了操作led的基础接口,想要让LED 灯妖娆多姿,那就需要trigger驱动出场了。按照需求使能以下trigger模块,后面会对每个功能模块的功能和使用作出解释。
--- LED Trigger support
│ │ <*> LED Timer Trigger
│ │ <*> LED One-shot Trigger
│ │ [*] LED MTD (NAND/NOR) Trigger
│ │ <*> LED Heartbeat Trigger
│ │ <*> LED backlight Trigger
│ │ [*] LED CPU Trigger
│ │ <*> LED activity Trigger
│ │ <*> LED GPIO Trigger
│ │ <*> LED Default ON Trigger
│ │ *** iptables trigger is under Netfilter config (LED target) ***
│ │ <*> LED Transient Trigger
│ │ <*> LED Camera Flash/Torch Trigger
│ │ [*] LED Panic Trigger
│ │ <*> LED Netdev Trigger
│ │ <*> LED Pattern Trigger
│ │ <*> Audio Mute LED Trigger
---
内核选项中把这些trigger类型全部使能,其中有些触发类型我至今也没弄明白该怎么使用,比如camera、Audio、backlight等,由于不从事多媒体类的设备开发,暂时没有机会去尝试这些触发类型调试。
设备树配置
gpio-leds配置:
/{
leds {
compatible = "gpio-leds";
sys_run {
label = "sys_run";
gpios = <&gpc 21 GPIO_ACTIVE_LOW INGENIC_GPIO_NOBIAS>;
linux,default-trigger = "heartbeat";
};
};
};
以上只是一个最基础的gpio-leds配置,将挂接在GPIO_PC(21)管脚上的一枚低点亮的LED配置为心跳状态(心跳灯的闪烁有点像飞机翅膀上的指示灯)。
进内核后,该指示灯会模拟心跳的跳动节奏闪烁,一般用来指示CPU的正常工作。
pwm-leds配置:
/{
pwm_leds {
compatible = "pwm-leds";
kpad0 {
label = "keypad0";
pwms = <&pwm 2 40000 0>;
max-brightness = <255>;
};
kpad1 {
label = "keypad1";
pwms = <&pwm 3 40000 0>;
max-brightness = <255>;
};
};
};
分别配置了两个挂接在pwm控制器的2、3通道上的两个led指示灯,和GPIO 控制的指示灯的区别在于,它可以有亮度的控制,比如很多手机流行的呼吸灯提示新消息等。当然,pwm几乎支持了所有的gpio-led的触发类型。
关于gpio和pwm通道的配置,一般厂商提供的设备树都有可以参考的;一般格式就是<&label x x>,后面的两个值和#gpio-cell以及#pwm-cells有关;
设备树的配置其实很复杂,可以设置各种属性,这里只是简单提一下,详细操作可以参考所使用内核源码工程的/Documentation\devicetree\bindings\leds\leds.gpio.yaml和leds-pwm.txt;
文档有很多更高级的用法,但很多配置可以进系统后,在sys目录的相关属性中手动配置,这样便于测试各种trigger效果。
sys/class下的配置
如果手里没有合适的开发板,随便找一台linux主机也可以测试。低头看看你正在敲击的键盘右上角上,是不是至少有3个灯。
哈哈,capslock、numlock、scrolllock也是通过led子系统管理的,我们可以暂借来玩玩。
进入目录:
cd /sys/class/leds/input3::capslock
查看支持的触发类型:
cat trigger
none usb-gadget usb-host rfkill-any rfkill-none kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrlock kbd-ctrllock kbd-altlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock disk-activity disk-read disk-write ide-disk mtd nand-disk cpu cpu0 cpu1 cpu2 cpu3 cpu4 cpu5 panic rc-feedback audio-mute audio-micmute timer [heartbeat]
这个属性会列出所有支持的触发类型,当前的触发类型会用[ ]括起来;
如果没有你想要的触发类型也没有关系,进入(不同版本的系统驱动模块位置可能有所差异)
cd /lib/modules/5.15.0-124-generic/kernel/drivers/leds/trigger/
直接插入你想要的驱动模块就可以支持上了。
当使能不同的触发类型,当前led的属性内容也会发生变化,例如timer触发,则会有属性来配置亮和灭的时间,netdev触发则会有属性来配置指示的网卡等;
说到网卡指示灯,当年有一个项目需要由led来指示,记得好像是4.19版本的内核,还没有支持到netdev-trigger,当年我可是费了很大的心血,在网卡驱动里面去添加了led-trigger来指示linkup 、linkdown、tx、tx等。后来升级到5.10版本内核,居然直接就支持上了。而且还支持usb网卡、甚至是虚拟网卡,功能可比我做的强大多了。
还有一个很好玩的,led One-shot,内核文档是这样描述的:
-
This is a LED trigger useful for signaling the user of an event where
there are no clear trap points to put standard led-on and led-off
settings. Using this trigger, the application needs only to signal
the trigger when an event has happened, than the trigger turns the
LED on and than keeps it off for a specified amount of time.This trigger is meant to be usable both for sporadic and dense
events. In the first case, the trigger produces a clear single
controlled blink for each event, while in the latter it keeps
blinking at constant rate, as to signal that the events are arriving
continuously.A one-shot LED only stays in a constant state when there are no
events. An additional “invert” property specifies if the LED has to
stay off (normal) or on (inverted) when not rearmed.
具体使用场景就是,当用户态需要点亮一段时间后并关闭led指示灯,但是没有适合的位置放置开启和关闭命令,那么可以调用该接口。该触发器可用于间歇性和密集的事件,在第一种情况下,触发器会为每个事件产生一个清晰的单次受控闪烁,而在第二种情况下,它会以恒定的速率持续闪烁,以表示事件正在连续到达。
使用方法是:
echo oneshot > trigger
echo 1 > shot #触发一次闪烁,delay也可以通过属性来进行配置
用户编程例如想用来展示串口或则其他通信口的状态,也可以按照系统调用的方法来触发。
再补充一个gpio类型的trigger,使能该触发类型后,还需要传入触发源GPIO,这个触发的功能效果就是,当你检测的那个gpio拉低或则拉高,对应的指示灯亮或则灭。分析这个驱动可以得知,监测原理是将监测gpio配置成输入模式,双边沿中断;分别在中断发生后,作出不同的点灯动作。所以当gpio硬件或则是驱动不支持双边沿中断时,这个功能是没法使用的。
其中也存在一个问题,就是目前很多cpu的usb都支持了otg功能,系统所能够提供的触发类型只有gadget和host ,所以我一般是参考这个框架,实现了usb-both的触发,当设置成OTG功能的时候,就可以两种模式都兼容。
功能太多了,这里就不一一解释了。更多功能等着大家一起区发掘,linux内核像是给我们提供了一个庞大的资源库一样,等着我们区发掘,还是那句话,内核的Documentation下,比我写的详细的多,如果有不对的地方或则不明白的地方,参考内核文档。
以上功能在sys下测试完成后,就可以直接在设备树中配置,这样上电直接就能运行。
仅仅是一个led灯,就可以玩出这么多花活。时间有限,如果有什么不清楚的地方,欢迎下面留言,我会一直维护此帖。