Linux内核驱动之PWM蜂鸣器

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/CSDNmianfeixiazai001/article/details/79949275

本次PWM内核驱动主要设计思路:

    (1)硬件控制(相关寄存器读写、IO映射)、模块函数(加载、卸载、ioctl)分开

    (2)相关声明都放在头文件中

以下是beep_drv.c代码,里面存放的都是模块函数

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

#include <linux/fs.h>
#include <linux/kdev_t.h> //设备号相关操作头文件
#include <linux/device.h> //class相关头文件



#include "fs4412_beep.h"
#include "beep_drv.h"


//定义beep蜂鸣器主次设备号
#define 	BEEP_MAJOR		251 //主设备号
#define 	BEEP_MINOR		0 //次设备号


//定义操作命令,这里注意命令只是和用户调用程序协定的,方便大家在应用层和底层保持一致
#define		MAGIC_NUM	'k'		//设备类型
#define		BEEP_ON		_IO(MAGIC_NUM, 0) //打开蜂鸣器指令
#define		BEEP_OFF	_IO(MAGIC_NUM, 1) //关闭蜂鸣器指令
#define		BEEP_FREQ	_IO(MAGIC_NUM, 2) //设置蜂鸣器频率指令


//定义beep相关属性对象
struct beep_cdev my_beep;


//定义beep操作方法集
static long beep_ioctl (struct file *filep, unsigned int cmd, unsigned long arg);
static int beep_open(struct inode *inode, struct file *filep);
static int beep_release(struct inode *inode, struct file *filep);
static ssize_t beep_read(struct file *, char __user *, size_t, loff_t *);

static struct file_operations beep_ops={
	.open = beep_open,
	.read = beep_read,
	.release = beep_release,
	
	.unlocked_ioctl = beep_ioctl,

};

//beep的ioctl控制函数
static long beep_ioctl (struct file *filep, unsigned int cmd, unsigned long arg)
{
	switch(cmd)
	{
		case BEEP_ON:
			fs4412_beep_open();
			break;
		case BEEP_OFF:
			fs4412_beep_close();
			break;
		case BEEP_FREQ:
			fs4412_beep_freq(arg);
			break;
		default:
			fs4412_beep_close();
			break;
	}

	return 0;//返回结果
}

//beep蜂鸣器打开函数
static int beep_open(struct inode *inode, struct file *filep)
{
	//返回文件操作操作符
	printk("beep open success!\n");
	return 0;
}

//beep蜂鸣器打开函数
static ssize_t beep_read (struct file *filep, char __user *buf, size_t size, loff_t * off_set)
{
	//返回文件操作操作符
	return 0;
}


//beep蜂鸣器注销函数
static int beep_release(struct inode *inode, struct file *filep)
{
	fs4412_beep_close();
	return 0;
}


static int __init beep_drv_init(void)
{
	int ret;

	printk("beep init!\n");


	//1、申请设备号和cdev对象
	my_beep.dev_num = MKDEV(BEEP_MAJOR, BEEP_MINOR); //组设备号

	//2、注册设备号和cdev初始化
	ret = register_chrdev(BEEP_MAJOR, "beep number", &beep_ops);
	if(ret) //注册失败
	{
		printk(KERN_ERR"register_chrdev err!\n");
		goto reg_err;
	}

	//3、创建设备节点
	my_beep.cls = class_create(THIS_MODULE, "beep class");
	if(IS_ERR(my_beep.cls)) //分配失败
	{
		printk(KERN_ERR"class create err!\n");
		goto class_create_err;
	}

	my_beep.test_device = device_create(my_beep.cls, NULL, my_beep.dev_num, NULL, "fs4412_beep");
	if(IS_ERR(my_beep.test_device))//device创建失败
	{
		printk(KERN_ERR"device create err!\n");
		goto device_create_err;
	}

	//4、beep蜂鸣器硬件初始化
	fs4412_beep_init();
	return 0;

device_create_err:
	//销毁class
	class_destroy(my_beep.cls);
class_create_err:
	//注销设备号和销毁cdev
	unregister_chrdev(my_beep.dev_num, "beep number");
	return -EINVAL;
reg_err:
	return ret;
}

static void __exit beep_drv_exit(void)
{
	//1、解除映射
	fs4412_beep_unmap();

	//2、销毁节点
	device_destroy(my_beep.cls, my_beep.dev_num);
	class_destroy(my_beep.cls);

	//3、注销设备号和回收cdev
	unregister_chrdev(BEEP_MAJOR, "beep number"	);

	printk("beep exit!\n");
}



module_init(beep_drv_init);
module_exit(beep_drv_exit);
MODULE_LICENSE("GPL");

以下是beep_drv.h头文件:

#include <linux/kdev_t.h>
#include <linux/fs.h>

#define	 	BEEP_IN_FREQ	100000//蜂鸣器定时器的频率


struct beep_cdev{
	dev_t dev_num;
	struct class *cls; //用来在udev自动创建节点号的时候使用
	struct device *test_device; //用来创建设备节点

	unsigned int  *gpd0con;//用来存放关于引脚相关的寄存器虚拟地址,这里直接指定类型是因为就一个寄存器长度肯定是4字节
	void *timer_base;//指向定时器相关寄存器的第一个寄存器基地址
}; 

extern struct beep_cdev my_beep;


然后是fs4412_beep.c关于寄存器和IO的内容:

#include <asm/io.h>
#include "beep_drv.h"

#define 	TIMER_REGISTER_CNT	5
#define 	REGISTER_SIZE		4

//寄存器基地址
#define 	GPD0CON		0x114000a0 //引脚GPD0的控制寄存器
#define 	TIMER_BASE	0x139D0000 //控制PWM的定时器基地址
//寄存器地址相对偏移
#define 	TCFG0       0x0000	//寄存器TCFG0针对TIMER_BASE的偏移量,该寄存器用来设置定时器                 
#define 	TCFG1       0x0004  //寄存器TCFG1针对TIMER_BASE的偏移量                     
#define 	TCON        0x0008  //寄存器TCON针对TIMER_BASE的偏移量 ,该寄存器用来控制定时器             
#define 	TCNTB0      0x000C  //寄存器TCNTB0针对TIMER_BASE的偏移量 ,该寄存器用来控制周期          
#define 	TCMPB0      0x0010  //寄存器TCMPB0针对TIMER_BASE的偏移量,该寄存器用来控制占空比


/*
蜂鸣器初始化函数:fs4412_beep_init
*/
void fs4412_beep_init(void)
{
	//1、IO虚拟地址映射
	my_beep.gpd0con = ioremap(GPD0CON, 4);
	my_beep.timer_base = ioremap(TIMER_BASE, TIMER_REGISTER_CNT*REGISTER_SIZE);

	//2、配置IO引脚GPD0_0引脚
	writel((readl(my_beep.gpd0con) & ~(0xf<<0))|(2<<0), my_beep.gpd0con);

	//3、设置一级分频(249)和二级分频(4)
	writel((readl(my_beep.timer_base + TCFG0) & ~(0xff<<0)) | (249<<0), my_beep.timer_base + TCFG0); //一级分频249
	writel((readl(my_beep.timer_base + TCFG1) & ~(0xf<<0)) | (2<<0), my_beep.timer_base + TCFG1);//二级分频4

	//4、设置周期和占空比
	writel(100, my_beep.timer_base + TCNTB0);//周期为100
	writel(50, my_beep.timer_base + TCMPB0);//占空比为50%

	//5、打开手动
	writel((readl(my_beep.timer_base + TCON) | (1<<1)), my_beep.timer_base + TCON);//手动装载周期和占空比

	//6、关闭手动开启自动装载
	writel((readl(my_beep.timer_base + TCON) & ~(1<<1)) | (1<<3), my_beep.timer_base + TCON);

	//7、默认关闭定时器
	writel(readl(my_beep.timer_base + TCON) & ~(1<<0), my_beep.timer_base + TCON);
}


//蜂鸣器开启接口
void fs4412_beep_open(void)
{
	printk("beep_open!\n");
	//打开定时器
	writel(readl(my_beep.timer_base + TCON) | (1<<0), my_beep.timer_base + TCON);
}

//关闭蜂鸣器
void fs4412_beep_close(void)
{
	printk("beep_close!\n");

	//关闭定时器
	writel(readl(my_beep.timer_base + TCON) & ~(1<<0), my_beep.timer_base + TCON);
}

//关闭蜂鸣器
void fs4412_beep_change(void)
{
	//关闭定时器
	writel(readl(my_beep.timer_base + TCON) ^ (1<<0), my_beep.timer_base + TCON);
}


//设置蜂鸣器频率
void fs4412_beep_freq(unsigned long arg)
{
	//蜂鸣器设置周期,占空比默认是50%	
	writel(BEEP_IN_FREQ/arg, my_beep.timer_base + TCNTB0);//设置周期
	writel(BEEP_IN_FREQ/(arg*2), my_beep.timer_base + TCMPB0); //50%占空比
}


//解除IO映射
void fs4412_beep_unmap(void)
{
	//解除IO引脚寄存器
	iounmap(my_beep.gpd0con);

	//解除定时器寄存器映射
	iounmap(my_beep.timer_base);
}

fs4412_beep.h头文件代码如下:


void fs4412_beep_init(void);
void fs4412_beep_open(void);
void fs4412_beep_close(void);
void fs4412_beep_freq(unsigned long arg);
void fs4412_beep_unmap(void);
void fs4412_beep_change(void);

最后是Makefile的内容:

APP_NAME = app_beep
APP_OBJ = app_beep.c
MODULE_NAME = beep

PWD = $(shell pwd)
CC =  arm-none-linux-gnueabi-gcc

APP_INSTALL_DIR = /source/rootfs/my_modules
MODULE_INSTALL_DIR = /source/rootfs/my_modules

EXTRA_CFLAGS += -I$(src)/include

ifeq ($(KERNELRELEASE),)
KER_DIR = /home/linux/kernel/linux-3.14-fs4412

modules:
	make -C $(KER_DIR) M=$(PWD) modules
	$(CC) app_beep.c -o $(APP_NAME)

clean:
	make -C $(KER_DIR) M=$(PWD) clean
	rm -rf *.ko  $(APP_NAME)  *.o  *.mod.*  modules.* Module.*	
	rm 	$(APP_INSTALL_DIR)/$(APP_NAME) 
	rm  $(MODULE_INSTALL_DIR)/$(MODULE_NAME).ko

install:
	cp $(MODULE_NAME).ko  $(MODULE_INSTALL_DIR)
	cp $(APP_NAME) $(APP_INSTALL_DIR)
   
else
	obj-m += $(MODULE_NAME).o
	$(MODULE_NAME)-y :=  beep_drv.o fs4412_beep.o

endif

根据以上代码编写的简单测试代码:

#include <stdio.h>
#include <fcntl.h> //和文件操作相关的
#include <sys/ioctl.h>//用来组件命令字的


//组件操作beep的,命令字,这里一定要和驱动中的命令字一致
#define	MAGIC_NUM		'k'
#define BEEP_ON			_IO(MAGIC_NUM, 0)
#define BEEP_OFF		_IO(MAGIC_NUM, 1)
#define BEEP_FREQ		_IO(MAGIC_NUM, 2)
int main(int argc, const char *argv[])
{
	int fd;

	//1、打开beep
	fd = open("/dev/fs4412_beep", O_RDWR);
	if(fd<0)
	{
		perror("open fail!\n");
		return 2;
	}

	//2、打开蜂鸣器
	ioctl(fd, BEEP_ON);
	sleep(6);

	//3、关闭蜂鸣器
	ioctl(fd, BEEP_OFF);
	sleep(6);
	
	
	//关闭beep
	close(fd);

	return 0;
}

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页