【JokerのZYNQ7020】LINUX_MIO_LED。
发布日期:2021-05-06 21:36:43 浏览次数:25 分类:技术文章

本文共 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地址空间映射到内核的虚拟地址空间,然后才能操作。

#include 
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 
#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也能正常点亮和熄灭。 

上一篇:【JokerのZYNQ7020】LINUX_EMIO_BUTTON。
下一篇:【JokerのZYNQ7020】LINUX_EMIO_LED(mmap、ioremap方式)。

发表评论

最新留言

网站不错 人气很旺了 加油
[***.192.178.218]2025年03月14日 00时31分41秒