最近看了看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;
}
来源:https://blog.csdn.net/origina1s/article/details/99676833