软件环境: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,点亮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("can't 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 off,mio51连接的led也能正常点亮和熄灭。