Created
February 23, 2015 02:13
-
-
Save ChuckM/8c63afdcd0a31070146f to your computer and use it in GitHub Desktop.
Perl code to calculate PLL constants
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env perl | |
# | |
# This is me doodling around with the various restrictions on PLL | |
# values in the STM32F Series Cortex-M chips to find the "best" | |
# settings for someone using the internal 16Mhz clock. Not sure | |
# why you would use one over the other exactly. | |
# | |
# According to the spec, the closer the "input" frequency to the | |
# PLL is to 2Mhz the less jitter it has, with an HSI of 16Mhz | |
# and PLLM set to /8 you get exactly 2Mhz, but you can go all | |
# the way down to /16 to get a 1Mhz input clock, still be in spec | |
# and that opens up a couple additional frequencies. | |
# use strict; use warnings; | |
use Getopt::Long; | |
use Data::Dumper; | |
my $clock_in; | |
my $clock_name; | |
my $clock_max; | |
GetOptions( | |
"freq=i" => \$clock_in, | |
"name=s" => \$clock_name, | |
"max=i" => \$clock_max, | |
); | |
$clock_in = $clock_in // 16; | |
$clock_name = $clock_name // "HSI"; | |
$clock_max = $clock_max // 180; | |
my %usb; | |
# | |
# These are the VCO frequencies for which there is an | |
# integer divider for PLLQ to get 48Mhz for USB OTG FS | |
# | |
for (my $i = 192; $i < 433; $i += 1) { | |
if (($i % 48) == 0) { | |
$usb{$i} = $i / 48; | |
} | |
} | |
# | |
# Compute every way to generate a VCO frequency between | |
# 192Mhz and 432Mhz, and pull out the ones that are | |
# integer multiples of 1Mhz. Note that having PLLM | |
# be '8' is preferable as it gives an 'optimum' 2Mhz | |
# frequency input into the PLL. | |
# | |
my %freqs; | |
for (my $pllm = 2; $pllm < 64; $pllm += 1) { | |
my $vco_in = $clock_in / $pllm; | |
next if (($vco_in < 1.0) or ($vco_in > 2.0)); | |
for (my $plln = 96; $plln < 433; $plln += 1) { | |
my $vco = $vco_in * $plln; | |
next if ($vco < 192.0); # too slow | |
next if ($vco > 432.0); # too fast | |
next if defined $freqs{int $vco}; # already got best one | |
# | |
# Save the combination of pllm and plln that gives us | |
# an integral number of Mhz frequency | |
# | |
if ((int $vco) == $vco) { | |
$freqs{int $vco}->{pllm} = $pllm; | |
$freqs{int $vco}->{plln} = $plln; | |
} | |
} | |
} | |
my %pllp_val = ( 2 => 0, 4 => 1, 6 => 2, 8 => 3 ); | |
# | |
# Now pick out all system clock speeds that are less than or equal | |
# to the maximum allowed System Clock. | |
# | |
# This is 120Mhz for the F40x/F41x, and 180Mhz for the F42x/F43x | |
# | |
my %sysclocks; | |
my $src = ($clock_name =~ /HSE/) ? 1 : 0; | |
for (my $vco = 192; $vco < 433; $vco += 1) { | |
next if not defined $usb{$vco}; | |
foreach my $pllp (2, 4, 6, 8) { | |
my $clk = $vco / $pllp; | |
next if $clk > $clock_max; | |
my $const = $freqs{$vco}->{pllm} << 0 | | |
$freqs{$vco}->{plln} << 6 | | |
$pllp_val{$pllp} << 16 | | |
$src | | |
$usb{$vco} << 24; | |
push @{$sysclocks{$clk}->{combo}}, { pllm => $freqs{$vco}->{pllm}, | |
plln => $freqs{$vco}->{plln}, | |
pllp => $pllp, | |
pllq => $usb{$vco}, | |
const => $const, | |
src => $src, | |
vco => $vco }; | |
} | |
} | |
# | |
# print out a table of system clocks and their respective PLL Values | |
# | |
printf " SYSCLK : VCO : PLLM : PLLN : PLLP : PLLQ\n"; | |
printf "---------+-------+------+------+------+------+\n"; | |
foreach my $k (sort { $a <=> $b} keys %sysclocks) { | |
foreach my $clk (@{$sysclocks{$k}->{combo}}) { | |
printf " %3d Mhz : %4d : %4d : %4d : %4d : %4d : 0x%08x\n", | |
$k, $clk->{vco}, $clk->{pllm}, $clk->{plln}, $clk->{pllp}, $clk->{pllq}, | |
$clk->{const}; | |
} | |
} | |
print "#define RCC_PLLCFGR_BITS(pllp, plln, pllq, pllm, src) (\\\n"; | |
print "\t\t((pllp & RCC_PLLCFGR_PLLP_MASK) << RCC_PLLCFGR_PLLP_SHIFT) |\\\n"; | |
print "\t\t((plln & RCC_PLLCFGR_PLLN_MASK) << RCC_PLLCFGR_PLLN_SHIFT) |\\\n"; | |
print "\t\t((pllq & RCC_PLLCFGR_PLLQ_MASK) << RCC_PLLCFGR_PLLQ_SHIFT) |\\\n"; | |
my $mask = 0xf << 24 | 0x3 << 16 | 0x1ff << 6 | 0x3f; | |
printf "#define HSI_PLLCFGR_PLL_MASK\t0x%08x\n", $mask; | |
my %clocks; | |
foreach my $k (sort { $a <=> $b} keys %sysclocks) { | |
foreach my $clk (@{$sysclocks{$k}->{combo}}) { | |
next if defined $clocks{$k}; | |
printf "#define RCC_%dMHZ_%s_%dMHZ_PLL\t\t%s\t/* VCO = %d Mhz */\n", $clock_in, $clock_name, $k, | |
use_macro($clk), $clk->{vco}; | |
$clocks{$k} = 1; | |
} | |
} | |
sub use_macro { | |
my ($s) = @_; | |
return "RCC_PLLCFGR_BITS($s->{pllp}, $s->{plln}, $s->{pllq}, $s->{pllm}, $s->{src})"; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment