嵌入式Linux之我行——PWM在ARM Linux中的原理和蜂鸣器驱动实例开发

嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤。一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便。如有错误之处,谢请指正。
  • 共享资源,欢迎转载:http://hbhuanggang.cublog.cn

一、开发环境

  • 主  机:VMWare--Fedora 9
  • 开发板:Mini2440--64MB Nand, Kernel:2.6.30.4
  • 编译器:arm-linux-gcc-4.3.2

二、PWM怎样工作在ARM Linux中

1. 什么是PWM?

   PWM(脉冲宽度调制)简单的讲是一种变频技术之一,是靠改变脉冲宽度来控制输出电压,通过改变周期来控制其输出频率。如果还不是很清楚,好吧,来看看我们实际生活中的例子,我们的电风扇为什么扭一下按扭,风扇的转速就会发生变化;调一下收音机的声音按钮,声音的大小就会发生变化;还有待会儿我们要讲的蜂鸣器也会根据不同的输入值而发出不同频率的叫声等等!!这些都是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
 
还是根据S3C2440手册的描述和上图的结构,要开始一个PWM定时器功能的步骤如下(假设使用的是第一个定时器):
 
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功能的使用。

三、蜂鸣器驱动实例
 
1. 蜂鸣器的种类和工作原理
    
   蜂鸣器主要分为压电式蜂鸣器和电磁式蜂鸣器两种类型。
 
   压电式蜂鸣器主要由多谐振荡器、压电蜂鸣片、阻抗匹配器及共鸣箱、外壳等组成。有的压电式蜂鸣器外壳上还装有发光二极管。多谐振荡器由晶体管或集成电路构成。当接通电源后(1.5~15V直流工作电压),多谐振荡器起振,输出1.5~2.5kHZ的音频信号,阻抗匹配器推动压电蜂鸣片发声。
 
   电磁式蜂鸣器由振荡器、电磁线圈、磁铁、振动膜片及外壳等组成。接通电源后,振荡器产生的音频信号电流通过电磁线圈,使电磁线圈产生磁场。振动膜片在电磁线圈和磁铁的相互作用下,周期性地振动发声。
 
   有源蜂鸣器和无源蜂鸣器的区别:这个“源”字是不是指电源,而是指震荡源,即有源蜂鸣器内有振荡源而无源蜂鸣器内部没有振荡源。有振荡源的通电就可以发声,没有振荡源的需要脉冲信号驱动才能发声。

   额外知识:简单蜂鸣器的制作方法
   1)制备电磁铁M:在长约6厘米的铁螺栓上绕100圈导线,线端留下5厘米作引线,用透明胶布把线圈粘好,以免线圈松开,再用胶布把它粘在一个盒子上,电磁铁就做好了;
   2)制备弹片P:从铁罐头盒上剪下一条宽约2厘米的长铁片,弯成直角,把电磁铁的一条引线接在弹片上,再用胶布把弹片紧贴在木板上;
   3)用曲别针做触头Q,用书把曲别针垫高,用胶布粘牢,引出一条导线,如图连接好电路;
   4)调节M与P之间的距离(通过移动盒子),使电磁铁能吸引弹片,调节触点与弹片之间的距离,使它们能恰好接触,通电后就可以听到蜂鸣声。

2. 开发板上蜂鸣器原理图分析
 

由原理图可以得知,蜂鸣器是通过GPB0 IO口使用PWM信号驱动工作的,而GPB0口是一个复用的IO口,要使用它得先把他设置成TOUT0 PWM输出模式。
 
3. 编写合适开发板的蜂鸣器驱动程序,文件名:my2440_pwm.c
 

/*
 ================================================
 Name        : my2440_pwm.c
 Author      : Huang Gang
 Date        : 25/11/09
 Copyright   : GPL
 Description : my2440 pwm driver
 ================================================
 */


#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <asm/io.h>
#include <mach/hardware.h>
#include <mach/regs-gpio.h>
#include <plat/regs-timer.h>

#define PWM_MAJOR 0                  //主设备号
#define PWM_NAME "my2440_pwm"        //设备名称

 

static int device_major = PWM_MAJOR; //系统动态生成的主设备号

//打开设备
static int pwm_open(struct inode *inode, struct file *file)
{
    //对GPB0复用口进行复用功能设置,设置为TOUT0 PWM输出
    s3c2410_gpio_cfgpin(S3C2410_GPB0, S3C2410_GPB0_TOUT0);

    return 0;
}

//关闭设备
static int pwm_close(struct inode *inode, struct file *file)
{
    return 0;
}

//对设备进行控制
static int pwm_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsignedlong arg)
{
    if(cmd <= 0)//如果输入的参数小于或等于0的话,就让蜂鸣器停止工作
    {
        //这里又恢复GPB0口为IO口输出功能,由原理图可知直接给低电平可让蜂鸣器停止工作
        s3c2410_gpio_cfgpin(S3C2410_GPB0, S3C2410_GPB0_OUTP);
        s3c2410_gpio_setpin(S3C2410_GPB0, 0);
    }
    else//如果输入的参数大于0,就让蜂鸣器开始工作,不同的参数,蜂鸣器的频率也不一样
    {
        //定义一些局部变量
        unsigned long tcon;
        unsigned long tcnt;
        unsigned long tcfg1;
        unsigned long tcfg0;

        struct clk *clk_p;
        unsigned long pclk;

        //以下对各寄存器的操作结合上面讲的开始一个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)/cmd;      //计算定时器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的手动更新位
    }

    return 0;
}

//设备操作结构体
static struct file_operations pwm_fops = 
{
    .owner   = THIS_MODULE,
    .open    = pwm_open,
    .release = pwm_close,
    .ioctl   = pwm_ioctl,
};

//定义一个设备类
static struct class *pwm_class;

static int __init pwm_init(void)
{
    //注册为字符设备,主设备号为0让系统自动分配,设备名为my2440_pwm,注册成功返回动态生成的主设备号
    device_major = register_chrdev(PWM_MAJOR, PWM_NAME, &pwm_fops);

    if(device_major < 0)
    {
        printk(PWM_NAME " register falid!\n");
        return device_major;
    }

    //注册一个设备类,使mdev可以在/dev/目录下自动建立设备节点
    pwm_class = class_create(THIS_MODULE, PWM_NAME);

    if(IS_ERR(pwm_class))
    {
        printk(PWM_NAME " register class falid!\n");
        return -1;
    }

    //创建一个设备节点,设备名为PWM_NAME,即:my2440_pwm
    device_create(pwm_class, NULL, MKDEV(device_major, 0), NULL, PWM_NAME);

    return 0;
}

static void __exit pwm_exit(void)
{
    //注销设备
    unregister_chrdev(device_major, PWM_NAME);

    //删除设备节点
    device_destroy(pwm_class, MKDEV(device_major, 0));

    //注销设备类
    class_destroy(pwm_class);
}

module_init(pwm_init);
module_exit(pwm_exit);

MODULE_LICENSE("PGL");
MODULE_AUTHOR("Huang Gang");
MODULE_DESCRIPTION("my2440 pwm driver");


4. 将PWM蜂鸣器驱动代码部署到内核中。
 

#cp -f my2440_pwm.c /linux-2.6.30.4/drivers/char //把驱动源码复制到内核驱动的字符设备下


#gedit /linux-2.6.30.4/drivers/char/Kconfig //添加PWM蜂鸣器设备配置

config MY2440_PWM_BEEP
    tristate "My2440 PWM Beep Device"
    depends on ARCH_S3C2440
    default y
    ---help---
      My2440 PWM Beep


#gedit /linux-2.6.30.4/drivers/char/Makefile //添加PWM蜂鸣器设备配置

obj-$(CONFIG_MY2440_PWM_BEEP) += my2440_pwm.o


5.配置内核,选择PWM蜂鸣器设备选项

#make menuconfig

Device Drivers --->
    Character devices ---> 
        <*> My2440 PWM Beep Device (NEW)


6. 编译内核并下载到开发板上。这里要注意,现在我们不需要手动的在开发板上创建设备的节点了,因为我们现在使用了mdev进行管理了(使用方法请看:设备文件系统剖析与使用),在驱动程序中也添加了对类设备接口的支持。之前讲的一些驱动都没有,以后我们都使用这种方法。现在可以查看到/dev目录下自动创建好的my2440_pwm设备节点,就直接可以使用它了。

7. 编写PWM蜂鸣器驱动的测试程序。文件名:pwm_test.c

/*
 ==============================================
 Name        : pwm_test.c
 Author      : Huang Gang
 Date        : 25/11/2009
 Copyright   : GPL
 Description : my2440 pwm driver test
 ==============================================
 */


#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>

int main(int argc, char **argv)
{
    int tmp;
    int fd;
    int i;

    //打开蜂鸣器设备
    fd = open("/dev/my2440_pwm", O_RDWR);

    if(fd < 0)
    {
        printf("Open PWM Device Faild!\n");
        exit(1);
    }

    //提示用户输入一个参数来对蜂鸣器进行调频,0表示停止工作
    printf("please enter the times number(0 is stop):\n");

    while(1)
    {
        //输入参数
        scanf("%d", &tmp);
        printf("times = %d\n", tmp);
        
        //IO控制
        ioctl(fd, tmp);

        if(tmp <= 0)
        {
            break;
        }
    }

    //关闭设备
    close(fd);

    return 0;
}


8. 在开发主机上交叉编译测试应用程序,并复制到文件系统的/usr/sbin目录下,然后重新编译文件系统下载到开发板上。

#arm-linux-gcc -o pwm_test pwm_test.c


9. 在开发板上运行测试程序。可以看到根据你输入参数的大小,蜂鸣器也会发生不同频率的叫声,输入0蜂鸣器停止鸣叫。

你可能感兴趣的:(linux,嵌入式,module,class,工作,c)