您的位置:首页 > 运维架构 > Linux

linux驱动开发之pwm蜂鸣器

2016-08-24 10:27 225 查看
驱动开发,控制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来实现亮度的变化!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息