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