上次,我们写过一个蜂鸣器叫的程序,但是那个程序仅仅只是驱动蜂鸣器,用电平1和0来驱动而已,跟驱动LED其实没什么两样。我们先来回顾一下蜂鸣器的硬件还有相关的寄存器吧:

还是和以前一样的步骤:

1、看电路图

(1)蜂鸣器接口位于电路板的底板,看电路图可知道是高电平有效。

(2)相对应的找到核心板的接口。由此可知,我们的蜂鸣器是GPD0_0

接下来找数据手册,找到对应的寄存器,然后配置它就可以了。

2、查数据手册,找到相关的寄存器,并配置

(1)找到GPD0CON,地址是0x114000A0,我们需要配置GPD0CON(0)为输出状态。也就是写0x1这个值到这个寄存器。

但是本节是PWM,我们就要配置成PWM模式,要选择第二位也就是0x2。

(2)找到GPD0DAT这个寄存器,用于配置蜂鸣器的高低电平,物理地址是0x114000A4,刚好与上一个差4个字节的偏移

我们只要对这个寄存器写1和写0,那么蜂鸣器就可以叫起来了,哈哈。是不是很简单?

以上就是我们之前说的蜂鸣器的电路图还有数据手册查找:

接下来我们来看看本节关于PWM的,首先先找到PWM的说明文档,先读读它是什么意思先:

大致的意思可以翻译成:

Exynos 4412单片机有五位脉冲宽度调制(PWM)定时器。这些定时器产生内部中断臂子系统。此外,定时器0,1,2,和3包括一个驱动一个外部输入/输出的脉宽调制函数信号。在定时器0中的脉宽调制有一个可选死区发电机的能力,以支持一个大电流设备。定时器4是一个没有输出引脚的内部定时器。定时器的使用apb-pclk作为时钟源。定时器0和1共用一个8位预分频器,可编程提供了分裂的第一级PCLK时钟。定时器2,3,4股不同的8分频器。每一个定时器它自己的时钟分频器,提供了一个二级时钟分频(分频器分为2,4,8,或16)。每一个定时器都有它的32位计数器,定时器时钟驱动器计数器。定时器计数缓冲寄存器(TCNTBn)的计数器加载初始值。如果计数器达到零,它会产生计时器中断请求通知中央处理器,定时器操作完成。如果计时器计数器达到零,相应的TCNTBn寄存器值自动重新载入到向下计数器开始下一个循环。然而,如果

例如,定时器停止,通过清除启用定时器定时器运行期间TCNTBn不加载到计数器。PWM功能用途的TCMPBn寄存器的值。定时器控制逻辑改变输出电平下计数器值与定时器控制逻辑中比较寄存器的值相匹配。因此,比较寄存器决定了时间或关断时间的脉宽调制输出。每个定时器是双缓冲结构,TCNTBn和TCMPBn寄存器允许定时器参数

在一个周期中更新。新的值不起作用,直到当前的计时器周期完成。

说了那么多,其实我们可以提取和我们要写的这个驱动程序的关键信息出来:

1、预分频

2、固定分频

3、pwm控制寄存器

4、还有一些跟定期器相关的

数据手册还告诉了我们如何来使用PWM的步骤,我们来看看:

从英文描述可以知道:

第一步:初始化TCNTBn寄存器159(50 + 109)还有TCMPBn 某个寄存器设置成109。

第二步:启动定时器:设置开始位和手动更新这个位的关闭。

第三步:将TCNTBn值159加载到向下计数器,然后输出toutn设置为低电平。

第四步:如果计数器计数下降值从TCNTBn在TCMPBn寄存器109的值,输出从低到高的变化。

第五步:如果向下计数器达到0,则会产生一个中断请求。

第六步:向下计数器自动重载TCNTBn。这是重新启动周期。

知道了其中的关键信息,我们就可以来看数据手册,看看怎么来配置这些相应的寄存器了。

以下是4个pwm输出口

看看具体的寄存器:

这个就是配置预分频.

0~7位是配置TIMER0/TIMER1

8~15位是配置TIMER2/TIMER3/TIMER4

根据要使用的定时器进行配置

以下这个是配置固定分频:

0~3:TIMER0

4~7:   TIMER1

8~11: TIMER2

12~15:TIMER3

16~19:TIMER4

根据要使用的定时器进行配置

以下这个是配置pwm控制寄存器:

第0位:开始/停止定时器0

第1位:更新TCNTB0和TCMTB0这两个计数器的值

第2位:TOUT_0 输出极性

第3位:定时器0自动装载    开或者关

第4位:死区发生器开关

对以上几位进行配置相应的参数就可以了:

以下这个是配置定时计数器:

TCNTB0:0-31:计数缓存buffer:递减数值

TCMPB0:0-31:定时器0比较buffer寄存器

TCNTO0:0-31:定时器计数观察

还有就是配置相应的定时器中断:

0~4:定时器0/1/2/3/4  中断状态/清中断

5~9:使能定时器0/1/2/3/4 中断

好了,接下来上代码,我们来看看驱动程序:

#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 <plat/gpio-cfg.h>

//定义设备的名字为 pwm
#define DEVICE_NAME				"pwm"

#define PWM_IOCTL_SET_FREQ		1
#define PWM_IOCTL_STOP			0

#define NS_IN_1HZ				(1000000000UL)

//蜂鸣器PWM_ID  0
#define BUZZER_PWM_ID			0
//蜂鸣器GPIO配置
#define BUZZER_PMW_GPIO			EXYNOS4_GPD0(0)

//定义一个结构体指针
static struct pwm_device *pwm4buzzer;
//定义一个结构体信号量指针,因为信号量与锁的机制差不多
//Mutex是一把钥匙,一个人拿了就可进入一个房间,出来的时候把钥匙交给队列的第一个。一般的用法是用于串行化对critical section代码的访问,保证这段代码不会被并行的运行。
//Semaphore是一件可以容纳N人的房间,如果人不满就可以进去,如果人满了,就要等待有人出来。对于N=1的情况,称为binary semaphore。一般的用法是,用于限//制对于某一资源的同时访问。
static struct semaphore lock;

//设置频率
static void pwm_set_freq(unsigned long freq) {
//PWM的占空比的配置
	int period_ns = NS_IN_1HZ / freq;

	pwm_config(pwm4buzzer, period_ns / 2, period_ns);
	pwm_enable(pwm4buzzer);
	//配置相应的GPIO,将蜂鸣器IO配置成PWM输出模式
	s3c_gpio_cfgpin(BUZZER_PMW_GPIO, S3C_GPIO_SFN(2));
}
//stop方法函数,来源于operations结构体
static  void pwm_stop(void) {
	s3c_gpio_cfgpin(BUZZER_PMW_GPIO, S3C_GPIO_OUTPUT);

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

//open方法函数,来源于operations结构体,主要打开pwm的操作
static int tiny4412_pwm_open(struct inode *inode, struct file *file) {
	if (!down_trylock(&lock)) //尝试加锁,如果失败返回0
		return 0;
	else
		return -EBUSY;
}

//close方法函数,来源于operations结构体,主要是关闭pwm操作
static int tiny4412_pwm_close(struct inode *inode, struct file *file) {
	up(&lock);
	return 0;
}
//控制io口方法函数,来源于operations结构体,其实就是上层系统调用传入一条命令,//驱动识别命令,然后执行相应过程。
static long tiny4412_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:
		default:
			pwm_stop();
			break;
	}

	return 0;
}

//这就是我们要看的结构体了,其实这个结构体的定义在另一个.h当中,看看它的初始//化方式,跟我们上面那个程序的分析基本上是一样的。对应的函数名(也就是函数的//首地址)赋值给对应的结构体成员,实现了整个结构体的初始化,这样的方法类似于//C++和JAVA等高级语言的操作。
static  struct file_operations tiny4412_pwm_ops = {
	.owner			= THIS_MODULE,  			//表示本模块拥有
	.open			= tiny4412_pwm_open,		//表示调用open函数
	.release		= tiny4412_pwm_close,         //…
	.unlocked_ioctl	= tiny4412_pwm_ioctl,
};

//杂类设备的注册
static struct miscdevice tiny4412_misc_dev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEVICE_NAME,
	.fops = &tiny4412_pwm_ops,
};
//pwm设备初始化,设备在被insmod插入模块到内核的过程中会调用这个函数
static int __init tiny4412_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);

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

	pwm_stop();

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

	printk(DEVICE_NAME "\tinitialized\n");

	return ret;
}
//设备在被卸载rmmod的过程中会调用这个函数
static void __exit tiny4412_pwm_dev_exit(void) {
	pwm_stop();

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

//模块初始化
module_init(tiny4412_pwm_dev_init);
//销毁模块
module_exit(tiny4412_pwm_dev_exit);
//声明GPL协议
MODULE_LICENSE("GPL");
//作者:yangyuanxin
MODULE_AUTHOR("Yangyuanxin");
//描述:三星PWM设备
MODULE_DESCRIPTION("Exynos4 PWM Driver");

好了,驱动程序咱们已经写完了,接着写Makefile进行编译(此过程略),然后插入模块等步骤略过。

接下里写一个测试程序:

#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_OPEN		1
//定义蜂鸣器关闭的操作
#define PWM_STOP			0
static void close_bell(void);
static void open_bell(void);
static void set_bell_freq(int freq);
static void stop_bell(void);
int main(int argc, char **argv)
{
	//设置蜂鸣器的频率的初始值为1000
	int freq = 1000 ;
	//打开蜂鸣器
	open_bell();
	stop_bell();
	while( 1 )
	{
		while(1){
		//设置蜂鸣器
		set_bell_freq(freq);
		//如果频率小于20000
		if(freq < 20000){
			//自加
			freq+=100 ;
			printf( "\tFreq = %d\n", freq );
			//跳出到另外一个循环
			if(freq == 20000){
				break ;
			}
		}
		}

		while(1){
			//设置蜂鸣器
			set_bell_freq(freq);
			//如果频率大于1000
			if(freq > 1000)
				//自减
				freq-=100 ;
			printf( "\tFreq = %d\n", freq );
			if(freq == 1000){
				break ;
			}
		}
		//周而复始的执行,于是蜂鸣器就会像唱歌一样
	}
}

static int fd = -1;
//打开蜂鸣器
static void open_bell(void)
{
	//打开设备
	fd = open("/dev/pwm", 0);
	//如果打开小于0表示失败
	if (fd < 0) {
		perror("open pwm_buzzer device");
		exit(1);
	}

	//初始化蜂鸣器的时候先关闭,不让它叫
	atexit(close_bell);
}

//关闭峰鸣器
static void close_bell(void)
{
	if (fd >= 0) {
		//关闭蜂鸣器
		ioctl(fd, PWM_STOP);
		if (ioctl(fd, 2) < 0) {
			perror("ioctl 2:");
		}
		close(fd);
		fd = -1;
	}
}
//设置蜂鸣器的频率
static void set_bell_freq(int freq)
{
	//设置频率
	int ret = ioctl(fd, PWM_OPEN, freq);
	if(ret < 0) {
		perror("set the frequency of the buzzer");
		exit(1);
	}
}
//停止蜂鸣器
static void stop_bell(void)
{
	//让蜂鸣器停止叫
	int ret = ioctl(fd, PWM_STOP);
	if(ret < 0) {
		perror("stop the buzzer");
		exit(1);
	}
	if (ioctl(fd, 2) < 0) {
		perror("ioctl 2:");
	}
}

编写完成之后进行编译后,执行程序会看到以下现象,然后蜂鸣器像唱歌一样开始叫:

基于ARM-contexA9-蜂鸣器pwm驱动开发的更多相关文章

  1. Openwrt:基于MT7628/MT7688的PWM驱动

    前言 MT7628/MT7688的PWM驱动相关资料较少,官方的datasheet基本也是一堆寄存器,啃了许久,终于嚼出了味道.由于PWM存在IO口复用的问题,所以要提前配置好GPIO的工作方式,不然 ...

  2. 基于友善之臂ARM-ContexA9-ADC驱动开发

    ADC,就是模数转换器,什么是模数转换器? 模数转换器,在电子技术中即是将模拟信号转换成数字信号,也称为数字量化. 当然还有一种叫DAC,就是数模转换,意思相反,即是将数字信号转换成模拟信号. 在友善 ...

  3. linux驱动开发—基于Device tree机制的驱动编写

    前言Device Tree是一种用来描述硬件的数据结构,类似板级描述语言,起源于OpenFirmware(OF).在目前广泛使用的Linux kernel 2.6.x版本中,对于不同平台.不同硬件,往 ...

  4. Android深度探索--HAL与驱动开发----第八章读书笔记

    通过蜂鸣器的实现原理,实现一个完整的蜂呜器驱动,可以打开和关闭蜂鸣器. PWM驱动的实现方式不同于LED驱动, PWM 驱动将由多个文件组成.这也是大多数 Linux 驱动的标准实现方式. 刚开始是L ...

  5. HarmonyOS USB DDK助你轻松实现USB驱动开发

    HDF(Hardware Driver Foundation)驱动框架是HarmonyOS硬件生态开放的基础,为开发者提供了驱动加载.驱动服务管理和驱动消息机制等驱动能力,让开发者能精准且高效地开发驱 ...

  6. ARM开发(2)基于STM32的蜂鸣器

    基于STM32的蜂鸣器 一 蜂鸣器原理:  1.1 本实验实现1个蜂鸣器间隔1S鸣叫.  1.2 实验思路:根据电路图原理,给蜂鸣器相关引脚赋予高低电平,实现电路的导通,使蜂鸣器实现鸣叫或不鸣.  1 ...

  7. 基于ARM-contexA9蜂鸣器驱动开发

    上次,我们写了一个LED的驱动程序,这一节,我们只需稍微改动一下就可以实现蜂鸣器的驱动,让我们来看看吧. 还是跟之前一样,先找电路图,找到电路板上对应的引脚和相关联的寄存器. 1.看电路图 (1)蜂鸣 ...

  8. 【转】基于V4L2的视频驱动开发

    编写基于V4L2视频驱动主要涉及到以下几个知识点:1> 摄像头方面的知识 要了解选用的摄像头的特性,包括访问控制方法.各种参数的配置方法.信号输出类型等.2> Camera解码器.控制器 ...

  9. 基于ARM-contexA9按键驱动开发

    之前我们写过LED和蜂鸣器的驱动,其实那两个都是一个模版的,因为都是将IO口配置成输出模式,然后用高低电平来驱动这些设备.其实linux设备驱动,说白了跟单片机开发的方式是差不多的,只不过内核的开发基 ...

随机推荐

  1. Linux Debugging (九) 一次生产环境下的“内存泄露”

    一个偶然的机会,发现一个进程使用了超过14G的内存.这个进程是一个RPC server,只是作为中转,绝对不应该使用这么多内存的.即使并发量太多,存在内存中的数据太多,那么在并发减少的情况下,这个内存 ...

  2. FORM中调用JAVA组件

    调用方式: 链接:可以在一个数据块中创建专门的 Bean Area项,使用 Implementation Class 特性链接到J a v a B e a n,使用W h e n - C u s t ...

  3. Python Skelve 库

    在Python中有一个简单的轻量级的类似于Key-value的存储型数据库,那就是Skelve.下面就来一起看一看这个库的简单的使用吧. 小例子 我本人比较喜欢从例子出发,然后再来研究这些内部的行为. ...

  4. OpenCV相机标定

    标签(空格分隔): Opencv 相机标定是图像处理的基础,虽然相机使用的是小孔成像模型,但是由于小孔的透光非常有限,所以需要使用透镜聚焦足够多的光线.在使用的过程中,需要知道相机的焦距.成像中心以及 ...

  5. Dynamics CRM 后台通过组织服务获取时间字段值的准确转换

    做CRM开发的都知道,在系统时间字段的处理上是有讲究的,因为数据库中存的是UTC时间,CRM的界面时间字段会根据个人设置中的时区以及格式自动调整,这是最基本的一面,那还有很多使用时间的场景,比如脚本使 ...

  6. 最简单的基于librtmp的示例:发布H.264(H.264通过RTMP发布)

    ===================================================== 最简单的基于libRTMP的示例系列文章列表: 最简单的基于librtmp的示例:接收(RT ...

  7. SpriteBuilder中使用GUI界面快速搭建RPG游戏中的地图名显示动画

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 在RPG游戏中我们在进入一个新的场景时,比如一个房间,一个村庄, ...

  8. HashMap方法介绍

    1. Map的遍历方式 (1) for each map.entrySet() Map<String, String> map = new HashMap<String, Strin ...

  9. OpenCV3.0 3.1版本的改进

     摘要        OpenCV现在更新到了3.1版本,相对OpenCV2有了很大改进,其中对于硬件加速,移动开发(IOS,android)的支持成为亮点.      新版的OpenCV采用了内 ...

  10. 利用openssl管理证书及SSL编程第1部分: openssl证书管理

    利用openssl管理证书及SSL编程第1部分 参考:1) 利用openssl创建一个简单的CAhttp://www.cppblog.com/flyonok/archive/2010/10/30/13 ...