欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 八卦 > 14.2 - VDMA彩条显示实验之动态时钟

14.2 - VDMA彩条显示实验之动态时钟

2025/4/22 0:08:07 来源:https://blog.csdn.net/XDUryan/article/details/147279383  浏览:    关键词:14.2 - VDMA彩条显示实验之动态时钟

文章目录

  • 1 实验任务
  • 2 系统框图
  • 3 硬件设计
  • 4 软件设计
    • 4.1 工程架构
    • 4.2 工程源码
      • 4.2.1 dynclk_api.h文件
      • 4.2.2 dynclk_api.c文件
      • 4.2.3 main.c文件
    • 4.3 Digilent公司动态时钟API
      • 4.3.1 clk_wiz.h文件
      • 4.3.2 clk_wiz.c文件

1 实验任务

参见14.1。

2 系统框图

参见14.1。

3 硬件设计

注意事项:基于14.1做如下改动

  1. 使能Clocking Wizard IP核的Dynamic Reconfig功能,并选择AXI4-Lite接口;
  2. 将Clocking Wizard IP核的AXI4-Lite接口通过AXI Interconnect连接到PS的M_AXI_GP接口。

特别说明:为什么不使用Digilent公司提供的axi_dynclk IP核(正点原子教程使用该IP),原因如下

  1. axi_dynclk IP核输出两个时钟,PXL_CLK_O和PXL_CLK_5X_O;
  2. PXL_CLK_5X_O由BUFIO驱动,PXL_CLK_O由BUFR驱动(5倍分频);
  3. PXL_CLK_O在本实验的Vivado工程中布线无法通过,提示路径太远,原因应该是BUFR是区域时钟缓冲器,能够驱动的资源有限,导致布线失败;
  4. 正点原子的教程使用axi_dynclk IP核编译可以通过,猜测原因可能是整个工程只使用一个时钟,即PS提供的FCLK_CLK0;
  5. 本实验的Vivado工程中使用三个时钟
    • PS输出的FCLK_CLK0(50MHz),用于连接AXI4-Lite接口;
    • PS输出的FCLK_CLK1(150MHz),用于连接AXI4接口;
    • 板载时钟(50MHz)经Clocking Wizard IP核输出的视频像素时钟(148.5MHz)
  6. 尝试在IP Packager中对axi_dynclk IP核进行修改,使其只输出一个时钟,且该时钟由BUFG驱动,但是修改完成之后,在对IP进行重新封装时,Vivado工具总是闪退,多次尝试并重启电脑,依旧如此,故放弃。

4 软件设计

动态时钟的软件配置部分代码,我是让deepseek帮我生成的,我负责提需求,包括:

  1. pg065中关于通过AXI4-Lite接口动态配置时钟的步骤;
  2. 输出频率Fout的计算公式
  3. 计算公式中各个参数的取值范围,包括
    • Fvco = 600MHz~1200MHz(和器件型号及等级有关)
    • CLKFBOUT_MULT = 2.0 ~ 64.0(MMCM,小数部分以0.125步进)/1~64(PLL)
    • DIVCLK_DIVIDE = 1 ~ 106(MMCM)/1~56(PLL)
    • CLKOUT0_DIVIDE = 1.000 ~ 128.000(MMCM,小数部分以0.125步进)/1~128(PLL)
  4. 关键寄存器的说明,包括
    • Software Reset Register (SRR),偏移地址0x00
    • Status Register (SR),偏移地址0x04
    • Clock Configuration Register 0,偏移地址0x200
    • Clock Configuration Register 2,偏移地址0x208
    • Clock Configuration Register 23,偏移地址0x25C

代码生成后,让deepseek形成xxx_api.c文件和xxx_api.h文件,使用很方便,只需提供输入时钟频率和期望的输出时钟频率(不能输出类似33.333MHz的时钟),效果非常好。

4.1 工程架构

工程架构如下图所示:
在这里插入图片描述

4.2 工程源码

4.2.1 dynclk_api.h文件

代码如下所示:

/********************************************************************/#ifndef SRC_DYNCLK_DYNCLK_API_H_
#define SRC_DYNCLK_DYNCLK_API_H_/********************************************************************///
#define DEBUG							1// 寄存器基地址
#define XCLKWIZ_BASEADDR				XPAR_CLK_WIZ_0_BASEADDR// VCO频率范围(MHz)
#define MIN_VCO_FREQ 					600.0
#define MAX_VCO_FREQ 					1200.0// 参数范围
#define MIN_CLKFBOUT_MULT 				2
#define MAX_CLKFBOUT_MULT 				64
#define MIN_DIVCLK_DIVIDE 				1
#define MAX_DIVCLK_DIVIDE 				106
#define MIN_CLKOUT0_DIVIDE 				1
#define MAX_CLKOUT0_DIVIDE 				128// 小数部分步进
#define FRAC_STEP 						0.125
#define FRAC_SCALE 						1000// 寄存器偏移量 (根据文档修正)
#define XCLKWIZ_SOFT_RESET_OFFSET 		0x0    // Soft Reset Register
#define XCLKWIZ_STATUS_OFFSET     		0x4    // Status Register
#define XCLKWIZ_CLK_CONFIG_00_OFFSET	0x200  // Clock Configuation Register 00
#define XCLKWIZ_CLK_CONFIG_02_OFFSET	0x208  // Clock Configuation Register 02
#define XCLKWIZ_CLK_CONFIG_23_OFFSET	0x25C  // Clock Configuation Register 23// 寄存器值定义
/** To activate software reset, the value 0x0000_000A must be written to the register.*/
#define XCLKWIZ_RESET_VALUE       		0xA
/** When '1' MMCM/PLL is Locked and ready for the reconfiguration. Status of this bit is '0' during the reconfiguration.*/
#define XCLKWIZ_STATUS_LOCKED_MASK 		0x1// 错误代码
#define XCLK_WIZ_SUCCESS 				0
#define XCLK_WIZ_ERROR_INVALID_PARAM	-1
#define XCLK_WIZ_ERROR_RESET_FAILED 	-2
#define XCLK_WIZ_ERROR_LOCK_FAILED 		-3// 100ms计数值定义
#define COUNT_NUM_100MS					(XPAR_CPU_CORTEXA9_0_CPU_CLK_FREQ_HZ / 10)/********************************************************************/int clk_wiz_configure(double input_freq, double output_freq);/********************************************************************/#endif /* SRC_DYNCLK_DYNCLK_API_H_ *//********************************************************************/

4.2.2 dynclk_api.c文件

代码如下所示:

/********************************************************************/#include "dynclk_api.h"
#include "xclk_wiz_hw.h"
#include "stdio.h"/********************************************************************/static inline double fmax(double x, double y) {return (x > y) ? x : y;
}static inline double fmin(double x, double y) {return (x < y) ? x : y;
}static int clk_wiz_is_locked(void);
static int clk_wiz_wait_lock(u32 timeout_count);
static int clk_wiz_reset(void);static int clk_wiz_calc_params(double input_freq, double output_freq, int *divclk_divide, double *clkfbout_mult, double *clkout0_divide);
static u32 bulid_clk_cfg_reg0_val(int divclk_divide, double clkfbout_mult);
static u32 bulid_clk_cfg_reg2_val(double clkout0_divide);/********************************************************************/static int clk_wiz_is_locked(void) {//u32 status = XClk_Wiz_ReadReg(XCLKWIZ_BASEADDR, XCLKWIZ_STATUS_OFFSET);//return (status & XCLKWIZ_STATUS_LOCKED_MASK) ? 1 : 0;
}/********************************************************************/static int clk_wiz_wait_lock(u32 timeout_count) {//while (timeout_count--) {if (clk_wiz_is_locked()) {return XCLK_WIZ_SUCCESS;}}//return XCLK_WIZ_ERROR_LOCK_FAILED;
}/********************************************************************/int clk_wiz_reset(void) {//
#if DEBUGprintf("2 - clocking wizard reset start.\n");
#endif// 发送复位信号XClk_Wiz_WriteReg(XCLKWIZ_BASEADDR, XCLKWIZ_SOFT_RESET_OFFSET, XCLKWIZ_RESET_VALUE);// 检查复位是否完成
#if DEBUGprintf("\t\tWaiting for clocking wizard lock...\n");
#endifif (clk_wiz_wait_lock(COUNT_NUM_100MS) != XCLK_WIZ_SUCCESS) { // 100ms超时printf("\t\tError : clocking wizard lock failed.\n");return XCLK_WIZ_ERROR_LOCK_FAILED;}//
#if DEBUGprintf("\t\tclocking wizard reset completed.\n");
#endif//return XCLK_WIZ_SUCCESS;
}/********************************************************************/static int clk_wiz_calc_params(double input_freq, double output_freq, int *divclk_divide, double *clkfbout_mult, double *clkout0_divide) {//double vco_freq;int found = 0;double min_vco = MIN_VCO_FREQ;double max_vco = MAX_VCO_FREQ;// 尝试不同的DIVCLK_DIVIDE值for (int d = MIN_DIVCLK_DIVIDE; d <= MAX_DIVCLK_DIVIDE && !found; d++) {// 计算可能的CLKFBOUT_MULT值范围double min_mult = min_vco / (input_freq / d);double max_mult = max_vco / (input_freq / d);// 确保在有效范围内 - 使用我们的fmax/fmin实现min_mult = (min_mult > MIN_CLKFBOUT_MULT) ? min_mult : MIN_CLKFBOUT_MULT;max_mult = (max_mult < MAX_CLKFBOUT_MULT) ? max_mult : MAX_CLKFBOUT_MULT;if (min_mult > max_mult) continue;// 计算整数部分范围int min_int = (int)min_mult;int max_int = (int)max_mult + 1;// 尝试CLKFBOUT_MULT值for (int m = min_int; m <= max_int && !found; m++) {// 尝试小数部分for (int f = 0; f <= 7; f++) {  // 0.125步进(0-0.875)double mult = m + f * FRAC_STEP;// 检查是否在范围内if (mult < min_mult || mult > max_mult) continue;// 计算VCO频率vco_freq = (input_freq / d) * mult;// 计算所需的CLKOUT0_DIVIDEdouble divide = vco_freq / output_freq;// 检查CLKOUT0_DIVIDE是否在范围内if (divide < MIN_CLKOUT0_DIVIDE || divide > MAX_CLKOUT0_DIVIDE) continue;// 检查小数部分double fractional_part = divide - (int)divide;int valid_fraction = 0;// 检查小数部分是否是0.125的整数倍for (int i = 0; i <= 7; i++) {if ((fractional_part - i * FRAC_STEP) < 0.0001 &&(fractional_part - i * FRAC_STEP) > -0.0001) {valid_fraction = 1;break;}}if (!valid_fraction && fractional_part > 0.0001) continue;// 找到有效参数*divclk_divide = d;*clkfbout_mult = mult;*clkout0_divide = divide;found = 1;break;}}}//return found ? 0 : -1;
}/********************************************************************/static u32 bulid_clk_cfg_reg0_val(int divclk_divide, double clkfbout_mult) {//u32 reg_value = 0;// 提取整数和小数部分int integer_part = (int)clkfbout_mult;double fractional_part = clkfbout_mult - integer_part;int fractional_value = (int)(fractional_part * FRAC_SCALE);// 构建寄存器值reg_value |= (divclk_divide & 0xFF);           // Bits [7:0]reg_value |= (integer_part & 0xFF) << 8;       // Bits [15:8]reg_value |= (fractional_value & 0x3FF) << 16; // Bits [25:16]//return reg_value;
}/********************************************************************/static u32 bulid_clk_cfg_reg2_val(double clkout0_divide) {//u32 reg_value = 0;// 提取整数和小数部分int integer_part = (int)clkout0_divide;double fractional_part = clkout0_divide - integer_part;int fractional_value = (int)(fractional_part * FRAC_SCALE);// 构建寄存器值reg_value |= (integer_part & 0xFF);           // Bits [7:0]reg_value |= (fractional_value & 0x3FF) << 8; // Bits [17:8]//return reg_value;
}/********************************************************************/int clk_wiz_configure(double input_freq, double output_freq) {//int divclk_divide;double clkfbout_mult, clkout0_divide;u32 reg0_value, reg2_value;// 参数检查if (input_freq <= 0 || output_freq <= 0) {printf("Error : invalid input or output frequency value.\n");return XCLK_WIZ_ERROR_INVALID_PARAM;}// 1. 计算PLL参数if (clk_wiz_calc_params(input_freq, output_freq, &divclk_divide, &clkfbout_mult, &clkout0_divide) != 0) {printf("Error : cannot find suitable clocking wizard parameters.\n");return XCLK_WIZ_ERROR_INVALID_PARAM;}#if DEBUGprintf("1 - calculate FVCO parameters:\n");printf("\t\tDIVCLK_DIVIDE  : %d\n", divclk_divide);printf("\t\tCLKFBOUT_MULT  : %.3f\n", clkfbout_mult);printf("\t\tCLKOUT0_DIVIDE : %.3f\n", clkout0_divide);
#endif// 2. 复位PLLif (clk_wiz_reset() != XCLK_WIZ_SUCCESS) {return XCLK_WIZ_ERROR_RESET_FAILED;}// 3. 构建寄存器值reg0_value = bulid_clk_cfg_reg0_val(divclk_divide, clkfbout_mult);reg2_value = bulid_clk_cfg_reg2_val(clkout0_divide);#if DEBUGprintf("3 - build clock configuration register values:\n");printf("\t\tReg0 (0x%x) : 0x%lx\n", XCLKWIZ_CLK_CONFIG_00_OFFSET, reg0_value);printf("\t\tReg2 (0x%x) : 0x%lx\n", XCLKWIZ_CLK_CONFIG_02_OFFSET, reg2_value);
#endif// 4. 配置寄存器XClk_Wiz_WriteReg(XCLKWIZ_BASEADDR, XCLKWIZ_CLK_CONFIG_00_OFFSET, reg0_value);XClk_Wiz_WriteReg(XCLKWIZ_BASEADDR, XCLKWIZ_CLK_CONFIG_02_OFFSET, reg2_value);#if DEBUGprintf("4 - write clock configuration register values.\n");
#endif// 5. 加载新配置XClk_Wiz_WriteReg(XCLKWIZ_BASEADDR, XCLKWIZ_CLK_CONFIG_23_OFFSET, 0x3);#if DEBUGprintf("5 - load clock configuration register values.\n");
#endif// 6. 等待锁定
#if DEBUGprintf("6 - waiting for clocking wizard lock...\n");
#endifif (clk_wiz_wait_lock(COUNT_NUM_100MS) != XCLK_WIZ_SUCCESS) {printf("Error: PLL Lock Failed.\n");return XCLK_WIZ_ERROR_LOCK_FAILED;}#if DEBUGprintf("\t\tclocking wizard lock succeeded.\n");
#endif//return XCLK_WIZ_SUCCESS;
}/********************************************************************/

4.2.3 main.c文件

代码如下所示:

/********************************************************************/#include "vdma/vdma_api.h"
#include "colorbar/colorbar_api.h"
#include "dynclk/dynclk_api.h"#include "xparameters.h"
#include "xtime_l.h"
#include "stdio.h"
#include "sleep.h"/********************************************************************/#define VDMA_DEVICE_ID		XPAR_AXIVDMA_0_DEVICE_ID
#define MEMORY_BASEADDR		XPAR_PS7_DDR_0_S_AXI_BASEADDR#define IMAGE_WIDTH			1920
#define IMAGE_HEIGHT		1080/********************************************************************/static void PrintTimeStats(XTime StartTime, XTime EndTime, const char* Label);/********************************************************************/XAxiVdma VdmaInst;int FrameBufferAddr = (MEMORY_BASEADDR + 0x02000000);/********************************************************************/int main()
{//int Status;u8* VdmaBufferAddr = (u8*)FrameBufferAddr;XTime StartTime;XTime EndTime;// 写入纯色图(用于确定RGB的字节位置)
#if 0GenPureColor(VdmaBufferAddr, (u32)IMAGE_WIDTH, (u32)IMAGE_HEIGHT);
#endif// 写入彩条图
#if 1XTime_GetTime(&StartTime);printf("Write colorbar to DDR.\n");GenColorBar(VdmaBufferAddr, (u32)IMAGE_WIDTH, (u32)IMAGE_HEIGHT);XTime_GetTime(&EndTime);PrintTimeStats(StartTime, EndTime, "GenColorBar DDR Write");
#endif// 启动VDMA读操作Status = run_vdma_frame_buffer(&VdmaInst, VDMA_DEVICE_ID, IMAGE_WIDTH, IMAGE_HEIGHT, FrameBufferAddr,0, 0,ONLY_READ);if (Status == XST_FAILURE) {printf("Error : run vdma frame buffer failed.\n");}else {printf("Run vdma frame buffer succeeded.\n");}//while (1) {//Status = clk_wiz_configure(50, 148.5);switch(Status) {case XCLK_WIZ_SUCCESS:printf("Clocking wizard configuration completed.\n\n");break;case XCLK_WIZ_ERROR_INVALID_PARAM:printf("Error : invalid input or output frequency values provided.\n\n");break;case XCLK_WIZ_ERROR_RESET_FAILED:printf("Error : clocking wizard reset failed.\n\n");break;case XCLK_WIZ_ERROR_LOCK_FAILED:printf("Error : clocking wizard lock failed.\n\n");break;default:printf("Error : unknown error occurred.\n\n");}sleep(15);//Status = clk_wiz_configure(50, 123.75);switch(Status) {case XCLK_WIZ_SUCCESS:printf("Clocking wizard configuration completed.\n\n");break;case XCLK_WIZ_ERROR_INVALID_PARAM:printf("Error : invalid input or output frequency values provided.\n\n");break;case XCLK_WIZ_ERROR_RESET_FAILED:printf("Error : clocking wizard reset failed.\n\n");break;case XCLK_WIZ_ERROR_LOCK_FAILED:printf("Error : clocking wizard lock failed.\n\n");break;default:printf("Error : unknown error occurred.\n\n");}sleep(15);}//return XST_SUCCESS;
}/*****************************************************************************/static void PrintTimeStats(XTime StartTime, XTime EndTime, const char* Label)
{// 计算耗时(单位:时钟周期)XTime ElapsedCycles = EndTime - StartTime;// 转换为秒(假设计时器频率为XPAR_CPU_CORTEXA9_0_CPU_CLK_FREQ_HZ)float ElapsedSeconds = (float)ElapsedCycles / (float)XPAR_CPU_CORTEXA9_0_CPU_CLK_FREQ_HZ;// 打印结果printf("[Time Consumed] - %s :\n", Label);printf("\t\tClock Cycles: %llu\n", ElapsedCycles);printf("\t\tTime (s):     %.6f\n", ElapsedSeconds);printf("\t\tTime (ms):    %.3f\n", ElapsedSeconds * 1000);
}/*****************************************************************************/

实测效果:每隔15s,重配置一次时钟频率,输出视频在1080p@50Hz和1080p@60Hz之间切换。

4.3 Digilent公司动态时钟API

4.3.1 clk_wiz.h文件

代码如下所示:

#ifndef CLK_WIZ_H_
#define CLK_WIZ_H_#include "xil_types.h"#define CLK_SR_OFFSET    0x04    //Status Register
#define CLK_CFG0_OFFSET  0x200   //Clock Configuration Register 0
#define CLK_CFG2_OFFSET  0x208   //Clock Configuration Register 2
#define CLK_CFG23_OFFSET 0x25C   //Clock Configuration Register 23void clk_wiz_cfg(u32 clk_base_addr,double freq);#endif /* CLK_WIZ_H_ */

4.3.2 clk_wiz.c文件

注意事项:该设计设定

  1. 输入时钟 = 100MHz
  2. CLKFBOUT_MULT = 10
  3. DIVCLK_DIVIDE = 1

于是,Fvco = 1000MHz为固定值,如此只需计算CLKOUT0_DIVIDE的值即可;但是,会出现输出时钟不准确的情况,例如需要输出148.5MHz的时钟

  1. div_factor = 6.734
  2. div_factor_int = 6
  3. div_factor_frac = 0.734 * 1000 = 734

实际情况是,CLKOUT0_DIVIDE的小数部分是以0.125为步进的,即小数值只能是0.000、0.125、0.250…0.875等值;对于0.734,个人认为Clocking Wizard IP核最后应该会选择一个最近的值0.750,实际输出148.148MHz;当我们在Vivado中配置Clocking Wizard IP核时,是可以精确的输出148.5MHz的时钟的;当然,对时钟要求不高的场合,这么做完全没问题。

//****************************************************************************************//#include "xclk_wiz.h"
#include "clk_wiz.h"
#include "xparameters.h"#define CLK_WIZ_IN_FREQ 100  //时钟IP核输入100MhzXClk_Wiz clk_wiz_inst;       //时钟IP核驱动实例//时钟IP核动态重配置
//参数1:时钟IP核的器件ID
//参数2:时钟IP核输出的时钟 单位:MHz
void clk_wiz_cfg(u32 clk_device_id,double freq){double div_factor = 0;u32 div_factor_int = 0,dviv_factor_frac=0;u32 clk_divide = 0;u32 status = 0;//初始化XCLK_WizXClk_Wiz_Config *clk_cfg_ptr;clk_cfg_ptr = XClk_Wiz_LookupConfig(clk_device_id);XClk_Wiz_CfgInitialize(&clk_wiz_inst,clk_cfg_ptr,clk_cfg_ptr->BaseAddr);if(freq <= 0)return;//配置时钟倍频/分频系数XClk_Wiz_WriteReg(clk_cfg_ptr->BaseAddr,CLK_CFG0_OFFSET,0x00000a01);  //10倍频,1分频//计算分频系数div_factor = CLK_WIZ_IN_FREQ * 10 / freq;div_factor_int = (u32)div_factor;dviv_factor_frac = (u32)((div_factor - div_factor_int) * 1000);clk_divide = div_factor_int | (dviv_factor_frac<<8);//配置分频系数XClk_Wiz_WriteReg(clk_cfg_ptr->BaseAddr,CLK_CFG2_OFFSET,clk_divide);//加载重配置的参数XClk_Wiz_WriteReg(clk_cfg_ptr->BaseAddr,CLK_CFG23_OFFSET,0x00000003);//获取时钟IP核的状态,判断是否重配置完成while(1){status = XClk_Wiz_ReadReg(clk_cfg_ptr->BaseAddr,CLK_SR_OFFSET);if(status&0x00000001)    //Bit0 Locked信号return ;}
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词