文章目录
第一部分
软件实验二是通过采用QSYS创建一个硬核工程,控制HPS端LED和KEY。
由于只需要控制HPS端的外设,因此其实现方法和实验一是相同的,编写软件程序即可。
在软件实验二的程序中,通过C程序编写点亮LED灯的时候,包含如下的头文件,这些头文件的环境配置以及hps_0.h头文件的生成都是重点要学习的地方。其中hps_0.h头文件就是通过我们创建的硬核工程通过命令生成的。下面均会一 一讲述。
环境变量配置
首先上面这些库是 SoC EDS 软件提供的, DS-5 中默认并没有包含该库,所以如果不进行环境变量配置,直接在程序中包含这些文件, DS-5 会提示找不到文件,因此需要在工程中设置头文件包含路径。
方法如下:
在工程的基础上 鼠标右击选择 Properties
首先进行hwlib.h的添加
根据安装的SOCEDS找到hwlib.h的路径
D:\intelFPGA\17.1\embedded\ip\altera\hps\altera_hps\hwlib\include
点击Add,输入路径,然后勾选 Add to all configurations 和 Add to all languages 选项, 然后点击 OK 即可,同样的方式可添加socal./hps.h头文件
D:\intelFPGA\17.1\embedded\ip\altera\hps\altera_hps\hwlib\include\soc_cv_av
最终得到如下:
hps_0.h头文件的生成
我们在使用qsys进行外设添加后,若想要对这些FPGA侧的外设IP进行操作,我们就要知道这些外设的硬件信息,该头文件定义了这些外设中的各种地址信息(比如外设在轻量级h2f桥上的偏移地址等)。将该文件生成并加入到C工程中。
生成方法:
此文件需要我们根据Qsys生成的硬件工程来生成。
当我们采用Qsys搭建完系统后,会生成如下的.sopcinfo文件
通过该文件即可生成hps_0头文件。
首先写一个sh脚本,其内容如下,"./soc_system.sopcinfo" \ 代表用qsys搭建的硬核工程的名字,根据自己的进行修改。
#!/bin/sh
sopc-create-header-files \
"./soc_system.sopcinfo" \
--single hps_0.h \
--module hps_0
此时该工程文件夹下有了SH文件。
打开SOCEDS。定位到当前含有sopcinfo文件的目录下
cd "D:/intelFPGA/project/my_first_hps_fpga/fpga-rtl"
通过ls命令可查看,现在定位成功。
输入如下命令
./generate_hps_qsys_header.sh
出现如下结果,说明成功
回到该工程文件夹下可看到生成了hps_0.h头文件。
将hps_0.h头文件添加到C工程中即可。
环境配置完成,且有了hps_0.h头文件,那么代码无错误的情况下即可成功编译。
补充:解决soc_cv_av 没有声明
#include “hwlib.h” 前面直接添加
#define soc_cv_av
第二部分
实验目的
创建一个HPS_GPIO的软件工程,用该工程来控制HPS端的LED和KEY。其中LED和KEY均作为HPS端的外设,因此采用软件工程的方式来控制的方法和软件实验(一)中采用C程序来编写hello_fpga类似,也无需Quartusii工程,只编写C程序即可。
实验原理
在C程序中,我们要采用虚拟地址映射的方式,来控制LED和KEY。其中用到的是Linux内核memory_mapped device(内存映射设备)驱动访问GPIO控制器的寄存器,这些被驱动的寄存器就相当于LED和KEY的I/O接口。在映射的时候我们就需要知道I/O的方向以及写入读出值。(也就是需要知道I/O端口是输入输出,同时输入的是多少,输出的是多少)
HPS GPIO的原理
下图为GPIO例程的方块图,LED和KEY都是连接到DE1-SoC HPS部分的GPIO控制器上的,同时采用memory_mapped device来驱动访问GPIO控制器的寄存器,从而实现GPIO的控制行为
如下是GPIO的接口方块图。可看到HPS提供了三个通用I/O接口模块,GPIO 0、GPIO 1、GPIO 2
前面提到GPIO控制器通过驱动访问GPIO控制器的寄存器,来实现I/O控制,那么下面就展示了对应的寄存器组所控制的I/O引脚的行为。
- 采用三种32位寄存器来实现引脚方向以及写入写出的控制:
- GPIO1_SWPORTA_DDR:配置IO引脚方向 (通过该寄存器组来控制输出高低电平,其中高电平1为输出,低电平0为输入)
- GPIO1_SWPORTA_DR : 写数据到输出引脚(如果写入1,则输出高电平,写入0,输出低电平)——相当于LED接收的是0还是1
- GPIO1_EXT_PORTA : 从输入引脚读数据(读出来的数据为1,则高电平,为0,低电平)——相当于从KEY读到的数据是0还是1
由此确定了输入输出,并知道了输入引脚和输出引脚的值,那么我们就能知道,输入KEY和输出LED的状态是0还是1。
- 如下是GPIO寄存器地址映射表:
其中GPIO 0 控制器的寄存器映射到基地址0xFF708000,共4KB寻址空间。GPIO 1 和 GPIO 2 同理。
软件API
分析完原理后,我们就要采用软件的方式来完成地址映射。可以通过如下软件API访问GPIO控制器的寄存器。(所用到的函数)
还可使用如下MACR0访问寄存器:(宏定义)
若想要使用上面的函数,那么就要包含如下的头文件:
#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"
头文件的说明:
#include “hwlib.h” :针对与HPS硬件编程有关的一些常量进行定义
#include “socal/socal.h” :该文件中是一些基本的底层操作函数(位,字节,字的读写)
#include “socal/hps.h” : 对HPS中各种外设地址信息进行了定义(虚拟地址映射的时候会用到)
LED和KEY控制
如下为HPS的KEY和LED的引脚分配,分别连接到54和53,由GPIO1控制器控制,另外该控制器还控制29~57
在GPIO1控制器中可看到,其中Bit-24,Bit-25分别控制LED和KEY。因此,我们即可通过控制前面介绍的三种32位的寄存器组(GPIO1_SWPORTA_DDR、GPIO1_SWPORTA_DR、GPIO1_EXT_PORTA )中相应的Bit-24,Bit-25来分别控制LED和KEY的方向,以及写入和读取。
具体代码分析
采用宏定义的方式来控制LED和KEY的方向,以及LED的输出状态
#define USER_IO_DIR (0x01000000) //LED引脚为输出引脚
#define BIT_LED (0x01000000) //点亮LED
#define BUTTON_MASK (0x02000000) //
GPIO1_SWPORTA_DDR寄存器组可设置IO引脚方向,alt_setbits_word 设定寄存器的指定位为1,如下语句是配置LED引脚为输出引脚
alt_setbits_word( ( virtual_base + ( ( uint32_t )( ALT_GPIO1_SWPORTA_DDR_ADDR ) & ( uint32_t )( HW_REGS_MASK ) ) ), USER_IO_DIR );
ALT_GPIO1_SWPORTA_DR寄存器组是写数据到输出引脚,alt_setbits_word 设定寄存器的指定位为1,如下语句是点亮LED
alt_setbits_word( ( virtual_base + ( ( uint32_t )( ALT_GPIO1_SWPORTA_DR_ADDR ) & ( uint32_t )( HW_REGS_MASK ) ) ), BIT_LED );
alt_clrbits_word设定寄存器的指定位为0
alt_clrbits_word( ( virtual_base + ( ( uint32_t )( ALT_GPIO1_SWPORTA_DR_ADDR ) & ( uint32_t )( HW_REGS_MASK ) ) ), BIT_LED );
ALT_GPIO1_EXT_PORTA寄存器组是从输入引脚读数据,从而检查KEY是0还是1,(按下还是释放)
alt_read_word( ( virtual_base + ( ( uint32_t )( ALT_GPIO1_EXT_PORTA_ADDR ) & ( uint32_t )( HW_REGS_MASK ) ) ) );
点亮LED的完整代码
如下是教材中提供的KEY控制LED的C程序:(根据错误提示进行一定修改),这部分采用DS-5软件来进行工程创建,并创建c文件。
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#define soc_cv_av
#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 )
//采用宏定义的方式来控制LED和KEY的方向,以及LED的输出状态
#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 );
}
//采用mmap函数,将LED寄存器的地址空间映射到用户空间
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
//GPIO1_SWPORTA_DDR设置IO引脚方向,配置LED引脚为输出引脚
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");
//GPIO1_SWPORTA_DR 写数据到输出引脚,目的是用来点亮LED
for(i=0;i<2;i++)
{
//alt_setbits_word是设定指定寄存器指定位为1
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是设定指定寄存器指定位为0
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");
//alt_read_word从指定寄存器读取一个值
while(1){
scan_input = alt_read_word( ( virtual_base + ( ( uint32_t )( ALT_GPIO1_EXT_PORTA_ADDR ) & ( uint32_t )( HW_REGS_MASK ) ) ) ); //GPIO1_EXT_PORTA是用来从输入引脚读数据
//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
//munmap清除内存映射
if( munmap( virtual_base, HW_REGS_SPAN ) != 0 ) {
printf( "ERROR: munmap() failed...\n" );
close( fd ); //关闭设备驱动
return( 1 );
}
close( fd );
return( 0 );
}
上板测试,成功点亮LED
头文件问题解决之后,编译C程序,然后生成可执行文件,将可执行文件拷贝到SD卡,然后上板并进行串口终端调试,即可完成在DE1-SOC开发板点亮LED,其具体步骤和实验一的hello_fpga是一样的。
已上板测试,按压开关,即可点亮LED。