心血来潮弄了个配置LTDC时钟的程序,基本方法就是简单粗暴的循环+判断对PLL3进行配置。
当前只对PLL3R负责并尽可能让另外两个的时钟速度高一些
[C] 纯文本查看 复制代码 /**
* @brief
*
* @param freq LTDC clock, MHz
*/
static void lcd_set_ltdc_clock(uint8_t freq)
{
/**
* @brief Set Clock PLL3R
* PLL3_VCO Input = HSE_VALUE / PLL3M
* PLL3_VCO Output = PLL3_VCO Input * PLL3N
* PLLLCDCLK = PLL3_VCO Output / PLL3R
*
* /DIVM3 = 1~63
* xDIVN3 = 4~512
* /DIVP3 = 2
* /DIVR3 = 1~128
*/
uint32_t pll3m, pll3n, pll3p, pll3q, pll3r;
uint16_t freq_osc, freq_sys, freq_pll3m, freq_pll3n; // MHz
uint16_t freq_pll3p, freq_pll3q, freq_pll3r; // MHz
log_i("try to set LTDC clock %uMhz", freq);
// 确定时钟源
if (RCC_GET_PLL_OSCSOURCE() == RCC_PLLSOURCE_HSE)
{
// HSE = 4~48M
freq_osc = HSE_VALUE / 1000000UL;
pll3m = HSE_VALUE / 1000000UL; // 1MHz
log_d("RCC_GET_PLL_OSCSOURCE is HSE");
}
else if (RCC_GET_PLL_OSCSOURCE() == RCC_PLLSOURCE_HSI)
{
// HSI = 64M.
// TODO:未测试
freq_osc = HSI_VALUE / 1000000UL;
pll3m = HSE_VALUE / 2000000UL; // 2MHz
log_d("RCC_GET_PLL_OSCSOURCE is HSI");
}
else
{
log_e("Failed to check PLL source");
return;
}
/**
* @brief 遍历PLL3参数
* PLL3频率尽可能高
* DIVP3=2 && PLL3P<SysClock
* DIVQ3=1~128 && PLL3Q<SysClock
* PLL3R尽可能接近目标值
*/
uint16_t last_freq_pll3r_err = 0xFFFF;
uint16_t last_freq_pll3q = 0;
freq_sys = HAL_RCC_GetSysClockFreq() / 1000000UL;
freq_pll3m = freq_osc / pll3m;
for (uint16_t divn3 = 4; divn3 <= 512; divn3++) // DIVN3
{
uint16_t divq3, divr3;
freq_pll3n = freq_pll3m * divn3;
// PLL3P <= SysClock
pll3p = 2; // 固定值
freq_pll3p = freq_pll3n / pll3p;
if (freq_pll3p > freq_sys)
continue;
// PLL3Q <= SysClock
uint8_t err = 1;
for (divq3 = 1; divq3 <= 128; divq3++)
{
freq_pll3q = freq_pll3n / divq3;
if (freq_pll3q <= freq_sys)
{
err = 0;
break;
}
}
if (err)
continue;
// PLL3R = freq
uint8_t save = 0;
for (divr3 = 3; divr3 <= 128; divr3++) // DIVR3
{
// 计算误差
int32_t freq_err;
freq_pll3r = freq_pll3n / divr3;
freq_err = freq_pll3r - freq;
if (freq_err < 0)
freq_err = -freq_err;
// 误差尽可能小,频率尽可能大
if (last_freq_pll3r_err >= freq_err)
{
last_freq_pll3r_err = freq_err;
/**
* @brief 二次判断
* 配置25M时存在极值DIVN3=480,PLL3P=240,PLL3Q=480,PLL3R=25
*/
if (freq_pll3r != freq ||
last_freq_pll3q < freq_pll3q)
{
last_freq_pll3q = freq_pll3q;
save = 1;
break;
}
}
}
if (save)
{
pll3n = divn3;
pll3q = divq3;
pll3r = divr3;
// elog_raw("DIVN3=%u,PLL3P=%u,PLL3Q=%u,PLL3R=%u\r\n",
// divn3, freq_pll3p, freq_pll3q, freq_pll3r);
}
}
log_d("DIVM3=%u, DIVN3=%u", pll3m, pll3n);
log_d("DIVP3=%u, DIVQ3=%u, DIV3R=%u", pll3p, pll3q, pll3r);
// Config
RCC_PeriphCLKInitTypeDef PeriphClkIniture;
PeriphClkIniture.PeriphClockSelection = RCC_PERIPHCLK_LTDC;
PeriphClkIniture.PLL3.PLL3M = pll3m;
PeriphClkIniture.PLL3.PLL3N = pll3n;
PeriphClkIniture.PLL3.PLL3P = pll3p;
PeriphClkIniture.PLL3.PLL3Q = pll3q;
PeriphClkIniture.PLL3.PLL3R = pll3r;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkIniture) != HAL_OK)
{
log_e("failed to set LTDC clk");
return;
}
/*Print result*/
freq_pll3n = freq_osc / PeriphClkIniture.PLL3.PLL3M * PeriphClkIniture.PLL3.PLL3N;
freq_pll3p = freq_pll3n / PeriphClkIniture.PLL3.PLL3P;
freq_pll3q = freq_pll3n / PeriphClkIniture.PLL3.PLL3Q;
freq_pll3r = freq_pll3n / PeriphClkIniture.PLL3.PLL3R;
log_i("Set PLL3 Freq: P=%uMHz, Q=%uMHz, R=%uMHz",
freq_pll3p, freq_pll3q, freq_pll3r);
}
刚写完,测试了25MHz、33MHz、50MHz和70MHz的配置
|