linux驱动开发之pwm蜂鸣器

2019-04-14 20:33发布

驱动开发,控制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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 ", 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 ", BUZZER_PWM_ID, DEVICE_NAME); return -ENODEV; } pwm_stop(); sema_init(&lock, 1); ret = misc_register(&smart210_misc_dev); printk(DEVICE_NAME " initialized "); 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 #include #include #include #include #include #include #include #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 "); 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 "); } int main(int argc, char **argv) { int freq = 1000 ; open_buzzer(); printf( " BUZZER TEST ( PWM Control ) " ); printf( "Press +/- to increase/reduce the frequency of the BUZZER " ) ; printf( "Press 'ESC' key to Exit this program " ); while( 1 ) { int key; set_buzzer_freq(freq); printf( " Freq = %d ", 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 ", 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 ", BUZZER_PWM_ID, DEVICE_NAME); return -ENODEV; } pwm_stop(); sema_init(&lock, 1); ret = misc_register(&smart210_misc_dev); printk(DEVICE_NAME " initialized "); 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 ", 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来实现亮度的变化!