先说说环境吧:
硬件:AX7021
软件:Vivado 2018.3
我只买了核心版,打算自己做底板。但是发现目前只有一块核心版好像并不是很好操作的样子,先这样吧。
不得不说,Vivado的界面很好看,类似于Planahead的布局,用起来很舒服。
跟着教程一路来到 第十一章 自定义 IP 实验 这里,将会把一个带有AXI总线的PWM的IP连到PS上,在自定义IP的时候,我注意到了IP内包含的文件包括Software Driver,里面有一些驱动文件,但是实际上并没有什么内容,虽然自动生成的AX_PWM_mWriteReg和AX_PWM_mReadReg已经足够使用,但是这并不是一个好的驱动。于是突发奇想这里将自己定制的Driver代码放进去,生成出来的代码不就能看了?
直接编辑,编辑界面并不是很友好,于是遍有了以下方法(就以PWM为例):
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\
。之后将它添加进来。
这里不要打勾拷贝文件到工程中的选项,那样代码就拷贝到HDL文件夹里面去了。
接下来会报错,添加了几个就报几个,这个是因为路径不算是工程路径导致的(明明就在。。。),现在需要手动编辑这个打包配置文件component.xml
,在新IP的目录下编辑它。找到那段很长的路径,将drivers单词前面的路径全部删除。比如说我的是这样的:
<spirit:name>C:/Users/Godenfreemans/Documents/FPGA/ip_repo/pwm_1.0/drivers/pwm_v1_0/src/pwm_l.hspirit:name>
<spirit:fileType>cSourcespirit:fileType>
spirit:file>
<spirit:file>
<spirit:name>C:/Users/Godenfreemans/Documents/FPGA/ip_repo/pwm_1.0/drivers/pwm_v1_0/src/pwm_i.hspirit:name>
<spirit:fileType>cSourcespirit:fileType>
spirit:file>
<spirit:file>
<spirit:name>C:/Users/Godenfreemans/Documents/FPGA/ip_repo/pwm_1.0/drivers/pwm_v1_0/src/pwm_g.cspirit:name>
<spirit:fileType>cSourcespirit:fileType>
spirit:file>
<spirit:file>
<spirit:name>C:/Users/Godenfreemans/Documents/FPGA/ip_repo/pwm_1.0/drivers/pwm_v1_0/src/pwm_sinit.cspirit:name>
<spirit:fileType>cSourcespirit:fileType>
改成:
<spirit:name>drivers/pwm_v1_0/src/pwm_l.hspirit:name>
<spirit:fileType>cSourcespirit:fileType>
spirit:file>
<spirit:file>
<spirit:name>drivers/pwm_v1_0/src/pwm_i.hspirit:name>
<spirit:fileType>cSourcespirit:fileType>
spirit:file>
<spirit:file>
<spirit:name>drivers/pwm_v1_0/src/pwm_g.cspirit:name>
<spirit:fileType>cSourcespirit:fileType>
spirit:file>
<spirit:file>
<spirit:name>drivers/pwm_v1_0/src/pwm_sinit.cspirit:name>
<spirit:fileType>cSourcespirit: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核的情况,表就可以自动更新了。
这个就不用多说了吧。
到这里就结束了,剩下的就是一些细微的东西了。当然这只是一个简单的例子,复杂的IP我还需要再研究研究。整体上来说,有些流程还是可以优化的,之所以要新建一个IP是因为之前的IP内还包含了上上个IP和上上上个IP的不知道什么信息。而上上上个IP和上上个IP的已经被我删掉了,所以总是会报找不到上上个IP和上上上个IP的警告。找不到解决办法,直接新建一个解决问题。
结束。