一.什么是pwm?
PWM一般指脉冲宽度调制。
PWM是Pulse Width Modulation 的缩写,中文意思就是脉冲宽度调制,简称脉宽调制。它是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,其控制简单、灵活和动态响应好等优点而成为电力电子技术最广泛应用的控制方式。
其应用领域包括测量,通信,功率控制与变换,电动机控制、伺服控制、调光、开关电源,甚至某些音频放大器,因此学习PWM具有十分重要的现实意义。
二.pwm驱动蜂鸣器的原理
蜂鸣器的发声原理由振动装置和谐振装置组成,而蜂鸣器又分为无源他激型与有源自激型,蜂鸣器的发声原理为:
1、无源他激型蜂鸣器的工作发声原理是:方波信号输入谐振装置转换为声音信号输出。
2、有源自激型蜂鸣器的工作发声原理是:直流电源输入经过振荡系统的放大取样电路在谐振装置作用下产生声音信号。
有源蜂鸣器和无源蜂鸣器的主要差别是:二者对输入信号的要求不一样,有源蜂鸣器工作的理想信号是直流电,一般标示为VDD、VDC等。因为蜂鸣器内部有一个简单的振荡电路,可以把恒定的直流电转变成一定频率的脉冲信号,从而产生磁场交变,带动钼片振动发出声音。
电路图原理:
1、该BUZZER是无源蜂鸣器,如果要想发出声音,需要正负极产生电流变化,我们通过生成方波,从而实现图中三极管1->2周期性导通和关闭来让BUZZER俩边电压产生变化,从而实现电流变化;
2、三极管的基极连接的是SOC的GPD0_0引脚;
3、产生方波我们借助的是PWM,标号为MOTOR_PWM。
pwm内部结构:
工作的步骤:
1、当时钟PCLK被使能后,定时器计数缓冲寄存器(TCNTBn)把计数器初始值下载到递减计数器中。
2、定时器比较缓冲寄存器(TCMPBn)把其初始值下载到比较寄存器中,并将该值与递减计数器的值进行比较。当递减计数器和比较寄存器值相同时,输出电平翻转。
3、递减计数器减至0后,输出电平再次翻转,完成一个输出周期。这种基于TCNTBn和TCMPBn的双缓冲特性使定时器在频率和占空比变化时能产生稳定的输出。
4、每个定时器都有一个专用的由定时器时钟驱动的16位递减计数器。当递减计数器的计数值达到0时,就会产生定时器中断请求来通知CPU定时器操作完成。当定时器递减计数器达到0的时候,如果设置了Auto-Reload 功能,相应的TCNTBn的值会自动重载到递减计数器中以继续下次操作。
5、然而,如果定时器停止了,比如在定时器运行时清除TCON中定时器使能位,TCNTBn的值不会被重载到递减计数器中。
6、TCMPBn 的值用于脉冲宽度调制。当定时器的递减计数器的值和比较寄存器的值相匹配的时候,定时器控制逻辑将改变输出电平。因此,比较寄存器决定了PWM 输出的开关时间。
三.pwm驱动蜂鸣器的具体实现步骤
1.plat_fom介绍:
从Linux2.6开始Linux加入了一套驱动管理和注册机制—platform总线驱动模型。platform总线是一条虚拟总线(只有一条),这类总线没有对应的硬件结构。platform_device为相应的设备,platform_driver为相应的驱动。与传统的bus/device/driver机制相比,platform由内核统一进行管理,提高了代码的可移植性和安全性。优点:
1.可以通过platform总线,可以遍历所有的platform总线设备;platform本质其实也是kset、 kobject,具有kobject的特性;
2.实现设备与驱动的分离,通过platform总线,设备与驱动是分开注册的,通过platform总线的 probe来随时检测与设备匹配的驱动,如匹配上即进行这个设备的驱动注册;
3.一个驱动可以供同类的几个设备使用;
2.设备树介绍:
设备树是一种描述硬件得数据结构,在操作系统引导阶段进行设备初始化得时候,数据结构中得硬件信息被检测并传递给操作系统。Liunx内核从3.x开始引入设备树得概念,用于实现驱动代码与设备信息相分离。在设备树出现之前,所有关于设备得具体信息都要写在驱动里面,一旦外围设备变化,驱动代码就要重写。引入设备树后,驱动代码只负责处理驱动得逻辑,而关于设备具体信息要存放到设备树文件中。
这样,如果只是硬件接口信息得变化而没有驱动逻辑得变化,驱动开发者只需要修改设备树文件信息,不需要改写驱动代码。
驱动主体框架
1、平台驱动架构,分为内核驱动与设备树。
内核驱动:为固定的逻辑架构,解决向上为应用层提供调用接口,向下连接内核。
设备树:为驱动提供设备具体参数(寄存器地址等)。
2、写驱动就是在内核编写函数,而编写的这些函数需要具备内核调用资格,就必须符合内核的规则,所以有了“架构”的概念。随便的自定义函数可能实现不了功能甚至导致内核崩溃进而使整个操作系统崩溃。所以写内核必须按照框架写。
3、因为编写的驱动需要符合内核的规则,就必须参考内核源码。可以借用source insight来看源码。
平台驱动框架
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/err.h>
#include <asm/io.h>
struct of_device_id of_matches[]={
//修改匹配规则,从设备树
//获取buzzer相关硬件信息
};
static struct platform_driver mydriver ={
//通过设备树匹配
};
static int mod_init(void)
{
return platform_driver_register(&mydriver); //平台驱动注册
}
static void mod_exit(void)
{
platform_driver_unregister(&mydriver); //平台驱动注销
}
module_init(mod_init);
module_exit(mod_exit);
MODULE_LICENSE("GPL");
1.实现入口函数 xxx_init() 和卸载函数 xxx_exit()
2.申请设备号 alloc_chrdev_region (与内核相关)
3.注册字符设备驱动 cdev_alloc,cdev_init,cdev_add (与内核相关)
4.利用 udev/mdev 机制创建设备文件(节点) class_create,device_create(与内核相关)
5.硬件部分初始化从设备树获取硬件资源platform_get_resource()
IO 资源映射 ioremap ,内核提供 gpio库函数(与硬件相关)
注册中断(与硬件相关)
初始化等待队列(与内核相关)
初始化定时器(与内核相关)
6.构建 file_operation 结构(与内核相关)
7.实现操作硬件的方法 xxx_open,xxx_read,xxx_write…(与硬件相关)
第一步,驱动的启动函数:
int my_probe(struct platform_device *pdev) { int ret; //通过设备树获取硬件资源 printk( "match\n"); //0代表匹配的设备树第0个地址为基地址 rescon = platform_ get_ resource (pdev , IORESOURCE MEM, 0); if ( rescon=-NULL){ goto failed_ getcon; } resdata = platform get_ resource (pdev , IORESOURCE MEM,1) ; if ( resdata==NULL ){ goto failed_ getdata; gpd0con = ioremap( rescon->start,4); //利用设备树获取各个寄存器的地址 tcfg0 = ioremap( resdata->start,4) ; tcfg1 = ioremap ( resdata->start+4,4) ; tcon = ioremap( resdata->start+8,4); tcntb0 = ioremap ( resdata->start+12,4); tcmpb0 = ioremap( resdata->start+16,4); //字符设备注册 ret = alloc_chrdev_region(&devnum,0,1,name);//1.申请设备号 if(ret!=0){ goto failed_alloc; } cdev_init(&mycdev,&myops); //2.cdev初始化 ret = cdev_add(&mycdev,devnum,1); //3.cdev添加到内核 if(ret!=0){ goto failed_add; } printk("register success %d,%d\n",MAJOR(devnum),MINOR(devnum)); myclass = class_create(THIS_MODULE,"myclass"); if(IS_ERR(myclass)){ goto failed_class; } mydevice = device_create(myclass,NULL,devnum,NULL,"buzzer"); if(IS_ERR(mydevice)){ goto failed_device; } //硬件操作 buzzer_init(); buzzer_off(); return 0; failed_device: class_destroy(myclass); failed_class: cdev_del(&mycdev); failed_add: unregister_chrdev_region(devnum,1); failed_alloc: return -1; }
第二步,驱动卸载
int my_remove(struct platform_device *pdev) { printk("driver remove\n"); iounmap(gpd0con); iounmap(tcfg0); iounmap(tcfg1); iounmap(tcon); iounmap(tcntb0); iounmap(tcmpb0); device_destroy(myclass,devnum); class_destroy(myclass); cdev_del(&mycdev); unregister_chrdev_region(devnum,1); return 0; }
具体实验步骤:
1、编译生成 driver.ko 目标文件
2、insmod driver.ko 加载驱动
3、编译应用层文件
4、运行应用层文件。
整体代码
#include<linux/init.h> #include<linux/module.h> #include<linux/platform_device.h> #include<linux/cdev.h> #include<linux/fs.h> #include<linux/err.h> #include<asm/io.h> #define BUZZER_ON _IO('B',1) #define BUZZER_OFF _IO('B',2) long myioctl (struct file *pf, unsigned int cmd, unsigned long arg); int my_remove (struct platform_device *pev); int my_probe (struct platform_device *pev); int get_buzzer_adress(void);//获取蜂鸣器寄存器地址 int buzzer_init(void);//蜂鸣器初始化 static dev_t devnum; //设备号 static struct class* myclass; static char *name ="mydev"; static int ret; struct cdev mycdev; //字符设备结构体 struct device* mydevice; struct resource *gpio1 =NULL; struct resource *gpio2 =NULL; static unsigned int * gpd0;//设备树第一个地址,蜂鸣器起始地址gdp_con static unsigned int * gpdx;//设备树第二个地址,pwd起始地址gdpx_fg0 static unsigned int * gdpx_fg1;//二级分频 static unsigned int * gdpx_fcon;//pwd控制器 static unsigned int * gdpx_tb0;//设置最大值 static unsigned int * gdpx_tb1;//设置最小值 //定义platform_driver对象 struct of_device_id of_matches[]={ {.compatible="fs,mybee"}, {}, }; struct file_operations myops={ .unlocked_ioctl = myioctl, }; //字符设备集(驱动具体功能实现) struct platform_driver mydriver ={ .probe = my_probe, //驱动注册 .remove = my_remove, //驱动注销 .driver={ .name = "mytest", .of_match_table = of_matches, //通过设备树匹配 }, }; static int mod_init(void) { return platform_driver_register(&mydriver);//平台驱动注册 } static void mod_exit(void) { platform_driver_unregister(&mydriver);//平台驱动注销 } int my_probe (struct platform_device *pdev) //驱动注册 { printk("match success\n"); //获取硬件资源 gpio1 = platform_get_resource(pdev,IORESOURCE_MEM,0);//连接硬件 if(gpio1 ==NULL) { goto failed_gpx2con; } gpd0 = (int*)ioremap(gpio1->start,4); printk(":%#x\n",gpio1->start); gpio2 = platform_get_resource(pdev,IORESOURCE_MEM,1);//连接硬件 if(gpio2 ==NULL) { goto failed_gpx2dat; } gpdx = (int*)ioremap(gpio2->start,4); printk(":%#x\n",gpio2->start); //字符设备的注册 ret = alloc_chrdev_region(&devnum,0,1,name); //申请设备号 if(ret !=0) goto failed_alloc; cdev_init(&mycdev,&myops); //初始化设备 ret =cdev_add(&mycdev,devnum,1);//设备加入内核 if(ret !=0) { goto failed_add ; } printk("%d %d\n",MAJOR(ret),MINOR(ret)); //自动创建设备文件 myclass =class_create(THIS_MODULE,"myclass"); // /sys/class 中创建一个类,方便管理 if(IS_ERR(myclass)) { goto failed_class; } mydevice= device_create(myclass,NULL,devnum,NULL,"buzzer"); //自动创建设备文件“myled” if(IS_ERR(mydevice)) { goto failed_device; } get_buzzer_adress();//获取蜂鸣器寄存器地址 buzzer_init();//蜂鸣器初始化 return 0; //失败资源回收 failed_device: class_destroy(myclass); failed_class: cdev_del(&mycdev); failed_add: unregister_chrdev_region(devnum,1); failed_alloc: iounmap(gpd0); failed_gpx2dat: iounmap(gpdx); failed_gpx2con: return -1; } int get_buzzer_adress(void)//获取蜂鸣器寄存器地址 { gpd0 = (int*)ioremap(gpio1->start,4); gpdx = (int*)ioremap(gpio2->start,4); gdpx_fg1 = (int*)ioremap(gpio2->start+4,4); gdpx_fcon = (int*)ioremap(gpio2->start+8,4); gdpx_tb0 = (int*)ioremap(gpio2->start+12,4); gdpx_tb1 = (int*)ioremap(gpio2->start+16,4); return 0; } int buzzer_init(void)//蜂鸣器初始化 { u32 tmp; printk("init\n"); *gpd0 &= ~0xf; *gpd0 |= 0x2; //设置蜂鸣器为pwm输出 *gpdx |= 0xff;//预分频 *gdpx_fg1 = (*gdpx_fg1 &(~0xf)) | 0x3;//二级分频 *gdpx_tb0 = 110;//自减初值 *gdpx_tb1 = 55;//比较值 *gdpx_fcon |= (0x1<<3); *gdpx_fcon |= (0x1<<1); // *gdpx_fcon &= ~(0x1<<1); tmp = readl(gdpx_fcon); tmp &= ~(0x1<<1); writel(tmp,gdpx_fcon); return 0; } int my_remove (struct platform_device *pdev) { iounmap(gpd0); iounmap(gpdx); device_destroy(myclass,devnum); class_destroy(myclass); cdev_del(&mycdev); unregister_chrdev_region(devnum,1); printk("driver remove\n"); return 0; } long myioctl (struct file *pf, unsigned int cmd, unsigned long arg) { switch (cmd) { case BUZZER_ON: *gdpx_fcon |= (0x1<<0); printk("o22n %d %d\n",*gpdx,*gdpx_tb0 ); printk("o22n %d\n",*gdpx_tb0); printk("o22n %d\n",*gdpx_tb0); break; case BUZZER_OFF: printk("o22n %d %d\n",*gpdx,*gdpx_tb0 ); *gdpx_fcon &= ~(0x1<<0); printk("off22\n"); break; default: break; } return 0; } module_init(mod_init); module_exit(mod_exit); MODULE_LICENSE("GPL");
应用层代码
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <sys/ioctl.h> #define BUZZER_ON _IO('B',1) #define BUZZER_OFF _IO('B',2) int main(void) { int fd; int ret; fd = open("/dev/buzzer",O_RDWR); if(fd==-1){ perror("open"); return -1; } //printf("open device success %d\n",fd); while(1){ ioctl(fd,BUZZER_ON); sleep(1); ioctl(fd,BUZZER_OFF); sleep(1); } close(fd); return 0; }
Makfile
obj-m := driver.o ///最终生成 driver.ko driver-objs := test1.o KERNELDIR ?= /home/wjt/mytftp/kernel/linux-3.14 // 改为源码目录 PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules cp driver.ko /home/wjt/nfserver/rootfs -rf //拷贝目标到自定义目录 clean: rm *.mod.c *.mod.o *.ko *.o .*.o.* .*.ko.* *.order Module.symvers .tmp_versions -rf