cuter

【原创】Linux下驱动Zynq GPIO (Switch、button、led)

2
阅读(12664)

版权声明:

本文由电子技术应用博主“cuter”发布。欢迎转载,但不得擅自更改博文内容,也不得用于任何盈利目的。转载时不得删除作者简介和作者单位简介。如有盗用而不说明出处引起的版权纠纷,由盗用者自负。

博客官方地址:

http://blog.chinaaet.com/cuter521

http://bbs.ednchina.com/BLOG_cuter521_356737.HTM


1 硬件设计:

1.1) Vivado Block Design

1.1.1) 设计自主AXI-Lite IP

关键逻辑如下:

下图所示的是寄存器读操作,主要目的是为了读取ledswitchbutton的状态。这里并没有做任何处理,对于button而言,可以利用硬件进行预处理之后,将结果传递给软件,这样可以减少软件工作量。

下图所示的逻辑用于将寄存器内的值送至led引脚。

IP制作方法,参考以前的博文《Vivado下创建基于AXI-Lite的用户IP

1.1.2) 添加IP核至block design

完成后的block design如下图所示。

1.1.3) 编写约束文件

ledswitchbutton所用的引脚都需要约束。使用Run Automation的时候,Vivado会帮助完成约束,我们自主IP暂时未实现Run Automation功能,所以要手动约束。约束代码如下:

#NET LD0           LOC = T22  | IOSTANDARD=LVCMOS33;  # "LD0"

set_property PACKAGE_PIN T22 [get_ports {zed_led[0]}]

set_property IOSTANDARD LVCMOS33 [get_ports {zed_led[0]}]


#NET LD1           LOC = T21  | IOSTANDARD=LVCMOS33;  # "LD1"

set_property PACKAGE_PIN T21 [get_ports {zed_led[1]}]

set_property IOSTANDARD LVCMOS33 [get_ports {zed_led[1]}]


#NET LD2           LOC = U22  | IOSTANDARD=LVCMOS33;  # "LD2"

set_property PACKAGE_PIN U22 [get_ports {zed_led[2]}]

set_property IOSTANDARD LVCMOS33 [get_ports {zed_led[2]}]


#NET LD3           LOC = U21  | IOSTANDARD=LVCMOS33;  # "LD3"

set_property PACKAGE_PIN U21 [get_ports {zed_led[3]}]

set_property IOSTANDARD LVCMOS33 [get_ports {zed_led[3]}]


#NET LD4           LOC = V22  | IOSTANDARD=LVCMOS33;  # "LD4"

set_property PACKAGE_PIN V22 [get_ports {zed_led[4]}]

set_property IOSTANDARD LVCMOS33 [get_ports {zed_led[4]}]


#NET LD5           LOC = W22  | IOSTANDARD=LVCMOS33;  # "LD5"

set_property IOSTANDARD LVCMOS33 [get_ports {zed_led[5]}]

set_property PACKAGE_PIN W22 [get_ports {zed_led[5]}]


#NET LD6           LOC = U19  | IOSTANDARD=LVCMOS33;  # "LD6"

set_property PACKAGE_PIN U19 [get_ports {zed_led[6]}]

set_property IOSTANDARD LVCMOS33 [get_ports {zed_led[6]}]


#NET LD7           LOC = U14  | IOSTANDARD=LVCMOS33;  # "LD7"

set_property IOSTANDARD LVCMOS33 [get_ports {zed_led[7]}]

set_property PACKAGE_PIN U14 [get_ports {zed_led[7]}]


#NET SW0           LOC = F22  | IOSTANDARD=LVCMOS18;  # "SW0"

set_property PACKAGE_PIN F22 [get_ports {zed_sw[0]}]

set_property IOSTANDARD LVCMOS18 [get_ports {zed_sw[0]}]


#NET SW1           LOC = G22  | IOSTANDARD=LVCMOS18;  # "SW1"

set_property PACKAGE_PIN G22 [get_ports {zed_sw[1]}]

set_property IOSTANDARD LVCMOS18 [get_ports {zed_sw[1]}]


#NET SW2           LOC = H22  | IOSTANDARD=LVCMOS18;  # "SW2"

set_property PACKAGE_PIN H22 [get_ports {zed_sw[2]}]

set_property IOSTANDARD LVCMOS18 [get_ports {zed_sw[2]}]


#NET SW3           LOC = F21  | IOSTANDARD=LVCMOS18;  # "SW3"

set_property PACKAGE_PIN F21 [get_ports {zed_sw[3]}]

set_property IOSTANDARD LVCMOS18 [get_ports {zed_sw[3]}]


#NET SW4           LOC = H19  | IOSTANDARD=LVCMOS18;  # "SW4"

set_property PACKAGE_PIN H19 [get_ports {zed_sw[4]}]

set_property IOSTANDARD LVCMOS18 [get_ports {zed_sw[4]}]


#NET SW5           LOC = H18  | IOSTANDARD=LVCMOS18;  # "SW5"

set_property PACKAGE_PIN H18 [get_ports {zed_sw[5]}]

set_property IOSTANDARD LVCMOS18 [get_ports {zed_sw[5]}]


#NET SW6           LOC = H17  | IOSTANDARD=LVCMOS18;  # "SW6"

set_property PACKAGE_PIN H17 [get_ports {zed_sw[6]}]

set_property IOSTANDARD LVCMOS18 [get_ports {zed_sw[6]}]


#NET SW7           LOC = M15  | IOSTANDARD=LVCMOS18;  # "SW7"

set_property PACKAGE_PIN M15 [get_ports {zed_sw[7]}]

set_property IOSTANDARD LVCMOS18 [get_ports {zed_sw[7]}]



#NET BTNC          LOC = P16  | IOSTANDARD=LVCMOS18;  # "BTNC"

set_property IOSTANDARD LVCMOS18 [get_ports {zed_btn[0]}]

set_property PACKAGE_PIN P16 [get_ports {zed_btn[0]}]

# BTNU

set_property IOSTANDARD LVCMOS18 [get_ports {zed_btn[1]}]

set_property PACKAGE_PIN T18 [get_ports {zed_btn[1]}]

# BTND

set_property IOSTANDARD LVCMOS18 [get_ports {zed_btn[2]}]

set_property PACKAGE_PIN R16 [get_ports {zed_btn[2]}]

# BTNL

set_property IOSTANDARD LVCMOS18 [get_ports {zed_btn[3]}]

set_property PACKAGE_PIN N15 [get_ports {zed_btn[3]}]

# BTNR

set_property IOSTANDARD LVCMOS18 [get_ports {zed_btn[4]}]

set_property PACKAGE_PIN R18 [get_ports {zed_btn[4]}]

1.1.4) 生成bitstream

1.2) 制作BOOT.bin

1.3) 修改dts文件

小改动,为简单起见,保持和驱动程序中的设备名称一致。IP的物理地址一定要改!

2 驱动设计:

2.1) digilent驱动学习

首先,姑且不管每个函数的具体作用,我们将驱动程序的框架剥离出来进行分析,这样程序结构更加清晰。

2.1.1) platform_driver成员函数

//设备的驱动:platform_driver这个结构体中包含probe()remove()shutdown()suspend() resume()函数,通常也需要由驱动实现。

struct platform_driver {

    int (*probe)(struct platform_device *);

    int (*remove)(struct platform_device *);

    void (*shutdown)(struct platform_device *);

    int (*suspend)(struct platform_device *, pm_message_t state);

    int (*suspend_late)(struct platform_device *, pm_message_t state);

    int (*resume_early)(struct platform_device *);

    int (*resume)(struct platform_device *);

    struct pm_ext_ops *pm;

    struct device_driver driver;

};


驱动程序对platform_driver进行初始化的代码:

/* platform driver structure for mygpio driver */

static struct platform_driver mygpio_driver =

{

       .driver =

       {

               .name = DRIVER_NAME,

               .owner = THIS_MODULE,

               .of_match_table = mygpio_of_match

       },

       .probe = mygpio_probe,

       .remove = __devexit_p(mygpio_remove),

       .shutdown = __devexit_p(mygpio_shutdown)

};

这些函数的命名本身具有一定的自明性,此段代码进一步阐明了probe()remove()shutdown()函数的作用,具体每个函数的作用可以参考函数体前面的注释,写得很详细。


2.1.2) 文件操作函数

关键代码:

static const struct file_operations proc_mygpio_operations = {

       .open = proc_mygpio_open,

       .read = seq_read,

       .write = proc_mygpio_write,

       .llseek = seq_lseek,

       .release = single_release

};

文件操作深究起来,也能独立成文了,对于字符设备而言,要提供的主要入口有:open()release()read()write()ioctl()llseek()poll()等,这里简单说一下用到的几个函数。

loff_t (*llseek) (struct file *, loff_t, int);

llseek 方法用作改变文件中的当前读/写位置,并且新位置作为(正的)返回值。

ssize_t (*read) (struct file * filp, char __user * buffer, size_t    size , loff_t *  p);

这个函数用来从设备中获取数据。

ssize_t (*write) (struct file *  filp, const char __user *   buffer, size_t  count, loff_t * ppos);

发送数据给设备。

int (*open) (struct inode * inode , struct file *  filp ) ;

对设备文件进行open()系统调用时,将调用驱动程序的open()函数。该函数主要作用是确定硬件处在就绪状态、验证次设备号的合法性、控制使用设备的进程数、根据执行情况返回状态量等。

int (*release) (struct inode *, struct file *);

release()函数在文件结构被释放时引用这个操作,当最后一个打开设备的用户进程执行close()系统调用的时候,内核将调用驱动程序release()函数。release()函数的主要任务是清理未结束的输入输出操作,释放资源,用户自定义排他标志的复位等。

2.2) 驱动修改

名字什么的就不多说了,必然要改的,最大的改动在于proc_xxxx_show(),添加了寄存器读取操作,具体代码如下:

static int proc_mygpio_show(struct seq_file *p, void *v)

{

       u32 mygpio_value;

       

       mygpio_value = ioread32(base_addr);        // read out data

       seq_printf(p, "led = 0x%x ", mygpio_value);

       mygpio_value = ioread32(base_addr+0x01);        // read out data

       seq_printf(p, "switch = 0x%x ", mygpio_value);

       mygpio_value = ioread32(base_addr+0x02);        // read out data

       seq_printf(p, "button = 0x%x ", mygpio_value);

       mygpio_value = ioread32(base_addr+0x03);        // read out data

       seq_printf(p, "reg3 = 0x%x ", mygpio_value);

       return 0;

}

Ps~关于这块,我在想,既然修改的地方这么具有规律性,那么是不是能够设计出一种方法自动创建驱动程序模板,从而可以把精力集中在读写函数的实现上来?


3 测试结果:

开关这块稍微有点问题:bit3状态读取结果始终为1,改变sw位置无效,具体原因待查。Buttonled正常(测试button时,长按5个按键中间一个BTNC)。

1 错误笔记

正所谓无知者无畏,在没有完全掌握一些知识就妄下定论,是不负责任的,以后要多注意。

上一篇博文提到:“这里发现一点小问题,初始化proc_myled_opertaions.read时使用了seq_read,但在驱动程序里定义的读函数却是proc_myled_show,在实际使用时,读led状态也是失败的。所以这里的初始化应该是有问题的,下次要改掉测试一下。”最近又深入学习了驱动程序里的各个函数,发现使用seq_readproc_myled_operations.read进行初始化是正确的,是一种“套路”。


调用cat指令时,系统首先会调用proc_myled_open()函数,在open()函数内会调用proc_myled_show将读取到的数据存入seq_file。读失败的真正原因是Vivado中使用的axi-gpio IP核导致,该IP核的引脚用作输入时,需要操作方向寄存器,将引脚设为输入才可以读取到引脚状态。但我尝试操作方向寄存器总是失败,换成自己的IP,操作多个寄存器又没有问题,不知道咋回事……