zynq7000按键中断驱动

基于zynq7000的按键中断驱动

创建zynq7000硬件工程

1、新建工程

打开Vivado开发环境
zynq7000按键中断驱动_第1张图片
点击create project
如下表操作
zynq7000按键中断驱动_第2张图片
开始创建工程

zynq7000按键中断驱动_第3张图片
创建完成后,点击在这里插入图片描述检查一下工程是否错误,然后Generate Output Products, 完成后Create HDL Wrapper生成顶层文件。完成之后开始写管脚约束
set_property IOSTANDARD LVCMOS33 [get_ports {pl_key_4_tri_i[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {pl_key_4_tri_i[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {pl_key_4_tri_i[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {pl_key_4_tri_i[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {pl_led_4_tri_o[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {pl_led_4_tri_o[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {pl_led_4_tri_o[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {pl_led_4_tri_o[0]}]
set_property PACKAGE_PIN M15 [get_ports {pl_led_4_tri_o[0]}]
set_property PACKAGE_PIN G14 [get_ports {pl_led_4_tri_o[1]}]
set_property PACKAGE_PIN M17 [get_ports {pl_led_4_tri_o[2]}]
set_property PACKAGE_PIN G15 [get_ports {pl_led_4_tri_o[3]}]
set_property PACKAGE_PIN M19 [get_ports {pl_key_4_tri_i[0]}]
set_property PACKAGE_PIN M20 [get_ports {pl_key_4_tri_i[1]}]
set_property PACKAGE_PIN L16 [get_ports {pl_key_4_tri_i[2]}]
set_property PACKAGE_PIN F16 [get_ports {pl_key_4_tri_i[3]}]

然后生成bit文件
zynq7000按键中断驱动_第4张图片
完成后,选择File
zynq7000按键中断驱动_第5张图片
Export
zynq7000按键中断驱动_第6张图片
打开SDK软件
launch SDK
进入SDK软件开始裸机测试,看看搭建的硬件环境是否正确
创建工程阶段省略

裸机测试代码为

#include 
#include "platform.h"
#include "xparameters.h"
#include "xscugic.h"
#include "xil_exception.h"
#include "xgpio.h"
#include "xscutimer.h"
#include "xil_types.h"
#include "ax_pwm.h"
#include  // usleep()


// Parameter definitions
#define INTC_DEVICE_ID          XPAR_PS7_SCUGIC_0_DEVICE_ID
#define KEY_DEVICE_ID           XPAR_AXI_GPIO_1_DEVICE_ID
#define LED_DEVICE_ID           XPAR_AXI_GPIO_0_DEVICE_ID

#define INTC_GPIO_INTERRUPT_ID  XPAR_FABRIC_AXI_GPIO_1_IP2INTC_IRPT_INTR
#define KEY_INT_MASK            XGPIO_IR_CH1_MASK

#define PL_LED_REG_BASSADDR			0x41200000
#define PL_KEY_REG_BASSADDR			0x41210000
#define PL_KEY_REG_INTERRUPT		0x41210128
#define PL_KEY_REG_GIER				0x4121011C
#define PL_KEY_INT_CLEAR			0x41210120

// global variable
XGpio LEDInst;
XGpio KEYInst;
XScuGic INTCInst;
static u8 keyVal;

// forward declaration
static void KeyIntrHandler(void * InstancePtr);
static int IntcInitFunction(u16 DeviceId);
static int InterruptSystemSetup(XScuGic * XScuGicInstancePtr);

int Gunner_In32(int Addr)
{
	return *(volatile int *) Addr;
}

void Gunner_Out32(int OutAddress, int Value)
{
	*(volatile int *) OutAddress = Value;
}


int main(void)
{
    init_platform();

    int status;

    status = XGpio_Initialize(&KEYInst, KEY_DEVICE_ID);
    if(status != XST_SUCCESS)
        return XST_FAILURE;

    // initial LED
    status = XGpio_Initialize(&LEDInst, LED_DEVICE_ID);
    if(status != XST_SUCCESS)
        return XST_FAILURE;

    // initial interrupt controller
    status = IntcInitFunction(INTC_DEVICE_ID);
    if(status != XST_SUCCESS)
        return XST_FAILURE;

    Gunner_Out32(XPAR_AX_PWM_0_S00_AXI_BASEADDR, 17179);//200hz

    printf(">>> Press PL KEY1 ~ KEY4 one by one, and check the PL LED1 ~ LED4\n");

    while (1)
    {
    	unsigned int duty;
    	for (duty = 0x8fffffff; duty < 0xffffffff; duty = duty + 100000)
    	{
    		Gunner_Out32(XPAR_AX_PWM_0_S00_AXI_BASEADDR + AX_PWM_S00_AXI_SLV_REG1_OFFSET, duty);
    		usleep(100);
    	}
    }

    cleanup_platform();
    return 0;
}

//----------------------------------------------------------------------------
// INTERRUPT SERVICE ROUTINE(ISR) also know as: INTERRUPT HANDLER FUNCTION
// called by the keys interrupt, performs push keys read
//----------------------------------------------------------------------------
static void KeyIntrHandler(void * InstancePtr)
{
	// default LED all turn off
    unsigned char ledVal = 0x00;

    // Ignore additional button presses
    usleep(100000); // 0.1s sleep, to debounce, in common, the meta-state will sustain no more than 20ms

    keyVal = XGpio_DiscreteRead(&KEYInst, 1) & 0x0f;
    switch(keyVal)
    {
        case 0x0E: ledVal = 0x01; printf("-PL KEY1 pressed, PL LED1 Turn On\n"); break;     // 0001 (1)
        case 0x0D: ledVal = 0x02; printf("--PL KEY2 pressed, PL LED2 Turn On\n"); break;    // 0010 (2)
        case 0x0B: ledVal = 0x04; printf("---PL KEY3 pressed, PL LED3 Turn On\n"); break;   // 0100 (4)
        case 0x07: ledVal = 0x08; printf("----PL KEY4 pressed, PL LED4 Turn On\n"); break;  // 1000 (8)
        default  : printf("Key released!\n\n"); break;
    }

    // Set LED value
    Gunner_Out32(PL_LED_REG_BASSADDR, ledVal);
    // Acknowledge GPIO interrupts
    Gunner_Out32(PL_KEY_INT_CLEAR, 0x1);

    // Enable GPIO interrupts
    Gunner_Out32(PL_KEY_REG_INTERRUPT, 0x1);
}


//----------------------------------------------------------------------------
// Interrupt controller initial function
//----------------------------------------------------------------------------
static int IntcInitFunction(u16 DeviceId)
{
    XScuGic_Config * IntcConfig;
    int status;
    uint32_t	Register;
    uint32_t	reg_val;
    int data = 0;
    // Interrupt controller initialization
    IntcConfig = XScuGic_LookupConfig(DeviceId);
    status = XScuGic_CfgInitialize(&INTCInst, IntcConfig, IntcConfig->CpuBaseAddress);
    if(status != XST_SUCCESS)
        return XST_FAILURE;

    // Call interrupt setup function
    status = InterruptSystemSetup(&INTCInst);
    if(status != XST_SUCCESS)
        return XST_FAILURE;

    // Register GPIO interrupt handler
    status = XScuGic_Connect(&INTCInst, INTC_GPIO_INTERRUPT_ID,
            (Xil_ExceptionHandler)KeyIntrHandler, (void*)data);
    if(status != XST_SUCCESS)
        return XST_FAILURE;

    // Enable GPIO interrupts
    Register = Gunner_In32(PL_KEY_REG_INTERRUPT);
    printf("Register is %ld\n", Register);
    Gunner_Out32(PL_KEY_REG_INTERRUPT, Register | 1);

    reg_val = Gunner_In32(PL_KEY_REG_GIER);
    printf("Reg_val is %ld\n", reg_val);
    Gunner_Out32(PL_KEY_REG_GIER, 0x80000000);

    // Enable GPIO interrupts in the controller
    XScuGic_Enable(&INTCInst, INTC_GPIO_INTERRUPT_ID);

    return XST_SUCCESS;
}

//----------------------------------------------------------------------------
// Interrupt system setup
//----------------------------------------------------------------------------
static int InterruptSystemSetup(XScuGic * XScuGicInstancePtr)
{
    // Register GIC interrupt handler
    Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
            (Xil_ExceptionHandler)XScuGic_InterruptHandler, XScuGicInstancePtr);

    Xil_ExceptionEnable();

    return XST_SUCCESS;
}

其中用到了很多寄存器的地址,该地址都在axi_gpio的ip文档里面有介绍
zynq7000按键中断驱动_第7张图片
详细细节请阅读文档

裸机测试过后,硬件工作正常就要开始上系统,写驱动

petalinux构建zynq linux系统

1、首先设置petalinux环境变量
zynq7000按键中断驱动_第8张图片
2、设置vivado环境变量
在这里插入图片描述
3、使用下面命令创建一个petalinux工程,我的工程名为test_peta。
petalinux-create --type project --template zynq --name test_peta

4、将之间创建好的硬件工程编译后的在这里插入图片描述
拷贝到linux系统中,记好该文件的目录
5、进入刚刚创建的peta工程
在这里插入图片描述
6、使用下面命令配置petalinux工程的硬件信息,其实也就是该文件中的system.hdf文件
petalinux-config --get-hw-description …/ps_pl_key_wrapper_hw_platform_0

7、之后会出现配置信息的窗口文件
zynq7000按键中断驱动_第9张图片
这里我选择自己下载并移植好的内核,如果选择默认uboot和linux内核来源,则是从git上下载的。
zynq7000按键中断驱动_第10张图片
zynq7000按键中断驱动_第11张图片
zynq7000按键中断驱动_第12张图片
该默认配置是从SD卡启动
退出之后开始配置,该阶段稍微需要点时间
zynq7000按键中断驱动_第13张图片
8、接下来全部选择默认
petalinux-config -c kernel 配置内核信息,按照个人需求来
petalinux-config -c rootfs 配置根文件系统,按照个人需求来

9、使用命令petalinux-build 配置编译uboot、内核、根文件系统、设备树等。
等待编译完成

10、编译完成后修改一下设备树
首先查看一下编译好的设备树
zynq7000按键中断驱动_第14张图片
该目录下有全部生成的设备树文件
打开pl.dtsi文件
zynq7000按键中断驱动_第15张图片
在amba_pl的子节点下有两个gpio节点,在axi_gpio_1节点中已经配置好了中断信息 interrupts = <0 29 4> 该中断号为29 对应数据手册中的61号中断 计算方法就是中断号 + 32 = 数据手册中的硬件中断

第一个0用于指示中断是否是SPI(共享中断,shared peripheral interrupt)。非0值表示它是SPI。事实上在Zynq硬件上,这些中断都是共享的,这里是为了方便才写0, 软件上认为它不共享。

第二个29表示中断号

第三个4表示 触发方式 4为高电平触发(1是上升沿触发)

因为设备树中的两个gpio节点的compatible相同 这样driver匹配起来不方便,所以我选择改一下有中断的gpio节点,在下图路径下有一个system-user.dtsi,是petalinux工具专门留给我们写设备树的文件在这里插入图片描述
zynq7000按键中断驱动_第16张图片
通过axi_gpio_1的lable,将axi_gpio_1的compatible属性改为了xlnx,zynq_gpio

11、修改完成后保存,回到peta工程目录中,执行petalinux-build指令

12、编译完成后,执行下面语句合成BOOT.BIN
petalinux-package --boot --fsbl ./images/linux/zynq_fsbl.elf --fpga ./images/linux/ps_pl_key_wrapper.bit --u-boot --force

13、在/images/linux/的文件夹里把BOOT.BIN和image.ub拷贝到SD卡中,启动开发板

根据设备树编写按键中断驱动

1、启动开发板,进入linux系统

2、进入以下目录查看axi_gpio_1节点的基本信息
在这里插入图片描述
进入gpio@41210000目录中查看该节点的compatible信息
zynq7000按键中断驱动_第17张图片
信息已经更改过来了,接下来要写按键中断驱动了

下面附上按键中断驱动代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
#define __devinit
#define __devexit
#define __devinitdata
 
#define DRIVER_NAME "KEY"
 
 
MODULE_AUTHOR("Pingbo An");
MODULE_LICENSE("GPL v2");
 
 
struct key_dev{
	struct cdev dev;
	struct class *key_drv_class;
	struct device *key_drv_class_dev;
	struct timer_list keys_timer;
	int irq;
	int major;
	unsigned long start_addr;
	unsigned long size;
	void __iomem *baseaddr;
	int width;
	int inout;
	int key_prs;
	int key_value;
	int key_value_r;
	
};
 
static DECLARE_WAIT_QUEUE_HEAD(press_queue);
static volatile int event_press=0;

static irqreturn_t key_interrupt(int irq, void *dev_id)
{
	struct key_dev *dev = dev_id;

	mod_timer(&dev->keys_timer, jiffies + HZ/5);

	dev->key_value = ioread32(dev->baseaddr) & 0x0f;

	iowrite32(0x1, dev->baseaddr + 0x120);
	
	return IRQ_HANDLED;
 
}
 
static int key_open(struct inode *inode, struct file *filp)
{
	struct key_dev *k_dev;
	struct resource *res;
	int err;
	int gpio_reg;
	int intr_reg;
	k_dev = container_of(inode->i_cdev, struct key_dev, dev);
	filp->private_data = k_dev;

	res = request_mem_region(k_dev->start_addr, k_dev->size, DRIVER_NAME);
	if(!res){
		printk("ERROR: cannot request mem\n");
		return 0;
	}
 
	k_dev->baseaddr = ioremap(k_dev->start_addr, k_dev->size);
	if(!(k_dev->baseaddr)){
		printk("ERROR: cannot remap addr\n");
		return 0;
	}
 
	gpio_reg = ioread32(k_dev->baseaddr + 4);
	iowrite32(gpio_reg | 0xF, k_dev->baseaddr + 4);
	
	intr_reg = ioread32(k_dev->baseaddr + 0x128);
	iowrite32(intr_reg | 0x01, k_dev->baseaddr + 0x128);//enable interrupt
 
	iowrite32(0x80000000, k_dev->baseaddr + 0x11C);

	err = request_irq(k_dev->irq, key_interrupt, IRQF_ONESHOT | IRQF_TRIGGER_HIGH, DRIVER_NAME, k_dev);
	if(err){
		printk("ERROR: cannot request interrupt err = %d\n", err);
		return 0;
	}

	k_dev->key_prs = 0;

	printk(KERN_INFO "have open the key device\n");
	return 0;
}
 
 
 
static int key_close(struct inode *inode, struct file *filp)
{
	struct key_dev *dev = (struct key_dev*)filp->private_data;	

	int intr_reg = ioread32(dev->baseaddr + 0x128);
	iowrite32(intr_reg & 0xFFFFFFF0, dev->baseaddr + 0x128);
 
	iounmap(dev->baseaddr);

	release_mem_region(dev->start_addr, dev->size);
	
	free_irq(dev->irq, dev);
	
	return 0;
 
}
 
 
ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *fops)
{

	int err;	
	struct key_dev *dev = filp->private_data;
	
	wait_event_interruptible(press_queue, event_press);
	
	event_press = 0;
	err = copy_to_user(buf, &dev->key_value, count);
	
	return err ? -EFAULT : 0;

}
 
static void keys_timer_function(unsigned long data)
{
	struct key_dev *dev = (struct key_dev *)data;

    switch(dev->key_value)
    {
        case 0x0E: dev->key_value_r = dev->key_value;printk("-PL KEY1 pressed\n"); break;     // 0001 (1)
        case 0x0D: printk("--PL KEY2 pressed, PL LED2 Turn On\n"); break;    // 0010 (2)
        case 0x0B: printk("---PL KEY3 pressed, PL LED3 Turn On\n"); break;   // 0100 (4)
        case 0x07: printk("----PL KEY4 pressed, PL LED4 Turn On\n"); break;  // 1000 (8)
        default  : dev->key_value = dev->key_value_r;printk("Key released!\n\n"); break;
    }

	event_press = 1;
	wake_up_interruptible(&press_queue);
}


struct file_operations key_fops={
	.owner = THIS_MODULE,
	.open = key_open,
	.read = key_read,
	.release = key_close,
};
 
static int key_cdev_init(struct key_dev *lp)
{
	dev_t devno;
	int rc;

	init_timer(&lp->keys_timer);
	lp->keys_timer.function = keys_timer_function;
	lp->keys_timer.expires = 0; 
	lp->keys_timer.data = (unsigned long)lp;

	add_timer(&lp->keys_timer);
	
	rc = alloc_chrdev_region(&devno, 0, 1, DRIVER_NAME);
	lp->major = MAJOR(devno);
	if(rc<0){
		printk(KERN_WARNING "cannot allocate chardev region\n");
		return rc;
	}
		
	cdev_init(&lp->dev, &key_fops);
	lp->dev.owner = THIS_MODULE;
	lp->dev.ops = &key_fops;
 
	rc = cdev_add(&lp->dev, devno, 1);
	if(rc < 0){
		printk(KERN_ERR "cannot add device\n");
		goto error;
	}

	lp->key_drv_class = class_create(THIS_MODULE, "gpio_interrupts");
	lp->key_drv_class_dev = device_create(lp->key_drv_class, NULL, MKDEV(lp->major, 0), NULL, DRIVER_NAME);
	
	return 0;
 
error:
	unregister_chrdev_region(MKDEV(lp->major, 0), 1);
	return -1;
 
}
 
static void key_cdev_free(struct key_dev *lp)
{
	dev_t devno = MKDEV(lp->major, 0);
	cdev_del(&lp->dev);
	unregister_chrdev_region(devno, 1);
	
}
 
 
static int __devinit key_probe(struct platform_device *pdev)
{
 
	struct device_node *node;
	struct resource *mem;
	unsigned int r_irq;
	struct key_dev *lp = NULL;
	struct device *dev = &pdev->dev;

	node = dev->of_node;
 
	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if(!mem){
		printk(KERN_ERR "get memory resource\n");
		return -ENODEV;
	}
 
	r_irq = irq_of_parse_and_map(node, 0);
	if(!r_irq){
		printk(KERN_ERR "get interrupt\n");
	}
 
	printk(KERN_INFO "irq number is %d", r_irq);
 
 
	lp = (struct key_dev *)kzalloc(sizeof(struct key_dev), GFP_KERNEL);
	if(!lp){
		printk(KERN_ERR "cannot allocate the key_dev\n");
		return -ENOMEM;
	}
	
	printk(KERN_INFO "mem is %x", mem->start);
	
	lp->start_addr = mem->start;
	lp->size = mem->end-mem->start;
	lp->irq = r_irq;
	
	dev_set_drvdata(dev, lp);
	key_cdev_init(lp);
	printk(KERN_INFO "succeed to probe and register key device\n");
	return 0;
 
}
 
static int __devexit key_remove(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct key_dev *lp = dev_get_drvdata(dev);

	key_cdev_free(lp);
 
	kfree(lp);
	dev_set_drvdata(dev, NULL);
	
	return 0;
 
}
 
static const struct of_device_id key_of_match[] __devinitdata={
	{.compatible = "xlnx,zynq_gpio"},
	{/*end of list*/},
};
 
 
MODULE_DEVICE_TABLE(of, key_of_match);
 
static struct platform_driver key_driver={
	.driver={
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
		.of_match_table = key_of_match,
	},
	.probe = key_probe,
	.remove = key_remove,
};
 

static int __init keys_init(void)
{
  printk(KERN_INFO "start key gpio\n");
  return platform_driver_register(&key_driver);
}
 
 
static void __exit keys_exit(void)
{
  platform_driver_unregister(&key_driver);
  printk(KERN_INFO "end key gpio\n");
}
 
module_init(keys_init);
module_exit(keys_exit);


MODULE_LICENSE("GPL");				
MODULE_AUTHOR("Gunner");				

接下来是makefile代码

# 开发板的linux内核的源码树目录
KERN_DIR = /home/gunner/opt/linux_kernel/linux-4.9

obj-m	+= key_drv.o

all:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERN_DIR) M=`pwd` modules 

cp:
	cp *.ko /home/gunner/opt/work/driver

.PHONY: clean	
clean:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERN_DIR) M=`pwd` modules clean

下面是app测试代码

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

/* sixthdrvtest 
  */
int main(int argc, char **argv)
{
	unsigned char key_val;
	int ret;
	int fd;
	
	fd = open("/dev/KEY", O_RDWR | O_NONBLOCK);
	if (fd < 0)
	{
		printf("can't open!\n");
		return -1;
	}

	while (1)
	{
		printf("read start\n");
		ret = read(fd, &key_val, 1);
		printf("key_val: 0x%x, ret = %d\n", key_val, ret);
	}
	return 0;
}

编译完成后,利用nfs服务将开发板挂载到虚拟机上
在这里插入图片描述
按下按键后运行结果如下图所示
zynq7000按键中断驱动_第18张图片

调试遇到的问题

1、当时没有注意按键的触发类型,参考网上一些人写的代码注册中断号的时候都是用的IRQF_TRIGGER_RISING 上升沿的触发方式所以,按键中断只能第一次触发,后来改成了高电平触发request_irq(k_dev->irq, key_interrupt, IRQF_ONESHOT | IRQF_TRIGGER_HIGH, DRIVER_NAME, k_dev)
2、改成高电平触发后,按下中断,程序会不断的在中断handler中循环,该问题在裸机程序中也遇到过,所以在中断handler函数中加入了iowrite32(0x1, dev->baseaddr + 0x120);该寄存器具体含义,可以参考xilinx提供的axi_gpio手册,里面有详细解释
3、利用定时器进行按键消抖

你可能感兴趣的:(zynq,驱动)