学无止境……
分类: 嵌入式
2014-06-26 17:28:44
一、开发环境
二、PWM怎样工作在ARM Linux中
1. PWM定义
PWM(脉冲宽度调制)简单的讲是一种变频技术之一,是靠改变脉冲宽度来控制输出电压,通过改变周期来控制其输出频率。
2. ARM Linux中的PWM
根据S3C2440的手册介绍,S3C2440A内部有5个16位的定时器,定时器0、1、2、3都带有脉冲宽度调制功能(PWM),定时器4是一个没有输出引脚的内部定时器,定时器0有一个用于大电流设备的死区生成器。
由S3C2440的技术手册和上面这幅结构图,2440内部定时器模块特性如下:
1)共5个16位的定时器,定时器0、1、2、3都带有脉冲宽度调制功能(PWM);
2)每个定时器都有一个比较缓存寄存器(TCMPB)和一个计数缓存寄存器(TCNTB);
3)定时器0、1共享一个8位的预分频器(预定标器),定时器2、3、4共享另一个8位的预分频器(预定标器),其值范围是0~255;
4)定时器0、1共享一个时钟分频器,定时器2、3、4共享另一个时钟分频器,这两个时钟分频器都能产生5种不同的分频信号值(即:1/2、1/4、1/8、1/16和TCLK);
5)两个8位的预分频器是可编程的且根据装载的值来对PCLK进行分频,预分频器和时钟分频器的值分别存储在定时器配置寄存器TCFG0和TCFG1中;
6)有一个TCON控制寄存器控制着所有定时器的属性和状态,TCON的第0~7位控制着定时器0、第8~11位控制着定时器1、第12~15位控制着定时器2、第16~19位控制着定时器3、第20~22位控制着定时器4。
开始一个PWM定时器功能的步骤如下(假设使用的是定时器0):
1)分别设置定时器0的预分频器值和时钟分频值,以供定时器0的比较缓存寄存器和计数缓存寄存器用;
2)设置比较缓存寄存器TCMPB0和计数缓存寄存器TCNTB0的初始值(即定时器0的输出时钟频率);
3)关闭定时器0的死区生成器(设置TCON的第4位);
4)开启定时器0的自动重载(设置TCON的第3位);
5)关闭定时器0的反相器(设置TCON的第2位);
6)开启定时器0的手动更新TCNTB0&TCMPB0功能(设置TCON的第1位);
7)启动定时器0(设置TCON的第0位);
8)清除定时器0的手动更新TCNTB0&TCMPB0功能(设置TCON的第1位)。
由此可以看到,PWM的输出频率跟比较缓存寄存器和计数缓存寄存器的取值有关,而比较缓存寄存器和计数缓存寄存器的值又跟预分频器和时钟分频器的值有关;要使用PWM功能其实也就是对定时器的相关寄存器进行操作。手册上也有一个公式:定时器输出频率 = PCLK / {预分频器值 + 1} / 时钟分频值。下面我们来通过一个蜂鸣器的实例来说明PWM功能的使用。
3、驱动的初始化和退出函数
static int __init BuzzerInit(void)
{
platform_device_register(&gitBuzzerDev);
platform_driver_register(&gitBuzzerDriver);
DEBUGP(DEVICE_NAME"\tinitialized\n");
return 0;
}
static void __exit BuzzerExit(void)
{
platform_driver_unregister(&gitBuzzerDriver);
platform_device_unregister(&gitBuzzerDev);
}
4、驱动探测函数和移除函数
static int BuzzerProbe(struct platform_device *pdev)
{
int ret = 0;
struct resource *res;
DPRINTK("probe:%s\n", __func__);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if(res == NULL)
{
return -ENOENT;
}
gidpGpbCon = ioremap(res->start, res->end - res->start);
if(gidpGpbCon == NULL)
{
ret = -EINVAL;
goto ERR_GPB;
}
gidpGpbDdat = gidpGpbCon + 1;
gidpGpbFup = gidpGpbCon + 2;
ret = misc_register(&gitBuzzerMisc);
return 0;
ERR_GPB:
iounmap(gidpGpbCon);
return ret;
}
static int BuzzerRemove(struct platform_device *dev)
{
iounmap(gidpGpbCon);
misc_deregister(&gitBuzzerMisc);
return 0;
}
5、从4中可以看出,我们把蜂鸣器定义为混杂设备,那么设备操作结构体定义如下
//设备操作结构体
static struct file_operations gitBuzzerFops =
{
.owner = THIS_MODULE,
.open = BuzzerOpen,
.release = BuzzerClose,
.ioctl = BuzzerIoctl,
};
static struct miscdevice gitBuzzerMisc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &gitBuzzerFops,
};
6、file_operations内容函数定义如下:
//对GPB0复用口进行复用功能设置,设置为TOUT0 PWM输出,为0,关闭
static int BuzzerOpen(struct inode *inode, struct file *file)
{
*gidpGpbCon &= ~(0x3 << 0); //清除相应位
*gidpGpbCon |= (0x1 << 0); //置为输出
*gidpGpbDdat &= ~(0x1 << 0);
return 0;
}
static int BuzzerClose(struct inode *inode, struct file *file)
{
return 0;
}
//设置Buzzer频率
static void SetFreq(unsigned int adFreq)
{
struct clk *clk_p;
unsigned long pclk = 0;
unsigned long tcon = 0;
unsigned long tcnt = 0;
unsigned long tcfg1 = 0;
unsigned long tcfg0 = 0;
DPRINTK("freq = %d\n", adFreq);
//以下对各寄存器的操作结合上面讲的开始一个PWM定时器的步骤和2440手册PWM寄存器操作部分来看就比较容易理解
tcfg1 = __raw_readl(S3C2410_TCFG1); //读取定时器配置寄存器1的值
tcfg0 = __raw_readl(S3C2410_TCFG0); //读取定时器配置寄存器0的值
tcfg0 &= ~S3C2410_TCFG_PRESCALER0_MASK;
tcfg0 |= (50 - 1); //设置tcfg0的值为49
tcfg1 &= ~S3C2410_TCFG1_MUX0_MASK;
tcfg1 |= S3C2410_TCFG1_MUX0_DIV16; //设置tcfg1的值为0x0011即:1/16
__raw_writel(tcfg1, S3C2410_TCFG1); //将值tcfg1写入定时器配置寄存器1中
__raw_writel(tcfg0, S3C2410_TCFG0); //将值tcfg0写入定时器配置寄存器0中
clk_p = clk_get(NULL, "pclk");
pclk = clk_get_rate(clk_p); //从系统平台时钟队列中获取pclk的时钟频率,在include/linux/clk.h中定义
tcnt = (pclk / 50 / 16) / adFreq; //计算定时器0的输出时钟频率(pclk/{prescaler0 + 1}/divider value)
__raw_writel(tcnt, S3C2410_TCNTB(0)); //设置定时器0计数缓存寄存器的值
__raw_writel(tcnt/2, S3C2410_TCMPB(0)); //设置定时器0比较缓存寄存器的值
tcon = __raw_readl(S3C2410_TCON); //读取定时器控制寄存器的值
tcon &= ~0x1f;
tcon |= 0xb; //关闭死区,自动重载,关反相器,更新TCNTB0&TCMPB0,启动定时器0
__raw_writel(tcon, S3C2410_TCON); //设置定时器控制寄存器的0-4位,即对定时器0进行控制
tcon &= ~2;
__raw_writel(tcon, S3C2410_TCON); //清除定时器0的手动更新位
}
//对设备进行控制,关于命令的定义,见头文件
static int BuzzerIoctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
int err = 0, ret = 0, idFreq = 0;
//检测命令的有效性
if(_IOC_TYPE(cmd) != BUZZER_IOC_MAGIC)
{
return -EINVAL;
}
if(_IOC_NR(cmd) > BUZZER_IOC_MAXNR)
{
return -EINVAL;
}
//根据命令类型,检测参数空间是否可以访问
if(_IOC_DIR(cmd) & _IOC_READ)
{
err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));
}
else if(_IOC_DIR(cmd) & _IOC_WRITE)
{
err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd));
}
if(err != 0)
{
return -EFAULT;
}
DPRINTK("led ioctl[kernel_space]\n");
//根据命令,执行相应的操作
switch(cmd)
{
case BUZZER_IOC_ON:
{
*gidpGpbDdat |= (0x1 << 0);
}
break;
case BUZZER_IOC_OFF:
{
*gidpGpbDdat &= ~(0x1 << 0); //直接给低电平可让蜂鸣器停止工作
}
break;
case BUZZER_IOC_SET:
{
ret = __get_user(idFreq, (int *)arg);
SetFreq(idFreq);
gidFreq = idFreq;
}
break;
case BUZZER_IOC_READ:
{
ret = __put_user(gidFreq, (int *)arg);
}
break;
default:
{
return -EINVAL;
}
break;
}/* end switch(cmd) */
return ret;
}
7、头文件内容如下:
#ifndef _HDW_PWM_H
#define _HDW_PWM_H
#define BUZZER_GPB_ADDR 0x56000010
#define PLAT_DEV_NAME "plat-buzzer"
#define DEVICE_NAME "HDW_BUZZER"
//定义幻数
#define BUZZER_IOC_MAGIC 'k'
//定义命令
#define BUZZER_IOC_MAXNR 4
#define BUZZER_IOC_ON _IO(BUZZER_IOC_MAGIC, 0)
#define BUZZER_IOC_OFF _IO(BUZZER_IOC_MAGIC, 1)
#define BUZZER_IOC_SET _IOW(BUZZER_IOC_MAGIC, 2, int)
#define BUZZER_IOC_READ _IOR(BUZZER_IOC_MAGIC, 3, int)
#undef DEBUG
#define _DEBUG
#ifdef DEBUG
#define DPRINTK printk
#else
#define DPRINTK(x...) (void)(0)
#endif
#endif
8、全局变量定义如下:
static unsigned int gidFreq = 0; //用于保存设置的频率值,用在读命令中
static volatile unsigned long *gidpGpbCon = NULL;
static volatile unsigned long *gidpGpbDdat = NULL;
static volatile unsigned long *gidpGpbFup = NULL;
五、编写PWM蜂鸣器驱动的测试程序
文件名:buttonApp.c
程序为:
int main(int argc, char **argv)int main(int argc, char **argv)
{
char str[6] = {0};
unsigned char i = 0;
unsigned int idCmd = 0;
int fd = 0, idFreq = 0;
fd = open("/dev/HDW_BUZZER", O_RDWR);
if(fd < 0)
{
printf("Open PWM Device Faild!\n");
exit(1);
}
while(1)
{
idCmd = 0;
printf("please enter the cmd and freq :\n");
scanf("%s%d", str, &idFreq);
printf("cmd = %s, idFreq = %d\n", str, idFreq);
if(idFreq > 0)
{
if(strcmp(str, "ON") == 0)
{
idCmd = BUZZER_IOC_ON;
}
else if(strcmp(str, "OFF") == 0)
{
idCmd = BUZZER_IOC_OFF;
}
else if(strcmp(str, "SET") == 0)
{
idCmd = BUZZER_IOC_SET;
}
else if(strcmp(str, "READ") == 0)
{
idCmd = BUZZER_IOC_READ;
}
else if(strcmp(str, "QUIT") == 0)
{
idCmd = BUZZER_IOC_OFF;
ioctl(fd, idCmd, &idFreq);
printf("quit buzzer control\n");
break;
}
else
{
printf("wrong cmd\n");
}
if(idCmd != 0)
{
if(ioctl(fd, idCmd, &idFreq) < 0)
{
printf("CMD fail\n");
break;
}
if(idCmd == BUZZER_IOC_READ)
{
printf("idFreq = %d\n", idFreq);
}
}
}/* end if(idFreq >= 0) */
else
{
printf("wrong input freq(< 0)\n");
}
}/* end while(1) */
close(fd);
return 0;
}
buttonApp.h程序为:
#ifndef _BUZZER_APP_H_
#define _BUZZER_APP_H_
#include
//定义幻数
#define BUZZER_IOC_MAGIC 'k'
//定义命令
#define BUZZER_IOC_MAXNR 4
#define BUZZER_IOC_ON _IO(BUZZER_IOC_MAGIC, 0)
#define BUZZER_IOC_OFF _IO(BUZZER_IOC_MAGIC, 1)
#define BUZZER_IOC_SET _IOW(BUZZER_IOC_MAGIC, 2, int)
#define BUZZER_IOC_READ _IOR(BUZZER_IOC_MAGIC, 3, int)
#endif
六、测试
输入相应命令,测试现象正常。能够按照相应频率发声,修改频率后,声音有变化。
输入开始或者停止命令后,蜂鸣器能够响应相应的动作。
输入错误的命令参数后,蜂鸣器无反应,保持输入命令以前的状态。
七、总结platform驱动移植步骤
综合移植LED和按键的platform驱动移植经验,移植platform驱动步骤为:
1、首先移植设备资源,定义设备的不同资源,对于本例,则是gitBuzzerResource。
2、移植设备的定义和驱动的定义,对于本例,分别为gitBuzzerDev和gitBuzzerDriver。
3、编写驱动的初始化和卸载函数,也即对设备和驱动的注册或者卸载,此处注意,在驱动的初始化函数中,最好先注册设备,然后再注册驱动,在驱动的卸载函数
中,先卸载驱动,再卸载设备。对应本例为BuzzerInit()和BuzzerExit()函数。
4、在2中,关于驱动的定义,开始移植probe函数和remove函数,对应本例为BuzzerProbe和BuzzerRemove。
5、在探测函数中,会初始化file_operations结构体,接下来,就是对file_operations结构体内容的移植,包括open,close,read,write,ioctl等等。