stm32/machine_i2s: Improve accuracy of SCK frequency.
Configures the I2S PLL to produce a frequency that the I2S clock generator can use to create an optimal SCK frequency. The I2S PLL configuration table is automatically generated at build time. Fixes issue #10280. Signed-off-by: Mike Teachman <mike.teachman@gmail.com>
This commit is contained in:
parent
f3889db265
commit
58112fc49a
|
@ -65,6 +65,7 @@ OPENOCD_CONFIG ?= boards/openocd_stm32f4.cfg
|
|||
include stm32.mk
|
||||
|
||||
PLLVALUES = boards/pllvalues.py
|
||||
PLLI2SVALUES = boards/plli2svalues.py
|
||||
MAKE_PINS = boards/make-pins.py
|
||||
BOARD_PINS = $(BOARD_DIR)/pins.csv
|
||||
PREFIX_FILE = boards/stm32f4xx_prefix.c
|
||||
|
@ -82,6 +83,7 @@ GEN_CDCINF_FILE = $(HEADER_BUILD)/pybcdc.inf
|
|||
GEN_CDCINF_HEADER = $(HEADER_BUILD)/pybcdc_inf.h
|
||||
|
||||
GEN_PLLFREQTABLE_HDR = $(HEADER_BUILD)/pllfreqtable.h
|
||||
GEN_PLLI2STABLE_HDR = $(HEADER_BUILD)/plli2stable.h
|
||||
GEN_STMCONST_HDR = $(HEADER_BUILD)/modstm_const.h
|
||||
GEN_STMCONST_MPZ = $(HEADER_BUILD)/modstm_mpz.h
|
||||
CMSIS_MCU_HDR = $(STM32LIB_CMSIS_ABS)/Include/$(CMSIS_MCU_LOWER).h
|
||||
|
@ -681,6 +683,11 @@ $(GEN_PLLFREQTABLE_HDR): $(PLLVALUES) | $(HEADER_BUILD)
|
|||
$(ECHO) "GEN $@"
|
||||
$(Q)$(PYTHON) $(PLLVALUES) -c -m $(CMSIS_MCU_LOWER) file:$(BOARD_DIR)/stm32$(MCU_SERIES)xx_hal_conf.h > $@
|
||||
|
||||
$(TOP)/extmod/machine_i2s.c: $(GEN_PLLI2STABLE_HDR)
|
||||
$(GEN_PLLI2STABLE_HDR): $(PLLI2SVALUES) | $(HEADER_BUILD)
|
||||
$(ECHO) "GEN $@"
|
||||
$(Q)$(PYTHON) $(PLLI2SVALUES) -c -m $(CMSIS_MCU_LOWER) hse:$(BOARD_DIR)/stm32$(MCU_SERIES)xx_hal_conf.h pllm:$(BOARD_DIR)/mpconfigboard.h > $@
|
||||
|
||||
$(BUILD)/modstm.o: $(GEN_STMCONST_HDR)
|
||||
$(HEADER_BUILD)/modstm_const.h: $(CMSIS_MCU_HDR) make-stmconst.py | $(HEADER_BUILD)
|
||||
$(ECHO) "GEN stmconst $@"
|
||||
|
|
|
@ -0,0 +1,213 @@
|
|||
"""
|
||||
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()
|
|
@ -33,6 +33,7 @@
|
|||
#include "py/mphal.h"
|
||||
#include "pin.h"
|
||||
#include "dma.h"
|
||||
#include "genhdr/plli2stable.h"
|
||||
|
||||
// Notes on this port's specific implementation of I2S:
|
||||
// - the DMA callbacks (1/2 complete and complete) are used to implement the asynchronous background operations
|
||||
|
@ -61,6 +62,13 @@ typedef enum {
|
|||
BOTTOM_HALF
|
||||
} ping_pong_t;
|
||||
|
||||
typedef struct _plli2s_config_t {
|
||||
uint32_t rate;
|
||||
uint8_t bits;
|
||||
uint8_t plli2sr;
|
||||
uint16_t plli2sn;
|
||||
} plli2s_config_t;
|
||||
|
||||
typedef struct _machine_i2s_obj_t {
|
||||
mp_obj_base_t base;
|
||||
uint8_t i2s_id;
|
||||
|
@ -98,12 +106,26 @@ STATIC const int8_t i2s_frame_map[NUM_I2S_USER_FORMATS][I2S_RX_FRAME_SIZE_IN_BYT
|
|||
{ 2, 3, 0, 1, 6, 7, 4, 5 }, // Stereo, 32-bits
|
||||
};
|
||||
|
||||
STATIC const plli2s_config_t plli2s_config[] = PLLI2S_TABLE;
|
||||
|
||||
void machine_i2s_init0() {
|
||||
for (uint8_t i = 0; i < MICROPY_HW_MAX_I2S; i++) {
|
||||
MP_STATE_PORT(machine_i2s_obj)[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
STATIC bool lookup_plli2s_config(int8_t bits, int32_t rate, uint16_t *plli2sn, uint16_t *plli2sr) {
|
||||
for (uint16_t i = 0; i < MP_ARRAY_SIZE(plli2s_config); i++) {
|
||||
if ((plli2s_config[i].bits == bits) && (plli2s_config[i].rate == rate)) {
|
||||
*plli2sn = plli2s_config[i].plli2sn;
|
||||
*plli2sr = plli2s_config[i].plli2sr;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// For 32-bit audio samples, the STM32 HAL API expects each 32-bit sample to be encoded
|
||||
// in an unusual byte ordering: Byte_2, Byte_3, Byte_0, Byte_1
|
||||
// where: Byte_0 is the least significant byte of the 32-bit sample
|
||||
|
@ -294,6 +316,29 @@ STATIC bool i2s_init(machine_i2s_obj_t *self) {
|
|||
HAL_GPIO_Init(self->sd->gpio, &GPIO_InitStructure);
|
||||
}
|
||||
|
||||
// configure I2S PLL
|
||||
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
|
||||
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;
|
||||
|
||||
// lookup optimal PLL multiplier (PLLI2SN) and divisor (PLLI2SR) for a given sample size and sampling frequency
|
||||
uint16_t plli2sn;
|
||||
uint16_t plli2sr;
|
||||
|
||||
if (lookup_plli2s_config(self->mode == I2S_MODE_MASTER_RX ? 32 : self->bits, self->rate, &plli2sn, &plli2sr)) {
|
||||
// match found
|
||||
PeriphClkInitStruct.PLLI2S.PLLI2SN = plli2sn;
|
||||
PeriphClkInitStruct.PLLI2S.PLLI2SR = plli2sr;
|
||||
} else {
|
||||
// no match for sample size and rate
|
||||
// configure PLL to use power-on default values when a non-standard sampling frequency is used
|
||||
PeriphClkInitStruct.PLLI2S.PLLI2SN = 192;
|
||||
PeriphClkInitStruct.PLLI2S.PLLI2SR = 2;
|
||||
}
|
||||
|
||||
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HAL_I2S_Init(&self->hi2s) == HAL_OK) {
|
||||
// Reset and initialize Tx and Rx DMA channels
|
||||
if (self->mode == I2S_MODE_MASTER_RX) {
|
||||
|
@ -306,13 +351,10 @@ STATIC bool i2s_init(machine_i2s_obj_t *self) {
|
|||
self->hi2s.hdmatx = &self->hdma_tx;
|
||||
}
|
||||
|
||||
__HAL_RCC_PLLI2S_ENABLE(); // start I2S clock
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void HAL_I2S_ErrorCallback(I2S_HandleTypeDef *hi2s) {
|
||||
|
|
Loading…
Reference in New Issue