Zynq TTC蜂鳴器驅動開發

目的:在Zynq 7030平臺開發ttc pwm驅動程序,以驅動蜂鳴器鳴叫。

硬件平臺:Zynq 7030

軟件平臺:linux-xlnx-xilinx-v2018.2

開發工具:vivado、SDK、Ubuntu

蜂鳴器:無源壓電式

驅動開發方法:linux雜項設備驅動

 

Zynq 7030並沒有集成pwm控制器,因此無法實現用pwm驅動蜂鳴器工作。但Zynq有兩個三路定時器TTC,可以利用TTC輸出pwm波,因此可以利用TTC來實現pwm蜂鳴器。

一、查閱原理圖和數據手冊

(1)原理圖分析

打開原理圖,搜索Buzzer可看到如下信息:

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定時器時鐘有內部時鐘和外部時鐘兩種,這裏使用內部時鐘,如下圖藍色方框所示:

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等幾個。下面一一介紹:

時鐘控制寄存器,這裏需要使用prescale,所以bit1需設置爲1,prescale value此處設爲1,整個時鐘控制寄存器設置值爲0x00000003。

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寄存器是隻讀寄存器,應是用來存儲計數值的,這裏不需設置。

Interval_Counter寄存器,用來設置間隔值,間隔值決定了波形的頻率,計數每次達到該值時,計數器將重置爲0。此處設置間隔值爲55555,計算公式爲

                     間隔值=輸入時鐘/(分頻設置*輸出頻率)

此處輸入時鐘爲111111115Hz,輸出頻率設爲500Hz。

Match_x_Counter寄存器,決定波形的佔空比,計數值每次達到匹配值時,波形輸出都會反轉。此處設置匹配值爲27777,計算公式爲

匹配值=(間隔值*佔空比)/100

此處佔空比選50。

 

中斷狀態寄存器,此處填入1,使能Interval中斷。但該寄存器填入0時,功能依然可正常運行。

中斷使能寄存器,此處填入0x00000001,此處也可設置爲0,不影響功能正常運行。

 

二、PWM驅動開發調試

(1)驅動程序開發

驅動程序使用misc設備驅動框架來編寫PWM蜂鳴器驅動,驅動程序如下

/*
 * LED support for the input layer
 *
 * Copyright 2010-2015 Samuel Thibault <[email protected]>
 *
 * 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 <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/io.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/ioport.h>
#include <linux/of.h>
#include <linux/delay.h>
#include <linux/io.h>

#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 <[email protected]>");
MODULE_AUTHOR("Dmitry Torokhov <[email protected]>");
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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <string.h>


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<time; i++)
	{
		printf("time = %d\n", time);	
		ioctl(fd, 1, 111111115);            //時鐘頻率
		usleep(100000);
		ioctl(fd, 0, 111111115);      
		usleep(time_long*1000);            //每次鳴叫的間隔時間
	}
	printf("break for loop. \n");
	ioctl(fd, 0, 111111115);      
	
	printf("after ioctl close pwm. \n");
	close(fd);
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章