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-address

二、字符设备驱动编写

  1. 创建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;
    }
    
  2. 编写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
    
  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缩进。

©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容