summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ports/stm32/Makefile7
-rw-r--r--ports/stm32/boards/plli2svalues.py213
-rw-r--r--ports/stm32/machine_i2s.c48
3 files changed, 265 insertions, 3 deletions
diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile
index 0bae26f2f..a1449e718 100644
--- a/ports/stm32/Makefile
+++ b/ports/stm32/Makefile
@@ -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 $@"
diff --git a/ports/stm32/boards/plli2svalues.py b/ports/stm32/boards/plli2svalues.py
new file mode 100644
index 000000000..8ff7f0ff8
--- /dev/null
+++ b/ports/stm32/boards/plli2svalues.py
@@ -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()
diff --git a/ports/stm32/machine_i2s.c b/ports/stm32/machine_i2s.c
index b3e1cee34..dbee81bb3 100644
--- a/ports/stm32/machine_i2s.c
+++ b/ports/stm32/machine_i2s.c
@@ -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) {