我是靠谱客的博主 纯真白昼,最近开发中收集的这篇文章主要介绍Linux高精度定时器的使用高精度定时器使用场景应用程序驱动程序,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

文章目录

  • 高精度定时器使用场景
  • 应用程序
  • 驱动程序

高精度定时器使用场景

使用高精度定时器,控制gpio脚的上下电模拟PWM控制风扇转速。

应用程序

编写一个测试程序,传入gpio引脚,周期和高电平时间,程序中用IOCTL函数将参数传入到驱动中进行配置。为此定义一个结构体:

typedef struct gpio_pwm_out_t
{
    int gpio_id;
    int pwm_period;//ns
    int pwm_duty_cycle;//high lever last times :ns
}gpio_pmw_out_para;

测试程序中通过IOCTL将上面的结构体传入驱动中。
测试程序

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <pthread.h>    /* POSIX Threads */

#define DEVNAME "/dev/gpio_ctrl"

#define IOCTL(fd, cmd, arg)  ({
    int r;
    r = ioctl(fd, cmd, arg);
    if (r < 0)
    {
        printf("[%s] fail n",  #cmd);
    }
    r;
})

// pwm 控制风扇
#define GPIO_TEST_IOCTL_PWM_FAN 55

typedef struct yw_gpio_pwm_out_t
{
    int gpio_id;
    int pwm_period;//ns
    int pwm_duty_cycle;//high lever last times :ns
}yw_gpio_pmw_out_para;

int main(int argc, char *argv[])
{ 
     printf("start testn");
     int fd = -1;
     if (argc < 4)
     {
           printf("argc < 4n");
           return 0;
     }
     
     yw_gpio_pmw_out_para pwm_data;

     pwm_data.gpio_id = atoi(argv[1]);
     pwm_data.pwm_period = atoi(argv[2]);
     pwm_data.pwm_duty_cycle = atoi(argv[3]);
     printf("id = %d,period = %d,duty = %dn", pwm_data.gpio_id, pwm_data.pwm_period, pwm_data.pwm_duty_cycle);     

     fd =  open(DEVNAME,O_RDWR);
     if(fd < 0)
     {
            printf("open %s fail !n", DEVNAME);
	    perror("open");
            return -1;
     }
     printf("open successn");
     IOCTL(fd, GPIO_TEST_IOCTL_PWM_FAN, &pwm_data);

     //close(fd);
     return 0;	
}

// a.out 40 100 60,gpio40,周期100,占空比60

驱动程序

在驱动入口init函数中,需要对定时器,及相关结构体进行初始化。
1.驱动中的重要结构体:

struct pwm_gpio_device_data
{
    gpio_pmw_out_para pwm_out_para; //该结构体用来保存应用程序传下来的参数
	u8 gpio_value;
	struct hrtimer timer; 			   //定时器
	bool is_actived;
	struct mutex lock;				   //锁
};
static struct pwm_gpio_device_data yw_pwm_data;

2.定时器初始化函数:

static int pwm_gpio_init(void)
{
    memset(&yw_pwm_data, 0x00, sizeof(yw_pwm_data)); 
	//timer init
	hrtimer_init(&yw_pwm_data.timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);//定时器初始化。
	pwm_data.timer.function = pwm_gpio_function; //定时器执行函数,定时时间到回去执行该函数。
	//mutex
	mutex_init(&pwm_data.lock); //初始化锁
	return 0;
}

3.hrtimer_init函数参数说明:

 参数timer是hrtimer指针,
 参数clock_id有如下常用几种选项:
        CLOCK_REALTIME	//实时时间,如果系统时间变了,定时器也会变
        CLOCK_MONOTONIC	//递增时间,不受系统影响
 参数mode有如下几种选项:
 	HRTIMER_MODE_ABS = 0x0,		/* 绝对模式 */
	HRTIMER_MODE_REL = 0x1,		/* 相对模式 */
	HRTIMER_MODE_PINNED = 0x02,	/* 和CPU绑定 */
	HRTIMER_MODE_ABS_PINNED = 0x02, /* 第一种和第三种的结合 */
	HRTIMER_MODE_REL_PINNED = 0x03, /* 第二种和第三种的结合 */
 

CLOCK_MONOTONIC与CLOCK_REALTIME区别
Linux内核高精度定时器hrtimer的使用
4.驱动执行流程
应用程序的ioctl会调到这里,并根据cmd进入到case GPIO_TEST_IOCTL_PWM_FAN分支中。

long gpio_ctrl_ioctl(struct file *flip, unsigned int cmd, unsigned long arg)
case 1:
case 2:
...... 

case GPIO_TEST_IOCTL_PWM_FAN: 
{
		if(gpio_pwm_out_ctl(arg) < 0)
		{
			printk("PWM control failedn");
			return -1;
		}
		
		break;
}
......
static int gpio_pwm_out_ctl(unsigned long arg)
{
    int period,duty; //定义周期和占空比
    int ret = 0;
    //接收应用程序传下来的数据
	if(copy_from_user(&pwm_data.pwm_out_para, (void __user *)arg, sizeof(gpio_pmw_out_para)))
    {
        printk("copy from user fail n");
        ret = -1;
        goto OUT;
    }
    //初始化风扇脚。配置配置为gpio模式,输出模式,低电平。
	set_PWM_pin_value(pwm_data.pwm_out_para.gpio_id,1,0);
    //usre set is ms,so chang it to ns
    printk("before period = %d n",pwm_data.pwm_out_para.pwm_period);
    pwm_data.pwm_out_para.pwm_period = 1000000 * pwm_data.pwm_out_para.pwm_period;//
    printk("after period = %d n",pwm_data.pwm_out_para.pwm_period);
    period = pwm_data.pwm_out_para.pwm_period;
	duty = pwm_data.pwm_out_para.pwm_duty_cycle;
	//当传入的参数中,周期或者占空比为0是,关闭定时器,即风扇停止转动。	
	if(period == 0 || duty == 0)
	{
		printk("start:FUNC:%s,LINE:%dn",__func__,__LINE__);
        pwm_gpio_stop();
	}
	else
	{
        pwm_gpio_start();
	}

OUT:
    return ret;
}

//开启定时器

static int pwm_gpio_start(void)
{
    int period,duty;
    int  high_on = 0;
	int low_on = 0;
	
	period = pwm_data.pwm_out_para.pwm_period;
	duty = pwm_data.pwm_out_para.pwm_duty_cycle;
	//计算高低电平时间
    high_on = (period / 100) * duty;
	low_on = period - high_on;
	printk("FUNC:%s,LINE:%d,pwm_data.is_actived:%dn",__func__,__LINE__,pwm_data.is_actived);
    mutex_lock(&pwm_data.lock);
	if(!pwm_data.is_actived)
	{
	    hrtimer_init(&pwm_data.timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);	
		pwm_data.timer.function = pwm_gpio_function;
		printk("start:FUNC:%s,LINE:%dn",__func__,__LINE__);
	  	gpio_set_value(pwm_data.pwm_out_para.gpio_id,1);
        hrtimer_start(&pwm_data.timer, ktime_add_ns(ktime_get(), high_on), HRTIMER_MODE_ABS); //开启定时器
		pwm_data.is_actived = true;
	printk("FUNC:%s,LINE:%d,pwm_data.is_actived:%dn",__func__,__LINE__,pwm_data.is_actived);
	}
	mutex_unlock(&pwm_data.lock);
	
    return 0;
}

注:设置gpio电平是不要采用gpio_direction_output,因为该函数中有锁,再加锁会出问题。
gpio_set_valuegpio_direction_output的区别
如果使用该GPIO时,不会动态地切换输入输出,建议在开始时就设置好GPIO 输出方向,后面拉高拉低时使用gpio_set_value()接口,而不建议使用gpio_direction_output(), 因为gpio_direction_output接口里面有mutex锁,对中断上下文调用会有错误异常,且相比 gpio_set_value,gpio_direction_output 所做事情更多,浪费。
//关闭定时器

static int pwm_gpio_disable(void)
{
	printk("FUNC:%s,LINE:%d,pwm_data.is_actived:%dn",__func__,__LINE__,pwm_data.is_actived);
    mutex_lock(&pwm_data.lock);
	printk("FUNC:%s,LINE:%d,pwm_data.is_actived:%dn",__func__,__LINE__,pwm_data.is_actived);
    if(pwm_data.is_actived)
    {
    	printk("start:FUNC:%s,LINE:%dn",__func__,__LINE__);
        hrtimer_cancel(&pwm_data.timer); //	取消定时器
		printk("start:FUNC:%s,LINE:%dn",__func__,__LINE__);
		gpio_set_value(pwm_data.pwm_out_para.gpio_id,0);//设置gpio为低电平。
		pwm_data.is_actived = false;
	}
	mutex_unlock(&pwm_data.lock);

	return 0;
}

//定时器回调函数

static enum hrtimer_restart pwm_gpio_function(struct hrtimer *data)
{
    int period,duty;
    int high_on = 0;
	int low_on = 0;
	int ns =  0;
    //ktime_t ks;
	period = pwm_data.pwm_out_para.pwm_period;
	duty = pwm_data.pwm_out_para.pwm_duty_cycle;

	high_on = (period / 100) * duty ;
	low_on = period - high_on;

	if(pwm_data.gpio_value > 0)
	{
        ns = low_on;
	}
	else
	{
	    ns = high_on;
	}
	//如果上次gpio为低,怎这次设为高,反之一样。每进入一次回调函数,改变一次gpio状态
	pwm_data.gpio_value = (pwm_data.gpio_value > 0) ? 0 : 1;
	gpio_set_value(pwm_data.pwm_out_para.gpio_id,pwm_data.gpio_value);
	//ks = ktime_set(0, ns);
	//printf("ks = %dn",ks);
	//设置超时时间,当定时时间到了,就去设置下次的超时时间。
	hrtimer_forward_now(&pwm_data.timer, ktime_set(0, ns));
	
	return HRTIMER_RESTART;
}

最后

以上就是纯真白昼为你收集整理的Linux高精度定时器的使用高精度定时器使用场景应用程序驱动程序的全部内容,希望文章能够帮你解决Linux高精度定时器的使用高精度定时器使用场景应用程序驱动程序所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(85)

评论列表共有 0 条评论

立即
投稿
返回
顶部