linux内核是经过Linux内核memory-mapped device驱动访问GPIO控制器的寄存器而控制HPS端用户的LED和KEY的。memory-mapped device驱动容许应用程序访问系统全部外设寄存器物理地址空间,包括GPIO控制器物理地址。GPIO 控制器的行为经过器寄存器来控制。应用程序经过内存映射设备驱动访问GPIO1控制器的寄存器。工程方块图以下:html
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 控制器控制。 linux
I/O 组引脚的行为是由 GPIO 控制器中对应的寄存器组所控制(参考Cyclone V系列中文手册第三卷22通用IO接口)。在这个例程中,只使用了 GPIO 控制器的三种 32-bit 寄存器:app
gpio_swporta_ddr: 配置 IO 引脚方向函数
gpio_swporta_dr: 写数据到输出引脚 工具
gpio_ext_porta: 从输入引脚读数据 post
对于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”则为输入。ui
gpio_swporta_dr 寄存器 data bit 和 I/O 的对应关系,和 gpio_swporta_ddr 同样,是最低位对应着 I/O 的最低位。在相应 bit 写入“1”对应 I/O 输出高电平,写入“0”对应 I/O 输出低电平。url
用户 KEY 的状态能够经过读取 gpio_ext_porta 寄存器来查询。寄存器 data bit 和 I/O的对应关系,和 gpio_swporta_ddr 同样,是最低位对应着 I/O 的最低位。寄存器 bit读值"1"说明相应 IO 输入状态为高电平,读值"0"则是低电平。spa
如图所示(图片截至Cyclone V系列中文手册),HPS 外设映射到 HPS 基地址 0xFC000000 上,共 64MB 的寻址空间。GPIO0控制器的寄存器映射到基地址 0xFF708000 共 4KB 寻址空间,GPIO2 控制器映射到基地址 0xFF70A000 共 4KB 寻址空间。设计
用户须要经过以下API访问GPIO控制器的寄存器:
一样能够经过宏指令来访问寄存器:
包含以上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"
能够在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 ) ) ) );
整个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 ); }
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 *~
定好编译规则后,打开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"
便可, 此时再从新编译便可获得可执行文件。
经过SSH或者U盘将文件传送到FPGA后(嵌入式系统软件设计),再串口端修改文件可执行属性以下:
运行程序后,能够看到FPGA闪烁两次,而后熄灭,按下HPS_KEY按键,LED会点亮。ctrl+c终止程序后,现象消失。