linux驅動開發之pwm蜂鳴器

驅動開發,控制pwm蜂鳴器!
蜂鳴器有多種類型,一種是給電就叫,另一種給電了還不行,還需要freq纔會叫。大概稱作有源和無源吧!

我們此時將buzzer的驅動加入到內核中去。

/*
 * linux/drivers/char/smart210_pwm.c
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/err.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/gpio.h>

#include <mach/gpio.h>
#include <mach/regs-gpio.h>
#include <plat/gpio-cfg.h>


#define DEVICE_NAME             "pwm-buzzer"


#define PWM_IOCTL_STOP          0
#define PWM_IOCTL_SET_FREQ      1
#define PWM_IOCTL_SET_DUTY      2

#define NS_IN_1HZ               (1000000000UL)


#define BUZZER_PWM_ID           0
#define BUZZER_PMW_GPIO         S5PV210_GPD0(0)

static struct pwm_device *pwm0buzzer;

static struct semaphore lock;


static void pwm_set_freq(unsigned long freq) {
    int period_ns = NS_IN_1HZ / freq;

    pwm_config(pwm0buzzer, period_ns / 2, period_ns);
    pwm_enable(pwm0buzzer);

    s3c_gpio_cfgpin(BUZZER_PMW_GPIO, S3C_GPIO_SFN(2));
}

static void pwm_stop(void) {
    s3c_gpio_cfgpin(BUZZER_PMW_GPIO, S3C_GPIO_OUTPUT);

    pwm_config(pwm0buzzer, 0, NS_IN_1HZ / 100);
    pwm_disable(pwm0buzzer);
}


static int smart210_pwm_open(struct inode *inode, struct file *file) {
    if (!down_trylock(&lock))
        return 0;
    else
        return -EBUSY;
}

static int smart210_pwm_close(struct inode *inode, struct file *file) {
    up(&lock);
    return 0;
}

static long smart210_pwm_ioctl(struct file *filep, unsigned int cmd,
        unsigned long arg)
{
    switch (cmd) {
        case PWM_IOCTL_SET_FREQ:
            if (arg == 0)
                return -EINVAL;
            pwm_set_freq(arg);
            break;

        case PWM_IOCTL_STOP:
        case PWM_IOCTL_SET_DUTY:
        default:
            pwm_stop();
            break;
    }

    return 0;
}


static struct file_operations smart210_pwm_ops = {
    .owner          = THIS_MODULE,
    .open           = smart210_pwm_open,
    .release        = smart210_pwm_close, 
    .unlocked_ioctl = smart210_pwm_ioctl,
};

static struct miscdevice smart210_misc_dev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = DEVICE_NAME,
    .fops = &smart210_pwm_ops,


};

static int __init smart210_pwm_dev_init(void) {
    int ret;

    ret = gpio_request(BUZZER_PMW_GPIO, DEVICE_NAME);
    if (ret) {
        printk("request GPIO %d for pwm failed\n", BUZZER_PMW_GPIO);
        return ret;
    }

    gpio_set_value(BUZZER_PMW_GPIO, 0);
    s3c_gpio_cfgpin(BUZZER_PMW_GPIO, S3C_GPIO_OUTPUT);

    pwm0buzzer = pwm_request(BUZZER_PWM_ID, DEVICE_NAME);
    if (IS_ERR(pwm0buzzer)) {
        printk("request pwm %d for %s failed\n", BUZZER_PWM_ID, DEVICE_NAME);
        return -ENODEV;
    }

    pwm_stop();

    sema_init(&lock, 1);
    ret = misc_register(&smart210_misc_dev);

    printk(DEVICE_NAME "\tinitialized\n");

    return ret;
}

static void __exit smart210_pwm_dev_exit(void) {
    pwm_stop();

    misc_deregister(&smart210_misc_dev);
    gpio_free(BUZZER_PMW_GPIO);
}

module_init(smart210_pwm_dev_init);
module_exit(smart210_pwm_dev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("FriendlyARM Inc.");
MODULE_DESCRIPTION("S5PV210 PWM Driver");

蜂鳴器的操作:可以打開,讓它叫,亦或者不叫,響的時候可以改變它的頻率。即蜂鳴器發出聲響的尖銳程度不同。
duty的改變在這裏無太大作用。蜂鳴器的工作電壓是一個恆定值,一般爲5v或者3.v。改變duty的結果無非和 開關的結果一樣,叫或者不叫而已,所以函數沒有實現這一部分!

將此文件關聯編譯到內核,重新編譯內核。make uImage
編譯成功。
使用uboot加載新內核,內核打印出 request pwm 0 for pwm-buzzer failed。
同時,在busybox勾線的nfs根文件目錄的dev目錄下未自動生成 pwm-buzzer設備文件。

這裏問題出現了。

程序在調用pwm_request 請求一個pwm設備時返回失敗。
pwm_request的聲明在 include/linux/pwm.h中

#if IS_ENABLED(CONFIG_PWM) || IS_ENABLED(CONFIG_HAVE_PWM)
/*
 * pwm_request - request a PWM device
 */
struct pwm_device *pwm_request(int pwm_id, const char *label);

這裏可以看出它是返回一個 pwm_device 結構體指針的函數,成功則返回請求的pwm_device的結構體指針,在這裏發現需要
把CONFIG_PWM 或者CONFIG_HAVE_PWM打開纔可以,否則會調用else下面的:

#else
static inline struct pwm_device *pwm_request(int pwm_id, const char *label)
{
    return ERR_PTR(-ENODEV);
}

這裏我們先重新配置內核,把CONFIG_PWM或者CONFIG_HAVE_PWM給選上。再編譯內核
再裝載內核,最後還是報錯在申請pwm device失敗那邊!
這邊有點不懂了。
不過有在網上和書上有看過別人的文章和思路。因爲pwm需要用到pwm定時器用於方波的產生,所以在此之前我們試着把pwm0對應的定時器初始化一下,試試看會不會有驚喜。
在arch/arm/mach-s5pv210/mach-smdkv210.c中,加一行:

static struct platform_device *smdkv210_devices[] __initdata = {
    &s3c_device_adc,
    &s3c_device_cfcon,
    &s3c_device_fb,
    &s3c_device_hsmmc0,
    &s3c_device_hsmmc1,
    &s3c_device_hsmmc2,
    &s3c_device_hsmmc3,
    &s3c_device_i2c0,
    &s3c_device_i2c1,
    &s3c_device_i2c2,
    &s3c_device_rtc,
    &s3c_device_ts,
    &s3c_device_usb_hsotg,
    &s3c_device_wdt,
    &s3c_device_timer[0],// for pwm buzzer
    &s5p_device_fimc0,
    &s5p_device_fimc1,
    &s5p_device_fimc2,
    &s5p_device_fimc_md,
    &s5p_device_jpeg,
    &s5p_device_mfc,
    &s5p_device_mfc_l,
    &s5p_device_mfc_r,
    &s5pv210_device_ac97,
    &s5pv210_device_iis0,
    &s5pv210_device_spdif,
    &samsung_asoc_idma,
    &samsung_device_keypad,
    &smdkv210_dm9000,
    &smdkv210_lcd_lte480wv,
};

此時,再重新編譯內核,加載內核到ram中,內核打印出pwm-buzzer initialized
然後在busybox構建的根文件系統的dev目錄下可以找到pwm-buzzer 設備文件,這裏,我們的實驗算是成功了一大半了!

寫測試程序。我們已經生成buzzer設備文件,並將對應的驅動綁定到此設備上!當我們打開此設備,並對此設備文件進行操作時便會調用到我們寫的驅動。
測試程序:

#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define PWM_IOCTL_SET_FREQ      1
#define PWM_IOCTL_STOP          0

#define ESC_KEY     0x1b

static int getch(void)
{
    struct termios oldt,newt;
    int ch;

    if (!isatty(STDIN_FILENO)) {
        fprintf(stderr, "this problem should be run at a terminal\n");
        exit(1);
    }
    // save terminal setting
    if(tcgetattr(STDIN_FILENO, &oldt) < 0) {
        perror("save the terminal setting");
        exit(1);
    }

    // set terminal as need
    newt = oldt;
    newt.c_lflag &= ~( ICANON | ECHO );
    if(tcsetattr(STDIN_FILENO,TCSANOW, &newt) < 0) {
        perror("set terminal");
        exit(1);
    }

    ch = getchar();

    // restore termial setting
    if(tcsetattr(STDIN_FILENO,TCSANOW,&oldt) < 0) {
        perror("restore the termial setting");
        exit(1);
    }
    return ch;
}

static int fd = -1;
static void close_buzzer(void);
static void open_buzzer(void)
{
    fd = open("/dev/pwm-buzzer", 0);
    if (fd < 0) {
        perror("open pwm_buzzer device");
        exit(1);
    }

    // any function exit call will stop the buzzer
    atexit(close_buzzer);
}

static void close_buzzer(void)
{
    if (fd >= 0) {
        ioctl(fd, PWM_IOCTL_STOP);
        //if (ioctl(fd, 2) < 0) {
        //  perror("ioctl 2:");
        //}
        close(fd);
        fd = -1;
    }
}

static void set_buzzer_freq(int freq)
{
    // this IOCTL command is the key to set frequency
    int ret = ioctl(fd, PWM_IOCTL_SET_FREQ, freq);
    if(ret < 0) {
        perror("set the frequency of the buzzer");
        exit(1);
    }
}
static void stop_buzzer(void)
{
    int ret = ioctl(fd, PWM_IOCTL_STOP);
    if(ret < 0) {
        perror("stop the buzzer");
        exit(1);
    }
    printf( "Pwm buzzer stop\n");
}

int main(int argc, char **argv)
{
    int freq = 1000 ;

    open_buzzer();

    printf( "\nBUZZER TEST ( PWM Control )\n" );
    printf( "Press +/- to increase/reduce the frequency of the BUZZER\n" ) ;
    printf( "Press 'ESC' key to Exit this program\n\n" );


    while( 1 )
    {
        int key;

        set_buzzer_freq(freq);
        printf( "\tFreq = %d\n", freq );

        key = getch();

        switch(key) {
        case '+':
            if( freq < 20000 )
                freq += 10;
            break;

        case '-':
            if( freq > 11 )
                freq -= 10 ;
            break;

        case ESC_KEY:
        case EOF:
            stop_buzzer();
            exit(0);

        default:
            break;
        }
    }
}

編譯檔案生成可執行程序後,放在文件系統的根目錄下,運行。
按+可以增加pwm的頻率,按-可以減小pwm的頻率。esc按鍵關掉蜂鳴器。
這樣基本上pwm蜂鳴器的驅動已經開發完成!

一些note:

struct miscdevice  {
    int minor;//次設備號,misc混雜設備公用一個主設備號,當.minor=MISC_DYNAMIC_MINOR,表示動態分配次設備號
    const char *name;//名稱,驅動作者自己定義
    const struct file_operations *fops;//文件操作接口,一般具體驅動在這裏實現
    struct list_head list;
    struct device *parent;
    struct device *this_device;
    const char *nodename;
    umode_t mode;
};
//misc device主要填充前3個內容,後面的不懂,以後有用到再研究!
struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    int (*readdir) (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, loff_t, loff_t, int datasync);
    int (*aio_fsync) (struct kiocb *, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
    int (*check_flags)(int);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    int (*setlease)(struct file *, long, struct file_lock **);
    long (*fallocate)(struct file *file, int mode, loff_t offset,
              loff_t len);
    int (*show_fdinfo)(struct seq_file *m, struct file *f);
};
//這裏我們僅僅初始化了file_operations裏面的幾個函式
static struct file_operations smart210_pwm_ops = {
    .owner          = THIS_MODULE,
    .open           = smart210_pwm_open,
    .release        = smart210_pwm_close, 
    .unlocked_ioctl = smart210_pwm_ioctl,
};
static int smart210_pwm_open(struct inode *inode, struct file *file);
static int smart210_pwm_close(struct inode *inode, struct file *file);
static long smart210_pwm_ioctl(struct file *filep, unsigned int cmd,
        unsigned long arg)
//我們可以看到這三個函式必須嚴格按照file_operations的內部對應的函數指針的參數來構造
//其實真正的驅動是寫在smart210_pwm_ioctl中,這裏實現pwm的頻率和duty:
static void pwm_set_freq(unsigned long freq) {
    int period_ns = NS_IN_1HZ / freq;

    pwm_config(pwm0buzzer, period_ns / 2, period_ns);
    pwm_enable(pwm0buzzer);//pwm0使能

    s3c_gpio_cfgpin(BUZZER_PMW_GPIO, S3C_GPIO_SFN(2));//pwm接口配置成pwm
}
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
//這裏很明確,period_ns填充代表pwm的頻率。duty_ns的填充代表duty,也就是佔空比!這裏的buzzer默認佔空比是50%
//函數裏面的實現暫時不去研究它是如何實現的
//最後我們來確認兩個主要函數
static void __exit smart210_pwm_dev_exit(void) {
    pwm_stop();

    misc_deregister(&smart210_misc_dev);//註銷misc設備
    gpio_free(BUZZER_PMW_GPIO);
}
static int __init smart210_pwm_dev_init(void) {
    int ret;

    ret = gpio_request(BUZZER_PMW_GPIO, DEVICE_NAME);
    if (ret) {
        printk("request GPIO %d for pwm failed\n", BUZZER_PMW_GPIO);
        return ret;
    }

    gpio_set_value(BUZZER_PMW_GPIO, 0);
    s3c_gpio_cfgpin(BUZZER_PMW_GPIO, S3C_GPIO_OUTPUT);

    pwm0buzzer = pwm_request(BUZZER_PWM_ID, DEVICE_NAME);
    if (IS_ERR(pwm0buzzer)) {
        printk("request pwm %d for %s failed\n", BUZZER_PWM_ID, DEVICE_NAME);
        return -ENODEV;
    }

    pwm_stop();

    sema_init(&lock, 1);
    ret = misc_register(&smart210_misc_dev);

    printk(DEVICE_NAME "\tinitialized\n");

    return ret;
}
//這個也比較好理解,你把buzzer當做一個混雜設備,用混雜設備來聲明,來實現它的驅動,那麼同樣,註冊、註銷設備時便需要把它當做
//misc設備來處理,這也是這裏爲什麼會有misc_deregister,misc_register的原因.

Misc設備是一個主設備號爲10的字符設備。

static int __init misc_init(void)
{
    int err;

#ifdef CONFIG_PROC_FS
    proc_create("misc", 0, NULL, &misc_proc_fops);
#endif
    misc_class = class_create(THIS_MODULE, "misc");//註冊一個misc類
    err = PTR_ERR(misc_class);
    if (IS_ERR(misc_class))
        goto fail_remove;

    err = -EIO;
    if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))//向內核註冊主設備號爲10的字符設備
        goto fail_printk;
    misc_class->devnode = misc_devnode;
    return 0;

fail_printk:
    printk("unable to get major %d for misc devices\n", MISC_MAJOR);
    class_destroy(misc_class);
fail_remove:
    remove_proc_entry("misc", NULL);
    return err;
}

int misc_register(struct miscdevice * misc)
{
    dev_t dev;
    int err = 0;

    INIT_LIST_HEAD(&misc->list);

    mutex_lock(&misc_mtx);

    if (misc->minor == MISC_DYNAMIC_MINOR) {
        int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);
        if (i >= DYNAMIC_MINORS) {
            mutex_unlock(&misc_mtx);
            return -EBUSY;
        }
        misc->minor = DYNAMIC_MINORS - i - 1;
        set_bit(i, misc_minors);
    } else {
        struct miscdevice *c;

        list_for_each_entry(c, &misc_list, list) {
            if (c->minor == misc->minor) {
                mutex_unlock(&misc_mtx);
                return -EBUSY;
            }
        }
    }

    dev = MKDEV(MISC_MAJOR, misc->minor);//生成設備主次號

    misc->this_device = device_create(misc_class, misc->parent, dev,
                      misc, "%s", misc->name);//自動創建設備節點使用
    if (IS_ERR(misc->this_device)) {
        int i = DYNAMIC_MINORS - misc->minor - 1;
        if (i < DYNAMIC_MINORS && i >= 0)
            clear_bit(i, misc_minors);
        err = PTR_ERR(misc->this_device);
        goto out;
    }

    /*
     * Add it to the front, so that later devices can "override"
     * earlier defaults
     */
    list_add(&misc->list, &misc_list);
 out:
    mutex_unlock(&misc_mtx);
    return err;
}

與蜂鳴器比較類似的應該是panel 背光設備了!背光一般固定頻率,duty可變,手機上的背光一般就是控制背光pwm的duty來實現亮度的變化!

發佈了37 篇原創文章 · 獲贊 5 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章