led 驱动: 心跳灯

1. 简介:

初次体验灯的跳动方式是 间隔 0.5s 和 1s 后都亮一下(刚开始的理解总是浅显的。。。)

内核中 make menuconfig 中需要配置上 CONFIG_LEDS_TRIGGER_HEARTBEAT

代码主要涉及到的文件:

driver/leds/下的

led-triggers.c // 对外提供函数

Ledtrig-heartbeat.c //实现心跳灯

2. 分析:

理解这部分,我们从两个主要的问题来进行:

匹配过程是怎样的?

定时器运行过程是怎样的?

匹配过程

 

我们知道 mach-mini2440.c 文件是 platfrom 设备文件,设备文件的名字是 "s3c24xx_led"。

则我们要找到驱动中相同名字的模块文件,即 leds-s3c24xx.c 文件。leds-s3c24xx.c 文件是led的 platfrom 驱动文件,

 

匹配上后开始执行 probe 方法。probe中主要做的是

①创建了一个 struct s3c24xx_gpio_led 结构体,并初始化相应部分,其中需要注意的一点是 led->cdev.default_trigger = pdata->def_trigger; default_trigger是用于在链表中进行比较的依据

②初始化硬件IO口

③注册 led_classdev: led_classdev_register(&dev->dev, &led->cdev);

static int s3c24xx_led_probe(struct platform_device *dev)
{
	struct s3c24xx_led_platdata *pdata = dev->dev.platform_data;
	struct s3c24xx_gpio_led *led;
	int ret;

	led = kzalloc(sizeof(struct s3c24xx_gpio_led), GFP_KERNEL);
	if (led == NULL) {
		dev_err(&dev->dev, "No memory for device\n");
		return -ENOMEM;
	}

	platform_set_drvdata(dev, led);

	led->cdev.brightness_set = s3c24xx_led_set;
	led->cdev.default_trigger = pdata->def_trigger;
	led->cdev.name = pdata->name;
	led->cdev.flags |= LED_CORE_SUSPENDRESUME;

	led->pdata = pdata;

	/* no point in having a pull-up if we are always driving */
	if (pdata->flags & S3C24XX_LEDF_TRISTATE) {
		s3c2410_gpio_setpin(pdata->gpio, 0);
		s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_INPUT);
	} else {
		s3c2410_gpio_pullup(pdata->gpio, 0);
		s3c2410_gpio_setpin(pdata->gpio, 0);
		s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_OUTPUT);
	}

	/* register our new led device */
	ret = led_classdev_register(&dev->dev, &led->cdev);
	if (ret < 0) {
		dev_err(&dev->dev, "led_classdev_register failed\n");
		kfree(led);
		return ret;
	}

	return 0;
}

注册 led_classdev 中将 led_cdev 加入到 leds_list 链表中,并在 led_trigger_set_default(led_cdev); 中比较 strcmp(led_cdev->default_trigger, trig->name) ,如果匹配成功,执行

led_trigger_set(led_cdev, trig);

        -> if (trigger->activate)  trigger->activate(led_cdev); 这里做了什么在下边介绍

int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{
	led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
				      "%s", led_cdev->name);
	if (IS_ERR(led_cdev->dev))
		return PTR_ERR(led_cdev->dev);

#ifdef CONFIG_LEDS_TRIGGERS
	init_rwsem(&led_cdev->trigger_lock);
#endif
	/* add to the list of leds */
	down_write(&leds_list_lock);
	list_add_tail(&led_cdev->node, &leds_list); // 这里 将 led_cdev 添加进 leds_list 链表中,在 led-triggers.c 文件的183行进行匹配(心跳灯部分)
	up_write(&leds_list_lock);

	if (!led_cdev->max_brightness)
		led_cdev->max_brightness = LED_FULL;

	led_update_brightness(led_cdev);

	init_timer(&led_cdev->blink_timer);
	led_cdev->blink_timer.function = led_timer_function;
	led_cdev->blink_timer.data = (unsigned long)led_cdev;

#ifdef CONFIG_LEDS_TRIGGERS
	led_trigger_set_default(led_cdev);
#endif

	printk(KERN_DEBUG "Registered led device: %s\n",
			led_cdev->name);

	return 0;
}

 

定时器的运行过程

在内核配置上相应的选项后

ledtrig-heartbeat.c 便会编译进内核,此模块的入口函数是 heartbeat_trig_init()

此入口函数中注册了一个 led 的 trigger。

 

其中 led_trigger_register() 函数中通过轮询 leds_list链表找到上图上的 .name 是 heartbeat 的节点,并调用 led_trigger_set() 函数

led_trigger_set() 函数中会执行一次 struct led_trigger 中的 activate 方法。

 

struct led_trigger 中的 activate 方法中便是具体的定时器实现的灯的闪烁。函数中初始化定时器,并开始执行一次。

 

定时器的回调函数中实现亮灯和灭灯,并相应的继续更改定时器的超时时间。开发人员把闪烁人为的分成了4个象限(phase),根据 phase 来决定执行那个象限。

static void led_heartbeat_function(unsigned long data)
{
	struct led_classdev *led_cdev = (struct led_classdev *) data;
	struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data;
	unsigned long brightness = LED_OFF;
	unsigned long delay = 0;

	/* acts like an actual heart beat -- ie thump-thump-pause... */
	switch (heartbeat_data->phase) {
	case 0:
		/*
		 * The hyperbolic function below modifies the
		 * heartbeat period length in dependency of the
		 * current (1min) load. It goes through the points
		 * f(0)=1260, f(1)=860, f(5)=510, f(inf)->300.
		 */
		heartbeat_data->period = 300 +
			(6720 << FSHIFT) / (5 * avenrun[0] + (7 << FSHIFT));
		heartbeat_data->period =
			msecs_to_jiffies(heartbeat_data->period);
        /* 亮 70ms */
		delay = msecs_to_jiffies(70);
		heartbeat_data->phase++;
		brightness = led_cdev->max_brightness;
		break;
	case 1:
        /* 灭 (period/4 - 70)ms */
		delay = heartbeat_data->period / 4 - msecs_to_jiffies(70);
		heartbeat_data->phase++;
		break;
	case 2:
        /* 亮 70ms */
		delay = msecs_to_jiffies(70);
		heartbeat_data->phase++;
		brightness = led_cdev->max_brightness;
		break;
	default:
        /* 灭 (period-period/4 - 70)ms */
		delay = heartbeat_data->period - heartbeat_data->period / 4 -
			msecs_to_jiffies(70);
		heartbeat_data->phase = 0; // 并重新从 0 相位开始
		break;
	}

	led_set_brightness(led_cdev, brightness);
	mod_timer(&heartbeat_data->timer, jiffies + delay); // delay时间后再次出发此函数
}

其中各个象限时间的计算如下:

FSHIFT = 11;

avenrun[0] 是 uptime 命令出来的第一个参数【这里暂时理解不是很深】

root@lip:~$ uptime

23:55:41 up 17 min, 1 users, load average: 0.00, 0.01, 0.00

体现出来就是跟任务量有关,如果长期空闲,这个值会比较小,如果任务量变大,会慢慢升上去,相应的闪烁的频率也会快。

heartbeat_data->period = 300 + (6720 << 11) / (5 * avenrun[0] + (7 << 11));

heartbeat_data->period = 300 + (6720 * 2^11) / (5 * avenrun[0] + (7 * 2^11))

 

                                      = 300+13762560/(5* avenrun[0] +14336)

                                      = 300 + 13762560/14336 = 300+960 = 1260

这个值对应注释中的 f(0)=1260

 

case 0 定时 70 ms,此时先亮这些个时间

case 1 定时 (period/4 - 70)ms = 315-70 ms,此时先灭这些个时间

case 2 定时 70 ms,此时先亮这些个时间

case 3 定时 (period-period/4 - 70)ms = 1260 - 315-70,此时先灭这些个时间

 

case 0 + case 1 + case 2 + case 3 = period/4 + period-period/4 = period

 

做了个简单的实验,在 linux 下运行 grep 命令,长时间的运行一段时间,发现心跳灯的闪烁频率变快,可以看到,此心跳的功能确实是模拟的心跳的功能,根据运行的任务的轻重,灯的闪烁频率会相应的改变。

 

 

 

 

 

 

 

 

 

 

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值