1. 程式人生 > >嵌入式Linux之我行——PWM在ARM Linux中的原理和蜂鳴器驅動例項開發

嵌入式Linux之我行——PWM在ARM Linux中的原理和蜂鳴器驅動例項開發

嵌入式Linux之我行,主要講述和總結了本人在學習嵌入式linux中的每個步驟。一為總結經驗,二希望能給想入門嵌入式Linux的朋友提供方便。如有錯誤之處,謝請指正。

一、開發環境

  • 主  機: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蜂鳴器停止鳴叫。