RK3568开发板gpio模拟LED呼吸灯

设备树

复制代码
		breathe_led: breathe-led {
        compatible = "breathe-led";
        led-gpios = <&gpio0 RK_PC0 GPIO_ACTIVE_HIGH>;  // 使用GPIO0_A0
        pwm-channel = <0>;  // 可选,如果使用PWM方式
        max-brightness = <255>;
				breathe-interval = <10>;  // 更小的间隔让呼吸更平滑
        #address-cells = <1>;
        #size-cells = <0>;
        status = "okay";
    };

代码文件

新建:/home/alientek/sdk/rk3568_linux_sdk/kernel/drivers/leds/leds-breathe-rk3568.c

复制代码
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
#include <linux/leds.h>
#include <linux/slab.h>
#include <linux/of.h>

#define DRIVER_NAME  "breathe-led"
#define BREATHE_INTERVAL_MS 10

// 呼吸曲线查找表(模拟正弦波,0-255范围)
static const u8 breathe_curve[] = {
    0,   0,   0,   0,   0,   1,   1,   2, 
    2,   3,   4,   5,   6,   7,   9,   10,
    12,  14,  16,  18,  20,  23,  25,  28,
    31,  34,  37,  41,  44,  48,  52,  56,
    60,  65,  69,  74,  79,  84,  89,  94,
    100, 105, 111, 117, 123, 129, 135, 141,
    148, 154, 161, 167, 174, 181, 188, 195,
    202, 209, 216, 223, 230, 237, 244, 251,
    255, 251, 244, 237, 230, 223, 216, 209,
    202, 195, 188, 181, 174, 167, 161, 154,
    148, 141, 135, 129, 123, 117, 111, 105,
    100, 94,  89,  84,  79,  74,  69,  65,
    60,  56,  52,  48,  44,  41,  37,  34,
    31,  28,  25,  23,  20,  18,  16,  14,
    12,  10,  9,   7,   6,   5,   4,   3,
    2,   2,   1,   1,   0,   0,   0,   0
};

#define BREATHE_CURVE_SIZE ARRAY_SIZE(breathe_curve)

struct breathe_led {
    struct device *dev;
    struct gpio_desc *gpiod;
    struct timer_list timer;
    struct work_struct work;

    int brightness;
    int direction;
    int max_brightness;
    int breathe_interval;

    int pwm_period; //pwm周期
    int pwm_duty;   //当前占空比
    int pwm_counter;    //pwm计数器

    int curve_index;  //呼吸曲线索引

    bool is_active;
};

static void breathe_led_pwm_control(struct breathe_led *led){
    if(!led->gpiod)
        return;

    led->pwm_counter++;
    if(led->pwm_counter >= led->pwm_period){
        led->pwm_counter = 0;
    }
    
    if(led->pwm_counter < led->pwm_duty){
        gpiod_set_value(led->gpiod,1);
    }else{
        gpiod_set_value(led->gpiod,0);
    }
}

static void update_breathe_brightness(struct breathe_led *led){
    //更新曲线索引
    led->curve_index++;
    if(led->curve_index >= BREATHE_CURVE_SIZE){
        led->curve_index = 0;
    }
    led->brightness = breathe_curve[led->curve_index];
    led->pwm_duty = (led->brightness * led->pwm_period) / 255;
}

static void breathe_led_work(struct work_struct *work){
    struct breathe_led *led = container_of(work,struct breathe_led,work);

    //使用查找表版本的呼吸曲线
    update_breathe_brightness(led);

    //应用pwm控制
    breathe_led_pwm_control(led);

    if(led->is_active){
        mod_timer(&led->timer,jiffies + msecs_to_jiffies(led->breathe_interval));
    }
}

static void breathe_led_timer(struct timer_list *t){
    struct breathe_led *led = from_timer(led,t,timer);
    schedule_work(&led->work);
}

static void breathe_led_start(struct breathe_led *led){
    if(led->is_active)
        return;

    led->is_active = true;
    led->brightness = 0;
    led->pwm_counter = 0;
    led->pwm_duty = 0;
    led->curve_index = 0;
    led->direction = 1;

    //启动定时器
    mod_timer(&led->timer,jiffies + msecs_to_jiffies(led->breathe_interval));
    dev_info(led->dev,"Breathe LED started, period: %d, interval: %dms\\n",led->pwm_period,led->breathe_interval);
}

static void breathe_led_stop(struct breathe_led *led){
    if(!led->is_active)
        return;

    led->is_active = false;
    del_timer_sync(&led->timer);
    cancel_work_sync(&led->work);

    //关闭LED
    gpiod_set_value(led->gpiod,0);
    dev_info(led->dev,"Breathe LED stopped\\n");
}

static ssize_t brightness_show(struct device *dev,struct device_attribute *attr,char *buf){
    struct breathe_led *led = dev_get_drvdata(dev);
    return sprintf(buf,"%d\\n",led->brightness);
}

static ssize_t brightness_store(struct device *dev,struct device_attribute *attr,const char *buf,size_t count){
    struct breathe_led *led = dev_get_drvdata(dev);
    int value;

    if(kstrtoint(buf,0,&value)){
        return -EINVAL;
    }

    if(value >= 0 && value <= led->max_brightness){
        led->brightness = value;
        led->pwm_duty = (value * led->pwm_period) / led->max_brightness;
        breathe_led_pwm_control(led);
    }
    return count;
}

static DEVICE_ATTR(brightness,0644,brightness_show,brightness_store);

static ssize_t breathe_show(struct device *dev,struct device_attribute *attr,char *buf){
    struct breathe_led *led = dev_get_drvdata(dev);
    return sprintf(buf,"%d\\n",led->is_active ? 1 : 0);
}

static ssize_t breathe_store(struct device *dev,struct device_attribute *attr,const char *buf,size_t count){
    struct breathe_led *led = dev_get_drvdata(dev);
    int value;
    
    if(kstrtoint(buf,0,&value))
        return -EINVAL;

    if(value){
        breathe_led_start(led);
    }else{
        breathe_led_stop(led);
    }
    return count;
}

static DEVICE_ATTR(breathe,0644,breathe_show,breathe_store);

static struct attribute *breathe_led_attrs[] = {
    &dev_attr_breathe.attr,
    &dev_attr_brightness.attr,//亮度控制
    NULL,
}; 

static struct attribute_group breathe_led_attr_group = {
    .attrs = breathe_led_attrs
};

static int breathe_led_probe(struct platform_device *pdev){
    struct device *dev = &pdev->dev;
    struct breathe_led *led;
    int ret;

    led = devm_kzalloc(dev,sizeof(*led),GFP_KERNEL);
    if(!led)
        return -ENOMEM;

    led->dev = dev;

    //获取GPIO
    led->gpiod = devm_gpiod_get(dev,"led",GPIOD_OUT_LOW);
    if(IS_ERR(led->gpiod)){
        dev_err(dev,"Failed to get GPIO\\n");
        return PTR_ERR(led->gpiod);
    }

    //从设备树获取参数
    device_property_read_u32(dev,"max-brightness",&led->max_brightness);
    if(!led->max_brightness)
        led->max_brightness = 255;

    device_property_read_u32(dev,"breathe-interval",&led->breathe_interval);
    if(!led->breathe_interval)
        led->breathe_interval = BREATHE_INTERVAL_MS;

    //设置pwm参数
    led->pwm_period = 50;
    led->pwm_counter = 0;
    led->pwm_duty = 0;
    led->curve_index = 0;

    //初始化定时器与工作队列
    timer_setup(&led->timer,breathe_led_timer,0);
    INIT_WORK(&led->work,breathe_led_work);

    platform_set_drvdata(pdev,led);

    ret = sysfs_create_group(&dev->kobj,&breathe_led_attr_group);
    if(ret){
        dev_err(dev,"Failed to create sysfs group\\n");
        return ret;
    }

    //启动呼吸灯效果
    breathe_led_start(led);

    dev_err(dev,"Breathe LED driver probed successfully\\n");
    dev_info(dev,"PWM period: %d,Max brightness: %d,Interval: %dms\\n",led->pwm_period,led->max_brightness,led->breathe_interval);
    return 0;
}

static int breathe_led_remove(struct platform_device *pdev){
    struct breathe_led *led = platform_get_drvdata(pdev);
    breathe_led_stop(led);
    sysfs_remove_group(&pdev->dev.kobj,&breathe_led_attr_group);

    dev_info(&pdev->dev,"Breathe LED driver removed\\n");
    return 0;
}

static const struct of_device_id breathe_led_of_match[] = {
    { .compatible = "breathe-led" },
    { }
};

MODULE_DEVICE_TABLE(of,breathe_led_of_match);

static struct platform_driver breathe_led_driver = {
    .probe = breathe_led_probe,
    .remove = breathe_led_remove,
    .driver = {
        .name = DRIVER_NAME,
        .of_match_table = breathe_led_of_match
    }
};

module_platform_driver(breathe_led_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Liangrong.Ren");
MODULE_DESCRIPTION("RK3568 Breathe LED Driver");
MODULE_VERSION("1.0");

Kconfig配置

修改/home/alientek/sdk/rk3568_linux_sdk/kernel/drivers/leds/Kconfig,加入下面内容

复制代码
config LEDS_BREATHE_RK3568
    tristate "RK3568 Breathe LED support"
    depends on (ARCH_ROCKCHIP && LEDS_CLASS) || COMPILE_TEST
    help
      This option enables support for breathe LED on RK3568 SoCs.
      It creates a breathing effect using GPIO and timer.

Makefile配置

修改/home/alientek/sdk/rk3568_linux_sdk/kernel/drivers/leds/Makefile,加入下面内容

复制代码
obj-$(CONFIG_LEDS_BREATHE_RK3568) += leds-breathe-rk3568.o

编译内核

执行编译内核命令,进入内核kernel的目录

复制代码
1. 进入sdk的内核目录
cd /home/alientek/sdk/rk3568_linux_sdk/kernel

2. 执行下面命令
make ARCH=arm64 menuconfig
 
3. 进入页面按/进入搜索CONFIG_LEDS_BREATHE_RK3568:
 Symbol: LEDS_BREATHE_RK3568 [=y]                                                                                                                    
  │ Type  : tristate                                                                                                                
  │ Prompt: RK3568 Breathe LED support          
  │   Location:                                         
  │     -> Device Drivers                                  
  │ (1)   -> LED Support (NEW_LEDS [=y])                             
  │   Defined at drivers/leds/Kconfig:765                                                     
  │   Depends on: NEW_LEDS [=y] && (ARCH_ROCKCHIP [=y] && LEDS_CLASS [=y] || COMPILE_TEST [=n])  
  
  
 4. 然后按上面数字1进入页面,按空格选中: 
  
 <*>   RK3568 Breathe LED support     
 
 5. 然后保存退出
 
 6. make ARCH=arm64 savedefconfig
 7. cp .config arch/arm64/configs/rockchip_defconfig
 8. 退回到sdk根目录编译内核,重新烧写boot.img

启动日志

复制代码
[    1.968598] breathe-led breathe-led: Breathe LED started, period: 50, interval: 10ms
[    1.968615] breathe-led breathe-led: Breathe LED driver probed successfully
[    1.968625] breathe-led breathe-led: PWM period: 50,Max brightness: 255,Interval: 10ms

看到这个日志就成功了,开发板上的LED灯两快闪一短闪,目前模拟实现就只能这样了。

相关推荐
UP_Continue1 小时前
Linux权限
linux·运维·服务器
d111111111d1 小时前
STM32低功耗学习-待机模式-(学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习
深思慎考1 小时前
微服务即时通讯系统(服务端)——网关服务设计与实现(7)
linux·c++·微服务·云原生·架构
hazy1k1 小时前
MSPM0L1306 从零到入门:第七章 通用定时器(GPTIM) —— 成为时间的主宰
stm32·单片机·嵌入式硬件·mcu·物联网·esp32·ti
天天爱吃肉82181 小时前
VBOX GNSS/INS系统在车辆动态坡度测量中的原理、精度与应用实践
python·嵌入式硬件·汽车
ShiLiu_mtx1 小时前
Keepalived,Haproxy负载均衡集群
linux·运维·负载均衡
幸福右手牵1 小时前
服务器 IP 地址配置方案
linux·服务器·tcp/ip·智能路由器
vortex51 小时前
Ubuntu 虚拟机配置静态 IP
linux·tcp/ip·ubuntu
橘颂TA1 小时前
【Linux】进程池
linux·运维·服务器·c++