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

linux驱动开发之pwm蜂鸣器

2016-11-23 15:01 399 查看
http://blog.csdn.net/changliang7731/article/details/52297561

驱动开发,控制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");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152

蜂鸣器的操作:可以打开,让它叫,亦或者不叫,响的时候可以改变它的频率。即蜂鸣器发出声响的尖锐程度不同。 

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);
1
2
3
4
5
1
2
3
4
5

这里可以看出它是返回一个 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);
}
1
2
3
4
5
1
2
3
4
5

这里我们先重新配置内核,把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,
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

此时,再重新编译内核,加载内核到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;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134

编译档案生成可执行程序后,放在文件系统的根目录下,运行。 

按+可以增加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的原因.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101

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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

与蜂鸣器比较类似的应该是panel 背光设备了!背光一般固定频率,duty可变,手机上的背光一般就是控制背光pwm的duty来实现亮度的变化!



顶0
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: