通过HPS控制FPGA的GPIO
1、学习目的
本例程主要是让 SoC FPGA 初学者了解 HPS/ARM 如何跟 FPGA 交互。“My First HPS-FPGA”工程演示了实现方法的细节。这个工程包括 Quartus II 工程和 ARM C 工程,它演示了 HPS/ARM 是如何去控制 FPGA 端的 LED。
2、关于DE1-SOC的AXI总线(详细AXI总线协议看链接,这里不做详细介绍)
在 Altera SoC FPGA 中,HPS 和 FPGA 之间的协议通信主要是通过 AXI -bridge. AXI bridge 是 FPGA 和 HPS之间数据交互的接口总线,它包括 FPGA-to-HPS AXI、HPS-to-FPGA AXI 和 Light-weight HPS-to-FPGA AXI。
AXI(Advanced eXtensible Interface)是一种总线协议,该协议是ARM公司提出的AMBA(Advanced Microcontroller Bus Architecture)3.0协议中最重要的部分,是一种面向高性能、高带宽、低延迟的片内总线。它的地址/控制和数据相位是分离的,支持不对齐的数据传输,同时在突发传输中,只需要首地址,同时分离的读写数据通道、并支持Outstanding传输访问和乱序访问,并更加容易进行时序收敛。
在Altera SOC FPGA中,HPS 作为主端(master),其可以访问 FPGA 端 Avalon MM slave 接口的所有组件。 HPS
作为主端时的 AXI-bridge 包括:
- HPS-to-FPGA Bridge
- Lightweight HPS-to-FPGA Bridge
FPGA 作为主端时的 AXI-bridge 包括:
- FPGA-to-HPS Bridge
下图FPGA 架构和连接到 HPS 的 L3 Switch 的 AXI 桥的方块图,每个主端(M)和从端(S)都标示出了个字的位宽。
可以看出:
- FPGA-to-HPS Bridge
- 地址位宽时32-bit,数据位宽32-bit/64-bit/128-bit用户可以自行设置,ID时8-bit
- HPS-to-FPGA Bridge
- 址位宽时30-bit,数据位宽32-bit/64-bit/128-bit用户可以自行设置,ID时128-bit。有0x3FFF0000,接近1G 的寻址空间。
- Lightweight HPS-to-FPGA Bridge
- 地址位宽时21-bit,数据位宽只有32-bit,ID时8-bit。寻址空间只有2M,适合数据量不大、速度不快的数据传输。
HPS-to-FPGA 桥是被 level 3(L3) main switch 掌控,轻量级 HPS-to-FPGA 桥是被 L3slave peripheral switch 掌控。在这个 Quartus II 演示程序中,HPS-to-FPGA 被 ARM/HPS用来控制 FPGA 端的 LEDs。
FPGA-to-HPS bridge 也可以作主端控制 L3 main switch,允许 FPGA 端的主端访问大部分的 HPS 从端。比如,FPGA 资源可以通过 FPGA-to-HPS 桥可以访问到 HPS 端的加速度传感器。
3、项目目的
设计实现基于ARM的linux应用程序控制FPGA端的PIO控制器pio_led。pio连接到HPS/ARM linghtweitht axi bridge从而获得在HPS/ARM总线上的物理地址空间。linux 应用程序通过 linux 内核 memory-mapped device 驱动访问 PIO 控制器 pio_led 的寄存器物理地址进而控制 pio_led 进行相应动作。Altera SoCEDS 用来编译应用程序。
4、项目组成
显然,我们的工程由两个部分组成
- 带HPS的FPGA工程(这里我们采用HPS基本概念及其设计文章中创建的工程)
- 我们自己编写的相关HPS软件工程及与FPGA相关生成的头文件
5、生成HPS头文件
linux控制pio_led组件需要用到pio_led组件的属性信息,但是我们的pio_led是由QSYS创建添加的,所有我们需要用一个脚本生成对应的头文件来提供给Linux调用。
在工程根目录下创建脚本,脚本名为generate_hps_qsys_header.sh。如图所示
在其内写下如下指令
#!/bin/sh
sopc-create-header-files \ #命令
"./som_hps.sopcinfo" \ #qsys文件--当前文件下
--single hps_0.h \ #要生成的文件
--module hps_som #模块名
运行 Altera SoC EDS command shell通过 shell 命令 cd 定位到 QuartusII 工程文件夹根目录。输入‘./generate_hps_qys_header.sh”并按 Enter 键执行,成功执行后,会生出名为 hps_0.h 的头文件。
打开生成的hps_0.h头文件,发现 在头文件中,包含 pio_led 在 Qsys 中分配的相对于 lwaxi 的基地址,它表现为一个宏定义 PIO_LED_BASE;pio_led 的位宽信息表示为宏定义PIO_LED_DATA_WIDTH.这两个参数将会是应用程序访问 pio_led 寄存器所需要的。
6、编写HPS程序
新建一个文件夹,将hps_0.h文件复制剪切到文件夹下,然后编写main.c程序。这里对mian.c程序的几个要点进行下讲解。
1、映射pio_led地址
得到物理地址后,我们还需将 pio_led 的物理地址映射成应用程序可以访问的虚拟地址。 下列程序展示了 C 应用程序从 pio_led 基地址转换出虚拟地址。
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 );
} h2p_lw_led_addr=virtual_base + ( ( unsigned long )( ALT_LWFPGASLVS_OFST + PIO_LED_BASE ) & ( unsigned long)( HW_REGS_MASK ) );
首先,系统调用函数 open用来打开 memory 设备驱动“/dev/mem”,然后用系统调用函数 mmap 映射 HPS 的 L3 外设区域物理地址到虚拟地址并表示为一个空指针变量 virtual_base.然后可以通过virtual_base 增加如下两个偏移地址计算得出 pio_led 的虚拟地址。
- 轻量级 HPS-to-FPGA AXI 总线相对于 HPS 的 L3 外设区域基地址的偏移地址
- pio_led 相对于轻量级 HPS-to-FPGA AXI 总线的偏移地址
第一个偏移地址在 hps.h 中被宏定义为 ALT_LWFPGASLVS_OFST。
第二个偏移地址是 pio_led 在 Qsys 中分配的相对 lwaxi 的基地址,这个在之前提到的头文件 hps_0.h 中,被宏定义为 PIO_LED_BASE。
pio_led 的虚拟地址被定义为空指针 h2p_lw_led_addr. 应用程序可以直接用这个指针变量访问 pio_led 控制器的寄存器。
2、LED控制
在控制 led 之前需要理解 PIO 控制器 pio_led 的寄存器映射。PIO 控制器的映射关系图如下。 每个寄存器都是 32 位宽度的。更详细的信息请参考 PIO 控制器的datasheet。
对于 LED 控制,我们仅仅需要写输出值到偏移地址为 0 的寄存器。由于DE1-SoC 上的 LED 是高电平有效,所以写 0x00000000 到偏移地址为 0 的寄存器,十个红色 LED 将会熄灭。写 0x000003ff 到偏移地址为 0 的寄存器,十个红色 LED 将会亮起。
在 本例的C 程序中,写值到偏移地址为 0 的寄存器的 C 语言表达式为:
loop_count = 0;
led_mask = 0x01;
led_direction = 0; // 0: left to right direction
while( loop_count < 60 ) { // control led, add ~ because the led is low-active
*(uint32_t *)h2p_lw_led_addr = ~led_mask; // wait 100ms
usleep( 100*1000 ); // update led mask
if (led_direction == 0){
led_mask <<= 1;
if (led_mask == (0x01 << (PIO_LED_DATA_WIDTH-1)))
led_direction = 1;
}else{
led_mask >>= 1;
if (led_mask == 0x01){
led_direction = 0;
loop_count++;
}
} } // while
其中*(uint32_t *)h2p_lw_led_addr = ~led_mask;会将空指针转换成无符号32位整型指针。所以C编译器知道是写32位值到h2p_lw_led_addr虚拟地址。
3、完整的main.c程序如下所示
#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"
#include "hps_0.h" #define HW_REGS_BASE ( ALT_STM_OFST )
#define HW_REGS_SPAN ( 0x04000000 )
#define HW_REGS_MASK ( HW_REGS_SPAN - 1 ) int main() { void *virtual_base;
int fd;
int loop_count;
int led_direction;
int led_mask;
void *h2p_lw_led_addr; // 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 );
} h2p_lw_led_addr=virtual_base + ( ( unsigned long )( ALT_LWFPGASLVS_OFST + PIO_LED_BASE ) & ( unsigned long)( HW_REGS_MASK ) ); // toggle the LEDs a bit loop_count = 0;
led_mask = 0x01;
led_direction = 0; // 0: left to right direction
while( loop_count < 60 ) { // control led, add ~ because the led is low-active
*(uint32_t *)h2p_lw_led_addr = ~led_mask; // wait 100ms
usleep( 100*1000 ); // update led mask
if (led_direction == 0){
led_mask <<= 1;
if (led_mask == (0x01 << (PIO_LED_DATA_WIDTH-1)))
led_direction = 1;
}else{
led_mask >>= 1;
if (led_mask == 0x01){
led_direction = 0;
loop_count++;
}
} } // while // 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 );
}
4、对应的Makefile规则如下
#
TARGET = my_first_hps-fpga #
CROSS_COMPILE = arm-linux-gnueabihf-
CFLAGS = -static -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 *~
7、编译工程及实验现象
同样,打开Altera Embedded Command Shell,到工程目录下,编译hps工程如下所示。
首先,打开quartus II下载之前HPS基本概念及其设计工程的.sof文件到FPGA
然后将Hps生成的可执行文件,复制到linux系统中,并运行。
可看到FPGA侧的10个led等依次闪烁。并在执行60次后停止。
通过HPS控制FPGA的GPIO的更多相关文章
- 通过HPS控制FPGA端的GPIO
该笔记主要记录HPS端如何通过AXI Bridge控制FPGA端口的GPIO,主要是如何操作FPGA侧的Led 1.AXI Bridge AXIB主要包括H2FB.F2HB.LWH2F ...
- HPS端如何通过AXI Bridge控制FPGA端口的GPIO
该笔记主要记录HPS端如何通过AXI Bridge控制FPGA端口的GPIO,主要是如何操作FPGA侧的Led 1.AXI Bridge AXIB主要包括H2FB.F2HB.LWH2F ...
- 关于HPS和FPGA之间的桥接学习笔记一
为了实现FPGA和HPS之间的存储器共享和数据传输,Altera SoC FPGA提供了两种方式用于FPGA和HPS通信.分别是FPGA to SDRAM和AXI bridge. FPGA to SD ...
- linux驱动初探之杂项设备(控制两个GPIO口)
关键字:linux驱动.杂项设备.GPIO 此驱动程序控制了外接的两个二极管,二极管是低电平有效. 上一篇博客中已经介绍了linux驱动程序的编写流程,这篇博客算是前一篇的提高篇,也是下一篇博客(JN ...
- 基于boa服务器的web控制mini2440的GPIO口
win7 系统 虚拟机:ubuntu12.04 开发板:mini2440 上一篇已经详细的讲解了如何配置boa服务器,在这里我们就要利用boa服务器带来的便利,利用web控制开发板上的GIPO口,这 ...
- FPGA开发随笔汇总
点击标题即可进入相关随笔. DE-SOC开发板VrilogHDL开发相关部分: (本过程需要Verilog HDL 的基本语言基础) 1.FPGA的发展史及FPGA 的基础架构 2.首先看一下友晶DE ...
- HPS基本概念及其设计
DE1-SOC开发版上的FPGA在一个基于ARM的用户定制系统(SOC)中集成了分立处理器(HPS).FPGA和数字信号处理(DSP)功能.HPS是基于ARM cortex-A9双核处理器,具有丰富的 ...
- 可编程逻辑(FPGA)与硬核处理器(HPS)之间互联的结构
本周我想进一步探究可编程逻辑(FPGA)与硬核处理器(HPS)之间互联的结构.我发现了三种主要方式,它们是如何映射并处理通信的,哪些组件需要管控时序并且有访问权限. AXI Bridge 为了能够实现 ...
- Kernel 中的 GPIO 定义和控制
最近要深一步用到GPIO口控制,写个博客记录下Kernel层的GPIO学习过程! 一.概念 General Purpose Input Output (通用输入/输出)简称为GPIO,或 总线扩展器. ...
随机推荐
- mysql数据库存中文字段
mysql数据默认编码是拉丁,而我们更多的使用utf8, 在创建库的时候执行参数即可: CREATE DATABASE IF NOT EXISTS yourdbname DEFAULT CHARSET ...
- T-SQL中CTE表 with关键字
Select字句在逻辑上是SQL语句最后进行处理的最后一步,所以,以下查询会发生错误: SELECT YEAR(OrderDate) AS OrderYear, COUNT(DISTINCT Cust ...
- mysql 索引type介绍
以下全部详细解析explain各个属性含义: 各属性含义: id: 查询的序列号 select_type: 查询的类型,主要是区别普通查询和联合查询.子查询之类的复杂查询 SIMPLE:查 ...
- linux显示历史命令history
history history//显示历史命令
- 通过scp拷贝文件时无需交互输入密码
工作中经常需要把一些文件从一个服务器传输到另一台服务器,linux环境下最习惯的方式当然是scp,但是scp需要交互输入密码有时候觉得麻烦,记录几种无需手动输入密码的方法. 方法一:建立SSH互信 此 ...
- MySQL InnoDB引擎B+树索引简单整理说明
本文出处:http://www.cnblogs.com/wy123/p/7211742.html (保留出处并非什么原创作品权利,本人拙作还远远达不到,仅仅是为了链接到原文,因为后续对可能存在的一些错 ...
- c++冒号作用
转自http://www.360doc.com/content/13/0605/11/3373961_290615318.shtml 1.冒号(:)用法 (1)表示机构内位域的定义(即该变量占几个bi ...
- R语言-增加图例
legend()函数 > plot(rain$Tokyo,type="l",col="red", + ylim=c(0,300), + main=&quo ...
- 【VBA】杨辉三角
Private Sub Workbook_Open() Dim loopA As Integer Dim loopB As Integer Dim loopNum As Integer Dim top ...
- Django的rest_framework的视图之基于ModelViewSet视图源码解析
前言 今天一直在整理Django的rest_framework的序列化组件,前面一共写了2篇博客,前面的博客给的方案都是一个中间的状态的博客,其中有很多的冗余的代码,如果有朋友不清楚,可以先看下我前面 ...