1、 HPS GPIO原理

1、功能方块图

  linux内核是通过Linux内核memory-mapped device驱动访问GPIO控制器的寄存器而控制HPS端用户的LED和KEY的。memory-mapped device驱动允许应用程序访问系统所有外设寄存器物理地址空间,包括GPIO控制器物理地址。GPIO 控制器的行为通过器寄存器来控制。应用程序通过内存映射设备驱动访问GPIO1控制器的寄存器。工程方块图如下:

2、GPIO接口图

HPS 提供了三个通用 I/O(GPIO)接口模块。 下图 是 GPIO 接口的方块图(图片截至DE1-SoC_v.3.1.3_HWrevC_revD_SystemCD\UserManual)。GPIO[28..0]被 GPIO0 控制器控制;GPIO[57..29]被 GPIO1 控制器控制;GPIO[70..58]和input-onlyGPI[13..0]被 GPIO2 控制器控制。

  

3、GPIO寄存器组

I/O 组引脚的行为是由 GPIO 控制器中对应的寄存器组所控制(参考Cyclone V系列中文手册第三卷22通用IO接口)。在这个例程中,只使用了 GPIO 控制器的三种 32-bit 寄存器:

 gpio_swporta_ddr: 配置 IO 引脚方向

  gpio_swporta_dr: 写数据到输出引脚

  gpio_ext_porta: 从输入引脚读数据

  对于LED 控制,我们通过 gpio_swporta_ddr 寄存器配置 LED 引脚为输出引脚并且通过gpio_swporta_dr 寄存器控制其输出高低电平。在 gpio_swporta_ddr 寄存器中,32bitsdata 的第一位(影响最小的位,LSB) 控制相应 GPIO 控制器的第一个 I/O 引脚的方向,第二位控制相应 GPIO 控制器第 2 个 I/O 引脚的方向,以此类推。在寄存器 bit 设定“1”则相应 I/O 方向设定为输出,设定“0”则为输入。

  gpio_swporta_dr 寄存器 data bit 和 I/O 的对应关系,和 gpio_swporta_ddr 一样,是最低位对应着 I/O 的最低位。在相应 bit 写入“1”对应 I/O 输出高电平,写入“0”对应 I/O 输出低电平。

  用户 KEY 的状态可以通过读取 gpio_ext_porta 寄存器来查询。寄存器 data bit 和 I/O的对应关系,和 gpio_swporta_ddr 一样,是最低位对应着 I/O 的最低位。寄存器 bit读值"1"说明相应 IO 输入状态为高电平,读值"0"则是低电平。

4、 GPIO 寄存器地址映射

  如图所示(图片截至Cyclone V系列中文手册),HPS 外设映射到 HPS 基地址 0xFC000000 上,共 64MB 的寻址空间。GPIO0控制器的寄存器映射到基地址 0xFF708000 共 4KB 寻址空间,GPIO2 控制器映射到基地址 0xFF70A000 共 4KB 寻址空间。

5、 软件API(软件程序可以直接从de1_soc_training\de1_soc_training\lab\SW\de1_soc_sw_lab2中找到,但这里还是做一个简要介绍以及不同版本的一些问题)

  用户需要通过如下API访问GPIO控制器的寄存器:

  • open:打开内存映射设备驱动。
  • mmap:映射物理地址到用户空间。
  • alt_read_word:从指定寄存器读取一个值。
  • alt_write_word:写一个值到指定寄存器。
  • munmap:清除内存映射。

同样可以通过宏指令来访问寄存器:

  • alt_setbits_word:设定指定寄存器的指定位为1.
  • alt_clrbits_wod:设定指定寄存器的指定位位0.

包含以上API的头文件为:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include "hwlib.h"
#include "socal/socal.h"
#include "socal/hps.h"
#include "socal/alt_gpio.h"

  

6、LED和KEY控制

  可以在DE1-SoC_v.3.1.3_HWrevC_revD_SystemCD\Schematic中查看开发板原理图知道,HPS_GPIO54和HPS_GPIO53分别连接的是HPS_KEY 和HPS_LED.如下图所示:

     这两个引脚都是被 GPIO1 控制器控制,同样它还控制着HPS_GPIO29~HPS_GPIO57。

    下图是gpio_swporta_ddr寄存器,bit-0 控制着 HPS_GPIO29 的方向。bit-24 控制着 HPS_GPIO53 的方向,这个引脚连接着 HPS_LED;bit-25 控制HPS_GPIO54 的方向,这个引脚连接 HPS_KEY。其它引脚以此类推。总言之,GPIO1 控制器的寄存器 gpio_swporta_ddr 的 bit-24,bit-25 控制 HPS_LED,HPS_KEY 的方向。相似的,HPS_LED 的输出状态是通过 GPIO1 控制器的 gpio_swporta_dr 的 bit-24 控制的。HPS_KEY 的状态则可以通过查询读取 GPIO1 控制器的 gpio_ext_porta 寄存器的 bit-25。

下面是相关寄存器定义和配置程序:

#define USER_IO_DIR     (0x01000000)
#define BIT_LED (0x01000000)
#define BUTTON_MASK (0x02000000)

下列程序用来配置LED为输出引脚:

alt_setbits_word( ( virtual_base + ( ( uint32_t )( ALT_GPIO1_SWPORTA_DDR_ADDR ) & ( uint32_t )( HW_REGS_MASK ) ) ), USER_IO_DIR );

下列语句可以点亮LED

alt_setbits_word( ( virtual_base +( ( uint32_t )( ALT_GPIO1_SWPORTA_DR_ADDR ) &( uint32_t )( HW_REGS_MASK ) ) ), BIT_LED );

如下语句可以用来读取

alt_read_word( ( virtual_base + ( ( uint32_t )(  ALT_GPIO1_EXT_PORTA_ADDR ) & ( uint32_t )( HW_REGS_MASK ) ) ) );

  

7、创建工程文件

  整个main.c函数文本如下:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include "hwlib.h"
#include "socal/socal.h"
#include "socal/hps.h"
#include "socal/alt_gpio.h" #define HW_REGS_BASE ( ALT_STM_OFST )
#define HW_REGS_SPAN ( 0x04000000 )
#define HW_REGS_MASK ( HW_REGS_SPAN - 1 ) #define USER_IO_DIR (0x01000000)
#define BIT_LED (0x01000000)
#define BUTTON_MASK (0x02000000) int main(int argc, char **argv) { void *virtual_base;
int fd;
uint32_t scan_input;
int i;
// map the address space for the LED registers into user space so we can interact with them.
// we'll actually map in the entire CSR span of the HPS since we want to access various registers within that span
if( ( fd = open( "/dev/mem", ( O_RDWR | O_SYNC ) ) ) == -1 ) {
printf( "ERROR: could not open \"/dev/mem\"...\n" );
return( 1 );
} virtual_base = mmap( NULL, HW_REGS_SPAN, ( PROT_READ | PROT_WRITE ), MAP_SHARED, fd, HW_REGS_BASE ); if( virtual_base == MAP_FAILED ) {
printf( "ERROR: mmap() failed...\n" );
close( fd );
return( 1 );
}
// initialize the pio controller
// led: set the direction of the HPS GPIO1 bits attached to LEDs to output
alt_setbits_word( ( virtual_base + ( ( uint32_t )( ALT_GPIO1_SWPORTA_DDR_ADDR ) & ( uint32_t )( HW_REGS_MASK ) ) ), USER_IO_DIR );
printf("led test\r\n");
printf("the led flash 2 times\r\n");
for(i=0;i<2;i++)
{
alt_setbits_word( ( virtual_base + ( ( uint32_t )( ALT_GPIO1_SWPORTA_DR_ADDR ) & ( uint32_t )( HW_REGS_MASK ) ) ), BIT_LED );
usleep(500*1000);
alt_clrbits_word( ( virtual_base + ( ( uint32_t )( ALT_GPIO1_SWPORTA_DR_ADDR ) & ( uint32_t )( HW_REGS_MASK ) ) ), BIT_LED );
usleep(500*1000);
}
printf("user key test \r\n");
printf("press key to control led\r\n");
while(1){
scan_input = alt_read_word( ( virtual_base + ( ( uint32_t )( ALT_GPIO1_EXT_PORTA_ADDR ) & ( uint32_t )( HW_REGS_MASK ) ) ) );
//usleep(1000*1000);
if(~scan_input&BUTTON_MASK)
alt_setbits_word( ( virtual_base + ( ( uint32_t )( ALT_GPIO1_SWPORTA_DR_ADDR ) & ( uint32_t )( HW_REGS_MASK ) ) ), BIT_LED );
else alt_clrbits_word( ( virtual_base + ( ( uint32_t )( ALT_GPIO1_SWPORTA_DR_ADDR ) & ( uint32_t )( HW_REGS_MASK ) ) ), BIT_LED );
}
// clean up our memory mapping and exit
if( munmap( virtual_base, HW_REGS_SPAN ) != 0 ) {
printf( "ERROR: munmap() failed...\n" );
close( fd );
return( 1 );
}
close( fd );
return( 0 );
}

  

8、创建Makefile文件

  Makefile文件如下:

#
TARGET = hps_gpio #
CROSS_COMPILE = arm-linux-gnueabihf-
CFLAGS = -g -Wall -I ${SOCEDS_DEST_ROOT}/ip/altera/hps/altera_hps/hwlib/include
LDFLAGS = -g -Wall
CC = $(CROSS_COMPILE)gcc
ARCH= arm build: $(TARGET) $(TARGET): main.o
$(CC) $(LDFLAGS) $^ -o $@ %.o : %.c
$(CC) $(CFLAGS) -c $< -o $@ .PHONY: clean
clean:
rm -f $(TARGET) *.a *.o *~

  

9、编译文件

  定好编译规则后,打开Altera Embedded Command Shell 工具,cd到工程目录下:

编译过程中发现两个错误。这都是因为quartus版本不同而造成的。

错误1:

教材用的是13版本的quartus,这里我用到的是17.0的quartus II, make时提示在hwlib.h之前确认SOC—a10还是SOC—AV—cv?

解决方法:

在EDS的安装路径下,找到对应的hwlib.h,加上#define soc_cv_av(因为这里用到的是cyclone V).

增加定义后,修改再次make 会发现第一个错误已经解决了。

错误2:

  为了解决错误2,这里又重新安装了一次13.0版本的EDS,对照后发现EDS可以顺利编译,到makefile路径下观察可以发现。

同样的,到达我安装的17.0版本下的路径观察却发现

果然,点进socal_cv_av可以发现原来一样的socal文件夹:

解决方法:

  问题找到了,解决方法也就有了,最简单的方法是,将soc_cv_av以及socal下的所有文件复制到include界面下

并修改头文件为:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include "hwlib.h"
#include "socal.h"
#include "hps.h"
#include "alt_gpio.h"

  即可, 此时再重新编译即可得到可执行文件。

10、运行结果

  通过SSH或者U盘将文件传送到FPGA后(嵌入式系统软件设计),再串口端修改文件可执行属性如下:

  运行程序后,可以看到FPGA闪烁两次,然后熄灭,按下HPS_KEY按键,LED会点亮。ctrl+c终止程序后,现象消失。

Cyclone V HPS存储器映射

通过linux核映射驱动访问GPIO的更多相关文章

  1. Linux内核驱动之GPIO子系统(一)GPIO的使用【转】

    转自:http://blog.csdn.net/tommy_wxie/article/details/9427047 一 概述 Linux内核中gpio是最简单,最常用的资源(和 interrupt  ...

  2. Linux内核驱动之GPIO子系统(一)GPIO的使用

    转自:http://blog.csdn.net/mirkerson/article/details/8464290 一 概述 Linux内核中gpio是最简单,最常用的资源(和 interrupt , ...

  3. Linux内核驱动之GPIO子系统API接口概述

    1.前言 在嵌入式Linux开发中,对嵌入式SoC中的GPIO进行控制非常重要,Linux内核中提供了GPIO子系统,驱动开发者在驱动代码中使用GPIO子系统提供的API函数,便可以达到对GPIO控制 ...

  4. Linux驱动之GPIO子系统和pinctrl子系统

    前期知识   1.如何编写一个简单的Linux驱动(一)--驱动的基本框架   2.如何编写一个简单的Linux驱动(二)--设备操作集file_operations   3.如何编写一个简单的Lin ...

  5. linux UART串口驱动开发文档

    转:http://www.360doc.com/content/10/0417/18/829197_23519037.shtml linux UART串口驱动开发文档时间:2010-01-09 14: ...

  6. Linux 视频设备驱动V4L2最常用的控制命令

    http://blog.csdn.net/shaolyh/article/details/6583226 Linux 视频设备驱动V4L2最常用的控制命令使用说明(1.02) 命令 功能 VIDIOC ...

  7. linux enc28j60网卡驱动移植(硬件spi和模拟spi)

    本来想移植DM9000网卡的驱动,无奈硬件出了点问题,通过杜邦线链接开发板和DM9000网卡模块,系统上电,还没加载网卡驱动就直接崩溃了,找不到原因...刚好手上有一个enc28j60的网卡模块,于是 ...

  8. 深入理解Linux字符设备驱动

    文章从上层应用访问字符设备驱动开始,一步步地深入分析Linux字符设备的软件层次.组成框架和交互.如何编写驱动.设备文件的创建和mdev原理,对Linux字符设备驱动有全面的讲解.本文整合之前发表的& ...

  9. Linux字符设备驱动file_operations

    struct _file_operations struct _file_operations在Fs.h这个文件里面被定义的,如下所示: struct file_operations { struct ...

随机推荐

  1. python-web自动化-元素定位

    # -*- coding:utf-8 -*- from selenium import webdriver from selenium.webdriver.common.by import By # ...

  2. python中定时任务

    今天看网络框架时,突然想看一下定时器,于是往上搜索了一下python中timer task的实现,但是由于python本身对线程的支持不是太好,因为全局排它锁的存在,使得多线程在访问资源时效率比较低. ...

  3. win10 linux 子系统 所在 目录

    C:\Users\用户名\AppData\Local\Packages\CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc\LocalState\r ...

  4. hadoop distcp hdfs://ns1/aaa hdfs://ns8/bbb UnknownHostException: xxx 两个高可用(ha)集群间distcp 如何识别两个集群逻辑名称

    在要执行distcp 的客户端配置添加 dfs.internal.nameservices 指local service 就是client 所在的hadoop 的逻辑名称 <!-- servic ...

  5. alias命令详解:给命令设置别名

    给命令设置别名,你可以把它当作命令的"小名",但是这样做有什么意义呢? 比如笔者刚接触 Linux 时,使用的编辑器是 Vi,但是现在 Vim 的功能明显比 Vi 的功能更加强大, ...

  6. 吴裕雄 python深度学习与实践(7)

    import cv2 import numpy as np img = np.mat(np.zeros((,))) cv2.imshow("test",img) cv2.waitK ...

  7. python入门(二):isinstance、内置函数、常用运算等

    1.    isinstance(变量名,类型)                           #判断什么类型 ps: 只支持输入两个参数,输入3个参数会报错 >>> isin ...

  8. Java 运行时常量池

    运行时常量池是方法区的一部分.class中除了有类的版本,字段,方法,接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放在方法区的运行时常量池 ...

  9. mysql linux安装

    Mysql(使用版本5.7.25) 1.  检查是否已安装 #rpm -qa|grep -i mysql 2.  下载安装包 网址:https://dev.mysql.com/downloads/my ...

  10. Python基础-python数据类型(四)

    python数据类型 在python中,变量就是变量,它没有类型,我们所说的类型是变量所指的内存中对象的类型. python中的数据类型: 1.数字 python中没有专门定义常量的方式,通常使用大写 ...