Linux misc设备(二)蜂鸣器驱动

Linux misc设备驱动

Linux misc设备(一)misc驱动框架

Linux misc设备(二)蜂鸣器驱动

Linux misc设备(二)蜂鸣器驱动


本文将介绍如何基于misc框架写一个蜂鸣器驱动程序

一、注册misc设备

首先先基于misc驱动框架注册一个misc设备

static int buzzer_open(struct inode *inode, struct file *file)
{
    return 0;
}


static int buzzer_close(struct inode *inode, struct file *file)
{
	return 0;
}

static int buzzer_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
    return 0;
}


static struct file_operations buzzer_fops = {
    .owner   =   THIS_MODULE,
    .open    =   buzzer_open,
    .release =   buzzer_close, 
    .ioctl   =   buzzer_ioctl,
};

static struct miscdevice buzzer_dev = {
	.minor  = MISC_DYNAMIC_MINOR, //动态分配次设备号
	.name   = "buzzer",
	.fops   = &buzzer_fops, //文件操作集
};

static int __init buzzer_init(void)
{
    /* 注册杂项设备 */
	misc_register(&buzzer_dev);

	return 0;
}

static void __exit buzzer_exit(void)
{
    /* 注销杂项设备 */
	misc_deregister(&buzzer_dev);
}

module_init(buzzer_init);
module_exit(buzzer_exit);
MODULE_LICENSE("GPL");

编译该驱动程序后,加载模块,就会生成/dev/buzzer设备节点

我们要实现这个驱动程序的功能是可以通过ioctl来控制蜂鸣器的频率

那么就需要在buzzer_ioctl里去操作硬件,下面来讲一讲如何去操控蜂鸣器

二、硬件操作

此驱动程序的芯片是三星S5PV210,硬件平台是X210

首先打开原理图,找到蜂鸣器

在这里插入图片描述

从原理图中可以看到蜂鸣器接在PWMTOUT2引脚处,表明该引脚可以用于PWM输出

只要该引脚为高电平,蜂鸣器就会响,那么通过PWM就可以控制蜂鸣器的频率

再看一看PWMTOUT2接到芯片的哪一个引脚

在这里插入图片描述

该GPIO为GPD0_2

要使该GPIO输出PWM,第一步就是设置该GPIO的功能,第二步就是设置PWM相关的寄存器

我们先看第一部分,打开datasheet,查找该GPIO相关的寄存器

2.1 GPIO设置

在这里插入图片描述

GPD0CON为GPD0组的GPIO的功能配置寄存器,其地址为0xE020_00A0,其中GPD0CON[2]表示GPD0_2的配置

配置中的TOUT_2表示PWM输出功能,那么要配置GPD0_2为PWM输出功能,可以这样做

GPD0CON |= 0x2<<4;

下面继续来介绍PWM相关的设置

从datasheet中可以得知,S5PV210有5个PWM定时器,其中定时器0、1、2、3有PWM输出引脚,定时器4没有PWM输出引脚

从我们上述的原理图可以看出,我们使用的是PWM定时器2,其对应输出引脚为GPD0_2

2.2 PWM定时器的时钟设置

这些定时器的时钟源为APB-PCLK,定时器0和1共享一个8位分频器,定时器2、3、4共享一个8位分频器,每个定时器还有二级的时钟分频器,如下图所示

在这里插入图片描述

也就是说,PWM的定时器的时钟源是APB-PCLK经过1级8位分频器分频,再经过2级复用器分频得来

查看相应的寄存器

在这里插入图片描述

在这里插入图片描述

其中TCFG0用于设置1级分频器倍数,TCFG1用于设置2级复用器

PWM定时器的频率可以这样计算

Frequency = PCLK / (一级分频 + 1) / (二级分频)

如果要设置PWM定时器2的频率,可以这样做

TCFG0 |= prescaler<<8;
TCFG1 |= mux<<8;

2.3 PWM定时器设置

每个PWM定时器都有一个32位的向下计数器

需要设置计数周期,PWM电平触发时间,然后手动更新计数周期(使其加载到定时器的计数器中)

需要设置自动装载,在每次定时器减到0后,会自动装载计数周期

在这里插入图片描述

图中所示,计数周期为159(50+109),PWM电平触发时间时间为109,表示每次加载到定时器的计数器的起始值为159,当计数器减到109时,触发电平装换

查看datasheet中描述寄存器

TCNTBn为计数周期的缓存寄存器

在这里插入图片描述

TCMPBn为比较寄存器,描述PWM电平转换时刻

在这里插入图片描述

TCON控制PWM定时器,设置自动重装载,手动更新计数周期,启动定时器

在这里插入图片描述

设置PWM定时器的步骤

  • 设置计数周期缓存寄存器TCNTBn
  • 设置比较器TCMPBn
  • 通过置位TCON中的手动更新TCMPBn的位,来加载TCNBn的值到PWM定时器的计数器中
  • 通过置位TCON中相应的位来开启自动重装载
  • 复位TCON中的手动更新TCMPBn的位
  • 通过设置TCON寄存器打开定时器

总结一下:pwm的设置总共三部分,第一设置GPIO功能,第二设置PWM定时器的时钟源,第三设置PWM定时器

三、源码

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/clk.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <plat/gpio-cfg.h>

#define TCFG0   0xE2500000
#define TCFG1   0xE2500004
#define TCON    0xE2500008
#define TCNTB2  0xE2500024
#define TCMPB2  0xE2500028

#define PWM_IOCTL_SET_FREQ		1
#define PWM_IOCTL_STOP			0

static volatile unsigned int *tcfg0;
static volatile unsigned int *tcfg1;
static volatile unsigned int *tcon;
static volatile unsigned int *tcntb2;
static volatile unsigned int *tcmpb2;

static void pwm_set_freq( unsigned long freq)
{
    unsigned long cfg;
	struct clk *clk_p;
	unsigned long pclk;

	/* 设置GPD0_2为PWM输出 */
	s3c_gpio_cfgpin(S5PV210_GPD0(2), S3C_GPIO_SFN(2));

	clk_p = clk_get(NULL, "pclk");
	pclk  = clk_get_rate(clk_p);

    /* 计算周期 */
	cfg  = (pclk/16/16)/freq;
    writel(cfg, tcntb2);
    writel(cfg/2, tcmpb2); //占空比50%

    /* 设置并启动定时器 */
    cfg = readl(tcon);
	cfg &= ~(0xf<<12);
	cfg |= (0xb<<12); //取消死区,使能自动装载,置位手动更新,开启定时器
	writel(cfg, tcon);

    /* 复位手动更新位 */
	cfg &= ~(2<<12);
	writel(cfg, tcon);
}

void pwm_stop( void )
{
	s3c_gpio_cfgpin(S5PV210_GPD0(2), S3C_GPIO_INPUT);
}

static int buzzer_open(struct inode *inode, struct file *file)
{
    return 0;
}


static int buzzer_close(struct inode *inode, struct file *file)
{
	return 0;
}

static int buzzer_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
	switch (cmd) 
	{
		case PWM_IOCTL_SET_FREQ:
            pwm_set_freq(arg);
			break;

		case PWM_IOCTL_STOP:
		default:
            pwm_stop();
			break;
	}

	return 0;
}


static struct file_operations buzzer_fops = {
    .owner   =   THIS_MODULE,
    .open    =   buzzer_open,
    .release =   buzzer_close, 
    .ioctl   =   buzzer_ioctl,
};

static struct miscdevice buzzer_dev = {
	.minor  = MISC_DYNAMIC_MINOR,
	.name   = "buzzer",
	.fops   = &buzzer_fops,
};

static int __init buzzer_init(void)
{
	int ret;
    unsigned long cfg;

    /* 注册杂项设备 */
	ret = misc_register(&buzzer_dev);
	
	/* 申请GPIO */
	ret = gpio_request(S5PV210_GPD0(2), "GPD0");

    /* 设置GPIO为输入,不让蜂鸣器响 */
	s3c_gpio_cfgpin(S5PV210_GPD0(2), S3C_GPIO_INPUT);

    /* 映射地址 */
    tcfg0  = ioremap(TCFG0, 4);
    tcfg1  = ioremap(TCFG1, 4);
    tcon   = ioremap(TCON, 4);
    tcntb2 = ioremap(TCNTB2, 4);
    tcmpb2 = ioremap(TCMPB2, 4);

    /* 配置定时器时钟 */
    /* prescaler1+1 */
    cfg = readl(tcfg0);
    cfg &= ~(0xFF<<8);
    cfg |= (0x0F<<8);
    writel(cfg, tcfg0);

    /* MUX1 */
    cfg = readl(tcfg1);
    cfg &= ~(0xF<<8);
    cfg |= (0x4<<8);
    writel(cfg, tcfg1);

	return ret;
}

static void __exit buzzer_exit(void)
{
    /* 注销杂项设备 */
	misc_deregister(&buzzer_dev);

    /* 取消地址映射 */
    iounmap(tcfg0);
    iounmap(tcfg1);
    iounmap(tcon);
    iounmap(tcntb2);
    iounmap(tcmpb2);
}

module_init(buzzer_init);
module_exit(buzzer_exit);
MODULE_LICENSE("GPL");

四、测试

编译驱动程序,加载模块,会生成/dev/buzzer设备节点

测试应用程序

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>


#define DEVNAME         "/dev/buzzer"

#define PWM_IOCTL_SET_FREQ              1
#define PWM_IOCTL_STOP                  0


int main(void)
{
        int fd = -1;

        fd = open(DEVNAME, O_RDWR);
        if (fd < 0)
        {
                perror("open");
                return -1;
        }

        ioctl(fd, PWM_IOCTL_SET_FREQ, 10000);
        sleep(1);
        ioctl(fd, PWM_IOCTL_STOP);
        sleep(1);
        ioctl(fd, PWM_IOCTL_SET_FREQ, 3000);
        sleep(1);
        ioctl(fd, PWM_IOCTL_STOP);
        sleep(1);


        close(fd);

        return 0;
}

运行测试程序,可以听到不同频率的蜂鸣器响声

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值