ZYNQ 学习笔记
硬件平台:zynq-7000 & xc7z100ffg900-2
linux开发平台:ubuntu16.04.4 LTS
zynq-linux内核:linux-xlnx-xilinx-v2017.4
LINUX篇
字符设备驱动控制AXI-GPIO
一、准备工作
- 确保已经安装好交叉编译器gcc-arm-linux-gnueabihf。
sudo apt-get install gcc-arm-linux-gnueabihf sudo apt-get update sudo apt-get upgrade
- 准备好zynq-linux内核,这里使用linux-xlnx-xilinx-v2017.4,路径设置为/home/user/linux/kernel/linux-xlnx-xilinx-v2017.4(此路径仅作参考,和驱动的Makefile中路径一致即可)。进入内核目录,对顶层Makefile修改,将250行左右的编译指令重新设置:
为了确保编译过程顺利进行,将内核文件更改用户为自己:# ARCH ?= $(SUBARCH) # CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%) # user add: change the value, to avoid long command input ARCH ?= arm CROSS_COMPILE ?= arm-linux-gnueabihf-
随后可以开始编译内核:cd /home/user/linux/kernel/linux-xlnx-xilinx-v2017.4 //进入内核目录 sudo chown -R user:user * //更改对当前目录下所有文件生效
make clean //第一次编译前清理一下 make xilinx_zynq_defconfig //配置linux内核 make -j8 //根据自己cpu核心数量进行多核编译
-
获取AXI-GPIO的内存地址,可在vivado-Address Editor或xilink SDK-system.mss中查看。
二、字符设备驱动编写
- 创建led_mod.c、ledApp.c文件,并编写程序。这里程序参考《正点原子ZYNQ-LED开发实验教程》,有所不同的是这里由于使用AXI-GPIO,控制设备时无需配置寄存器等,获取地址后直接写入、读取即可。
/* led_mod.c */ #include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #define LED_MAJOR 200 /* 主设备号 */ #define LED_NAME "axi-led" /* 设备名字 */ #define ZYNQ_AXI_GPIO_0_BASE 0x41210000 static void __iomem *data_addr; /* 映射后的寄存器虚拟地址指针 */ static int led_open(struct inode *inode, struct file *filp) { return 0; } static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { return 0; } static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) { int ret; int val; char kern_buf[1]; ret = copy_from_user(kern_buf, buf, cnt); // 得到应用层传递过来的数据 if(0 > ret) { printk(KERN_ERR "kernel write failed!\r\n"); return -EFAULT; } val = readl(data_addr); printk("write-read:0x%x\r\n", val); val = kern_buf[0]; printk("write 0x%x now\r\n", val); writel(val, data_addr); return 0; } static int led_release(struct inode *inode, struct file *filp) { return 0; } static struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .read = led_read, .write = led_write, .release = led_release, }; static int __init led_init(void) { u32 val; int ret; /* 1.寄存器地址映射 */ data_addr = ioremap(ZYNQ_AXI_GPIO_0_BASE, 4); /* 7.注册字符设备驱动 */ ret = register_chrdev(LED_MAJOR, LED_NAME, &led_fops); if(0 > ret){ printk(KERN_ERR "Register LED driver failed!\r\n"); return ret; } printk("led-mod init now\r\n"); return 0; } static void __exit led_exit(void) { /* 1.卸载设备 */ unregister_chrdev(LED_MAJOR, LED_NAME); /* 2.取消内存映射 */ iounmap(data_addr); printk("led-mod exit\r\n"); } /* 驱动模块入口和出口函数注册 */ module_init(led_init); module_exit(led_exit); MODULE_AUTHOR("mlia"); MODULE_DESCRIPTION("ZYNQ AXI-GPIO LED Test Driver"); MODULE_LICENSE("GPL");
/* ledApp.c */ #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> int main(int argc, char *argv[]) { int fd, ret; unsigned char buf[1]; if(3 != argc) { printf("Usage:\n" "\t./ledApp /dev/led 1 @ close LED\n" "\t./ledApp /dev/led 0 @ open LED\n" ); return -1; } /* 打开设备 */ fd = open(argv[1], O_RDWR); if(0 > fd) { printf("file %s open failed!\r\n", argv[1]); return -1; } /* 将字符串转换为int型数据 */ buf[0] = atoi(argv[2]); /* 向驱动写入数据 */ ret = write(fd, buf, sizeof(buf)); if(0 > ret){ printf("LED Control Failed!\r\n"); close(fd); return -1; } /* 关闭设备 */ close(fd); return 0; }
- 编写Makefile[1]以进行模块编译:
KERN_DIR := /home/user/linux/kernel/linux-xlnx-xilinx-v2017.4 obj-m := led_mod.o all: make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERN_DIR) M=`pwd` modules clean: make -C $(KERN_DIR) M=`pwd` clean
- 编译输出.ko与执行文件:
make //输出.ko文件 arm-linux-gnueabihf-gcc ledApp.c -o ledApp //输出测试程序
三、测试与实验
- 开启开发板,这里使用nfs共享文件。
mount -t nfs -o nolock 192.168.0.116:/home/user/nfs /mnt
- 加载模块,这里使用动态加载驱动模块,进入.ko等文件路径:
insmod led_mod.ko //加载模块 mknod /dev/led c 200 0 //创建设备节点 ls /dev //查看节点是否创建成功 ./ledApp /dev/led 1 //写入值,此时led1应被点亮 ./ledApp /dev/led 15 //写入值,此时led1~4应被点亮 ./ledApp /dev/led 16 //写入值,此时led1~4均熄灭(axi-gpio虽有4byte宽度,但只关注width位的值) rmmod led_mod //测试完成,卸载驱动 dmesg | tail //查看一下测试过程中的printk信息
四、总结
至此,测试完全结束。
-
Makefile文件尤其要注意tab缩进。 ↩