經過linux核映射驅動訪問GPIO 嵌入式系統軟件設計

一、 HPS GPIO原理

一、功能方塊圖

  linux內核是經過Linux內核memory-mapped device驅動訪問GPIO控制器的寄存器而控制HPS端用戶的LED和KEY的。memory-mapped device驅動容許應用程序訪問系統全部外設寄存器物理地址空間,包括GPIO控制器物理地址。GPIO 控制器的行爲經過器寄存器來控制。應用程序經過內存映射設備驅動訪問GPIO1控制器的寄存器。工程方塊圖以下:html

二、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 控制器控制。 linux

 

  

三、GPIO寄存器組

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

四、 GPIO 寄存器地址映射

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

 

 

五、 軟件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"

  

六、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 ) ) ) );

  

七、建立工程文件

  整個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文件

  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終止程序後,現象消失。

 

 

 

 

Cyclone V HPS存儲器映射

相關文章
相關標籤/搜索