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

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

还是和以前一样的步骤:

1、看电路图生气

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

基于ARM-contexA9-蜂鸣器pwm驱动开发_第1张图片

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


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

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

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

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

 基于ARM-contexA9-蜂鸣器pwm驱动开发_第2张图片

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

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

基于ARM-contexA9-蜂鸣器pwm驱动开发_第3张图片

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

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

基于ARM-contexA9-蜂鸣器pwm驱动开发_第4张图片

大致的意思可以翻译成:

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的步骤,我们来看看:

基于ARM-contexA9-蜂鸣器pwm驱动开发_第5张图片

从英文描述可以知道:

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

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

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

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

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

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


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

以下是4个pwm输出口

基于ARM-contexA9-蜂鸣器pwm驱动开发_第6张图片

看看具体的寄存器:

这个就是配置预分频.

0~7位是配置TIMER0/TIMER1

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

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

基于ARM-contexA9-蜂鸣器pwm驱动开发_第7张图片

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

0~3:TIMER0

4~7:   TIMER1

8~11: TIMER2 

12~15:TIMER3
16~19:TIMER4

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

基于ARM-contexA9-蜂鸣器pwm驱动开发_第8张图片

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

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

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

第2位:TOUT_0 输出极性

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

第4位:死区发生器开关

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

基于ARM-contexA9-蜂鸣器pwm驱动开发_第9张图片

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

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

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

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

基于ARM-contexA9-蜂鸣器pwm驱动开发_第10张图片

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

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

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

基于ARM-contexA9-蜂鸣器pwm驱动开发_第11张图片

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

#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驱动开发_第12张图片




你可能感兴趣的:(基于ARM-contexA9-蜂鸣器pwm驱动开发)