目的:在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.”的描述信息,由此可知该例程可以输出方波。
下面详细叙述开发过程。
(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,不影响功能正常运行。
驱动程序使用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