最近在一个A33 Android4.4项目上,遇到客户要求用gpio模拟呼吸灯效果,大家都知道Linux是存在任务调度的,于是我尝试了用misc设备提供ioctl接口上去,在HAL层做逻辑处理,结果并不太理想,呼吸时会有闪烁效果,如果开个APP,则闪烁效果更明显,根本无法保证呼吸灯的效果,因为存在系统调用以及任务调度的延时,甚至在HAL做过把处理呼吸灯效果的逻辑线程绑定指定的CPU核心,结果只能有一点点改善,还是无法达到客户的要求。话不多说,最后用Linux中的hrtimer实现了gpio模拟呼吸灯的效果,效果很棒!!
实现代码如下:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>
#include <linux/slab.h>
#include <linux/stddef.h>
/* mach/sys_config.h for allwinner platform */
#include <mach/sys_config.h>
struct analog_pwm_t{
struct hrtimer hrtimer;
ktime_t kt;
unsigned long long peroid; /* ns */
unsigned long active_zone; /* pwm有效区 */
unsigned long dead_zone; /* pwm死区 */
unsigned long total_zone; /* pwm周期 = total_zone * hrtimer_tick_accuracy */
unsigned int hrtimer_tick; /* hrimer的滴答计数 */
unsigned long hrtimer_tick_accuracy; /* pwm的精度 */
int gpio; /* gpio number */
u32 f_polarity; /* pwm极性 */
u32 f_hrtimer_tick_reset;
u32 f_set_pwm_dead; /* 设置pwm死区标志 */
};
static struct analog_pwm_t *g_analog_pwm = NULL;
static int fetch_sysconfig(void)
{
int err = 0;
u32 led_used = 0;
script_item_u val;
script_item_value_type_e type;
type = script_get_item("led_para", "led_used", &val);
if (SCIRPT_ITEM_VALUE_TYPE_INT != type) {
printk(KERN_ERR "%s script_parser_fetch \"led_para\" led_used = %d\n",
__FUNCTION__, val.val);
err = -1;
goto exit;
}
led_used = val.val;
if(!led_used) {
printk(KERN_ERR "%s led is not used in config\n", __FUNCTION__);
err = -2;
goto exit;
}
type = script_get_item("led_para", "led_rd", &val);
if(SCIRPT_ITEM_VALUE_TYPE_PIO != type) {
printk(KERN_ERR "led_rd gpio ctrl io type err!");
err = -1;
goto exit;
}else{
g_analog_pwm->gpio = val.gpio.gpio;
if(0 != gpio_request(g_analog_pwm->gpio, "red led pwm")) {
printk(KERN_ERR "ERROR: analog_pwm red led c gpio_request is failed\n");
err = -1;
goto exit;
}
}
gpio_direction_output(g_analog_pwm->gpio, 0);
return err;
exit:
return err;
}
static enum hrtimer_restart hrtimer_handler(struct hrtimer *timer)
{
if(g_analog_pwm->hrtimer_tick == g_analog_pwm->active_zone && !g_analog_pwm->f_set_pwm_dead){
g_analog_pwm->active_zone++; /* 增加pwm有效区 */
g_analog_pwm->dead_zone = g_analog_pwm->total_zone - g_analog_pwm->active_zone; /* 计算出pwm死区时间 = g_analog_pwm->dead_zone * g_analog_pwm->kt */
g_analog_pwm->f_set_pwm_dead = true; /* 下次应该设置pwm死区 */
g_analog_pwm->hrtimer_tick = 0; /* 计数清0 */
//printk("[PWM] =================>total = %d, active = %d\n", g_analog_pwm->total_zone, g_analog_pwm->active_zone);
/* 如果死区为0,则表明一个呼吸cycle结束结束 则反转pwm的极性 */
if(g_analog_pwm->dead_zone == 0){
g_analog_pwm->active_zone = 0;
g_analog_pwm->f_hrtimer_tick_reset = true;
g_analog_pwm->f_polarity = !g_analog_pwm->f_polarity;
}
if(g_analog_pwm->f_polarity) /* pwm极性判断 */
gpio_set_value(g_analog_pwm->gpio, 1);
else
gpio_set_value(g_analog_pwm->gpio, 0);
}
if(g_analog_pwm->hrtimer_tick == g_analog_pwm->dead_zone && g_analog_pwm->f_set_pwm_dead){
g_analog_pwm->f_set_pwm_dead = false; /* 下次应该设置pwm有效区 */
g_analog_pwm->f_hrtimer_tick_reset = true;
//printk("[PWM] =================>total = %d, dead = %d\n", g_analog_pwm->total_zone, g_analog_pwm->dead_zone);
//printk("[PWM] =================> one cycle =================> \n");
if(g_analog_pwm->f_polarity)
gpio_set_value(g_analog_pwm->gpio, 0);
else
gpio_set_value(g_analog_pwm->gpio, 1);
}
g_analog_pwm->kt = ktime_set(0, g_analog_pwm->hrtimer_tick_accuracy);
hrtimer_forward_now(&g_analog_pwm->hrtimer, g_analog_pwm->kt);
/* 此处目的是为了不让g_analog_pwm->hrtimer_tick会多增加1次计数 */
if(!g_analog_pwm->f_hrtimer_tick_reset){
g_analog_pwm->hrtimer_tick++;
}else{
g_analog_pwm->hrtimer_tick = 0;
g_analog_pwm->f_hrtimer_tick_reset = false;
}
return HRTIMER_RESTART;
}
static void analog_pwm_start(void)
{
hrtimer_init(&g_analog_pwm->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
g_analog_pwm->hrtimer.function = hrtimer_handler;
g_analog_pwm->kt = ktime_set(0, 10000);
hrtimer_start(&g_analog_pwm->hrtimer, g_analog_pwm->kt, HRTIMER_MODE_REL);
pr_info("[assert] analog_pwm_start ... \n");
}
static int __init hrtimer_gpio_pwm_init(void)
{
int ret = 0;
g_analog_pwm = kzalloc(sizeof(struct analog_pwm_t), GFP_KERNEL);
if(g_analog_pwm == NULL){
pr_err("Function:%s kzalloc failed!\n", __func__);
return -ENOMEM;
}
g_analog_pwm->peroid = 5120000; /* ns */
g_analog_pwm->f_polarity = 0;
g_analog_pwm->total_zone = 256;
g_analog_pwm->hrtimer_tick_accuracy = g_analog_pwm->peroid / g_analog_pwm->total_zone;
printk("hrtimer_gpio_pwm_init: count_accuracy = %u\n", g_analog_pwm->hrtimer_tick_accuracy);
ret = fetch_sysconfig();
if(ret){
kfree(g_analog_pwm);
if(ret == -2)
printk("%s led is not used in sys_config.fex !!!", __func__);
else
printk("fetch_sysconfig is failed!\n");
return -1;
}
analog_pwm_start();
return 0;
}
static void __exit hrtimer_gpio_pwm_exit(void)
{
hrtimer_cancel(&g_analog_pwm->hrtimer);
gpio_set_value(g_analog_pwm->gpio, 0);
gpio_free(g_analog_pwm->gpio);
kfree(g_analog_pwm);
}
module_init(hrtimer_gpio_pwm_init);
module_exit(hrtimer_gpio_pwm_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("orange@sochip, assert");
MODULE_DESCRIPTION("A driver uses hrtimer to let gpio simulate Pwm");