Linux利用platform_driver和设备树实现PWM驱动
字符设备PWM驱动
Linux环境下利用platform_driver和设备树实现PWM驱动,此文以开发板FS4412为例。
一、PWM驱动的硬件资源
PWM,脉宽调制器,顾名思义就是一个输出脉冲宽度可以调整的硬件器件,它不仅脉冲宽度可调,频率也可以调整,它的核心部件是一个硬件定时器。
1.PWM工作原理
PWM管脚默认输出高电平,在图1中的时刻1将计数值设为 109,比较值设为109,在时刻2启动定时器,PWM立即输出低电平,在时钟的作用下,计数器开始做减法计数,当计数值减到和比较值一致时(时刻3),输出翻转,之后一直输出高电平。当计数到达0后(时刻4),再完成一次计数,在时刻5重新从109开始计数,输出再次变成低电平,如此周而复始就形成一个矩形波。波形的周期由计数值决定,占空比由比较值决定。在图1中,占空比为110/160,如果用于计数的时钟频率为freq,那么波形的频率就为freq/160。

2.PWM电路原理
通过查找原理图,FS4412使用了其中一路PWM输出(PWM0,对应管脚为GPD0.0)接蜂鸣器,如图2

3.PWM内部结构
PWM内部结构图如图3,PWM的输入时钟是PCLK,经讨8位的预分频后再经过第二次分频的时钟最终给到PWM0所对应的计数器0.TCNTB0是计数值寄存器,用于控制PWM输出波形的频率,
TCMPB0是比较寄存器,用于控制PWM输出波形的占空比。

二、具体代码
1.设备树
- 修改设备树,与驱动程序进行匹配 {.compatible = “fs,mybee”},然后驱动程序,才能通过设备树获取硬件资源
- 设备树修改后需要重新编译,并下载到开发板运行,通过ls /proc/device-tree/ 可以在系统中查看获取的设备树信息
/linux-3.14/arch/arm/boot/dts下对应的设备树文件添加之后重新编译
mybee@11000ca0{
compatible ="fs,mybee";
reg = <0x114000A0 0x4>,<0x139d0000 0x14>;
};
2.应用程序代码
代码如下(示例):
#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;
}
3.驱动程序代码
头文件和命令
#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)//需要与应用程序一致
提前准备好硬件相关的寄存器
#define GPD0CON 0x114000A0
#define TCFG0 0x139D0000
#define TCFG1 0x139D0004
#define TCON 0x139D0008
#define TCNTB0 0x139D000C
#define TCMPB0 0x139D0010
unsigned int *gpd0con;
unsigned int *tcfg0;
unsigned int *tcfg1;
unsigned int *tcon;
unsigned int *tcntb0;
unsigned int *tcmpb0;
定义相关的函数和需要用到的变量
//实现platform_driver
int my_probe(struct platform_device *pdev);
int my_remove(struct platform_device *pdev);
long my_ioctl(struct file *pf, unsigned int cmd, unsigned long args);
static struct resource *rescon;
static struct resource *resdata;//设备资源
static dev_t devnum;//申请设备号时使用
static char *name ="mybuzzer";//设备名
static struct cdev mycdev ;//字符设备
static struct class * myclass;
static struct device * mydevice;//设备节点
static struct file_operations myops={
.unlocked_ioctl = my_ioctl,
};
int buzzer_init(void);//操作函数
int buzzer_on(void);
int buzzer_off(void);
//定义platform_driver对象
struct of_device_id of_matches[]={
{.compatible="fs,mybee"}, //修改匹配规则,从设备树
{}, //获取buzzer相关硬件信息
};
static struct platform_driver mydriver ={
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "mytest",
.of_match_table = of_matches, //通过设备树匹配
},
};
对蜂鸣器相关的操作函数
int buzzer_init(void)//注册
{
u32 tmp;
tmp = readl(gpd0con);
tmp &= ~0xf;
tmp |= 0x2;
writel(tmp,gpd0con);
tmp = readl(tcfg0);
tmp |= 0xff;
writel(tmp,tcfg0);
tmp = readl(tcfg1);
tmp &= ~0xf;
tmp |= 0x3;
writel(tmp,tcfg1);
writel(110,tcntb0);
writel(110/2,tcmpb0);
tmp = readl(tcon);
tmp |= 0x1<<3;
tmp |= 0x1<<1;
writel(tmp,tcon);
tmp = readl(tcon);
tmp &= ~(0x1<<1);
writel(tmp,tcon);
return 0;
}
int buzzer_on(void)//打开
{
u32 tmp;
printk("buzzer_on\n");
tmp = readl(tcon);
tmp |= 0x1;
writel(tmp,tcon);
return 0;
}
int buzzer_off(void)//关闭
{
u32 tmp;
printk("buzzer_off\n");
tmp = readl(tcon);
tmp &= ~0x1;
writel(tmp,tcon);
return 0;
}
平台驱动相关函数
int my_probe(struct platform_device *pdev)//与设备树匹配成功执行
{
int ret;
//通过设备树获取 硬件资源
printk("match\n");
rescon = platform_get_resource(pdev,IORESOURCE_MEM,0);
if(rescon==NULL){
return -1;
}
printk("%#x\n",rescon->start);
gpd0con = ioremap(rescon->start,4);
resdata = platform_get_resource(pdev,IORESOURCE_MEM,1);
if(resdata==NULL){
return -1;
}
printk("%#x\n",resdata->start);
tcfg0 = ioremap(resdata->start,4);
//gpd0con = ioremap(GPD0CON,4);
//tcfg0 = ioremap(TCFG0,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;
}
long my_ioctl(struct file *pf, unsigned int cmd, unsigned long args)//当应用层调用IO操作时就会调用到此函数
{
switch (cmd)
{
case BUZZER_ON: buzzer_on(); break;
case BUZZER_OFF: buzzer_off();break;
default: return -1;
}
return 0;
}
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");//模块信息
本文详细介绍了如何在Linux环境下利用platform_driver和设备树实现PWM驱动,涉及PWM工作原理、电路结构、设备树配置及关键代码示例,包括头文件、设备注册、驱动函数和操作蜂鸣器的方法。
7364





