本文設計了一個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 <stdio.h>
#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 <stdio.h>
#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 <stdio.h>
#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核。