pwm脉宽调制:原理是周期性产生方波,通过控制方波高电平与低电平占有的时间比率来达到功率控制的目的。例如高电平的时间在一个周期内占10%,那么负载功率就只有满功率的10%。相比于使用电阻器来说,PWM控制功率理论上效率是100%,不存在损耗(不考虑开关损耗)。
用arm带有pwm控制器,可以产生pwm波。原理是使用一个递减寄存器,在脉冲驱动下寄存器值不断减小,通过与另外一个寄存器的值作比较来决定输出是高电平还是低电平。递减寄存器的值到0自动重装,也可以手动重装,产生pwm波一般情况下自动重装。这个递减寄存器的初始值除以递减频率就是pwm波的周期了,比较寄存器的值就是控制占空比的。
寄存器版本
先看一下数据手册
timer 0,1,2,3可以用来产生PWM,timer 0可以产生死区时间。PWM时钟源来在APB-PCLK,timer 0,1共享一个8位的一级分频寄存器,2、3、4共享另外一个8位寄存器。每个timer还有自己的私有分频器,可以用作2、4、5、16分频。
每个timer有一个32位的递减寄存器,通过自己的时钟递减,通过TCNTBn寄存器初始化和重新装填。减到0会产生中断。禁用TCPNn的enable位可以停止timer。
通过与TCMPBn寄存器的值做大小比较来确定PWM输出高电平还是低电平。
电路图中可以看出分频寄存器跟上面描述的是一样的
我这个PWM控制器支持双缓冲,可以在不停止PWM的情况下更改PWM的频率。意思其实TCNTOn就是timer的递减寄存器,TCNTBn的值可以随时更改,下一次重装生效。
下面是寄存器版本代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interrupt.h> //含有request_irq、free_irq函数
#include <linux/irq.h> //含有IRQ_HANDLED\IRQ_TYPE_EDGE_RISING
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <mach/platform.h>
MODULE_LICENSE("GPL");
#pragma pack(4)
static struct GPIO{
unsigned int out_put;
unsigned int out_enb;
unsigned int detect_md_1;
unsigned int detect_md_2;
unsigned int int_enb;
unsigned int event_detect;
unsigned int pad_state;
unsigned int resv;
unsigned int func0;
unsigned int func1;
}* gpio_d;
static struct TIMER{
unsigned int tcfg0;
unsigned int tcfg1;
unsigned int tcon;
unsigned int tcntb0;
unsigned int tcmpb0;
unsigned int tcnto0;
unsigned int resv_t1_t4[3*3];
unsigned int tint;
}* timer_0;
#pragma pack()
static int rate = 1000,duty = 10;
module_param(rate, int, 0644); //声明模块参数
module_param(duty, int, 0644); //声明模块参数
dev_t devid;
struct cdev char_dev;
struct class * char_class;
int buffer_size = 100;
char * char_data;
static int open(struct inode * node, struct file * fl){
return 0;
}
static long ioctl(struct file * fl, unsigned int cmd, unsigned long arg){
printk("dir:%d,size:%d,type:%d,nr:%d\n",_IOC_DIR(cmd),_IOC_SIZE(cmd),_IOC_TYPE(cmd),_IOC_NR(cmd));
printk("arg:%ld\n",arg);
if(cmd & IOC_OUT){
timer_0->tcon |= 1; //启动
}else{
timer_0->tcon &= ~1; //停止
}
switch(_IOC_TYPE(cmd)){
case 1:
rate = min(max(1,(int)arg),999999);
timer_0->tcntb0 = rate;
break;
case 2:
duty = min(max(1,(int)arg),99);
timer_0->tcmpb0 = rate/100 * duty;
break;
}
return 0;
}
static ssize_t read(struct file * fl, char __user * buf, size_t len, loff_t * offset){
return 0;
}
static ssize_t write(struct file * fl, const char __user * buf, size_t len, loff_t * offset){
return 0;
}
struct file_operations my_opts = {
.owner = THIS_MODULE,
.open = open,
.read = read,
.write = write,
.unlocked_ioctl = ioctl
};
static irqreturn_t hello_irq(int irq,void * dev_id)
{
timer_0->tint &= ~(1<<5); //清除中断
// printk("pwm\n");
return IRQ_HANDLED;
}
unsigned int int_num,gpio_d_1_int_num;
static int __init char_init(void){
int ret = 0;
devid = MKDEV(241, 1); //换算设备号
ret = register_chrdev_region(devid, 1, "char_test");//注册设备,在/proc/drivers下面可以看到
if (ret < 0)
goto err0;
cdev_init(&char_dev,&my_opts); //绑定opt结构体
char_dev.owner = THIS_MODULE;
ret = cdev_add(&char_dev,devid,1); //注册字符设备驱动
if (ret < 0)
goto err1;
char_class = class_create(THIS_MODULE,"char_test"); //在/sys/class中创建文件夹
device_create(char_class,NULL,devid,NULL,"char_test_dev_%d",1);//在上一步文件夹中创建char_test_dev_1
char_data = kzalloc(buffer_size,GFP_KERNEL);
gpio_d = (struct GPIO *)ioremap(0xc001d000,sizeof(struct GPIO)); //映射地址
gpio_d->func0 &= ~(3 << 2);
gpio_d->func0 |= (1 << 2);
timer_0 = (struct TIMER *)ioremap(0xc0018000,sizeof(struct TIMER)); //映射地址
timer_0->tcfg1 &= ~(0xf);
timer_0->tcfg1 |= 0x02; //1/4
timer_0->tcon |= (1 << 3); //自动重装
timer_0->tcon &= ~(1 << 2); //不颠倒电平
timer_0->tcon &= ~(1 << 1); //不手动更新
timer_0->tcntb0 = rate;
timer_0->tcmpb0 = rate/100 * min(max(1,duty),99);
gpio_d_1_int_num = IRQ_PHY_PWM_INT0;
if (!request_irq(gpio_d_1_int_num,hello_irq,IRQF_DISABLED | IRQF_SHARED,"gpio_irq1",(void *)devid)){
printk("irq registed:%d\n", gpio_d_1_int_num);
int_num = gpio_d_1_int_num;
}else{
printk("irq regist fail:%d\n",gpio_d_1_int_num);
}
timer_0->tint &= ~1; //使能中断
timer_0->tcon |= 1; //使能
printk("char init\n");
return 0;
err1:
unregister_chrdev_region(devid, 1);
err0:
return ret;
}
static void __exit char_exit(void){
if(int_num){
free_irq(gpio_d_1_int_num,(void *)devid); //取消中断函数注册
}
timer_0->tcon &= ~1; //停止
iounmap(gpio_d);
iounmap(timer_0);
unregister_chrdev_region(devid, 1);
cdev_del(&char_dev);
device_destroy(char_class,devid);
class_destroy(char_class);
printk("char exit\n");
}
module_init(char_init);
module_exit(char_exit);
API版本
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interrupt.h> //含有request_irq、free_irq函数
#include <linux/irq.h> //含有IRQ_HANDLED\IRQ_TYPE_EDGE_RISING
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <mach/soc.h>
#include <mach/platform.h>
MODULE_LICENSE("GPL");
static int rate = 1000,duty = 10;
module_param(rate, int, 0644); //声明模块参数
module_param(duty, int, 0644); //声明模块参数
dev_t devid;
struct cdev char_dev;
struct class * char_class;
int buffer_size = 100;
char * char_data;
static int open(struct inode * node, struct file * fl){
return 0;
}
static long ioctl(struct file * fl, unsigned int cmd, unsigned long arg){
printk("dir:%d,size:%d,type:%d,nr:%d\n",_IOC_DIR(cmd),_IOC_SIZE(cmd),_IOC_TYPE(cmd),_IOC_NR(cmd));
printk("arg:%ld\n",arg);
if(cmd & IOC_OUT){
nxp_soc_pwm_start(0,0);
}else{
nxp_soc_pwm_stop(0,0);
}
switch(_IOC_TYPE(cmd)){
case 1:
rate = min(max(1,(int)arg),999999);
break;
case 2:
duty = min(max(1,(int)arg),99);
break;
}
nxp_soc_pwm_set_frequency(0,rate,min(max(1,duty),99));
return 0;
}
static ssize_t read(struct file * fl, char __user * buf, size_t len, loff_t * offset){
return 0;
}
static ssize_t write(struct file * fl, const char __user * buf, size_t len, loff_t * offset){
return 0;
}
struct file_operations my_opts = {
.owner = THIS_MODULE,
.open = open,
.read = read,
.write = write,
.unlocked_ioctl = ioctl
};
static int __init char_init(void){
int ret = 0;
devid = MKDEV(241, 1); //换算设备号
ret = register_chrdev_region(devid, 1, "char_test");//注册设备,在/proc/drivers下面可以看到
if (ret < 0)
goto err0;
cdev_init(&char_dev,&my_opts); //绑定opt结构体
char_dev.owner = THIS_MODULE;
ret = cdev_add(&char_dev,devid,1); //注册字符设备驱动
if (ret < 0)
goto err1;
char_class = class_create(THIS_MODULE,"char_test"); //在/sys/class中创建文件夹
device_create(char_class,NULL,devid,NULL,"char_test_dev_%d",1);//在上一步文件夹中创建char_test_dev_1
char_data = kzalloc(buffer_size,GFP_KERNEL);
nxp_soc_gpio_set_io_func(PAD_GPIO_D + 1, 1);
nxp_soc_pwm_set_frequency(0,rate,min(max(1,duty),99));
nxp_soc_pwm_start(0,0);
printk("char init\n");
return 0;
err1:
unregister_chrdev_region(devid, 1);
err0:
return ret;
}
static void __exit char_exit(void){
nxp_soc_pwm_stop(0,0);
unregister_chrdev_region(devid, 1);
cdev_del(&char_dev);
device_destroy(char_class,devid);
class_destroy(char_class);
printk("char exit\n");
}
module_init(char_init);
module_exit(char_exit);
application
#include <stdio.h>
#include <linux/ioctl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
int ret,fd,cmd,arg,key,value;
fd = open("/dev/char_test_dev_1",O_RDWR);
ret = fd;
if(ret < 0){
perror("open /dev/char_test_dev_1 error");
return ret;
}
cmd = _IOC(_IOC_READ,0x01,0x0,0x0);
ret = ioctl(fd,cmd,1000);
if(ret < 0){
perror("ioctl error");
return ret;
}
while(1){
scanf("%d",&key);
scanf("%d",&value);
cmd = _IOC(_IOC_READ,key,0x0,0x0);
ret = ioctl(fd,cmd,(unsigned long)value);
if(ret < 0){
perror("ioctl error");
return ret;
}
}
close(ret);
return 0;
}
结果
[root@minicoco pwm]# insmod pwm.ko
[ 541.276000] irq registed:27
[ 541.280000] char init
[root@minicoco pwm]# ./main
[ 543.740000] dir:2,size:0,type:1,nr:0
[ 543.744000] arg:1000
2 90
[ 549.144000] dir:2,size:0,type:2,nr:0
[ 549.148000] arg:90
2 10
[ 554.468000] dir:2,size:0,type:2,nr:0
[ 554.472000] arg:10