本文设计了一个ZYNQ7010的裸跑工程,包含了1路定时器中断、2路PL中断、自制IP(PS与PL寄存器交互)、EMIO和MIO功能,方便裁剪。
详讲工程设计步骤和代码,并给出一些值得注意的细节和Trick。
在ZYNQ7010中通过IP核的方式实现PS与PL的协同工作。
(1).在“Create Block Design”中添加IP:ZYNQ7 Processing System,设置Peripheral I/O Pins中的UART1、DDR Configuration中的DDR和Interrupt中的PL-PS Interrupt Ports。
如下图:
(2).添加IP:AXI GPIO,设置如下:
(3).添加IP:concat,设置如下:
(4).自制IP核:Tool->Create and Package IP->Create AXI4 Peripheral->命名example_button_ip->Number of Registers=2-> Add IP to repository
IP Catalog->example_button_ip->Edit in IP Package
在定层文件中加入接口并在子模块中例化接口:
在子模块中:
然后添加代码:
再“run implementation”
双击“IP-XAT”下的“component.xml”
Ports and Interfaces->merge changes from ports and interfaces Wizard
如果在IP设计中添加了别的.v文件,则在此处还要处理File Groups,直至Packaging Steps中除了Review and Package项外全为对号。
Review and Package->Re-Package IP
(5).回到原工程,添加刚才自制的IP->Run Connection Automation->Run Block Automation
Concat的dout连接ZYNQ7的IRQ_F2P。对concat的In0、In1和自制IP的button和led右键make external。最后对引脚重命名后Regenerate Layout。至此IP设计完成,如下图:
(6).Generate output products->Create HDL wrapper,检查顶层文件引脚是否齐全:
(7).在引脚约束文件中约束这些引脚:
(8).run implemtation->generate Bitstream->export hardwar->launch SDK->file->Application Project->Hello world
(9).SDK中的代码如下,给出了较为详细的注释
//hello world.c
#include
#include "platform.h"
int main()
{
init_platform();
init_key();//初始化按键
init_timer();//初始化定时器
init_led();//初始化PLgpio用于led显示
init_PSgpio();//初始化PSgpio用于led显示
while(1)
{
PSgpio_task();//PSgpio控制的led亮灭
}
cleanup_platform();
return 0;
}
//led.c
#include "xgpio.h"
#define led_ID XPAR_AXI_GPIO_0_DEVICE_ID
//添加ip核axi gpio后会在xparameters.h中自动定义XPAR_AXI_GPIO_0_DEVICE_ID
XGpio LED;//GPIO的结构体
void init_led()
{
int status;
// 初始化按键
status = XGpio_Initialize(&LED, led_ID);//根据外设ID,找到其寄存器地址
if(status != XST_SUCCESS) return XST_FAILURE;
XGpio_SetDataDirection(&LED, 1, 0x00);//设置LED IO的方向为输出
XGpio_DiscreteWrite(&LED,1,0x03);//设置LED 灯熄灭
}
void open_led()
{
XGpio_DiscreteWrite(&LED,1,0x00);//设置LED 灯亮
}
void close_led()
{
XGpio_DiscreteWrite(&LED,1,0x03);//设置LED 灯熄灭
}
//timer.c
#include
#include "xadcps.h"
#include "xil_types.h"
#include "Xscugic.h"
#include "Xil_exception.h"
#include "xscutimer.h"
#define TIMER_DEVICE_ID XPAR_XSCUTIMER_0_DEVICE_ID//定时器外设ID
#define INTC_DEVICE_ID XPAR_PS7_SCUGIC_0_DEVICE_ID//中断外设ID
#define TIMER_IRPT_INTR XPAR_SCUTIMER_INTR//定时器中断源
#define TIMER_LOAD_VALUE 0x13D92D3F
extern XScuGic INTCInst; //一个工程里的XScuGic要使用同一个,两个GPIO中断和定时器中断使用同一个
static XScuTimer Timer;//timer
static void SetupInterruptSystem(XScuGic *GicInstancePtr,
XScuTimer *TimerInstancePtr, u16 TimerIntrId);
static void TimerIntrHandler(void *CallBackRef);
void init_timer()
{
XScuTimer_Config *TMRConfigPtr; //timer config
TMRConfigPtr = XScuTimer_LookupConfig(TIMER_DEVICE_ID);//根据外设ID找到寄存器地址
XScuTimer_CfgInitialize(&Timer, TMRConfigPtr,TMRConfigPtr->BaseAddr);
XScuTimer_SelfTest(&Timer);
//加载计数周期,私有定时器的时钟为CPU的一般,为333MHZ,如果计数1S,加载值为1sx(333x1000x1000)(1/s)-1=0x13D92D3F
XScuTimer_LoadTimer(&Timer, TIMER_LOAD_VALUE);//自动装载
XScuTimer_EnableAutoReload(&Timer);//启动定时器
XScuTimer_Start(&Timer);//set up the interrupts
SetupInterruptSystem(&INTCInst,&Timer,TIMER_IRPT_INTR);
}
void SetupInterruptSystem(XScuGic *GicInstancePtr,XScuTimer *TimerInstancePtr, u16 TimerIntrId)
{
XScuGic_Config *IntcConfig; //GIC config
Xil_ExceptionInit();//initialise the GIC
IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
XScuGic_CfgInitialize(GicInstancePtr, IntcConfig,IntcConfig->CpuBaseAddress);
//connect to the hardware
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,
GicInstancePtr);
//set up the timer interrupt
XScuGic_Connect(GicInstancePtr, TimerIntrId,
(Xil_ExceptionHandler)TimerIntrHandler,
(void *)TimerInstancePtr);
XScuGic_Enable(GicInstancePtr, TimerIntrId);//enable the interrupt for the Timer at GIC
XScuTimer_EnableInterrupt(TimerInstancePtr);//enable interrupt on the timer
Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ);// Enable interrupts in the Processor.
}
static void TimerIntrHandler(void *CallBackRef)
{
static int sec = 0; //计数
XScuTimer *TimerInstancePtr = (XScuTimer *) CallBackRef;
XScuTimer_ClearInterruptStatus(TimerInstancePtr);
printf("sec = %d\n\r",sec++); //每秒打印输出一次
}
//button_ip.c
#include "example_button_ip.h"//定义了PL中 寄存器的偏移地址、写寄存器函数和读寄存器函数
#define button_ip_BASEADDR 0x43C00000//ip核地址可以在vivado的address editor中找到
void button_IP_task()
{
EXAMPLE_BUTTON_IP_mWriteReg (button_ip_BASEADDR,
8, EXAMPLE_BUTTON_IP_mReadReg (button_ip_BASEADDR, 0));
//将寄存器0的内容写给寄存器2
}
//key.c
#include
#include "xscugic.h"
#include "xil_exception.h"
#define INT_CFG0_OFFSET 0x00000C00
// Parameter definitions
#define SW1_INT_ID 61//PL中断源优先级61
#define SW2_INT_ID 62//PL中断源优先级62
#define INTC_DEVICE_ID XPAR_PS7_SCUGIC_0_DEVICE_ID
#define INT_TYPE_RISING_EDGE 0x03
#define INT_TYPE_HIGHLEVEL 0x01
#define INT_TYPE_MASK 0x03
XScuGic INTCInst;//一个工程里的XScuGic要使用同一个,两个GPIO中断和定时器中断使用同一个
static void SW1_intr_Handler(void *param);
static void SW2_intr_Handler(void *param);
static int IntcInitFunction(u16 DeviceId);
static void SW1_intr_Handler(void *param)
{
int sw_id = (int)param;
printf("SW%d int\n\r", sw_id);
open_led();
button_IP_task();
}
static void SW2_intr_Handler(void *param)
{
int sw_id = (int)param;
printf("SW%d int\n\r", sw_id);
close_led();
}
void IntcTypeSetup(XScuGic *InstancePtr, int intId, int intType)
{
int mask;
intType &= INT_TYPE_MASK;
mask = XScuGic_DistReadReg(InstancePtr, INT_CFG0_OFFSET + (intId/16)*4);
mask &= ~(INT_TYPE_MASK << (intId%16)*2);
mask |= intType << ((intId%16)*2);
XScuGic_DistWriteReg(InstancePtr, INT_CFG0_OFFSET + (intId/16)*4, mask);
}
int IntcInitFunction(u16 DeviceId)
{
XScuGic_Config *IntcConfig;
int status;
// Interrupt controller initialisation
IntcConfig = XScuGic_LookupConfig(DeviceId);
status = XScuGic_CfgInitialize(&INTCInst, IntcConfig, IntcConfig->CpuBaseAddress);
if(status != XST_SUCCESS) return XST_FAILURE;
// Call to interrupt setup
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,
&INTCInst);
Xil_ExceptionEnable();
// Connect SW1~SW3 interrupt to handler
status = XScuGic_Connect(&INTCInst,
SW1_INT_ID,
(Xil_ExceptionHandler)SW1_intr_Handler,
(void *)1);
if(status != XST_SUCCESS) return XST_FAILURE;
status = XScuGic_Connect(&INTCInst,
SW2_INT_ID,
(Xil_ExceptionHandler)SW2_intr_Handler,
(void *)2);
if(status != XST_SUCCESS) return XST_FAILURE;
// Set interrupt type of SW1~SW3 to rising edge
IntcTypeSetup(&INTCInst, SW1_INT_ID, INT_TYPE_RISING_EDGE);
IntcTypeSetup(&INTCInst, SW2_INT_ID, INT_TYPE_RISING_EDGE);
// Enable SW1~SW3 interrupts in the controller
XScuGic_Enable(&INTCInst, SW1_INT_ID);
XScuGic_Enable(&INTCInst, SW2_INT_ID);
return XST_SUCCESS;
}
void init_key()
{
IntcInitFunction(INTC_DEVICE_ID);
}
//PSgpio.c
#include "xgpiops.h"
#include "sleep.h"
static XGpioPs psGpioInstancePtr;
static XGpioPs_Config* GpioConfigPtr;
static int iPinNumber= 0; //LED0连接的是MIO0
static u32 uPinDirection = 0x1; //1表示输出,0表示输入
static int xStatus;
void init_PSgpio()
{
//--MIO的初始化
GpioConfigPtr = XGpioPs_LookupConfig(XPAR_PS7_GPIO_0_DEVICE_ID);
if(GpioConfigPtr == NULL)
return XST_FAILURE;
xStatus = XGpioPs_CfgInitialize(&psGpioInstancePtr,GpioConfigPtr, GpioConfigPtr->BaseAddr);
if(XST_SUCCESS != xStatus)
print(" PS GPIO INIT FAILED \n\r");
//--MIO的输入输出操作
XGpioPs_SetDirectionPin(&psGpioInstancePtr, iPinNumber,uPinDirection);//配置MIO输出方向
XGpioPs_SetOutputEnablePin(&psGpioInstancePtr, iPinNumber,1);//配置MIO的第7位输出
}
void PSgpio_task()
{
XGpioPs_WritePin(&psGpioInstancePtr, iPinNumber, 1);//点亮MIO的第0位输出1
usleep(500000); //延时
XGpioPs_WritePin(&psGpioInstancePtr, iPinNumber, 0);//熄灭MIO的第0位输出0
usleep(500000); //延时
}
细节:
(1). 时刻保存工程,在自制IP核工程关闭之后偶尔会把原工程也关掉。
(2). 管脚约束的引脚定义要去
(3). 在SDK中下载bitstream时要注意文件别下错了
(4). 函数默认是extern的,不想被别的.c调用或为了避免命名冲突,要对变量和函数使用static声明
(5). 在vivado中更新硬件export hardware(.hdf文件)之后,关联的SDK工程会自动更新工程,如果SDK没自动更新硬件,可以手动在SDK的hw_platform文件夹上右键”change hardware platform specification”
(6). 更新.bd之后重新生成顶层和底层文件之后要仔细检查顶层文件是否正确,尤其是管脚有没有更正,如果文件不对,可以手动delete掉顶层wrapper文件,再重新create一个,对于底层文件可以直接按按钮“reload”生成。此处如果没更新正确的话,SDK则检测不到IP核。