全志Android下使用hrtimer模拟pwm实现呼吸灯效果

最近在一个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");

猜你喜欢

转载自blog.csdn.net/tsb151/article/details/81637541
今日推荐