Linux 字符设备驱动开发基础(二)—— 编写简单 PWM 设备驱动

       编写驱动的第一步仍是看原理图:

Linux 字符设备驱动开发基础(二)—— 编写简单 PWM 设备驱动_第1张图片

       可以看到,该蜂鸣器由 GPD0_0 来控制 ,查手册可知该I/O口由Time0 来控制,找到相应的寄存器:

a -- I/O口寄存器及地址

      GPD0CON  0x114000a0

b -- Time0 寄存器及地址

      基地址为:TIMER_BASE 0x139D0000 

      这些物理寄存器地址都是相邻的,我们这里用偏移量来表示:

      寄存器名      地址偏移量            所需配置

        TCFG0          0x0000              [7-0]     0XFF

        TCFG1          0x0004              [3-0]     0X2              

        TCON            0x0008              [3-0]     0X2       0X9   0X0

        TCNTB0        0x000C             500

        TCMPB0       0x0010              250


       前面已经知道,驱动是无法直接操纵物理地址的,所以这里仍需物理地址向虚拟地址的转换,用到 ioremap() 函数、writel()函数、readl()函数:

1、地址映射操作

unsigned int   *gpd0con;
void *timer_base;<span style="white-space:pre">	</span>//之所以是void类型,偏移量为4时,只是移动4个字节,方便理解

gpd0con = ioremap(GPD0CON,4);
timer_base = ioremap(TIMER_BASE , 0x14);

2、Time0初始化操作(这里使用的已经是虚拟地址)

       这里现将数据从寄存器中读出,修改后再写回寄存器,具体寄存器操作可以移步Exynos4412裸机开发——PWM定时器:   

writel((readl(gpd0con)&~(0xf<<0)) | (0x2<<0),gpd0con);
writel ((readl(timer_base +TCFG0  )&~(0xff<<0)) | (0xff <<0),timer_base +TCFG0); 
writel ((readl(timer_base +TCFG1 )&~(0xf<<0)) | (0x2 <<0),timer_base +TCFG1 ); 

3、装载数据,配置占空比

writel(500, timer_base +TCNTB0  );
writel(250, timer_base +TCMPB0 );
writel ((readl(timer_base +TCON )&~(0xf<<0)) | (0x2 <<0),timer_base +TCON ); 

4、相关控制函数

void beep_on(void)
{
	writel ((readl(timer_base +TCON )&~(0xf<<0)) | (0x9 <<0),timer_base +TCON );
}
 
void beep_off(void)
{
	writel ((readl(timer_base +TCON )&~(0xf<<0)) | (0x0 <<0),timer_base +TCON );
} 

下面是驱动程序,这里我们用到了 write() read() ioctl() 函数,具体解析移步:

驱动程序:beep.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm/uaccess.h>

static int major = 250;
static int minor=0;
static dev_t devno;
static struct class *cls;
static struct device *test_device;

#define GPD0CON       0x114000a0
#define TIMER_BASE    0x139D0000           
#define TCFG0         0x0000               
#define TCFG1         0x0004                            
#define TCON          0x0008             
#define TCNTB0        0x000C          
#define TCMPB0        0x0010           

static unsigned int *gpd0con;
static void *timer_base;
#define  MAGIC_NUMBER    'k'
#define  BEEP_ON    _IO(MAGIC_NUMBER    ,0)
#define  BEEP_OFF   _IO(MAGIC_NUMBER    ,1)
#define  BEEP_FREQ   _IO(MAGIC_NUMBER   ,2)

static void fs4412_beep_init(void)
{
	gpd0con = ioremap(GPD0CON,4);
	timer_base = ioremap(TIMER_BASE,0x14);
	
	writel ((readl(gpd0con)&~(0xf<<0)) | (0x2<<0),gpd0con);
	writel ((readl(timer_base +TCFG0  )&~(0xff<<0)) | (0xff <<0),timer_base +TCFG0); 
	writel ((readl(timer_base +TCFG1 )&~(0xf<<0)) | (0x2 <<0),timer_base +TCFG1 ); 

	writel (500, timer_base +TCNTB0  );
	writel (250, timer_base +TCMPB0 );
	writel ((readl(timer_base +TCON )&~(0xf<<0)) | (0x2 <<0),timer_base +TCON ); 
}

void fs4412_beep_on(void)
{
	writel ((readl(timer_base +TCON )&~(0xf<<0)) | (0x9 <<0),timer_base +TCON );
}

void fs4412_beep_off(void)
{
	writel ((readl(timer_base +TCON )&~(0xf<<0)) | (0x0 <<0),timer_base +TCON );
}


static int beep_open (struct inode *inode, struct file *filep)
{
 //	fs4412_beep_on();
	return 0;
}

static int beep_release(struct inode *inode, struct file *filep)
{
	 fs4412_beep_off();
	 return 0;
}

#define BEPP_IN_FREQ 100000
static void beep_freq(unsigned long arg)
{
	writel(BEPP_IN_FREQ/arg, timer_base +TCNTB0  );
	writel(BEPP_IN_FREQ/(2*arg), timer_base +TCMPB0 );

}

static long beep_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
	switch(cmd)
	{
		case BEEP_ON:
			fs4412_beep_on();
			break;
		case BEEP_OFF:
			fs4412_beep_off();
			break;
		case BEEP_FREQ:
			beep_freq( arg );
			break;
		default :
			return -EINVAL;
	}
}

static struct file_operations beep_ops=
{
	.open     = beep_open,
	.release = beep_release,
	.unlocked_ioctl      = beep_ioctl,
};

static int beep_init(void)
{
	int ret;	
	devno = MKDEV(major,minor);
	ret = register_chrdev(major,"beep",&beep_ops);

	cls = class_create(THIS_MODULE, "myclass");
	if(IS_ERR(cls))
	{
		unregister_chrdev(major,"beep");
		return -EBUSY;
	}
	test_device = device_create(cls,NULL,devno,NULL,"beep");//mknod /dev/hello
	if(IS_ERR(test_device))
	{
		class_destroy(cls);
		unregister_chrdev(major,"beep");
		return -EBUSY;
	}	
	fs4412_beep_init();
	return 0;
}

void fs4412_beep_unmap(void)
{
	iounmap(gpd0con);
	iounmap(timer_base);
}

static void beep_exit(void)
{
	fs4412_beep_unmap();

	device_destroy(cls,devno);
	class_destroy(cls);	
	unregister_chrdev(major,"beep");
	printk("beep_exit \n");
}

MODULE_LICENSE("GPL");
module_init(beep_init);
module_exit(beep_exit);

makefile:

ifneq  ($(KERNELRELEASE),)
obj-m:=beep.o
$(info "2nd")
else
#KDIR := /lib/modules/$(shell uname -r)/build
KDIR := /home/fs/linux/linux-3.14-fs4412
PWD:=$(shell pwd)
all:
	$(info "1st")
	make -C $(KDIR) M=$(PWD) modules
	arm-none-linux-gnueabi-gcc test.c -o beeptest
	sudo cp beep.ko beeptest /tftpboot
clean:
	rm -f *.ko *.o *.symvers *.mod.c *.mod.o *.order
endif

下面是是个简单的测试程序test.c,仅实现蜂鸣器响6秒的功能:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>

#define  MAGIC_NUMBER    'k'
#define   BEEP_ON    _IO(MAGIC_NUMBER    ,0)
#define   BEEP_OFF   _IO(MAGIC_NUMBER    ,1)
#define   BEEP_FREQ   _IO(MAGIC_NUMBER    ,2)

main()
{
	int fd;

	fd = open("/dev/beep",O_RDWR);
	if(fd<0)
	{
		perror("open fail \n");
		return ;
	}

	ioctl(fd,BEEP_ON);

	sleep(6);
	ioctl(fd,BEEP_OFF);	

	close(fd);
}

这是个音乐播放测试程序,慎听!!分别为《大长今》、《世上只有妈妈好》、《渔船》,这个单独编译一下

/*
 * main.c : test demo driver
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include "pwm_music.h"

/*ioctl 鍛戒护*/
#define magic_number 'k'
#define BEEP_ON _IO(magic_number,0)
#define BEEP_OFF _IO(magic_number,1)
#define SET_FRE _IO(magic_number,2)



int main(void)
{
	int i = 0;
	int n = 2;
	int dev_fd;
	int div;
	dev_fd = open("/dev/beep",O_RDWR | O_NONBLOCK);
	if ( dev_fd == -1 ) {
		perror("open");
		exit(1);
	}

	for(i = 0;i<sizeof(GreatlyLongNow)/sizeof(Note);i++ )
	{
		div = (GreatlyLongNow[i].pitch);
	
		ioctl(dev_fd, SET_FRE, div);
		ioctl(dev_fd, BEEP_ON);
		usleep(GreatlyLongNow[i].dimation * 100); 
		ioctl(dev_fd, BEEP_OFF);
	}
	
	for(i = 0;i<sizeof(MumIsTheBestInTheWorld)/sizeof(Note);i++ )
	{
		div = (MumIsTheBestInTheWorld[i].pitch);
		ioctl(dev_fd, SET_FRE, div);
		ioctl(dev_fd, BEEP_ON);
		
		usleep(MumIsTheBestInTheWorld[i].dimation * 100); 
		ioctl(dev_fd, BEEP_OFF);
	}



	for(i = 0;i<sizeof(FishBoat)/sizeof(Note);i++ )
	{
		div = (FishBoat[i].pitch);
		ioctl(dev_fd, SET_FRE, div);
		ioctl(dev_fd, BEEP_ON);
		usleep(FishBoat[i].dimation * 100); 
		ioctl(dev_fd, BEEP_OFF);
	}
	return 0;
}
附所用头文件:

#ifndef __PWM_MUSIC_H
#define __PWM_MUSIC_H

#define BIG_D

#define PCLK (202800000/4)

typedef struct
{
	int pitch; 
	int dimation;
}Note;
// 1			2		3		 4 			5	  		6       7
// C 			D	 	E	 	 F	 	 	G	 		A	 	B
//261.6256	293.6648   329.6276 349.2282   391.9954		440		493.8833

//C澶ц皟
#ifdef BIG_C
#define DO 262
#define RE 294
#define MI 330
#define FA 349
#define SOL 392
#define LA  440
#define SI  494
#define TIME 6000
#endif
 
 //D澶ц皟
#ifdef BIG_D
#define DO 293
#define RE 330
#define MI 370
#define FA 349
#define SOL 440
#define LA  494
#define SI  554
#define TIME 6000
#endif



Note MumIsTheBestInTheWorld[]={
	//6.	  		  //_5		 //3		 //5	
	{LA,TIME+TIME/2}, {SOL,TIME/2},{MI,TIME},{SOL,TIME},

	//1^		   //6_		  //_5		//6-
	{DO*2,TIME},{LA,TIME/2},{SOL,TIME/2} ,{LA,2*TIME},
	// 3      //5_		//_6		   //5
	{MI,TIME},{SOL,TIME/2},{LA,TIME/2},{SOL,TIME},
	// 3	 	//1_		//_6,
	{MI,TIME},{DO,TIME/2},{LA/2,TIME/2},
	//5_		//_3		//2-		   //2.
	{SOL,TIME/2},{MI,TIME/2},{RE,TIME*2},{RE,TIME+TIME/2},
	//_3 	//5			//5_			//_6
	{MI,TIME/2},{SOL,TIME},{SOL,TIME/2},{LA,TIME/2},
	// 3		//2			//1-			//5.
	{MI,TIME},{RE,TIME},{DO,TIME*2},{SOL,TIME+TIME/2},
	//_3		//2_		//_1 		//6,_
	{MI,TIME/2},{RE,TIME/2},{DO,TIME/2},{LA/2,TIME/2},
	//_1		//5,--
	{DO,TIME/2},{SOL/2,TIME*3}

};


Note GreatlyLongNow[]={  	
	// 2		3			3		3.				_2				1
	{RE,TIME}, {MI,TIME},{MI,TIME},{MI,TIME+TIME/2},{RE,TIME/2},{DO,TIME},
	//6,		1			2		1--				2			3			3
	{LA/2,TIME},{DO,TIME},{RE,TIME},{DO,TIME*3},{RE,TIME},{MI,TIME},{MI,TIME},
	//3.				_5			3			3			2			3
	{MI,TIME+TIME/2},{SOL,TIME/2},{MI,TIME},{MI,TIME},{RE,TIME},{MI,TIME},
	//3--		5			6			6		  6.				_5
	{MI,TIME*3},{SOL,TIME},{LA,TIME},{LA,TIME},{LA,TIME+TIME/2},{SOL,TIME/2},
	// 3		3		5				6		5---			2			3
	{MI,TIME},{MI,TIME},{SOL,TIME},{LA,TIME},{SOL,TIME*3},{RE,TIME},{MI,TIME},
	// 3		2.				_3				3		  2			3
	{MI,TIME},{RE,TIME+TIME/2},{MI,TIME/2},{MI,TIME},{RE,TIME},{MI,TIME},
	//6,		1_			  _6,			  6,-
	{LA/2,TIME},{DO,TIME/2},{LA/2,TIME/2},{LA/2,TIME*2},
	//2_		_2			2_				_1			6,
	{RE,TIME/2},{RE,TIME/2},{RE,TIME/2},{DO,TIME/2},{LA/2,TIME},
	//2_		_2			2_				_1	  		6,
	{RE,TIME/2},{RE,TIME/2},{RE,TIME/2},{DO,TIME/2},{LA/2,TIME},
	// 2		3		1			2.					_3			5
	{RE,TIME},{MI,TIME},{DO,TIME},{RE,TIME+TIME/2},{MI,TIME/2},{SOL,TIME},
	//6_		_6				6_			_5			3
	{LA,TIME/2},{LA,TIME/2},{LA,TIME/2},{SOL,TIME/2},{MI,TIME},
	//2_		_2			2_				_1			6,
	{RE,TIME/2},{RE,TIME/2},{RE,TIME/2},{DO,TIME/2},{LA/2,TIME},
	//6,		5,.					  _6,			 6,--
	{LA/2,TIME},{SOL/2,TIME+TIME/2},{LA/2,TIME/2},{LA/2,TIME*3},
	//2_		_2			2_				_1			6,
	{RE,TIME/2},{RE,TIME/2},{RE,TIME/2},{DO,TIME/2},{LA/2,TIME},
	//2_		_2			2_				_1	  		6,
	{RE,TIME/2},{RE,TIME/2},{RE,TIME/2},{DO,TIME/2},{LA/2,TIME},
	// 2		3		1			2.					_3			5
	{RE,TIME},{MI,TIME},{DO,TIME},{RE,TIME+TIME/2},{MI,TIME/2},{SOL,TIME},
	//6_		_6				6_			_5			3
	{LA,TIME/2},{LA,TIME/2},{LA,TIME/2},{SOL,TIME/2},{MI,TIME},
	//2_		_2			2_				_1			6,
	{RE,TIME/2},{RE,TIME/2},{RE,TIME/2},{DO,TIME/2},{LA/2,TIME},
	//6,		5,.					  _6,			 6,--
	{LA/2,TIME},{SOL/2,TIME+TIME/2},{LA/2,TIME/2},{LA/2,TIME*3}

};
Note FishBoat[]={ //3.				_5			6._					=1^			 6_
	{MI,TIME+TIME/2},{SOL,TIME/2},{LA,TIME/2+TIME/4},{DO*2,TIME/4},{LA,TIME/2},
	//_5			3 -.		2		  1.			 _3			 2._
	{SOL,TIME/2},{MI,TIME*3},{RE,TIME},{DO,TIME+TIME/2},{MI,TIME/2},{RE,TIME/2+TIME/4},
	//=3			2_			_1		 2--			3.				_5
	{MI,TIME/4},{RE,TIME/2},{DO,TIME/2},{RE,TIME*4},{MI,TIME+TIME/2},{SOL,TIME/2},
	// 2		1		6._					=1^			 	6_			_5
	{RE,TIME},{DO,TIME},{LA,TIME/2+TIME/4},{DO*2,TIME/4},{LA,TIME/2},{SOL,TIME/2},
	//6-		 5,.					_6,			1._					=3
	{LA,TIME*2},{SOL/2,TIME+TIME/2},{LA/2,TIME/2},{DO,TIME/2+TIME/4},{MI,TIME/4},
	//2_			_1		 5,--
	{RE,TIME/2},{DO,TIME/2},{SOL/2,TIME*4},
	//3.				_5			6._					=1^			6_
	{MI,TIME+TIME/2},{SOL,TIME/2},{LA,TIME/2+TIME/4},{DO*2,TIME/4},{LA,TIME/2},
	//_5			3-.			5_			_6			1^_				   _6
	{SOL,TIME/2},{MI,TIME*3},{SOL,TIME/2},{LA,TIME/2},{DO*2,TIME+TIME/2},{LA,TIME/2},
	//5._					=6			5_		  _3			2--
	{SOL,TIME/2+TIME/4},{LA,TIME/4},{SOL,TIME/2},{MI,TIME/2},{RE,TIME*4},
	//3.				_5			2._					=3			2_			_1
	{MI,TIME+TIME/2},{SOL,TIME/2},{RE,TIME/2+TIME/4},{MI,TIME/4},{RE,TIME/2},{DO,TIME/2},
	//6._				=1^				6_			_5			6-			1.
	{LA,TIME/2+TIME/4},{DO*2,TIME/4},{LA,TIME/2},{SOL,TIME/2},{LA,TIME*2},{DO,TIME+TIME/2},
	//_2	  	 3_			_5			    2_			_3			1--
	{RE,TIME/2},{MI,TIME/2},{SOL,TIME/2},{RE,TIME/2},{MI,TIME/2},{DO,TIME*4}
};
#endif


编译好程序后

# insmod beep.ko

#mknod /dev/beep c 250 0

#./music

便会听到悦耳的音乐了!




你可能感兴趣的:(linux,驱动开发,字符设备,PWM驱动)