上次,我们写过一个蜂鸣器叫的程序,但是那个程序仅仅只是驱动蜂鸣器,用电平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. 有两个序列a,b,大小都为n,序列元素的值是任意整数,无序。

    要求:通过交换a,b中的元素,使[序列a元素的和]与[序列b元素的和]之间的差最小. 例如: var a=[100,99,98,1,2, 3]; var b=[1, 2, 3, 4,5,40]. in ...

  2. 第一行代码阅读笔记---详解分析第一个Android程序

    以下是我根据作者的思路,创建的第一个Android应用程序,由于工具强大,代码都自动生成了,如下: package com.example.first_app; import android.os.B ...

  3. JQuery之DOM操作及常用函数

    属性操作 attr(name)获取属性值 var imgSrc = $("img").attr("src") attr(name,value)设置属性值 $(& ...

  4. Dynamics CRM2015 2015版本可用的OData Query Designer工具

    2015后很多工具无法使用,包括2011版的OData Query Designer,这里介绍一款可用的工具,Dynamics XRM Tools for CRM 2015,下载地址:https:// ...

  5. JQuery EasyUI combobox(下拉列表框)

     下拉列表框 继承 $.fn.combo.defaults. 重写 $.fn.combobox.defaults. 组合框显示一个可编辑的文本框和下拉列表,用户选择一个或多个值.用户可以直接输入文 ...

  6. 03 RadioButton 单选按钮

    >概念:从多个互斥选项中选择一个  如果是选项全部展开  RadioButton  不是展开的Spinner(下拉列表)     >属性: android:checked="tr ...

  7. UNIX环境高级编程——线程属性

    pthread_attr_t 的缺省属性值 属性 值 结果 scope PTHREAD_SCOPE_PROCESS 新线程与进程中的其他线程发生竞争. detachstate PTHREAD_CREA ...

  8. OpenCL异构计算资料收集

    Easy OpenCL with Python   原文  http://www.drdobbs.com/open-source/easy-opencl-with-python/240162614   ...

  9. Chipmunk僵尸物理对象的出现和解决(八)

    如何解决? 等到碰撞方法返回后在调用Star类方法.碰撞方法在物理引擎的一帧内应该会处理完成,在下一帧里碰撞回调已经结束.所以我们将Star类方法的调用放到下一帧里执行即可,代码如下: //... @ ...

  10. Touch Handling in Cocos2D 3.x(二)

    接受触摸 在Cocos2d 3.0中每一个CCNode和每一个CCNode的子类都可以接收触摸.你只需要开启一个选项.让我们在定制的初始化器里完成它.替换MainScene.m中init方法的代码: ...