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的警告。找不到解决办法,直接新建一个解决问题。
结束。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值