打开Vivado开发环境
点击create project
如下表操作
开始创建工程
创建完成后,点击检查一下工程是否错误,然后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文件
完成后,选择File
Export
打开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文档里面有介绍
详细细节请阅读文档
裸机测试过后,硬件工作正常就要开始上系统,写驱动
1、首先设置petalinux环境变量
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、之后会出现配置信息的窗口文件
这里我选择自己下载并移植好的内核,如果选择默认uboot和linux内核来源,则是从git上下载的。
该默认配置是从SD卡启动
退出之后开始配置,该阶段稍微需要点时间
8、接下来全部选择默认
petalinux-config -c kernel 配置内核信息,按照个人需求来
petalinux-config -c rootfs 配置根文件系统,按照个人需求来
9、使用命令petalinux-build 配置编译uboot、内核、根文件系统、设备树等。
等待编译完成
10、编译完成后修改一下设备树
首先查看一下编译好的设备树
该目录下有全部生成的设备树文件
打开pl.dtsi文件
在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工具专门留给我们写设备树的文件
通过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信息
信息已经更改过来了,接下来要写按键中断驱动了
下面附上按键中断驱动代码
#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服务将开发板挂载到虚拟机上
按下按键后运行结果如下图所示
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、利用定时器进行按键消抖