Vivado下創建一個帶BSP驅動的IP

先說說環境吧:

硬件:AX7021
軟件:Vivado 2018.3

我只買了核心版,打算自己做底板。但是發現目前只有一塊核心版好像並不是很好操作的樣子,先這樣吧。
不得不說,Vivado的界面很好看,類似於Planahead的佈局,用起來很舒服。

跟着教程一路來到 第十一章 自定義 IP 實驗 這裏,將會把一個帶有AXI總線的PWM的IP連到PS上,在自定義IP的時候,我注意到了IP內包含的文件包括Software Driver,裏面有一些驅動文件,但是實際上並沒有什麼內容,雖然自動生成的AX_PWM_mWriteRegAX_PWM_mReadReg已經足夠使用,但是這並不是一個好的驅動。於是突發奇想這裏將自己定製的Driver代碼放進去,生成出來的代碼不就能看了?
直接編輯,編輯界面並不是很友好,於是遍有了以下方法(就以PWM爲例):

  1. 先將IP創建好,然後退出,一路向下,生成bitstream。導出硬件,打開SDK。
  2. 創建一個應用。
  3. 在bsp項目中按照路徑ps7_cortexa9_0\libsrc\pwm_v1_0\src\就可以找到PWM的驅動代碼。
  4. 接下來改寫它就可以了。

比如我這裏需要一個設置週期和佔空比的函數。就按照GPIO的驅動來抄好了。
在SDK的安裝目錄下搜索gpio會得到一個C:\Xilinx\SDK\2018.3\data\embeddedsw\XilinxProcessorIPLib\drivers\gpio_v4_3的路徑。

gpio_v4_3
/*  data
    /*  gpio.mdd
    /*  gpio.tcl
    /*  gpio_header.h
    /*  gpio_intr_header.h
    /*  gpio_tapp.tcl
/*  src
    /*  Makefile
    /*  xgpio.c
    /*  xgpio.h
    /*  xgpio_extra.c
    /*  xgpio_g.c
    /*  xgpio_i.h
    /*  xgpio_inir.c
    /*  xgpio_I.h
    /*  xgpio_selftest.c
    /*  xgpio_sinit.c

先從src開始。將比較典型的一些函數挑出來。

/**
 * This typedef contains configuration information for the device.
 * 這個定義包含了設備的配置信息
 */
typedef struct {
	u16 DeviceId;		/* Unique ID  of device */
	UINTPTR BaseAddress;	/* Device base address */
	int InterruptPresent;	/* Are interrupts supported in h/w */
	int IsDual;		/* Are 2 channels supported in h/w */
} XGpio_Config;

/**
 * The XGpio driver instance data. The user is required to allocate a
 * variable of this type for every GPIO device in the system. A pointer
 * to a variable of this type is then passed to the driver API functions.
 * 這個就類似於32的HAL庫裏的驅動。
 */
typedef struct {
	UINTPTR BaseAddress;	/* Device base address */
	u32 IsReady;		/* Device is initialized and ready */
	int InterruptPresent;	/* Are interrupts supported in h/w */
	int IsDual;		/* Are 2 channels supported in h/w */
} XGpio;

/****************************************************************************/

/**
* Initialize the XGpio instance provided by the caller based on the
* given DeviceID.
* 通過設備ID來初始化GPIO設備,具體是靠XGpio_LookupConfig和XGpio_CfgInitialize
* 來實現的。
*****************************************************************************/
int XGpio_Initialize(XGpio *InstancePtr, u16 DeviceId);

/**
* Lookup the device configuration based on the unique device ID.  The table
* ConfigTable contains the configuration info for each device in the system.
* 通過設備ID查找設備的配置文件。
******************************************************************************/
XGpio_Config *XGpio_LookupConfig(u16 DeviceId);

/*
 * API Basic functions implemented in xgpio.c
 */

/**
* Initialize the XGpio instance provided by the caller based on the
* given configuration data.
* 初始化,就是把cfg裏的數據放到XGpio裏。
******************************************************************************/
int XGpio_CfgInitialize(XGpio *InstancePtr, XGpio_Config * Config,
			UINTPTR EffectiveAddr);
/**
 * 這些就沒必要看了。都是GPIO的一些東西,包括下面的。
******************************************************************************/
void XGpio_SetDataDirection(XGpio *InstancePtr, unsigned Channel,
			    u32 DirectionMask);
u32 XGpio_GetDataDirection(XGpio *InstancePtr, unsigned Channel);
u32 XGpio_DiscreteRead(XGpio *InstancePtr, unsigned Channel);
void XGpio_DiscreteWrite(XGpio *InstancePtr, unsigned Channel, u32 Mask);

再分析一下這些函數的定義都是在哪些文件裏的就可以了。

src
/*  Makefile
/*  xgpio.c
    /*  XGpio_CfgInitialize()		--config配置
/*  xgpio_g.c
    /*  XGpio_ConfigTable			--config表
/*  xgpio_i.h
    /*  XGpio_ConfigTable			--config表
/*  xgpio_intr.c					--中斷
/*  xgpio_I.h						--lowlevel
/*  xgpio_selftest.c				--自檢
/*  xgpio_sinit.c
    /*  XGpio_LookupConfig()
    /*  XGpio_Initialize()

pwm這邊也對應創建相關文件,首先是頭文件pwm.h

/**************************** Type Definitions *****************************/

/**
 * This typedef contains configuration information for the device.
 */
typedef struct {
	u16 DeviceId; /* Unique ID  of device */
	UINTPTR BaseAddress; /* Device base address */
} Pwm_Config;

/**
 * The XGpio driver instance data. The user is required to allocate a
 * variable of this type for every GPIO device in the system. A pointer
 * to a variable of this type is then passed to the driver API functions.
 */
typedef struct {
	UINTPTR BaseAddress; /* Device base address */
	u32 IsReady; /* Device is initialized and ready */
    u32 Peroid; /* Period of PWM */
	u32 Duty; /* Duty of PWM */
} Pwm;

/************************** Function Prototypes ****************************/

/*
 * Initialization functions in pwm_sinit.c
 */
int Pwm_Initialize(PWM *InstancePtr, u16 DeviceId);
Pwm_Config*Pwm_LookupConfig(u16 DeviceId);

/*
 * API Basic functions implemented in pwm.c
 */
int Pwm_CfgInitialize(XGpio *InstancePtr, XGpio_Config * Config,
		UINTPTR EffectiveAddr);
void Pwm_SetPeroid(XGpio *InstancePtr, u32 Peroid);
u32 Pwm_GetPeroid(XGpio *InstancePtr);
void Pwm_SetDuty(XGpio *InstancePtr, u32 Duty);
u32 Pwm_GetDuty(XGpio *InstancePtr);

/*
 * API Functions implemented in pwm_selftest.c
 */
XStatus PWM_Reg_SelfTest(void * baseaddr_p);

接下來是pwm_sinit.c文件:

#ifndef XPAR_PWM_NUM_INSTANCES
#define XPAR_PWM_NUM_INSTANCES		0
#endif
Pwm_Config *Pwm_LookupConfig(u16 DeviceId) {
	Pwm_Config *CfgPtr = NULL;

	int Index;

	for (Index = 0; Index < XPAR_PWM_NUM_INSTANCES; Index++) {
		if (Pwm_ConfigTable[Index].DeviceId == DeviceId) {
			CfgPtr = &Pwm_ConfigTable[Index];
			break;
		}
	}

	return CfgPtr;
}

int Pwm_Initialize(Pwm *InstancePtr, u16 DeviceId) {
	Pwm_Config *ConfigPtr;

	/*
	 * Assert arguments
	 */
	Xil_AssertNonvoid(InstancePtr != NULL);

	/*
	 * Lookup configuration data in the device configuration table.
	 * Use this configuration info down below when initializing this
	 * driver.
	 */
	ConfigPtr = Pwm_LookupConfig(DeviceId);
	if (ConfigPtr == (Pwm_Config *) NULL) {
		InstancePtr->IsReady = 0;
		return (XST_DEVICE_NOT_FOUND);
	}

	return Pwm_CfgInitialize(InstancePtr, ConfigPtr, ConfigPtr->BaseAddress);
}

這部分照搬即可,基本不用動。

下面是pwm_l.h文件

#include "xil_types.h"
#include "xil_assert.h"
#include "xil_io.h"

#define PWM_S00_AXI_SLV_REG0_OFFSET 0
#define PWM_S00_AXI_SLV_REG1_OFFSET 4
#define PWM_S00_AXI_SLV_REG2_OFFSET 8
#define PWM_S00_AXI_SLV_REG3_OFFSET 12

#define PWM_WriteReg(BaseAddress, RegOffset, Data) \
  	Xil_Out32((BaseAddress) + (RegOffset), (u32)(Data))

#define PWM_ReadReg(BaseAddress, RegOffset) \
    Xil_In32((BaseAddress) + (RegOffset))

pwm.c文件

int Pwm_CfgInitialize(Pwm *InstancePtr, Pwm_Config * Config,
		UINTPTR EffectiveAddr) {
	/* Assert arguments */
	Xil_AssertNonvoid(InstancePtr != NULL);
	Xil_AssertNonvoid(Config != NULL);

	/* Set some default values. */
	InstancePtr->BaseAddress = EffectiveAddr;

	/*
	 * Indicate the instance is now ready to use, initialized without error
	 */
	InstancePtr->IsReady = XIL_COMPONENT_IS_READY;
	return (XST_SUCCESS);
}

void Pwm_SetPeroid(Pwm *InstancePtr, u32 Peroid) {
	Xil_AssertVoid(InstancePtr != NULL);
	Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);

	PWM_WriteReg(InstancePtr->BaseAddress, PWM_S00_AXI_SLV_REG0_OFFSET,
			Peroid);
}

u32 Pwm_GetPeroid(Pwm *InstancePtr) {
	Xil_AssertNonvoid(InstancePtr != NULL);
	Xil_AssertNonvoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);

	return PWM_ReadReg(InstancePtr->BaseAddress, PWM_S00_AXI_SLV_REG0_OFFSET);
}

void Pwm_SetDuty(Pwm *InstancePtr, u32 Duty) {
	Xil_AssertVoid(InstancePtr != NULL);
	Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);

	PWM_WriteReg(InstancePtr->BaseAddress, PWM_S00_AXI_SLV_REG1_OFFSET,
			Duty);
}

u32 Pwm_GetDuty(Pwm *InstancePtr) {
	Xil_AssertNonvoid(InstancePtr != NULL);
	Xil_AssertNonvoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);

	return PWM_ReadReg(InstancePtr->BaseAddress, PWM_S00_AXI_SLV_REG1_OFFSET);
}

這裏有個小細節,Xil_AssertNonvoidXil_AssertVoid都是斷言,但是前者用在有返回值的函數中,後者用在無返回的函數裏,不然幾乎會出現警告。
pwm_g.c文件

#include "xparameters.h"
#include "pwm.h"
Pwm_Config Pwm_ConfigTable[XPAR_PWM_NUM_INSTANCES] =
{
	{
		XPAR_PWM_0_DEVICE_ID,
		XPAR_PWM_0_S00_AXI_BASEADDR
	},
	{
		XPAR_PWM_1_DEVICE_ID,
		XPAR_PWM_1_S00_AXI_BASEADDR
	}
};

pwm_i.c文件

#include "pwm.h"
extern Pwm_Config Pwm_ConfigTable[];

pwm_selftest.c文件

XStatus PWM_Reg_SelfTest(void * baseaddr_p) {
	u32 baseaddr;
	int write_loop_index;
	int read_loop_index;

	baseaddr = (u32) baseaddr_p;

	xil_printf("******************************\n\r");
	xil_printf("* PWM Self Test\n\r");
	xil_printf("******************************\n\n\r");

	/*
	 * Write to user logic slave module register(s) and read back
	 */
	xil_printf("PWM slave module test...\n\r");

	for (write_loop_index = 0; write_loop_index < 4; write_loop_index++)
		PWM_WriteReg(baseaddr, write_loop_index * 4,
				(write_loop_index+1)*READ_WRITE_MUL_FACTOR);
	for (read_loop_index = 0; read_loop_index < 4; read_loop_index++)
		if ( PWM_ReadReg(baseaddr, read_loop_index * 4)
				!= (u32) ((read_loop_index + 1) * READ_WRITE_MUL_FACTOR)) {
			xil_printf("Error reading register value at address %x\n",
					(int) baseaddr + read_loop_index * 4);
			return XST_FAILURE;
		}

	xil_printf("   - slave register write/read passed\n\n\r");

	return XST_SUCCESS;
}

一共7個文件。
首先保證可以編譯通過,可以正常使用。接下來。

  1. 重建IP

將ip_repo下的pwm_v1_0移動到其他地方。這個目錄具體在Vivado的設置裏。
接下來重新創建IP。在打包之前停一下,將之前做好的代碼對應拷貝到新的IP裏去,目錄應該是drivers\<your ip name>\src。之後將它添加進來。
在這裏插入圖片描述
在這裏插入圖片描述
這裏不要打勾拷貝文件到工程中的選項,那樣代碼就拷貝到HDL文件夾裏面去了。
接下來會報錯,添加了幾個就報幾個,這個是因爲路徑不算是工程路徑導致的(明明就在。。。),現在需要手動編輯這個打包配置文件component.xml,在新IP的目錄下編輯它。找到那段很長的路徑,將drivers單詞前面的路徑全部刪除。比如說我的是這樣的:

        <spirit:name>C:/Users/Godenfreemans/Documents/FPGA/ip_repo/pwm_1.0/drivers/pwm_v1_0/src/pwm_l.h</spirit:name>
        <spirit:fileType>cSource</spirit:fileType>
      </spirit:file>
      <spirit:file>
        <spirit:name>C:/Users/Godenfreemans/Documents/FPGA/ip_repo/pwm_1.0/drivers/pwm_v1_0/src/pwm_i.h</spirit:name>
        <spirit:fileType>cSource</spirit:fileType>
      </spirit:file>
      <spirit:file>
        <spirit:name>C:/Users/Godenfreemans/Documents/FPGA/ip_repo/pwm_1.0/drivers/pwm_v1_0/src/pwm_g.c</spirit:name>
        <spirit:fileType>cSource</spirit:fileType>
      </spirit:file>
      <spirit:file>
        <spirit:name>C:/Users/Godenfreemans/Documents/FPGA/ip_repo/pwm_1.0/drivers/pwm_v1_0/src/pwm_sinit.c</spirit:name>
        <spirit:fileType>cSource</spirit:fileType>

改成:

        <spirit:name>drivers/pwm_v1_0/src/pwm_l.h</spirit:name>
        <spirit:fileType>cSource</spirit:fileType>
      </spirit:file>
      <spirit:file>
        <spirit:name>drivers/pwm_v1_0/src/pwm_i.h</spirit:name>
        <spirit:fileType>cSource</spirit:fileType>
      </spirit:file>
      <spirit:file>
        <spirit:name>drivers/pwm_v1_0/src/pwm_g.c</spirit:name>
        <spirit:fileType>cSource</spirit:fileType>
      </spirit:file>
      <spirit:file>
        <spirit:name>drivers/pwm_v1_0/src/pwm_sinit.c</spirit:name>
        <spirit:fileType>cSource</spirit:fileType>

在Vivado中重新打開打包文件component.xml
在這裏插入圖片描述
這樣代碼就拷貝好了。

  1. 改寫tcl腳本

目錄在drivers\pwm_v1_0\data\pwm.tcl

原來的是這樣的:

proc generate {drv_handle} {
	xdefine_include_file $drv_handle "xparameters.h" "Pwm" "NUM_INSTANCES" "DEVICE_ID"  "C_S00_AXI_BASEADDR" "C_S00_AXI_HIGHADDR"
}

這裏增加一個Table生成語句

proc generate {drv_handle} {
	xdefine_include_file $drv_handle "xparameters.h" "Pwm" "NUM_INSTANCES" "DEVICE_ID"  "C_S00_AXI_BASEADDR" "C_S00_AXI_HIGHADDR"
	::hsi::utils::define_config_file $drv_handle "pwm_g.c" "Pwm"  "DEVICE_ID" "C_S00_AXI_BASEADDR"
}

說明一下參數,這裏的pwm_g.c就是之前做的配置表所在的文件,Pwm這裏填寫的是配置表的命名格式,我之前定義的PWM的配置變量是Pwm_Config所以這裏就填寫Pwm,如果是PWM_Config那就填寫PWM,剩下的就按照Pwm_Config的定義方式來填寫就行了,內容可以從上面xdefine_include_file 裏抄。
添加這個語句之後,之後如果有添加多個IP相同IP核的情況,表就可以自動更新了。

  1. 重新添加IP,生成BSP文件。

這個就不用多說了吧。

  1. 效果

再添加一個PWM,重新導出硬件後SDK就會提示更新。
在這裏插入圖片描述

到這裏就結束了,剩下的就是一些細微的東西了。當然這只是一個簡單的例子,複雜的IP我還需要再研究研究。整體上來說,有些流程還是可以優化的,之所以要新建一個IP是因爲之前的IP內還包含了上上個IP和上上上個IP的不知道什麼信息。而上上上個IP和上上個IP的已經被我刪掉了,所以總是會報找不到上上個IP和上上上個IP的警告。找不到解決辦法,直接新建一個解決問題。
結束。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章