Zynq TTC蜂鸣器驱动开发

目的:在Zynq 7030平台开发ttc pwm驱动程序,以驱动蜂鸣器鸣叫

硬件平台:Zynq 7030

软件平台:xilinx linux2018.2版本(源码linux-xlnx-xilinx-v2018.2)

开发工具:vivado、SDK、Ubuntu

蜂鸣器:无源压电式

驱动开发方法:linux杂项设备驱动

 

Zynq 7030并没有集成pwm控制器,因此无法实现用pwm驱动蜂鸣器工作。但Zynq有两个三路定时器TTC,可以利用TTC输出pwm波,因此可以利用TTC来实现pwm蜂鸣器。

xilinx的维基官网有TTC输出方波的裸机例程代码,可供参考,见如下链接https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18841648/TTC+Standalone+Driver,该链接下有“TTC low level example”例程,并有“This example uses triple timer counter in polled mode to generate square wave output on waveform out pin.”的描述信息,由此可知该例程可以输出方波。 

Zynq TTC蜂鸣器驱动开发_第1张图片

下面详细叙述开发过程。

一、查阅原理图和数据手册

(1)原理图分析

打开原理图,搜索Buzzer可看到如下信息:

Zynq TTC蜂鸣器驱动开发_第2张图片

Buzzer连接在IO_L19P_T3_12管脚,因此需要将pwm输出连到此管脚。Zynq没有集成pwm功能,但有两个三路定时器TTC,可以设置TTC以输出pwm波,但首先需要修改硬件配置,使TTC输出连接到Buzzer管脚,这部分工作要借助Xilinx开发工具vivado来完成,这里不对此作过多叙述。

(2)硬件寄存器分析

打开Zynq数据手册ug585-Zynq-7000-TRM,在Ch.8章可看到TTC定时器相关介绍。TTC定时器时钟有内部时钟和外部时钟两种,这里使用内部时钟,如下图蓝色方框所示:

Zynq TTC蜂鸣器驱动开发_第3张图片

TTC相关的定时器有Clock_Control、Counter_Control、Counter_Value、Interval_Counter、Match_x_Counter、Interrupt_Register、Interrupt_Enable、Event_Control_Timer、Event_Register,其中必须的为Clock_Control、Counter_Control、Counter_Value、Interval_Counter、Match_x_Counter等几个。下面一一介绍:

Zynq TTC蜂鸣器驱动开发_第4张图片

Zynq TTC蜂鸣器驱动开发_第5张图片

时钟控制寄存器,这里需要使用prescale,所以bit1需设置为1,prescale value此处设为1,整个时钟控制寄存器设置值为0x00000003。

Zynq TTC蜂鸣器驱动开发_第6张图片

Zynq TTC蜂鸣器驱动开发_第7张图片

Counter_Control寄存器,有效宽度为7bit,其中bit0是使能counter位,设为0,使能counter;

bit1设为1,打开Interval模式;

bit2设为0,关闭计数器逆序计数;

bit3设为1,打开Match模式;

bit4用来重置计数值,即使计数器重新开始计数,此位初始化时可置为1;

bit5用来设置波形输出使能,低位有效,所以写0;

bit6用来设置波形极性,置为0。

最终该寄存器要写入的值为0001 1010,即十进制26。

要格外注意其中的bit4,将此位设置为高,计数器值会reset,并且重新开始计数;重新开始时该位会被自动清除。所以Counter_Control写入0001 1010(即26),读出的值却是0000 1010(即10),表现出读出的寄存器值与写入的不一致的现象。

当Counter_Control填入的数为0000 1010(即10时),读出的数为0000 1010(即10),此时也可正常工作,与填入26时看不出差异,可见bit4位初始化为0或者1大体上不影响该功能正常工作。

Counter_Value寄存器是只读寄存器,应是用来存储计数值的,这里不需设置。

Zynq TTC蜂鸣器驱动开发_第8张图片

Interval_Counter寄存器,用来设置间隔值,间隔值决定了波形的频率,计数每次达到该值时,计数器将重置为0。此处设置间隔值为55555,计算公式为

                     间隔值=输入时钟/(分频设置*输出频率)

此处输入时钟为111111115Hz,输出频率设为500Hz。

Zynq TTC蜂鸣器驱动开发_第9张图片

Match_x_Counter寄存器,决定波形的占空比,计数值每次达到匹配值时,波形输出都会反转。此处设置匹配值为27777,计算公式为

匹配值=(间隔值*占空比)/100

此处占空比选50。

Zynq TTC蜂鸣器驱动开发_第10张图片

 

Zynq TTC蜂鸣器驱动开发_第11张图片

中断状态寄存器,此处填入1,使能Interval中断。但该寄存器填入0时,功能依然可正常运行。

Zynq TTC蜂鸣器驱动开发_第12张图片

Zynq TTC蜂鸣器驱动开发_第13张图片

中断使能寄存器,此处填入0x00000001,此处也可设置为0,不影响功能正常运行。

 

二、PWM驱动开发调试

(1)驱动程序开发

驱动程序使用misc设备驱动框架来编写PWM蜂鸣器驱动,驱动程序如下

/*
 * LED support for the input layer
 *
 * Copyright 2010-2015 Samuel Thibault 
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BEEP_SUCCESS			0
#define BEEP_FAILURE			1

#define DEVICE_NAME				"pwm-buzzermy"

#define PWM_IOCTL_SET			1
#define PWM_IOCTL_STOP			0

#define TTC0_BASE_ADDR			0XF8001000U			//shizhongkongzhi
#define TTC0_CNT_CNTRL_ADDR		0XF800100CU			//计数器控制寄存器
#define TTC0_CNT_VAL_ADDR		0XF8001018U			//计数器数值寄存器
#define TTC0_INTERVAL_ADDR		0XF8001024U			//间隔寄存器
#define TTC0_MATCH_ADDR			0XF8001030U			//匹配值寄存器
#define TTC0_ISR_ADDR			0XF8001054U			//中断寄存器

/** @name Register Map
 *
 * Register offsets from the base address of the device.
 *
 * @{
 */
#define XTTCPS_CLK_CNTRL_OFFSET		0x00000000U  /**< Clock Control Register */
#define XTTCPS_CNT_CNTRL_OFFSET		0x0000000CU  /**< Counter Control Register*/
#define XTTCPS_COUNT_VALUE_OFFSET	0x00000018U  /**< Current Counter Value */
#define XTTCPS_INTERVAL_VAL_OFFSET	0x00000024U  /**< Interval Count Value */
#define XTTCPS_MATCH_0_OFFSET		0x00000030U  /**< Match 1 value */
#define XTTCPS_MATCH_1_OFFSET		0x0000003CU  /**< Match 2 value */
#define XTTCPS_MATCH_2_OFFSET		0x00000048U  /**< Match 3 value */
#define XTTCPS_ISR_OFFSET			0x00000054U  /**< Interrupt Status Register */
#define XTTCPS_IER_OFFSET			0x00000060U  /**< Interrupt Enable Register */
/* @} */

/** @name Counter Control Register
 * Counter Control Register definitions
 * @{
 */
#define XTTC0_CNT_CNTRL_DIS_MASK		0x00000001U /**< Disable the counter */
#define XTTC0_CNT_CNTRL_INT_MASK		0x00000002U /**< Interval mode */
#define XTTCPS_CNT_CNTRL_DECR_MASK		0x00000004U /**< Decrement mode */
#define XTTC0_CNT_CNTRL_MATCH_MASK		0x00000008U /**< Match mode */
#define XTTC0_CNT_CNTRL_RST_MASK		0x00000010U /**< Reset counter */
#define XTTC0_CNT_CNTRL_EN_WAVE_MASK	0x00000020U /**< Enable waveform */
#define XTTCPS_CNT_CNTRL_POL_WAVE_MASK	0x00000040U /**< Waveform polarity */
#define XTTCPS_CNT_CNTRL_RESET_VALUE	0x00000021U /**< Reset value */
/* @} */


#define XTTCPS_CLK_CNTRL_PS_VAL_SHIFT	1U				/**< Prescale shift */
#define XTTCPS_CLK_CNTRL_PS_VAL_MASK	0x0000001EU		/**< Prescale value */
#define XTTCPS_CLK_CNTRL_PS_EN_MASK		0x00000001U		/**< Prescale enable */


static void __iomem *TTC0_BASE_Reg;	//volatile unsigned int *TTC0_BASE_Reg;
static void __iomem *CNT_CNTRL_Reg;
static void __iomem *CNT_VAL_Reg;
static void __iomem *INTERVAL_Reg;
static void __iomem *MATCH_Reg;
static void __iomem *ISR_Reg;
static void __iomem *ISR_EN_Reg;

typedef struct {
	u32 OutputHz;		/* The frequency the timer should output on the
				   waveout pin */
	u8 OutputDutyCycle;	/* The duty cycle of the output wave as a
				   percentage */
	u8 PrescalerValue;	/* Value of the prescaler in the Count Control
				   register */
}TmrCntrSetup;

static u32 PrescalerSettings[] = {
   2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384,
   32768, 65536, 1
};

static TmrCntrSetup SettingsTable[] = {
	/* Table offset of 0 */
	{10, 50, 6},
	{10, 25, 6},
	{10, 75, 6},

	/* Table offset of 3 */
	{100, 50, 3},
	{200, 25, 2},
	{2700/*400*/, /*12.5*/50, 1},

	/* Table offset of 6 */
	{500, 50, 1},
	{1000, 50, 0},
	{5000, 50, 16},

	/* Table offset of 9 */
	{10000, 50, 16},
	{50000, 50, 16},
	{100000, 50, 16},

	/* Table offset of 12 */
	{500000, 50, 16},
	{1000000, 50, 16},
	{5000000, 50, 16},
	/* Note: at greater than 1 MHz the timer reload is noticeable. */

};

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

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

static long beeper_ioctl(struct file *filep, unsigned int cmd, unsigned long PCLK_FREQ_HZ)
{
	unsigned int RegValue;
	unsigned int IntervalValue, MatchValue;
	TmrCntrSetup *CurrSetup;
	unsigned char SettingsTableOffset = 5;	//10; //9; //7;//8;//7;//5;//6;

	switch(cmd)
	{
		case PWM_IOCTL_SET:

		CurrSetup = &SettingsTable[SettingsTableOffset];

		/*
		 * Set the Clock Control Register
		 */
		if (16 > CurrSetup->PrescalerValue) 
		{
			/* Use the clock prescaler */
			RegValue =(CurrSetup->PrescalerValue << XTTCPS_CLK_CNTRL_PS_VAL_SHIFT) & XTTCPS_CLK_CNTRL_PS_VAL_MASK;
			RegValue |= XTTCPS_CLK_CNTRL_PS_EN_MASK;
		}
		else 
		{
			/* Do not use the clock prescaler */
			RegValue = 0;
		}
		iowrite32(RegValue, TTC0_BASE_Reg);

		/*
		 * Set the Interval register. This determines the frequency of
		 * the waveform. The counter will be reset to 0 each time this
		 * value is reached.
		 */
		IntervalValue = PCLK_FREQ_HZ /
			(u32) (PrescalerSettings[CurrSetup->PrescalerValue] *
				   CurrSetup->OutputHz);

		/*
		 * Make sure the value is not to large or too small
		 */
		if ((65535 < IntervalValue) || (4 > IntervalValue)) {
			return BEEP_FAILURE;
		}
		iowrite32(IntervalValue, INTERVAL_Reg);
		
		/*
		 * Set the Match register. This determines the duty cycle of the
		 * waveform. The waveform output will be toggle each time this
		 * value is reached.
		 */
		MatchValue = (IntervalValue * CurrSetup->OutputDutyCycle) / 100;
		
		/*
		 * Make sure the value is not to large or too small
		 */
		if ((65535 < MatchValue) || (4 > MatchValue)) {
			return BEEP_FAILURE;
		}
		iowrite32(MatchValue, MATCH_Reg);

		/*
		 * Set the Counter Control Register
		 */
		RegValue =
			~(XTTC0_CNT_CNTRL_DIS_MASK |
			  XTTC0_CNT_CNTRL_EN_WAVE_MASK) &
			(XTTC0_CNT_CNTRL_INT_MASK |
			 XTTC0_CNT_CNTRL_MATCH_MASK |
			 XTTC0_CNT_CNTRL_RST_MASK);

		iowrite32(RegValue, CNT_CNTRL_Reg);
		break;

		case PWM_IOCTL_STOP:
		iowrite32(0x21, CNT_CNTRL_Reg);
		break;

	}

	return 0;
}

static struct file_operations beeper_ops = {
        .owner				= THIS_MODULE,
        .open				= beeper_open,
        .release			= beeper_close,
        .unlocked_ioctl		= beeper_ioctl,
};

static struct miscdevice beeper_dev = {
		.minor = MISC_DYNAMIC_MINOR,
		.name = DEVICE_NAME,
		.fops = &beeper_ops,
};

static int __init zynq_beeper_init(void)
{
	int ret;

	if(!request_mem_region(TTC0_BASE_ADDR, 0x00000060, DEVICE_NAME))
        {
                printk("request_mem_region TTC0_BASE_ADDR error. \n");
                return -EINVAL;
        }
        else
                printk("request_mem_region TTC0_BASE_ADDR success. \n");
	
	TTC0_BASE_Reg = ioremap(TTC0_BASE_ADDR, 0x00000060);
	if(TTC0_BASE_Reg == NULL)
	{
		printk("TTC0 PWM Buzzer: [ERROR] Access address is NULL!\n");
		return -EIO;
	} 

	CNT_CNTRL_Reg = TTC0_BASE_Reg + 0x0000000C;
	CNT_VAL_Reg = TTC0_BASE_Reg + 0x00000018;
	INTERVAL_Reg = TTC0_BASE_Reg + 0x00000024;	
	MATCH_Reg = TTC0_BASE_Reg + 0x00000030;	
	ISR_Reg = TTC0_BASE_Reg + 0x00000054;	
	ISR_EN_Reg = TTC0_BASE_Reg + 0x00000060;
				
	ret = misc_register(&beeper_dev);	
	if(ret)
	{
		printk("pwm beeper:[ERROR] Misc device register failed.\n");
		return ret;
	}
	printk(DEVICE_NAME "\tinitialized\n");
	return 0;
}

static void __exit zynq_beeper_exit(void)
{
	iounmap(TTC0_BASE_Reg);
	release_mem_region(TTC0_BASE_ADDR, 0x00000060);
	misc_deregister(&beeper_dev);
	
	printk("zynq_beeper_exit success!\n");
}

module_init(zynq_beeper_init);
module_exit(zynq_beeper_exit);

MODULE_AUTHOR("Samuel Thibault ");
MODULE_AUTHOR("Dmitry Torokhov ");
MODULE_DESCRIPTION("Input -> PWM Bridge");
MODULE_LICENSE("GPL v1");

 

编译用的Makefile如下:

KERN_SRC=/home/zynq_linux/Kernel/linux-xlnx-xilinx-v2018.2
#obj-m := my_gpio.o
#obj-m := gpio_driver.o
obj-m := beeper_zynq.o

all:
	make -C $(KERN_SRC) ARCH=arm M=`pwd` modules
clean:
	#make -C $(KERN_SRC) ARCH=arm M=`pwd=` clean
	rm beeper_zynq.m*
	rm modules.order
	rm Module.symvers
	rm beeper_zynq.ko
	rm beeper_zynq.o 

此Makefile只有需要将驱动编译成模块时用到,编译方法为直接在驱动和Makefile同一目录下make即可,成功则会生成beeper_zynq.ko文件。

 

如果要将驱动编译进内核,使用如下方法:

(1)将beeper_zynq.c文件拷贝到内核源码./drivers/misc/目录下。
(2)打开内核drivers/misc/Kconfig目录,添加如下内容
config XILINX_PWM_Buzzer
        tristate "Xilinx zynq7030 PWM Buzzer driver"
        help
          This option enables support for the Xilinx zynq7030 ttc0 pwm driver,
          which can be used to drive the buzzer work.

如示例文件Kconfig第533行所示。

(3)修改drivers/misc/Makefile,加入如下内容
obj-$(CONFIG_XILINX_PWM_Buzzer) += beeper_zynq.o

(4)make menuconfig进入
Device Drivers  --->
    Misc devices  --->
        <*> Xilinx zynq7030 PWM Buzzer driver

选择 Xilinx zynq7030 PWM Buzzer driver选项,保存退出。

(2)应用测试程序

应用测试代码如下:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


int main(int argc, char *argv[])
{
	int fd = 0;
	int ret;
	int time;
	int time_long;
	int i;

	if(argc != 3)
	{
		printf("beeper_zynq_app argument error!\n");
		return 1;
	}	
	fd = open("/dev/pwm-buzzermy", O_RDWR);
	if(fd < 0)
	{
		printf("beeper_zynq:[ERROR] Can't open device.\n");
		return 1;
	}	
	printf("beeper_zynq: Open device. Filedescription of pwm-buzzer is %d\n",fd);

	time = atoi(argv[1]);                    //蜂鸣器鸣叫次数
	time_long = atoi(argv[2]);
	for(i=0; i

 

你可能感兴趣的:(Linux驱动,zynq,嵌入式,zynq,linux驱动,TTC蜂鸣器)