参见14.1。
参见14.1。
注意事项:基于14.1做如下改动
特别说明:为什么不使用Digilent公司提供的axi_dynclk IP核(正点原子教程使用该IP),原因如下
动态时钟的软件配置部分代码,我是让deepseek帮我生成的,我负责提需求,包括:
代码生成后,让deepseek形成xxx_api.c文件和xxx_api.h文件,使用很方便,只需提供输入时钟频率和期望的输出时钟频率(不能输出类似33.333MHz的时钟),效果非常好。
代码如下所示:
#ifndef XCLK_WIZ_API_H
#define XCLK_WIZ_API_H
#include "xparameters.h"
#include "xclk_wiz_hw.h"
#include "math_compat.h"
// 寄存器基地址
#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)
/*
*
* @brief 配置时钟向导IP
* @param input_freq 输入时钟频率(MHz)
* @param output_freq 期望的输出时钟频率(MHz)
* @return 成功返回XCLK_WIZ_SUCCESS,失败返回错误代码
*
*/
int clk_wiz_configure(double input_freq, double output_freq);
/*
*
* @brief 复位时钟向导IP
* @return 成功返回XCLK_WIZ_SUCCESS,失败返回错误代码
*
*/
int clk_wiz_reset(void);
/*
*
* @brief 检查时钟向导是否锁定
* @return 锁定返回1,未锁定返回0
*
*/
int clk_wiz_is_locked(void);
#endif // XCLK_WIZ_API_H
代码如下所示
#ifndef MATH_COMPAT_H
#define MATH_COMPAT_H
/*****************************************************************************/
// 替代ceil函数实现
static inline double ceil(double x) {
if (x >= 0.0) {
int int_part = (int)x;
return (x == (double)int_part) ? x : (double)(int_part + 1);
}
else {
int int_part = (int)x;
return (double)int_part;
}
}
/*****************************************************************************/
// 替代floor函数实现
static inline double floor(double x) {
if (x >= 0.0) {
return (double)((int)x);
}
else {
int int_part = (int)x;
return (x == (double)int_part) ? x : (double)(int_part - 1);
}
}
/*****************************************************************************/
// 替代fmax函数实现
static inline double fmax(double x, double y) {
return (x > y) ? x : y;
}
/*****************************************************************************/
// 替代fmin函数实现
static inline double fmin(double x, double y) {
return (x < y) ? x : y;
}
/*****************************************************************************/
// 替代fabs函数实现(虽然math.h中有声明,但可能缺少实现)
static inline double fabs(double x) {
return (x < 0.0) ? -x : x;
}
/*****************************************************************************/
#endif // MATH_COMPAT_H
代码如下所示:
/*****************************************************************************/
#include "dynclk_api.h"
#include
/*****************************************************************************/
// 内部函数声明
static int calculate_pll_params(double input_freq, double output_freq, int *divclk_divide, double *clkfbout_mult, double *clkout0_divide);
static u32 build_reg0_value(int divclk_divide, double clkfbout_mult);
static u32 build_reg2_value(double clkout0_divide);
static int wait_for_lock(u32 timeout_us);
/*****************************************************************************/
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 wait_for_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) {
//
printf("2 - PLL Reset Start.\n");
// 发送复位信号
XClk_Wiz_WriteReg(XCLKWIZ_BASEADDR, XCLKWIZ_SOFT_RESET_OFFSET, XCLKWIZ_RESET_VALUE);
// 检查复位是否完成
printf("\tWaiting for PLL lock...\n");
if (wait_for_lock(COUNT_NUM_100MS) != XCLK_WIZ_SUCCESS) { // 100ms超时
printf("\tError: PLL Lock Failed.\n");
return XCLK_WIZ_ERROR_LOCK_FAILED;
}
//
printf("\tPLL Reset Completed.\n\n");
//
return XCLK_WIZ_SUCCESS;
}
/*****************************************************************************/
static int calculate_pll_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_DIVIDE
double 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 build_reg0_value(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 build_reg2_value(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 Frequency Parameters.\n");
return XCLK_WIZ_ERROR_INVALID_PARAM;
}
// 1. 计算PLL参数
if (calculate_pll_params(input_freq, output_freq, &divclk_divide,
&clkfbout_mult, &clkout0_divide) != 0) {
printf("Error: Cannot Find Suitable PLL Parameters.\n");
return XCLK_WIZ_ERROR_INVALID_PARAM;
}
printf("1 - Calculate FVCO Parameters:\n");
printf("\tDIVCLK_DIVIDE: %d\n", divclk_divide);
printf("\tCLKFBOUT_MULT: %.3f\n", clkfbout_mult);
printf("\tCLKOUT0_DIVIDE: %.3f\n\n", clkout0_divide);
// 2. 复位PLL
if (clk_wiz_reset() != XCLK_WIZ_SUCCESS) {
return XCLK_WIZ_ERROR_RESET_FAILED;
}
// 3. 构建寄存器值
reg0_value = build_reg0_value(divclk_divide, clkfbout_mult);
reg2_value = build_reg2_value(clkout0_divide);
printf("3 - Build Clock Configuration Register Values:\n");
printf("\tReg0 (0x%x): 0x%lx\n", XCLKWIZ_CLK_CONFIG_00_OFFSET, reg0_value);
printf("\tReg2 (0x%x): 0x%lx\n\n", XCLKWIZ_CLK_CONFIG_02_OFFSET, reg2_value);
// 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);
printf("4 - Write Clock Configuration Register Values.\n\n");
// 5. 加载新配置
XClk_Wiz_WriteReg(XCLKWIZ_BASEADDR, XCLKWIZ_CLK_CONFIG_23_OFFSET, 0x3);
printf("5 - Load Clock Configuration Register Values.\n\n");
// 6. 等待锁定
printf("6 - Waiting for PLL Lock...\n");
if (wait_for_lock(COUNT_NUM_100MS) != XCLK_WIZ_SUCCESS) {
printf("Error: PLL Lock Failed.\n");
return XCLK_WIZ_ERROR_LOCK_FAILED;
}
printf("\tPLl Lock Succeeded.\n\n");
//
return XCLK_WIZ_SUCCESS;
}
/*****************************************************************************/
在14.1提供的main函数基础上,修改while循环部分,代码如下所示:
while (1) {
//
sleep(20);
result = clk_wiz_configure(50, 123.75);
switch(result) {
case XCLK_WIZ_SUCCESS:
printf("PLL Configuration Completed.\n\n");
break;
case XCLK_WIZ_ERROR_INVALID_PARAM:
printf("Error: Invalid Parameters Provided.\n\n");
break;
case XCLK_WIZ_ERROR_RESET_FAILED:
printf("Error: PLL Reset Failed.\n\n");
break;
case XCLK_WIZ_ERROR_LOCK_FAILED:
printf("Error: PLL Lock Failed.\n\n");
break;
default:
printf("Error: Unknown Error Occurred.\n\n");
}
//
sleep(20);
result = clk_wiz_configure(50, 148.5);
switch(result) {
case XCLK_WIZ_SUCCESS:
printf("PLL Configuration Completed.\n\n");
break;
case XCLK_WIZ_ERROR_INVALID_PARAM:
printf("Error: Invalid Parameters Provided.\n\n");
break;
case XCLK_WIZ_ERROR_RESET_FAILED:
printf("Error: PLL Reset Failed.\n\n");
break;
case XCLK_WIZ_ERROR_LOCK_FAILED:
printf("Error: PLL Lock Failed.\n\n");
break;
default:
printf("Error: Unknown Error Occurred.\n\n");
}
}
实测效果:每隔20s,重配置一次时钟频率,输出视频在1080p@50Hz和1080p@60Hz之间切换。