目的:在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;
}