
本文共 6986 字,大约阅读时间需要 23 分钟。
软件环境: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,当然是900+51啦。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,点亮ledecho 0 > /sys/class/gpio/gpio951/value 设置MIO51输出0,熄灭led
方法2.将方法1操作写到app里
批话不多说,直接上代码,很简单,也没啥好解释的。
#include#include #include 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#include #include #include #include #include #include #include #include #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 //MIO51static 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地址空间映射到内核的虚拟地址空间,然后才能操作。
#includevoid *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#include #include #include #include int main(int argc,char **argv){ int fd; int val = 1; fd = open("/dev/mio_gpio",O_RDWR); if(fd < 0) { printf("can't open!\n"); } if(argc != 2) { printf("Usage : \n"); printf("%s \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 off,mio51连接的led也能正常点亮和熄灭。
发表评论
最新留言
关于作者
