
软件环境vivado 2017.4 硬件平台XC7Z020网上关于EMIO不论是裸机也罢或者Linux下也罢操作的教程都非常的多也有可能是因为对EMIO可以直接对映射地址操作比较方便的原因而MIO更类似于传统Linux下操作ARM的GPIO的过程ZYNQ上了Linux系统后对MIO操作的教程比较少也是摸索着调了一下在这边分享出来。例程还是跟上一篇一样的例程。既然说是MIO的操作MIO当然要勾选了。剩下的倒没什么特别要注意的编译uboot、kernel烧写系统。方法1./sys/class/gpio/说也好笑在看资料的时候/sys/class/gpio下面mio口的编号是多少的都有902的也有、906的也有而我自己900。我自己这边实际情况是gpiochip900是MIO0 gpiochip1018是emio_led的axi_gpio地址gpiochip1022是emio_button的axi_gpio地址我这边PS的MIO51外接了一个LED就以这个LED来说明MIO在Linux的操作方法。echo 951 /sys/class/gpio/export 先导出MIO51的控制文件然后/sys/class/gpio下会多出一个gpio951文件夹及MIO51的控制目录为什么是951当然是90051啦。900是MIO0。接下来的操作与EMIO在/sys/class/leds下的操作类似。cat /sys/class/gpio/gpio951/direction 查看当前MIO51是输入还是输出 echo out /sys/class/gpio/gpio951/direction 设置MIO51为输出 echo 1 /sys/class/gpio/gpio951/value 设置MIO51输出1点亮led echo 0 /sys/class/gpio/gpio951/value 设置MIO51输出0熄灭led方法2.将方法1操作写到app里批话不多说直接上代码很简单也没啥好解释的。#include stdio.h #include stdlib.h #include fcntl.h int main() { int value, fd, direction; printf(MIO_LED test running...\n); // export the GPIO fd open(/sys/class/gpio/export, O_WRONLY); if (fd 0) { printf(Cannot open GPIO to export it\n); exit(1); } write(fd, 951, 4); close(fd); printf(MIO_LED exported successfully\n); // Update the direction of the GPIO to be an output direction open(/sys/class/gpio/gpio951/direction, O_RDWR); if (direction 0) { printf(Cannot open GPIO direction it\n); exit(1); } write(direction, out, 4); close(direction); printf(GPIO direction set as output successfully\n); // Get the GPIO value ready to be toggled value open(/sys/class/gpio/gpio951/value, O_RDWR); if (value 0) { printf(Cannot open GPIO value\n); exit(1); } printf(GPIO value opened, now toggling...\n); // toggle the GPIO as fast a possible forever, a control c is needed // to stop it while (1) { write(value,1, 2); sleep(1); write(value,0, 2); sleep(1); } }编译以后传开发板提权、运行就能看到MIO的Led闪烁了。方法3.驱动app前两种方法其实是屏蔽了驱动对寄存器的操作所以这个方法就详细说下ZYNQ-LINUX下操作GPIO寄存器的常规流也更接近实际嵌入式ARM驱动应用操作风格。先看下源码。mio_driver.c#include linux/init.h #include linux/platform_device.h #include linux/module.h #include linux/miscdevice.h #include linux/ioport.h #include linux/of.h #include linux/fs.h #include asm/io.h #include linux/uaccess.h #define DEVICE_NAME mio_gpio #define MY_GPIO_BASE_ADDR 0xE000A000 //MIO基址 #define XGPIOPS_DIRM_OFFSET 0x00000244U //方向 #define XGPIOPS_OEN_OFFSET 0x00000248U //使能 #define XGPIOPS_DATA_MSW_OFFSET 0x0000000CU //输出 #define SLCR_BASE_ADDR 0xF8000000 #define APER_CLK_OFFSET 0x0000012C #define MIO_PIN_OFFSET 0x000007CC //MIO51 static int gpio_driver_major; static struct class* gpio_driver_class NULL; static struct device* gpio_driver_device NULL; volatile unsigned long *Gpio_DIR NULL; volatile unsigned long *Gpio_EN NULL; volatile unsigned long *Gpio_DATA NULL; volatile unsigned long *DATA NULL; volatile unsigned long *CLK NULL; volatile unsigned long *MIN_PIN_51 NULL; static int gpio_open(struct inode * inode,struct file * filp) { iowrite32(0x3200,MIN_PIN_51); //1.8v 上拉 iowrite32(ioread32(CLK)|0x400000,CLK); //gpio时钟使能 iowrite32(ioread32(Gpio_DIR)|0x80000,Gpio_DIR); iowrite32(ioread32(Gpio_EN)|0x80000,Gpio_EN); //printk(GPIO_DIR_ADDR %x DATA %x \n,Gpio_DIR,ioread32(Gpio_DIR)); //printk(GPIO_EN_ADDR %x DATA %x \n,Gpio_EN,ioread32(Gpio_EN)); //printk(CLK_ADDR %x DATA %x \n,CLK,ioread32(CLK)); return 0; } static ssize_t gpio_write(struct file *file,const char __user *buf,size_t count,loff_t * ppos) { int val; int state; state copy_from_user(val,buf,count); if(val 1) { printk(mio_51 led on\n); iowrite32(0xfff70008,Gpio_DATA); } else if(val 0) { printk(mio_51 led off\n); iowrite32(0xfff70000,Gpio_DATA); } return 0; } static struct file_operations gpio_drv_fops { .owner THIS_MODULE, .open gpio_open, .write gpio_write, }; int major; static int __init gpio_drv_init(void) { printk(mio_drv_init \n); major register_chrdev(0,mio_gpio,gpio_drv_fops); if(major 0){ printk(failed to register device.\n); return -1; } gpio_driver_class class_create(THIS_MODULE,mio_gpio); if(IS_ERR(gpio_driver_class)){ printk(failed to create mio_gpio moudle class.\n); unregister_chrdev(major,mio_gpio); return -1; } gpio_driver_device device_create(gpio_driver_class,NULL,MKDEV(major,0),NULL,mio_gpio); if(IS_ERR(gpio_driver_device)){ printk(failed to create device.\n); unregister_chrdev(major,mio_gpio); return -1; } //映射 Gpio_DIR (volatile unsigned long *)ioremap(MY_GPIO_BASE_ADDR XGPIOPS_DIRM_OFFSET,4); Gpio_EN (volatile unsigned long *)ioremap(MY_GPIO_BASE_ADDR XGPIOPS_OEN_OFFSET,4); Gpio_DATA (volatile unsigned long *)ioremap(MY_GPIO_BASE_ADDR XGPIOPS_DATA_MSW_OFFSET,4); CLK (volatile unsigned long *)ioremap(SLCR_BASE_ADDR APER_CLK_OFFSET,4); //ug585 1587 MIN_PIN_51 (volatile unsigned long *)ioremap(SLCR_BASE_ADDR MIO_PIN_OFFSET,4); //ug585 1683 return 0; } static void __exit gpio_drv_exit(void) { printk(mio_drv_exit \n); device_destroy(gpio_driver_class,MKDEV(major,0)); class_unregister(gpio_driver_class); class_destroy(gpio_driver_class); unregister_chrdev(major,mio_gpio); iounmap(Gpio_DIR); iounmap(Gpio_EN); iounmap(Gpio_DATA); iounmap(CLK); iounmap(MIN_PIN_51); } module_init(gpio_drv_init); module_exit(gpio_drv_exit); MODULE_AUTHOR(Joker); MODULE_DESCRIPTION(MIO_LED moudle dirver); MODULE_VERSION(v1.0); MODULE_LICENSE(GPL);驱动结构和写法大体上都是固定的具体还是想说说寄存器地址在哪找、怎么设置手册UG585附录B(P793)可以看到GPIO基址0xE000A000点进去以后可以看到有很多GPIO相关寄存器及他们的偏移量。设计MIO控制的实际也就是方向寄存器、使能寄存器、数据寄存器依次来看首先说方向寄存器。要看清楚想要操作的MIO标号在不在这个寄存器控制的范围内如果在看下寄存器偏移是多少要写什么值进去。可以看到对于方向寄存器来说写0是输入写1是输出。接下来说下使能寄存器。写1使能写0失能。最后数据寄存器。数据寄存器呢就要多逼逼两句因为这个32bit寄存器里高16bit是掩码低16bit才是数据掩码使能后数据才有效而这个寄存器里只管着MIO[53:48]所以只有[5:0]和[21:16]有用。GPIO方向、使能和数据基址是在0xE000A000而电平标准和时钟使能是在SLCR寄存器0xF8000000偏移量和设置方法与上面类似而且代码中宏定义部分都有给出就不啰嗦了。寄存器的读取使用ioread32(地址)写入使用iowrite32(值地址)。需要注意的是需要用ioremap将IO地址空间映射到内核的虚拟地址空间然后才能操作。#include io.h void *ioremap(unsigned long phys_addr, unsigned long size) phys_addr要映射的起始的IO地址 size是要映射的长度单位是字节从mio_driver程序中可以看到在驱动初始化时候注册了设备、做了IO映射如果设备注册成功会在/dev下生成mio_driver驱动模块在应用加载mio_driver时设置了mio_51上拉、时钟使能、输出、使能然后根据应用的调用情况将使mio_51通过iowrite32输出1或者0。mio_app.c#include sys/types.h #include sys/stat.h #include fcntl.h #include stdio.h #include string.h int main(int argc,char **argv) { int fd; int val 1; fd open(/dev/mio_gpio,O_RDWR); if(fd 0) { printf(cant open!\n); } if(argc ! 2) { printf(Usage : \n); printf(%s on|off\n,argv[0]); return 0; } if(strcmp(argv[1],on) 0) { val 1; } else if(strcmp(argv[1],off) 0) { val 0; } write(fd,val,4); return 0; }应用就很简单了根据运行时候带的on或者off参数使mio_51输出高或者低从而点亮或者熄灭mio_51连接的led。可以看到实际运行结果与预期一致在mio_driver加载以后在/dev目录下出现了mio_gpio驱动应用调用时候也能正常输出led on和led offmio51连接的led也能正常点亮和熄灭。