ZYNQ学习笔记-LINUX篇-字符设备驱动控制AXI-GPIO

ZYNQ 学习笔记

硬件平台:zynq-7000 & xc7z100ffg900-2
linux开发平台:ubuntu16.04.4 LTS
zynq-linux内核:linux-xlnx-xilinx-v2017.4

LINUX篇

字符设备驱动控制AXI-GPIO

一、准备工作
  1. 确保已经安装好交叉编译器gcc-arm-linux-gnueabihf。
    sudo apt-get install gcc-arm-linux-gnueabihf
    sudo apt-get update
    sudo apt-get upgrade
    
  2. 准备好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核心数量进行多核编译
    
  3. 获取AXI-GPIO的内存地址,可在vivado-Address Editor或xilink SDK-system.mss中查看。
    axi_gpio_led_address
二、字符设备驱动编写
  1. 创建led_mod.c、ledApp.c文件,并编写程序。这里程序参考《正点原子ZYNQ-LED开发实验教程》,有所不同的是这里由于使用AXI-GPIO,控制设备时无需配置寄存器等,获取地址后直接写入、读取即可。
    /* led_mod.c */
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #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 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    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;
    }
    
  2. 编写Makefile1以进行模块编译:
    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
    
  3. 编译输出.ko与执行文件:
    make 										//输出.ko文件
    arm-linux-gnueabihf-gcc ledApp.c -o ledApp 	//输出测试程序
    
三、测试与实验
  1. 开启开发板,这里使用nfs共享文件。
    mount -t nfs -o nolock 192.168.0.116:/home/user/nfs /mnt
    
  2. 加载模块,这里使用动态加载驱动模块,进入.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信息
    
四、总结

至此,测试完全结束。


  1. Makefile文件尤其要注意tab缩进。 ↩︎

你可能感兴趣的:(ZYNQ,c语言,linux,嵌入式,内核,kernel)