micropython/ports/stm32/boards/plli2svalues.py

214 lines
7.5 KiB
Python

"""
This program computes I2S PLL parameters for STM32
processors supporting an I2S PLL in the clock tree.
Those processors are listed below in the mcu_support_plli2s[] list.
"""
import re
from collections import namedtuple
class MCU:
def __init__(self, range_plli2sn, range_plli2sr):
self.range_plli2sn = range_plli2sn
self.range_plli2sr = range_plli2sr
mcu_default = MCU(range_plli2sn=range(50, 432 + 1), range_plli2sr=range(2, 7 + 1))
mcu_table = {"stm32f401xe": MCU(range_plli2sn=range(192, 432 + 1), range_plli2sr=range(2, 7 + 1))}
# list of stm32 processors that support an I2S PLL in the clock tree
mcu_support_plli2s = [
"stm32f405xx",
"stm32f401xe",
"stm32f407xx",
"stm32f411xe",
"stm32f412zx",
"stm32f413xx",
"stm32f427xx",
"stm32f429xx",
"stm32f439xx",
"stm32f446xx",
"stm32f722xx",
"stm32f733xx",
"stm32f746xx",
"stm32f756xx",
"stm32f767xx",
"stm32f769xx",
]
# The following function calculates the multiplier (plli2sn) and divider (plli2sr) parameters
# for the I2S PLL that leads to the best possible accuracy for the I2S sampling clock.
# This is done by creating a set of candidate parameters (plli2sn, plli2sr)
# and then determining which candidate parameters lead to a sampling clock frequency (Fs)
# that most closely matches the desired sampling clock frequency (I2S rate).
#
# A description of the clock tree helps to understand this function:
# The clock tree on a STM32 device is complex. A subset of the clock tree is used for I2S, as follows:
# 1. HSE clock is divided by the PLLM divider and feeds the I2S PLL (called PLLI2S in the STM32 clock tree).
# 2. The output frequency of the I2S PLL is controlled by two parameters, plli2sn and plli2sr.
# 3. The clock output of the I2S PLL is called PLLI2SCLK
# 4. PLLI2SCLK is gated into the I2S peripheral, where the name changes to I2SxCLK
# 5. I2SxCLK is an input to the I2S clock generator.
# 6. The output frequency of the I2S clock generator is controlled by
# two configuration parameters, I2SDIV and ODD.
# 7. The output of the I2S clock generator is the audio sampling frequency (Fs),
# which is used to create the signal at the I2S WS output pin.
#
# Example references:
# RM0090 Reference manual STM32F405/415, STM32F407/417, STM32F427/437 and STM32F429/439
# - section 6.3.23 RCC PLLI2S configuration register (RCC_PLLI2SCFGR)
# - section 28.4.4 Clock generator
# RM0385 Reference manual STM32F75xxx and STM32F74xxx
# - section 5.3.23 RCC PLLI2S configuration register (RCC_PLLI2SCFGR)
# - section 32.7.5 Clock generator
#
# The calculations below mimic the fixed-point integer calculations in the STM32 I2S driver,
# in the function HAL_I2S_Init().
def compute_plli2s_table(hse, pllm):
plli2s = namedtuple("plli2s", "bits rate plli2sn plli2sr i2sdiv odd error")
plli2s_table = []
for bits in (16, 32):
for rate in (8_000, 11_025, 12_000, 16_000, 22_050, 24_000, 32_000, 44_100, 48_000):
plli2s_candidates = []
for plli2sn in mcu.range_plli2sn:
for plli2sr in mcu.range_plli2sr:
I2SxCLK = hse // pllm * plli2sn // plli2sr
if I2SxCLK < 192_000_000:
# compute I2S clock generator parameters: i2sdiv, odd
tmp = (((I2SxCLK // (bits * 2)) * 10) // rate) + 5
tmp = tmp // 10
odd = tmp & 1
i2sdiv = (tmp - odd) // 2
Fs = I2SxCLK / ((bits * 2) * ((2 * i2sdiv) + odd))
error = (abs(Fs - rate) / rate) * 100
plli2s_candidates.append(
plli2s(
bits=bits,
rate=rate,
plli2sn=plli2sn,
plli2sr=plli2sr,
i2sdiv=i2sdiv,
odd=odd,
error=error,
)
)
# sort based on error
plli2s_candidates_sorted = sorted(plli2s_candidates, key=lambda x: x.error)
# select the best candidate
plli2s_table.append(plli2s_candidates_sorted[0])
return plli2s_table
def generate_c_table(plli2s_table, hse, pllm):
print("// MAKE generated file, created by plli2svalues.py: DO NOT EDIT")
print("// This table is used in machine_i2s.c")
print(f"// HSE_VALUE = {hse}")
print(f"// MICROPY_HW_CLK_PLLM = {pllm} \n")
print("#define PLLI2S_TABLE \\")
print("{ \\")
for plli2s in plli2s_table:
print(
f" {{{plli2s.rate}, "
f"{plli2s.bits}, "
f"{plli2s.plli2sr}, "
f"{plli2s.plli2sn} }}, "
f"/* i2sdiv: {int(plli2s.i2sdiv)}, "
f"odd: {plli2s.odd}, "
f"rate error % (desired vs actual)%: {plli2s.error:.4f} */ \\"
)
print("}")
def search_header(filename, re_include, re_define, lookup, val):
regex_include = re.compile(re_include)
regex_define = re.compile(re_define)
with open(filename) as f:
for line in f:
line = line.strip()
m = regex_include.match(line)
if m:
# Search included file
search_header(m.group(1), re_include, re_define, lookup, val)
continue
m = regex_define.match(line)
if m:
# found lookup value
if m.group(1) == lookup:
val[0] = int(m.group(3))
return val
def main():
global mcu
# parse input args
import sys
argv = sys.argv[1:]
c_table = False
mcu_series = "stm32f4"
hse = None
pllm = None
while True:
if argv[0] == "-c":
c_table = True
argv.pop(0)
elif argv[0] == "-m":
argv.pop(0)
mcu_series = argv.pop(0).lower()
else:
break
if mcu_series in mcu_support_plli2s:
if len(argv) != 2:
print("usage: pllvalues.py [-c] [-m <mcu_series>] <hse in MHz> <pllm in MHz>")
sys.exit(1)
if argv[0].startswith("hse:"):
# extract HSE_VALUE from header file
(hse,) = search_header(
argv[0][len("hse:") :],
r'#include "(boards/[A-Za-z0-9_./]+)"',
r"#define +(HSE_VALUE) +\((\(uint32_t\))?([0-9]+)\)",
"HSE_VALUE",
[None],
)
if hse is None:
raise ValueError("%s does not contain a definition of HSE_VALUE" % argv[0])
argv.pop(0)
if argv[0].startswith("pllm:"):
# extract MICROPY_HW_CLK_PLLM from header file
(pllm,) = search_header(
argv[0][len("pllm:") :],
r'#include "(boards/[A-Za-z0-9_./]+)"',
r"#define +(MICROPY_HW_CLK_PLLM) +\((\(uint32_t\))?([0-9]+)\)",
"MICROPY_HW_CLK_PLLM",
[None],
)
if pllm is None:
raise ValueError(
"%s does not contain a definition of MICROPY_HW_CLK_PLLM" % argv[0]
)
argv.pop(0)
# Select MCU parameters
mcu = mcu_default
for m in mcu_table:
if mcu_series.startswith(m):
mcu = mcu_table[m]
break
plli2s_table = compute_plli2s_table(hse, pllm)
if c_table:
generate_c_table(plli2s_table, hse, pllm)
if __name__ == "__main__":
main()