【JokerのZYNQ7020】LINUX_MIO_LED。

软件环境: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也能正常点亮和熄灭。 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值