当前位置:   article > 正文

zynq linux内核驱动编写,【原创】Linux下驱动Zynq GPIO (Switch、button、led)

switch gpio

版权声明:

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

博客官方地址:

1硬件设计:

1.1) Vivado Block Design

1.1.1)设计自主AXI-Lite IP核

关键逻辑如下:

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

635947911742290000520854.jpg

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

635947911755580000717599.jpg

1.1.2)添加IP核至block design

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

635947911767740000753098.jpg

1.1.3)编写约束文件

led、switch和button所用的引脚都需要约束。使用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的物理地址一定要改!

635947911813270000188114.jpg

635947911828230000264290.jpg

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位置无效,具体原因待查。Button和led正常(测试button时,长按5个按键中间一个BTNC)。

635947911840430000042680.jpg

附1:错误笔记

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

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/寸_铁/article/detail/744629
推荐阅读
相关标签
  

闽ICP备14008679号