嵌入式学习8--模拟PWM与呼吸灯

谁说胖子不能爱 提交于 2019-11-27 13:02:38

    最近看了看PWM,但是我手上的板子4路PWM只接出来2路,还都占用了,没有办法,就想试试软件模拟pwm,本身模拟PWM是比较简单的事,但是在做了以后我又想做做呼吸灯,在呼吸灯上卡了挺久了,不过经过调试,也算勉强实现了

    1.PWM概念

    其实PWM的概念比较简单,无非就是在固定的周期内,设置高电平占用的时间长短,简单的说一秒一个周期,这个周期的占空比是50%。说明高电平的时间和低电平的时间是一样的,如果控制灯的话,就会看到灯在1S内会亮一次然后灭一次。

     虽说PWM的概念很简单,但是要用好想当困难,对我而言是这样,特别是在肉眼可见的情况下,你需要考虑到肉眼每秒能接受多少次动作,所以需要设置PWM的频率,我就是在这个地方卡了很久,待会代码里面会解释。

     PWM 用于背光、呼吸灯、舵机等器件之上,大部分PWM的实现是依赖硬件的,通过配置相应的寄存器来达到目的。硬件PWM的精度和稳定性会比软件PWM更稳定,所以在板子上一般会自带PWM。

    2.PWM与hrtimer

     为了使PWM更精确,所以使用了hetimer来控制,hrtimer可以做到ns级别的控制,相对来说会更加精确

     对于hrtimer主要使用的是注册、定时器中断、启动、重置触发时间、注销

     注册:hrtimer_init(&pwm_dev->mytimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);

     定时器中断:pwm_dev->mytimer.function = rtimer_isr;

     启动:hrtimer_start(&pwm_dev->mytimer, ktime_set(0, pwm_dev->period - pwm_dev->duty), HRTIMER_MODE_REL);

     重置触发时间:hrtimer_forward_now(&pwm_dev->mytimer, pwm_dev->k_time);

     注销:hrtimer_cancel(&pwm_dev->mytimer);

     3.PWM与呼吸灯

      这里直接就上代码了,其实驱动代码都是些常规操作,主要在于上层如何去调用是关键

驱动代码:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/hrtimer.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/err.h>
#include <asm/io.h>
#include <mach/platform.h>

#define DEVICE_NAME "mypwm"
#define GPIO_NAME "GPIOE13"
#define LED_D7_GPIOE13 (PAD_GPIO_E+13)

#define PWM_SET_DUTY    _IOW('P', 0X00, int)
#define PWM_SET_PERIOD  _IOW('P', 0X01, int)
#define PWM_SET_START   _IO ('P', 0X02     )

typedef struct soft_pwm {
    struct class *pwm_class;
    struct device *pwm_device;
    struct hrtimer mytimer;
    struct cdev cdev;
    unsigned long duty;
    unsigned long period;
    dev_t pwm_cdev_num;
    ktime_t k_time;
}SOFT_PWM;

static SOFT_PWM *pwm_dev;

static unsigned int mojor = 0;
static unsigned int minor = 0;

static int pwm_start(void);
static enum hrtimer_restart rtimer_isr(struct hrtimer *timer);

static int mypwm_open(struct inode *inode, struct file *filp)
{
    printk("<4>" "mypwm_open\n");
    return 0;
}

static long mypwm_ioctl(struct file *filp, unsigned int cmd, unsigned long args)
{
    //printk("<4>" "mypwm_ioctl\n");
    switch(cmd){
    case PWM_SET_DUTY:  //为了方便呼吸灯的实现,所以可以实时设置占空比
        pwm_dev->duty = args;
        //printk("<4>" "pwm_dev->duty = %ld\n", pwm_dev->duty);
        break;
    
    case PWM_SET_PERIOD:  //设置周期
        pwm_dev->period = args;
        //printk("<4>" "pwm_dev->period = %ld\n", pwm_dev->period);
        break;
    
    case PWM_SET_START:   //启动PWM
        pwm_start();
        break;
    
    default:
        break;
    }
    
    return 0;
}

int mypwm_release(struct inode *inode, struct file *filp)
{
    hrtimer_cancel(&pwm_dev->mytimer);
    printk("<4>" "mypwm_release");
    return 0;
}

static int pwm_start(void)
{
    hrtimer_init(&pwm_dev->mytimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);  //注册
    pwm_dev->mytimer.function = rtimer_isr;  //中断函数
    hrtimer_start(&pwm_dev->mytimer, ktime_set(0, pwm_dev->period - pwm_dev->duty), HRTIMER_MODE_REL);  //启动
    //printk("<4>" "pwm_start\n");
    return 0;
}

static enum hrtimer_restart rtimer_isr(struct hrtimer *timer)
{
    //printk("<4>" "rtimer_isr_start\n");
    if (gpio_get_value(LED_D7_GPIOE13)){
        if (pwm_dev->period != pwm_dev->duty){ //占空比等于周期说明不用操作引脚
            gpio_set_value(LED_D7_GPIOE13, 0);
            pwm_dev->k_time = ktime_set(0, pwm_dev->period - pwm_dev->duty);  //设置定时器的触发时间,ns级别
        }
        hrtimer_forward_now(&pwm_dev->mytimer, pwm_dev->k_time);
    } else {
        if (pwm_dev->duty != 0){ //占空比不等于0,才需要操作引脚,因为这时需要让引脚保持占空比时长的变化,使得PWM起效
            printk("<4>" "pwm_dev->duty = %ld\n", pwm_dev->duty);
            gpio_set_value(LED_D7_GPIOE13, 1);
            pwm_dev->k_time = ktime_set(0, pwm_dev->duty);
        }
        hrtimer_forward_now(&pwm_dev->mytimer, pwm_dev->k_time); //设置定时器的触发时间,ns级别
    }
    
    return HRTIMER_RESTART;  //此处需要返回的值是固定的,如果需要定时器循环处理就需要返回restart,否则运行一次就会停止
}

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open  = mypwm_open,
    .release = mypwm_release,
    .unlocked_ioctl = mypwm_ioctl,
};

static int __init mypwm_init(void)
{
    int ret;
    
    pwm_dev = kzalloc(sizeof(SOFT_PWM),GFP_KERNEL);
    if(pwm_dev == NULL){
        printk("<4>" "kzalloc error\n");
        return -1;
    }
    
    pwm_dev->period = 0;
    pwm_dev->duty = 0;
    
    pwm_dev->pwm_cdev_num = MKDEV(mojor, minor);

    if(mojor)
        ret = register_chrdev_region(pwm_dev->pwm_cdev_num, 1, DEVICE_NAME);
    else 
        ret = alloc_chrdev_region(&pwm_dev->pwm_cdev_num, 0, 1, DEVICE_NAME);
    
    if (ret < 0){
        printk("<4>" "register chrdev fail\n");
        return ret;
    }

    cdev_init(&pwm_dev->cdev, &fops);
    
    ret = cdev_add(&pwm_dev->cdev, pwm_dev->pwm_cdev_num, 1);
    if(ret < 0){
        printk("<4>" "cdev add fail\n");
        goto cdev_add_error;
    }
    
    pwm_dev->pwm_class = class_create(THIS_MODULE, DEVICE_NAME);
    if(pwm_dev->pwm_class == NULL){
        printk("<4>" "class add fail\n");
        ret = -EFAULT;
        goto class_create_error;
    }
    
    pwm_dev->pwm_device = device_create(pwm_dev->pwm_class, NULL, pwm_dev->pwm_cdev_num, NULL, DEVICE_NAME);
    if(pwm_dev->pwm_device == NULL){
        printk("<4>" "device add fail\n");
        ret = -EFAULT;
        goto device_create_error;
    }
    
    gpio_request(LED_D7_GPIOE13, GPIO_NAME);
        
    ret = gpio_direction_output(LED_D7_GPIOE13, 1);
    if (ret < 0){
        printk("<4>" "gpio_direction_output fail\n");
        ret = -EFAULT;
        goto gpio_request_err;
    }

    printk("<4>" "mypwm_init\n");
    return 0;

gpio_request_err:
    gpio_free(LED_D7_GPIOE13);

device_create_error:
    class_destroy(pwm_dev->pwm_class);
    
class_create_error:
    cdev_del(&pwm_dev->cdev);
    
cdev_add_error:
    unregister_chrdev_region(pwm_dev->pwm_cdev_num, 1);
    printk("<4>" "pwm drv init fail\n");
    return ret;
}

static void __exit mypwm_exit(void)
{
    hrtimer_cancel(&pwm_dev->mytimer);
    
    gpio_free(LED_D7_GPIOE13);
    
    device_destroy(pwm_dev->pwm_class, pwm_dev->pwm_cdev_num);
    
    class_destroy(pwm_dev->pwm_class);
    
    cdev_del(&pwm_dev->cdev);
    
    unregister_chrdev_region(pwm_dev->pwm_cdev_num, 1);
    
    kfree(pwm_dev);
    
    printk("<4>" "mypwm_exit\n");
}

module_init(mypwm_init);
module_exit(mypwm_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ayl0521@sina.com");

 

上层测试代码:

#include "stdio.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "unistd.h"
#include "sys/ioctl.h"

#define PWM_SET_DUTY      _IOW('P', 0X00, int)
#define PWM_SET_PERIOD  _IOW('P', 0X01, int)
#define PWM_SET_START    _IO ('P', 0X02         ) 

int main(int argc,char **argv)
{
    int fd = -1;
    unsigned long  on = 100;

    fd = open("/dev/mypwm", O_RDWR);
    if(fd < 0) {
        return -1;
    }

    /*此处频率的设置非常关键,因为肉眼的可见范围需要较小,超过就会没有感受,这里选择100Hz

    *10000000ns/1000/1000 = 10ms 1s/10ms = 100Hz

    */

    ioctl(fd, PWM_SET_PERIOD, 10000000);  
    ioctl(fd, PWM_SET_DUTY, on);
    ioctl(fd, PWM_SET_START);
    
    while(1) 
    {
        if(on >= 10000000) 
        {

            /*这个while是由亮到暗*/
            while(1)
            {

                /* //这里的1是尝试出来比较好的值,根据实际情况调试,

                *但是在快到最小亮度时候会开始闪烁,我感觉还是我的频率设置有问题,后续再调试

                */
                on -= 1;
                ioctl(fd, PWM_SET_DUTY, on);
            

                /*设置100的原因是不要让灯熄灭,所以占空比的范围是有限制的*/
                if(on <= 100)
                {
                    on = 100;
                    break;
                }
            }
        }
        

        /*此处是由暗到亮*/
        on += 2;  //这里的2是尝试出来比较好的值,根据实际情况调试
        ioctl(fd, PWM_SET_DUTY, on);
    }

    close(fd);

    return 0;
}

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!