先說說環境吧:
硬件:AX7021
軟件:Vivado 2018.3
我只買了核心版,打算自己做底板。但是發現目前只有一塊核心版好像並不是很好操作的樣子,先這樣吧。
不得不說,Vivado的界面很好看,類似於Planahead的佈局,用起來很舒服。
跟着教程一路來到 第十一章 自定義 IP 實驗 這裏,將會把一個帶有AXI總線的PWM的IP連到PS上,在自定義IP的時候,我注意到了IP內包含的文件包括Software Driver,裏面有一些驅動文件,但是實際上並沒有什麼內容,雖然自動生成的AX_PWM_mWriteReg和AX_PWM_mReadReg已經足夠使用,但是這並不是一個好的驅動。於是突發奇想這裏將自己定製的Driver代碼放進去,生成出來的代碼不就能看了?
直接編輯,編輯界面並不是很友好,於是遍有了以下方法(就以PWM爲例):
-
先將IP創建好,然後退出,一路向下,生成bitstream。導出硬件,打開SDK。
-
創建一個應用。
-
在bsp項目中按照路徑
ps7_cortexa9_0\libsrc\pwm_v1_0\src\
就可以找到PWM的驅動代碼。 -
接下來改寫它就可以了。
比如我這裏需要一個設置週期和佔空比的函數。就按照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_AssertNonvoid
和Xil_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個文件。
首先保證可以編譯通過,可以正常使用。接下來。
將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
這樣代碼就拷貝好了。
目錄在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核的情況,表就可以自動更新了。
這個就不用多說了吧。
再添加一個PWM,重新導出硬件後SDK就會提示更新。
到這裏就結束了,剩下的就是一些細微的東西了。當然這只是一個簡單的例子,複雜的IP我還需要再研究研究。整體上來說,有些流程還是可以優化的,之所以要新建一個IP是因爲之前的IP內還包含了上上個IP和上上上個IP的不知道什麼信息。而上上上個IP和上上個IP的已經被我刪掉了,所以總是會報找不到上上個IP和上上上個IP的警告。找不到解決辦法,直接新建一個解決問題。
結束。