經過HPS控制FPGA的GPIO

一、學習目的

  本例程主要是讓 SoC FPGA 初學者瞭解 HPS/ARM 如何跟 FPGA 交互。「My First HPS-FPGA」工程演示了實現方法的細節。這個工程包括 Quartus II 工程和 ARM C 工程,它演示了 HPS/ARM 是如何去控制 FPGA 端的 LED。 html

二、關於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傳輸訪問和亂序訪問,並更加容易進行時序收斂。 
linux

  在Altera SOC FPGA中,HPS 做爲主端(master),其能夠訪問 FPGA 端 Avalon MM slave 接口的全部組件。   HPSshell

  做爲主端時的 AXI-bridge 包括:bash

  • HPS-to-FPGA Bridge
  • Lightweight HPS-to-FPGA Bridge 

FPGA 做爲主端時的 AXI-bridge 包括:
架構

  •  FPGA-to-HPS Bridge 

下圖FPGA 架構和鏈接到 HPS 的 L3 Switch 的 AXI 橋的方塊圖,每一個主端(M)和從端(S)都標示出了個字的位寬。 app

 

能夠看出: 函數

  • 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。 oop

  FPGA-to-HPS bridge 也能夠做主端控制 L3 main switch,容許 FPGA 端的主端訪問大部分的 HPS 從端。好比,FPGA 資源能夠經過 FPGA-to-HPS 橋能夠訪問到 HPS 端的加速度傳感器。 post

 

三、項目目的

  設計實現基於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 用來編譯應用程序。 性能

四、項目組成

  顯然,咱們的工程由兩個部分組成

  1. 帶HPS的FPGA工程(這裏咱們採用HPS基本概念及其設計文章中建立的工程)
  2. 咱們本身編寫的相關HPS軟件工程及與FPGA相關生成的頭文件

五、生成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 寄存器所須要的。

 

六、編寫HPS程序

  新建一個文件夾,將hps_0.h文件複製剪切到文件夾下,而後編寫main.c程序。這裏對mian.c程序的幾個要點進行下講解。

一、映射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 控制器的寄存器。

二、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虛擬地址。

三、完整的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 );
}

四、對應的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 *~ 

  

七、編譯工程及實驗現象

  一樣,打開Altera Embedded Command Shell,到工程目錄下,編譯hps工程以下所示。

  首先,打開quartus II下載以前HPS基本概念及其設計工程的.sof文件到FPGA

 

  而後將Hps生成的可執行文件,複製到linux系統中,並運行。

可看到FPGA側的10個led等依次閃爍。並在執行60次後中止。

相關文章
相關標籤/搜索