本文转载自:https://blog.csdn.net/lxllinux/article/details/80885331
一、关于PWM:
PWM(Pulse Width Modulation)——脉宽调制,它是利用微控制器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用于测量、通信、功率控制与变换等许多领域。
通过S3C2440底板原理图可知蜂鸣器接2440的GPB0/TOUT0.即我们的蜂鸣器是通过GPB0 IO口使用PWM信号驱动工作的,而且GPB0口是一个复用的IO口,要使用它得把他设置成TOUT0 PWM输出模式。
蜂鸣器可以发声,靠的不仅仅是蜂鸣器硬件的驱动,还有Linux下的PWM(脉冲宽度调制)这种变频技术,靠改变脉冲宽度来控制输出电压,通过改变周期来控制其输出频率。通过改变频率可以使蜂鸣器发出不同的声音。
二、修改设备树
-
-
compatible = "pwm-beeper";
-
pwms = <&pwm 0 1000000 0>;
-
pinctrl-names = "default";
-
-
四、修改配置文件:
[yangni@yangni linux-3.0.54]$make menuconfig
[ * ] PWM device support
DeviceDrivers --->
Input device support --->
[* ] Miscellaneous devices --->
< * > PWM beeper support
配置完成以后就可以make了,然后将重新编译好的内核下载到开发板上,开发板启动的时候会听到“滴”的一声响。并且,在开发板会打印出如下相关的信息:
五、测试程序:
首先得查找出事件编号。因为后来移植过按键驱动,所以按键驱动被分配为input0了,导致后来再运行打开不了,所以测试程序写的时候,open的时候要open对。
cat proc/bus/input/devices
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
int main(int argc, char *argv[])
-
-
-
-
struct input_event event;
-
if ((fd = open("/dev/event1", O_RDWR)) < 0) {
-
-
-
-
-
-
if(!strcmp (argv[1],"1"))
-
-
-
-
-
ret = write(fd, &event, sizeof(struct input_event));
-
-
-
ret = write(fd, &event, sizeof(struct input_event));
-
-
-
else if(!strcmp (argv[1],"0"))
-
-
-
ret = write(fd, &event, sizeof(struct input_event));
-
-
-
-
从应用程序中得到的input_event.code只能为SND_BELL或SND_TONE,否则退出。
如果为SND_BELL,不管input_event.value为多少,最终的value只能为1000或0,即不能改变蜂鸣器的频率。1000表示蜂鸣器打开,0表示蜂鸣器关闭。
如果为SND_TONE,则可以通过改变input_event.value的值来调整蜂鸣器的频率,从而发出各种不同的音调。
六、驱动程序分析:
该驱动程序位于:drivers/input/misc/pwm-beeper.c
[yangni@yangni linux-3.0.54]$ vim drivers/input/misc/pwm-beeper.c
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
#include <linux/module.h>
-
#include <linux/kernel.h>
-
#include <linux/platform_device.h>
-
-
-
-
-
-
-
-
#define HZ_TO_NANOSECONDS(x) (1000000000UL/(x))
-
-
static int pwm_beeper_event(struct input_dev *input,
-
unsigned int type, unsigned int code, int value)
-
-
-
struct pwm_beeper *beeper = input_get_drvdata(input);
-
-
if (type != EV_SND || value < 0)
-
-
-
-
value = value ? 1000 : 0;
-
-
-
-
-
-
-
-
pwm_config(beeper->pwm, 0, 0);
-
pwm_disable(beeper->pwm);
-
-
period = HZ_TO_NANOSECONDS(value);
-
ret = pwm_config(beeper->pwm, period / 2, period);
-
-
-
ret = pwm_enable(beeper->pwm);
-
-
-
-
-
-
-
static int __devinit pwm_beeper_probe(struct platform_device *pdev)
-
-
unsigned long pwm_id = (unsigned long)pdev->dev.platform_data;
-
struct pwm_beeper *beeper;
-
-
-
beeper = kzalloc(sizeof(*beeper), GFP_KERNEL);
-
-
-
beeper->pwm = pwm_request(pwm_id, "pwm beeper");
-
if (IS_ERR(beeper->pwm)) {
-
error = PTR_ERR(beeper->pwm);
-
dev_err(&pdev->dev, "Failed to request pwm device: %d\n", error);
-
-
-
beeper->input = input_allocate_device();
-
-
dev_err(&pdev->dev, "Failed to allocate input device\n");
-
-
-
-
beeper->input->dev.parent = &pdev->dev;
-
beeper->input->name = "pwm-beeper";
-
beeper->input->phys = "pwm/input0";
-
beeper->input->id.bustype = BUS_HOST;
-
beeper->input->id.vendor = 0x001f;
-
beeper->input->id.product = 0x0001;
-
beeper->input->id.version = 0x0100;
-
beeper->input->evbit[0] = BIT(EV_SND);
-
-
beeper->input->sndbit[0] = BIT(SND_TONE) | BIT(SND_BELL);
-
beeper->input->event = pwm_beeper_event;
-
input_set_drvdata(beeper->input, beeper);
-
error = input_register_device(beeper->input);
-
-
-
dev_err(&pdev->dev, "Failed to register input device: %d\n", error);
-
-
-
platform_set_drvdata(pdev, beeper);
-
-
err_input_free: input_free_device(beeper->input);
-
err_pwm_free: pwm_free(beeper->pwm);
-
err_free: kfree(beeper); return error;
-
-
-
static int __devexit pwm_beeper_remove(struct platform_device *pdev)
-
-
struct pwm_beeper *beeper = platform_get_drvdata(pdev);
-
platform_set_drvdata(pdev, NULL);
-
input_unregister_device(beeper->input);
-
pwm_disable(beeper->pwm);
-
-
-
-
-
-
static int pwm_beeper_suspend(struct device *dev)
-
-
struct pwm_beeper *beeper = dev_get_drvdata(dev);
-
-
pwm_disable(beeper->pwm);
-
-
-
static int pwm_beeper_resume(struct device *dev)
-
-
struct pwm_beeper *beeper = dev_get_drvdata(dev);
-
-
{ pwm_config(beeper->pwm, beeper->period / 2, beeper->period);
-
-
-
-
-
static SIMPLE_DEV_PM_OPS(pwm_beeper_pm_ops, pwm_beeper_suspend, pwm_beeper_resume);
-
#define PWM_BEEPER_PM_OPS (&pwm_beeper_pm_ops)
-
-
#define PWM_BEEPER_PM_OPS NULL
-
-
-
static struct platform_driver pwm_beeper_driver = {
-
.probe = pwm_beeper_probe,
-
.remove = __devexit_p(pwm_beeper_remove),
-
.driver = { .name = "pwm-beeper",
-
-
-
-
-
-
static int __init pwm_beeper_init(void)
-
-
return platform_driver_register(&pwm_beeper_driver);
-
-
module_init(pwm_beeper_init);
-
static void __exit pwm_beeper_exit(void)
-
-
platform_driver_unregister(&pwm_beeper_driver);
-
-
module_exit(pwm_beeper_exit);
-
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
-
MODULE_DESCRIPTION("PWM beeper driver");
-
-
MODULE_ALIAS("platform:pwm-beeper");
内核使能的是驱动,而设备是我们自己添加的。该驱动程序工作流程与之前的类似:
1、创建平台驱动 static struct platform_driver pwm_beeper_driver()
平台驱动的成员 name要与上面我们在mach-smdk2440.c添加的设备机构体成员name名字相同,方便用于匹配。
2、注册平台驱动
pwm_beeper_init(void)中的函数platform_driver_register()函数用于驱动注册。
3、调用pwm_beeper_probe()函数
设备与驱动匹配后,会调用pwm_beeper_probe()函数。该驱动中probe函数主要是用于申请pwm设备以及给输入子系统赋值(指定事件处理函数)。
beeper->input->evbit[0] = BIT(EV_SND);
-
beeper->input->sndbit[0] = BIT(SND_TONE) | BIT(SND_BELL);
-
PWM蜂鸣器的事件类型为EV_SND,声音的类型为SND_TONE或SND_BELL
-
这两项内容就是我们在应用程序中要用到的input_event中的type和code
4、事件处理函数:
-
static int pwm_beeper_event(struct input_dev *input,
-
unsigned int type, unsigned int code, int value)
-
-
-
struct pwm_beeper *beeper = input_get_drvdata(input);
-
-
if (type != EV_SND || value < 0)
-
-
-
-
value = value ? 1000 : 0;
-
-
-
-
-
-
-
-
pwm_config(beeper->pwm, 0, 0);
-
pwm_disable(beeper->pwm);
-
-
period = HZ_TO_NANOSECONDS(value);
-
ret = pwm_config(beeper->pwm, period / 2, period);
-
-
-
ret = pwm_enable(beeper->pwm);
-
-
-
-
-
-
这里是应用程序调用的关键。首先通过判断事件类型event是不是EV_SND,不是则退出。
如果是event是EV_SND类型,则接着判断声音类型code的值:
- 如果code为SND_BELL,不管input_event.value为多少,最终的value只能为1000或0,即不能改变蜂鸣器的频率。0表示关闭。
- 如果code为SND_TONE,则可以通过改变input_event.value的值来调整蜂鸣器的频率,从而发出各种不同的音调。
5、卸载平台驱动
pwm_beeper_exit(void)中的函数platform_driver_unregister函数用于驱动退出。
6、输入子系统简介:
Linux输入子系统包括三个层次:事件处理层(Event Handler)、核心层(Input Core)和驱动层(Input Driver)。
1.事件处理层负责与用户程序打交道,将核心层传来的事件报告给用户程序。
2.核心层是链接其他两个层之间的纽带与桥梁,向下提供驱动层的接口,向上提供事件处理层的接口。
3.驱动层负责操作具体的硬件设备,这层的代码是针对具体的驱动程序的,键盘、鼠标、触摸屏等字符设备驱动功能的实现工作主要就在这层。
输入子系统有三个核心结构体:input_dev,input_handle和input_handler。input_dev表示一个输入设备,包含输入设备的一些相关信息;input_handler表示对输入事件的具体处理,它为输入设备的功能实现了一个接口;input_handle是用来连接输入设备和输入事件。输入子系统主要的任务就是把这三个结构体连接在一起。
总结:
该驱动新的知识点主要涉及申请pwm设备以及对事件处理函数的理解,还有就是输入子系统的使用,输入子系统详细使用可参考博客:
一、关于PWM:
PWM(Pulse Width Modulation)——脉宽调制,它是利用微控制器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用于测量、通信、功率控制与变换等许多领域。
通过S3C2440底板原理图可知蜂鸣器接2440的GPB0/TOUT0.即我们的蜂鸣器是通过GPB0 IO口使用PWM信号驱动工作的,而且GPB0口是一个复用的IO口,要使用它得把他设置成TOUT0 PWM输出模式。
蜂鸣器可以发声,靠的不仅仅是蜂鸣器硬件的驱动,还有Linux下的PWM(脉冲宽度调制)这种变频技术,靠改变脉冲宽度来控制输出电压,通过改变周期来控制其输出频率。通过改变频率可以使蜂鸣器发出不同的声音。
二、修改设备树
-
-
compatible = "pwm-beeper";
-
pwms = <&pwm 0 1000000 0>;
-
pinctrl-names = "default";
-
-
四、修改配置文件:
[yangni@yangni linux-3.0.54]$make menuconfig
[ * ] PWM device support
DeviceDrivers --->
Input device support --->
[* ] Miscellaneous devices --->
< * > PWM beeper support
配置完成以后就可以make了,然后将重新编译好的内核下载到开发板上,开发板启动的时候会听到“滴”的一声响。并且,在开发板会打印出如下相关的信息:
五、测试程序:
首先得查找出事件编号。因为后来移植过按键驱动,所以按键驱动被分配为input0了,导致后来再运行打开不了,所以测试程序写的时候,open的时候要open对。
cat proc/bus/input/devices
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
int main(int argc, char *argv[])
-
-
-
-
struct input_event event;
-
if ((fd = open("/dev/event1", O_RDWR)) < 0) {
-
-
-
-
-
-
if(!strcmp (argv[1],"1"))
-
-
-
-
-
ret = write(fd, &event, sizeof(struct input_event));
-
-
-
ret = write(fd, &event, sizeof(struct input_event));
-
-
-
else if(!strcmp (argv[1],"0"))
-
-
-
ret = write(fd, &event, sizeof(struct input_event));
-
-
-
-
从应用程序中得到的input_event.code只能为SND_BELL或SND_TONE,否则退出。
如果为SND_BELL,不管input_event.value为多少,最终的value只能为1000或0,即不能改变蜂鸣器的频率。1000表示蜂鸣器打开,0表示蜂鸣器关闭。
如果为SND_TONE,则可以通过改变input_event.value的值来调整蜂鸣器的频率,从而发出各种不同的音调。
六、驱动程序分析:
该驱动程序位于:drivers/input/misc/pwm-beeper.c
[yangni@yangni linux-3.0.54]$ vim drivers/input/misc/pwm-beeper.c
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
#include <linux/module.h>
-
#include <linux/kernel.h>
-
#include <linux/platform_device.h>
-
-
-
-
-
-
-
-
#define HZ_TO_NANOSECONDS(x) (1000000000UL/(x))
-
-
static int pwm_beeper_event(struct input_dev *input,
-
unsigned int type, unsigned int code, int value)
-
-
-
struct pwm_beeper *beeper = input_get_drvdata(input);
-
-
if (type != EV_SND || value < 0)
-
-
-
-
value = value ? 1000 : 0;
-
-
-
-
-
-
-
-
pwm_config(beeper->pwm, 0, 0);
-
pwm_disable(beeper->pwm);
-
-
period = HZ_TO_NANOSECONDS(value);
-
ret = pwm_config(beeper->pwm, period / 2, period);
-
-
-
ret = pwm_enable(beeper->pwm);
-
-
-
-
-
-
-
static int __devinit pwm_beeper_probe(struct platform_device *pdev)
-
-
unsigned long pwm_id = (unsigned long)pdev->dev.platform_data;
-
struct pwm_beeper *beeper;
-
-
-
beeper = kzalloc(sizeof(*beeper), GFP_KERNEL);
-
-
-
beeper->pwm = pwm_request(pwm_id, "pwm beeper");
-
if (IS_ERR(beeper->pwm)) {
-
error = PTR_ERR(beeper->pwm);
-
dev_err(&pdev->dev, "Failed to request pwm device: %d\n", error);
-
-
-
beeper->input = input_allocate_device();
-
-
dev_err(&pdev->dev, "Failed to allocate input device\n");
-
-
-
-
beeper->input->dev.parent = &pdev->dev;
-
beeper->input->name = "pwm-beeper";
-
beeper->input->phys = "pwm/input0";
-
beeper->input->id.bustype = BUS_HOST;
-
beeper->input->id.vendor = 0x001f;
-
beeper->input->id.product = 0x0001;
-
beeper->input->id.version = 0x0100;
-
beeper->input->evbit[0] = BIT(EV_SND);
-
-
beeper->input->sndbit[0] = BIT(SND_TONE) | BIT(SND_BELL);
-
beeper->input->event = pwm_beeper_event;
-
input_set_drvdata(beeper->input, beeper);
-
error = input_register_device(beeper->input);
-
-
-
dev_err(&pdev->dev, "Failed to register input device: %d\n", error);
-
-
-
platform_set_drvdata(pdev, beeper);
-
-
err_input_free: input_free_device(beeper->input);
-
err_pwm_free: pwm_free(beeper->pwm);
-
err_free: kfree(beeper); return error;
-
-
-
static int __devexit pwm_beeper_remove(struct platform_device *pdev)
-
-
struct pwm_beeper *beeper = platform_get_drvdata(pdev);
-
platform_set_drvdata(pdev, NULL);
-
input_unregister_device(beeper->input);
-
pwm_disable(beeper->pwm);
-
-
-
-
-
-
static int pwm_beeper_suspend(struct device *dev)
-
-
struct pwm_beeper *beeper = dev_get_drvdata(dev);
-
-
pwm_disable(beeper->pwm);
-
-
-
static int pwm_beeper_resume(struct device *dev)
-
-
struct pwm_beeper *beeper = dev_get_drvdata(dev);
-
-
{ pwm_config(beeper->pwm, beeper->period / 2, beeper->period);
-
-
-
-
-
static SIMPLE_DEV_PM_OPS(pwm_beeper_pm_ops, pwm_beeper_suspend, pwm_beeper_resume);
-
#define PWM_BEEPER_PM_OPS (&pwm_beeper_pm_ops)
-
-
#define PWM_BEEPER_PM_OPS NULL
-
-
-
static struct platform_driver pwm_beeper_driver = {
-
.probe = pwm_beeper_probe,
-
.remove = __devexit_p(pwm_beeper_remove),
-
.driver = { .name = "pwm-beeper",
-
-
-
-
-
-
static int __init pwm_beeper_init(void)
-
-
return platform_driver_register(&pwm_beeper_driver);
-
-
module_init(pwm_beeper_init);
-
static void __exit pwm_beeper_exit(void)
-
-
platform_driver_unregister(&pwm_beeper_driver);
-
-
module_exit(pwm_beeper_exit);
-
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
-
MODULE_DESCRIPTION("PWM beeper driver");
-
-
MODULE_ALIAS("platform:pwm-beeper");
内核使能的是驱动,而设备是我们自己添加的。该驱动程序工作流程与之前的类似:
1、创建平台驱动 static struct platform_driver pwm_beeper_driver()
平台驱动的成员 name要与上面我们在mach-smdk2440.c添加的设备机构体成员name名字相同,方便用于匹配。
2、注册平台驱动
pwm_beeper_init(void)中的函数platform_driver_register()函数用于驱动注册。
3、调用pwm_beeper_probe()函数
设备与驱动匹配后,会调用pwm_beeper_probe()函数。该驱动中probe函数主要是用于申请pwm设备以及给输入子系统赋值(指定事件处理函数)。
beeper->input->evbit[0] = BIT(EV_SND);
-
beeper->input->sndbit[0] = BIT(SND_TONE) | BIT(SND_BELL);
-
PWM蜂鸣器的事件类型为EV_SND,声音的类型为SND_TONE或SND_BELL
-
这两项内容就是我们在应用程序中要用到的input_event中的type和code
4、事件处理函数:
-
static int pwm_beeper_event(struct input_dev *input,
-
unsigned int type, unsigned int code, int value)
-
-
-
struct pwm_beeper *beeper = input_get_drvdata(input);
-
-
if (type != EV_SND || value < 0)
-
-
-
-
value = value ? 1000 : 0;
-
-
-
-
-
-
-
-
pwm_config(beeper->pwm, 0, 0);
-
pwm_disable(beeper->pwm);
-
-
period = HZ_TO_NANOSECONDS(value);
-
ret = pwm_config(beeper->pwm, period / 2, period);
-
-
-
ret = pwm_enable(beeper->pwm);
-
-
-
-
-
-
这里是应用程序调用的关键。首先通过判断事件类型event是不是EV_SND,不是则退出。
如果是event是EV_SND类型,则接着判断声音类型code的值:
- 如果code为SND_BELL,不管input_event.value为多少,最终的value只能为1000或0,即不能改变蜂鸣器的频率。0表示关闭。
- 如果code为SND_TONE,则可以通过改变input_event.value的值来调整蜂鸣器的频率,从而发出各种不同的音调。
5、卸载平台驱动
pwm_beeper_exit(void)中的函数platform_driver_unregister函数用于驱动退出。
6、输入子系统简介:
Linux输入子系统包括三个层次:事件处理层(Event Handler)、核心层(Input Core)和驱动层(Input Driver)。
1.事件处理层负责与用户程序打交道,将核心层传来的事件报告给用户程序。
2.核心层是链接其他两个层之间的纽带与桥梁,向下提供驱动层的接口,向上提供事件处理层的接口。
3.驱动层负责操作具体的硬件设备,这层的代码是针对具体的驱动程序的,键盘、鼠标、触摸屏等字符设备驱动功能的实现工作主要就在这层。
输入子系统有三个核心结构体:input_dev,input_handle和input_handler。input_dev表示一个输入设备,包含输入设备的一些相关信息;input_handler表示对输入事件的具体处理,它为输入设备的功能实现了一个接口;input_handle是用来连接输入设备和输入事件。输入子系统主要的任务就是把这三个结构体连接在一起。
总结:
该驱动新的知识点主要涉及申请pwm设备以及对事件处理函数的理解,还有就是输入子系统的使用,输入子系统详细使用可参考博客: