pwm(脉冲宽度调制)是一种模拟控制方式,根据相应载荷的变化来调制晶体管基极或MOS管栅极的偏置,来实现晶体管或MOS管导通时间的改变,从而实现开关稳压电源输出的改变。这种方式能使电源的输出电压在工作条件变化时保持恒定,是利用微处理器的数字信号对模拟电路进行控制的一种非常有效的技术。可运用于如控制电子设备的音量大小、LED灯的亮度渐变、电机强度控制等。
脉宽调制(PWM)基本原理:控制方式就是对逆变电路开关器件的通断进行控制,使输出端得到一系列幅值相等的脉冲,用这些脉冲来代替正弦波或所需要的波形。也就是在输出波形的半个周期中产生多个脉冲,使各脉冲的等值电压为正弦波形,所获得的输出平滑且低次谐波少。按一定的规则对各脉冲的宽度进行调制,既可改变逆变电路输出电压的大小,也可改变输出频率。pwm的输出是由占空比、周期和频率决定的。
在模拟PWM的驱动程序中,只要能够调节占空比就基本上可以说实现了模拟PWM。基本思路是采用定时器快速翻转IO口的电平值,通过调节在一个时间切片内某IO口保持高电平时间的占比,从而实现模拟PWN。采用定时器模拟的PWM输出的波形是方波。
本驱动程序基于Linux4.1.15版本的内核,程序采用标准字符设备驱动框架编写在TQ-IMX6UL平台下运行。实现了led呼吸灯。存在如下缺点:
(1)受限于linux内核定时器精度的原因,模拟出来的pwm频率非常之低通过示波器发现只有6.67HZ
(2)程序粗糙,调节占空比时变化不够细致。
因此,对频率、精度要求高的场合可以采用linux内核的高精度定时器或者硬件定时器,但强烈建议还是不要使用模拟PWM而是直接使用硬件PWM。
首先往tq-imx6ul设备树文件tq-imx6ul.dts增加如下代码:
led1 {
compatible = "gpio-leds";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_leds>;
label = "LED1";
gpios = <&gpio5 7 GPIO_ACTIVE_LOW>; //led 使用的引脚,与触发电平
};
对应驱动程序(还未经过调试,将会出现bug,等设备树整明白了我会改过来,权且先做个参考)如下:
#include <linux/kernel.h>
#include <linux/module.h> //驱动模块
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/slab.h> /*动态内存分配*/
#include <linux/io.h>
#include <linux/delay.h>
#include <leds.h>
#include <linux/gpio.h> //GPIO口库函数配置头文件
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/timer.h>
struct gpio_led_dev {
int value;
unsigned gpio;
struct gpio_desc * gpiod;
struct mutex mtx;
const char* name;
int light_level;
struct device *dev;
int high_time;
int low_time;
unsigned long flags;
unsigned active_low;
struct timer_list pwm_timer;
};
static int tcount = 0, flag = 0;
//让高电平和低电平的时间频率快速转换
//转的快高电平久一点,转得慢低电平久一点
//以微秒为单位
//为了马达转动不间隔,低电平的时间应该尽可能短
void pwm_timer_func(unsigned long data)
{
tcount++;
struct gpio_led_dev *gled_dev = (struct gpio_led_dev *)data;
if(gled_dev->high_time && flag==0) {
gpio_set_value(gled_dev->gpio, 0);
tcount = 0;
flag = 1;
}
else if(gled_dev->low_time && flag==1) {
gpio_set_value(gled_dev->gpio, 1);
tcount = 0;
flag = 0;
}
mod_timer(&gled_dev->pwm_timer, msecs_to_jiffies(1));
}
static ssize_t light_level_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct gpio_led_dev *g_dev = (struct gpio_led_dev *)dev_get_drvdata(dev);
return sprintf(buf, "%d\n", g_dev->vibration_level);
}
static ssize_t light_level_store(struct device *dev, struct device_attribute *attr, char *buf, size_t count)
{
int light_level = 0;
struct gpio_led_dev *g_dev = (struct gpio_led_dev *)dev_get_drvdata(dev);
kstrtoull(buf, 10, &light_level);
if( light_level > 15) {
light_level = 15; //us
}
g_dev->vibration_level = light_level;
g_dev->high_time = light_level ; //us
g_dev->low_time = (15 - g_dev->high_time) ;
return count;
}
static DEVICE_ATTR(light_level, S_IRUGO|S_IWUSR,light_level_show, light_level_store);
static const struct of_device_id of_gpio_leds_match[] = {
{ .compatible = "gpio-leds", },
{},
};
MODULE_DEVICE_TABLE(light_level, of_gpio_leds_match);
static struct gpio_led_dev * gpio_leds_create(struct device *dev, struct gpio_led_dev *gled_dev)
{
int count = 0, ret = 0;
struct gpio_led_dev *g_dev;
struct gpio_led led;
struct device_node *np;
unsigned long flags;
unsigned gpio;
ret = of_property_read_string(dev->of_node, "label", gled_dev->name);
if(ret < 0)
return -1;
gpio = of_get_named_gpio(dev->of_node, "gpios", 0);
if(gpio < 0)
return -1;
gled_dev->gpiod = gpio;
gled_dev->gpiod = gpio_to_desc(gpio);
gled_dev->flags = GPIOF_ACTIVE_LOW;
return 0;
}
static int gpio_led_probe(struct platform_device *pdev)
{
int count = 0, ret = 0;
struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct device *dev = &pdev->dev;
struct gpio_led_dev *gled_dev;
gled_dev = devm_kzalloc(dev, sizeof(struct gpio_led_dev), GFP_KERNEL);
if (!gled_dev)
return -1;
ret = gpio_leds_create(dev, gled_dev);
if(!ret) {
return -1;
}
/* skip leds that aren't available */
if (!gpio_is_valid(gled_dev->gpio)) {
dev_info(parent, "Skipping unavailable LED gpio %d (%s)\n",
gled_dev->gpio, gled_dev->name);
return 0;
}
ret = devm_gpio_request_one(dev, gled_dev->gpio, gled_dev->flags, gled_dev->name);
if (ret < 0)
return ret;
gled_dev->gpiod = gpio_to_desc(gled_dev->gpio);
if (IS_ERR(gled_dev->gpiod))
return PTR_ERR(gled_dev->gpiod);
gpiod_direction_output(gled_dev->gpiod, 0);//配置为输出模式
platform_set_drvdata(pdev, gled_dev);
ret = device_create_file(dev, &dev_attr_light_level);
gled_dev->pwm_timer.expires = usecs_to_jiffies(50);
gled_dev->pwm_timer.function = pwm_timer_func;
gled_dev->pwm_timer.data = (unsigned long)gled_dev;
init_timer(&gled_dev->pwm_timer);
add_timer(&gled_dev->pwm_timer);
err:
return ERR_PTR(ret);
}
static int gpio_led_remove(struct platform_device *pdev)
{
struct gpio_led_dev *gled_dev = (struct gpio_led_dev *)dev_get_drvdata(device);
del_timer(&gpio_led_dev->pwm_timer);
device_remove_file(gpio_led_dev->dev, &dev_attr_light_level);
dev_set_drvdata(gpio_led_dev->dev, NULL);
}
static struct platform_driver gpio_led_driver = {
.probe = gpio_led_probe,
.remove = gpio_led_remove,
.driver = {
.name = "leds-gpio",
.of_match_table = of_gpio_leds_match,
},
};
module_platform_driver(gpio_led_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lucky");