linux映射gpio base空间,通过linux核映射驱动访问GPIO

1、 HPS GPIO原理

1、功能方块图

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

269c17f18b30aca9966273972abe325b.png

2、GPIO接口图

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

1633032816e527cdb6502cab70d492ac.png

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 寻址空间。

9b21271fd5ca8151eb80f34a5ae64cc9.png

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

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

open:打开内存映射设备驱动。

mmap:映射物理地址到用户空间。

alt_read_word:从指定寄存器读取一个值。

alt_write_word:写一个值到指定寄存器。

munmap:清除内存映射。

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

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

alt_clrbits_wod:设定指定寄存器的指定位位0.

包含以上API的头文件为:

#include

#include

#include

#include

#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_SystemCDSchematic中查看开发板原理图知道,HPS_GPIO54和HPS_GPIO53分别连接的是HPS_KEY 和HPS_LED.如下图所示:

16966ee48180051a6870ce834e62b46e.png

这两个引脚都是被 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。

3858e4b4b63d09c708086268237f206a.png

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

#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

#include

#include

#include

#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"...

" );

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...

" );

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

");

printf("the led flash 2 times

");

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

");

printf("press key to control led

");

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...

" );

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到工程目录下:

3c72a31320317498a6bad10eea592504.png

编译过程中发现两个错误。这都是因为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).

861773a1046cbd543af254a4788fa92a.png

17c18a7a6e7993520ecc79595ca7a0b5.png

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

e6eba47fcd2ab561f676d21826df1b4e.png

错误2:

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

dfac8bf8241974d6aec63cdd99d1a7e4.png

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

a191c5e1d24db3053630bbf33d53501d.png

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

cda6004f0886edbf979f942874867c00.png

解决方法:

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

288cbb65a9f2698725e1ccce86fb0b6e.png

并修改头文件为:

#include

#include

#include

#include

#include "hwlib.h"

#include "socal.h"

#include "hps.h"

#include "alt_gpio.h"

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

c9a6561406cf5ec6f57ca0cebe76dcfd.png

c5a90bcbce428f67141bfec32163edd5.png

10、运行结果

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

aaab5ca24ad3ce7c347b94e3645b4f41.png

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

206c94c5a9bd78d68f3e969809708721.png

0b2eeeb5122006668a59fb0c353b45a7.png

ed68e52b99859ed20ea7dcecdccbc082.png

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux可以通过映射 GPIO 地址的方式来控制 GPIO 引脚的电平状态。具体步骤如下: 1. 调用 ioremap 函数,将 GPIO 控制器的物理地址映射到内虚拟地址空间中,代码如下: ```c #define BCM2708_PERI_BASE 0x20000000 #define GPIO_BASE (BCM2708_PERI_BASE + 0x200000) volatile unsigned *gpio; void setup_io() { gpio = ioremap(GPIO_BASE, SZ_4K); } ``` 2. 设置 GPIO 引脚的输入输出状态。GPIO 控制寄存器中每个寄存器都对应一个 GPIO 引脚,通过设置相应的位可以将引脚设置为输入或输出模式。例如,将第 7 个 GPIO 引脚设置为输出模式,代码如下: ```c #define GPFSEL0 0 #define GPFSEL1 1 #define GPFSEL2 2 #define GPFSEL3 3 #define GPFSEL4 4 #define GPFSEL5 5 #define GPIO_OUTPUT 1 #define GPIO_INPUT 0 gpio[GPFSEL0] |= (GPIO_OUTPUT << ((7 % 10) * 3)); ``` 3. 控制 GPIO 引脚电平。通过设置 GPIO 控制寄存器中相应的位可以将 GPIO 引脚的电平设置为高或低。例如,将第 7 个 GPIO 引脚设置为高电平,代码如下: ```c #define GPSET0 7 #define GPCLR0 10 gpio[GPSET0] |= (1 << 7); ``` 4. 调用 iounmap 函数,释放 GPIO 控制器的虚拟地址空间,代码如下: ```c void cleanup_io() { iounmap(gpio); } ``` 需要注意的是,在内中使用映射 GPIO 地址的方式需要小心操作,因为对 GPIO 控制寄存器的任何误操作都可能导致系统崩溃或引脚损坏。同时,使用此方式需要具备内编程的基本知识,并且需要非常小心,因为对 GPIO 控制寄存器的任何误操作都可能导致系统崩溃或引脚损坏。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值