From 8f66fcc26cae20d94bf9c2d16af6d97972a99947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Ledneczki?= Date: Wed, 28 Apr 2021 00:00:00 +0000 Subject: [PATCH 01/28] [architecture] I2sMaster created Co-authored-by: Raphael Lehmann --- .../architecture/interface/i2s_master.hpp | 103 ++++++++++++++++++ src/modm/architecture/module.lb | 15 +++ 2 files changed, 118 insertions(+) create mode 100644 src/modm/architecture/interface/i2s_master.hpp diff --git a/src/modm/architecture/interface/i2s_master.hpp b/src/modm/architecture/interface/i2s_master.hpp new file mode 100644 index 0000000000..bc33f0c13e --- /dev/null +++ b/src/modm/architecture/interface/i2s_master.hpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2021, Marton Lednczki + * Copyright (c) 2022, Raphael Lehmann + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_INTERFACE_I2S_MASTER_HPP +#define MODM_INTERFACE_I2S_MASTER_HPP + +#include +#include "i2s.hpp" + +namespace modm +{ + +/** + * Interface for a I2S Master + * + * @author Marton Ledneczki + * @ingroup modm_architecture_i2s + */ +class I2sMaster : public ::modm::PeripheralDriver +{ +#ifdef __DOXYGEN__ +public: + /** + * @brief Connect GPIOs to the peripheral and configure them. + * + * This configures the CK (serial clock), MCK (master clock), + * SD (serial data) and WS (word select) signals and connects them. + * + * @tparam Signals One CK, SD, WS, MCK signal are required and can be + * passed out-of-order. + */ + template< class... Signals > + static void + connect(); + + /** + * @brief Initializes the hardware and sets the samplerate. + * + * @tparam SystemClock the currently active system clock + * @tparam samplerate the desired sample rate in Hz + * @tparam tolerance the allowed relative tolerance for the resulting samplerate + */ + template< class SystemClock, frequency_t samplerate, percent_t tolerance=pct(0.019) > + static void + initialize(); + + + /** + * @brief Set the buffer + * + * @param address Pointer to the buffer. The user has to ensure the buffer is in DMA-able memory. + * @param length Size of the buffer + */ + static void + setTxBuffer(uintptr_t address, std::size_t length); + + /** + * @brief Set transfer complete handler function + * + * The transfer complete handler should set the next tx buffer using + * `setTxBuffer()` and then restart the DMA transfer using `startDma()`. + */ + static void + setTransferCompleteIrqHandler(DmaBase::IrqHandler handleDmaTransferComplete); + + /** + * @brief Starts the I2S peripheral + */ + static void + start(); + + /** + * @brief (Re-)Starts a DMA transfer + * + * This will be usually called inside the transfer complete handler after + * a (new) tx buffer address was set. + * + * @see setTransferCompleteIrqHandler() + */ + static void + startDma(); + + /** + * @brief Stops I2S + */ + static void + stop(); + +#endif +}; + +} // namespace modm + +#endif // MODM_INTERFACE_SPI_MASTER_HPP diff --git a/src/modm/architecture/module.lb b/src/modm/architecture/module.lb index 1d880d0f79..b536ee9931 100644 --- a/src/modm/architecture/module.lb +++ b/src/modm/architecture/module.lb @@ -231,6 +231,20 @@ class I2cMultiplexer(Module): env.copy("interface/i2c_multiplexer.hpp") # ----------------------------------------------------------------------------- +class I2s(Module): + def init(self, module): + module.name = "i2s" + module.description = "Inter-IC Sound (I2S)" + + def prepare(self, module, options): + module.depends(":processing:resumable") + return True + + def build(self, env): + env.outbasepath = "modm/src/modm/architecture" + env.copy("interface/i2s_master.hpp") +# ----------------------------------------------------------------------------- + class Interrupt(Module): def init(self, module): module.name = "interrupt" @@ -367,6 +381,7 @@ def prepare(module, options): module.add_submodule(I2c()) module.add_submodule(I2cDevice()) module.add_submodule(I2cMultiplexer()) + module.add_submodule(I2s()) module.add_submodule(Interrupt()) module.add_submodule(Memory()) module.add_submodule(OneWire()) From e158b11065cbbc8b5952c0ba6e9530da5edfe902 Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Sat, 30 Oct 2021 00:43:22 +0200 Subject: [PATCH 02/28] [dma] Support double buffer (only stm32-stream-channel) --- src/modm/platform/dma/stm32/dma.hpp.in | 41 +++++++++++++-- src/modm/platform/dma/stm32/dma_base.hpp.in | 9 ++++ src/modm/platform/dma/stm32/dma_hal.hpp.in | 55 ++++++++++++++++++--- src/modm/platform/dma/stm32/module.lb | 1 + 4 files changed, 96 insertions(+), 10 deletions(-) diff --git a/src/modm/platform/dma/stm32/dma.hpp.in b/src/modm/platform/dma/stm32/dma.hpp.in index 5f56237bbf..933a944f0d 100644 --- a/src/modm/platform/dma/stm32/dma.hpp.in +++ b/src/modm/platform/dma/stm32/dma.hpp.in @@ -137,10 +137,18 @@ public: MemoryIncrementMode memoryIncrement, PeripheralIncrementMode peripheralIncrement, Priority priority = Priority::Medium, - CircularMode circularMode = CircularMode::Disabled) + CircularMode circularMode = CircularMode::Disabled +%% if doubleBuffer + ,DoubleBufferMode doubleBufferMode = DoubleBufferMode::Disabled +%% endif + ) { - ChannelHal::configure(direction, memoryDataSize, peripheralDataSize, - memoryIncrement, peripheralIncrement, priority, circularMode); + ChannelHal::configure(direction, memoryDataSize, peripheralDataSize, memoryIncrement, + peripheralIncrement, priority, circularMode +%% if doubleBuffer + , doubleBufferMode +%% endif + ); } /** @@ -183,6 +191,33 @@ public: { ChannelHal::setMemoryAddress(address); } + +%% if doubleBuffer + /** + * Set the secondary memory address of the DMA channel + * + * Only used in double buffer mode + * + * @param[in] address Source address + */ + static void + setMemoryAddress2(uintptr_t address) + { + ChannelHal::setMemoryAddress2(address); + } + + /** + * Detect which buffer is currently in use + * + * Only meaningful in double buffer mode + */ + static bool + isPrimaryBufferActive() + { + return ChannelHal::isPrimaryBufferActive(); + } +%% endif + /** * Set the peripheral address of the DMA channel * diff --git a/src/modm/platform/dma/stm32/dma_base.hpp.in b/src/modm/platform/dma/stm32/dma_base.hpp.in index 0b1f12a7d8..4142da3256 100644 --- a/src/modm/platform/dma/stm32/dma_base.hpp.in +++ b/src/modm/platform/dma/stm32/dma_base.hpp.in @@ -196,6 +196,15 @@ public: Enabled = {{ reg_prefix }}_CIRC, ///< circular mode }; +%% if doubleBuffer + enum class + DoubleBufferMode : uint32_t + { + Disabled = 0, + Enabled = {{ reg_prefix }}_DBM, ///< double buffer mode + }; +%% endif + enum class DataTransferDirection : uint32_t { diff --git a/src/modm/platform/dma/stm32/dma_hal.hpp.in b/src/modm/platform/dma/stm32/dma_hal.hpp.in index 7d123cbd05..dfb76de319 100644 --- a/src/modm/platform/dma/stm32/dma_hal.hpp.in +++ b/src/modm/platform/dma/stm32/dma_hal.hpp.in @@ -181,12 +181,17 @@ public: * @param[in] circularMode Transfer data in circular mode? */ static void - configure(DataTransferDirection direction, MemoryDataSize memoryDataSize, - PeripheralDataSize peripheralDataSize, - MemoryIncrementMode memoryIncrement, - PeripheralIncrementMode peripheralIncrement, - Priority priority = Priority::Medium, - CircularMode circularMode = CircularMode::Disabled) + configure(DataTransferDirection direction, + MemoryDataSize memoryDataSize, + PeripheralDataSize peripheralDataSize, + MemoryIncrementMode memoryIncrement, + PeripheralIncrementMode peripheralIncrement, + Priority priority = Priority::Medium, + CircularMode circularMode = CircularMode::Disabled +%% if doubleBuffer + , DoubleBufferMode doubleBufferMode = DoubleBufferMode::Disabled +%% endif + ) { stop(); @@ -194,7 +199,11 @@ public: Base->{{ cr }} = uint32_t(direction) | uint32_t(memoryDataSize) | uint32_t(peripheralDataSize) | uint32_t(memoryIncrement) | uint32_t(peripheralIncrement) | uint32_t(priority) | - uint32_t(circularMode); + uint32_t(circularMode) +%% if doubleBuffer + | uint32_t(doubleBufferMode) +%% endif + ; } /** @@ -236,6 +245,38 @@ public: %% endif } +%% if doubleBuffer + /** + * Set the secondary memory address of the DMA channel + * + * Only used in double buffer mode + * + * @param[in] address Source address + */ + static void + setMemoryAddress2(uintptr_t address) + { + DMA_Channel_TypeDef *Base = (DMA_Channel_TypeDef *) CHANNEL_BASE; +%% if dmaType in ["stm32-stream-channel"] + Base->M1AR = address; +%% endif + } + + /** + * Detect which buffer is currently in use + * + * Only meaningful in double buffer mode + */ + static bool + isPrimaryBufferActive() + { + DMA_Channel_TypeDef *Base = (DMA_Channel_TypeDef *) CHANNEL_BASE; +%% if dmaType in ["stm32-stream-channel"] + return (Base->CR & DMA_SxCR_CT); +%% endif + } +%% endif + /** * Set the peripheral address of the DMA channel * diff --git a/src/modm/platform/dma/stm32/module.lb b/src/modm/platform/dma/stm32/module.lb index 4caaf4e270..974df2f25c 100644 --- a/src/modm/platform/dma/stm32/module.lb +++ b/src/modm/platform/dma/stm32/module.lb @@ -186,6 +186,7 @@ def build(env): properties["dmaType"] = dma["type"] properties["dmaSignals"] = signal_names properties["dmaController"] = controller + properties["doubleBuffer"] = (dma["type"] in ["stm32-stream-channel"]) ## TODO: which other types support double buffer mode? properties['channel_count'] = { "min" : min(controller, key=lambda c: c["min_channel"])["min_channel"], From 294b3806b25f1c18f8a48f8b3dc229479f23fcb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Ledneczki?= Date: Sun, 18 Apr 2021 00:00:00 +0000 Subject: [PATCH 03/28] [rcc] Add support for I2S pll on STM32F4 and remove deprecated enablePll() function that are not using `enablePll` struct. Co-authored-by: Raphael Lehmann --- src/modm/platform/clock/stm32/module.lb | 1 + src/modm/platform/clock/stm32/rcc.cpp.in | 23 ++++ src/modm/platform/clock/stm32/rcc.hpp.in | 127 ++++++++--------------- 3 files changed, 66 insertions(+), 85 deletions(-) diff --git a/src/modm/platform/clock/stm32/module.lb b/src/modm/platform/clock/stm32/module.lb index 32387af17f..805764ee70 100644 --- a/src/modm/platform/clock/stm32/module.lb +++ b/src/modm/platform/clock/stm32/module.lb @@ -77,6 +77,7 @@ def build(env): (target["family"] == "l4" and target["name"][0] in ["p", "q", "r", "s"]) properties["pllsai_p_usb"] = (target["family"] == "f7") or \ ((target["family"] == "f4") and target["name"] in ["46", "69", "79"]) + properties["plli2s"] = (target["family"] == "f4") properties["cfgr1"] = ("CDCFGR1" if target.name in ["a0", "a3", "b0", "b3"] else "D1CFGR") \ if target.family == "h7" else "CFGR" properties["d1"] = ("CD" if target.name in ["a0", "a3", "b0", "b3"] else "D1") \ diff --git a/src/modm/platform/clock/stm32/rcc.cpp.in b/src/modm/platform/clock/stm32/rcc.cpp.in index 3e1c73ae44..9448648c7c 100644 --- a/src/modm/platform/clock/stm32/rcc.cpp.in +++ b/src/modm/platform/clock/stm32/rcc.cpp.in @@ -317,6 +317,29 @@ Rcc::enablePll{{id}}(PllSource source, const PllFactors& pllFactors, uint32_t wa %% endfor +%% if plli2s +bool +modm::platform::Rcc::enablePllI2s(const PllI2sFactors& pllFactors, uint32_t waitCycles) +{ + // Read reserved and don't care values and clear all other values + uint32_t tmp = RCC->PLLI2SCFGR & ~(RCC_PLLI2SCFGR_PLLI2SN | RCC_PLLI2SCFGR_PLLI2SR); + // set PLL divider and multiplier + tmp |= (((uint32_t) pllFactors.pllN) << RCC_PLLI2SCFGR_PLLI2SN_Pos) & RCC_PLLI2SCFGR_PLLI2SN; + tmp |= (((uint32_t) pllFactors.pllR) << RCC_PLLI2SCFGR_PLLI2SR_Pos) & RCC_PLLI2SCFGR_PLLI2SR; + RCC->PLLI2SCFGR = tmp; + + // enable I2S PLL + RCC->CR |= RCC_CR_PLLI2SON; + // wait till I2S PLL gets ready or time is up + while ((RCC->CR & RCC_CR_PLLI2SRDY) == 0) + { + if (not --waitCycles) + return false; + } + return true; +} +%% endif + %% if pllsai_p_usb bool Rcc::enablePllSai(const PllSaiFactors& pllFactors, uint32_t waitCycles) diff --git a/src/modm/platform/clock/stm32/rcc.hpp.in b/src/modm/platform/clock/stm32/rcc.hpp.in index cea2de4a83..99787cfd40 100644 --- a/src/modm/platform/clock/stm32/rcc.hpp.in +++ b/src/modm/platform/clock/stm32/rcc.hpp.in @@ -593,91 +593,6 @@ public: enablePllSai(const PllSaiFactors& pllFactors, uint32_t waitCycles = 2048); %% endif - // DEPRECATE: 2022q1 -%% if target.family in ["f2", "f4", "f7", "l4", "g0", "g4"] - /** - * Enable PLL. - * - * \code - * VCO input frequency = PLL input clock frequency / PLLM [with 2 <= PLLM <= 63] - * VCO output frequency = VCO input frequency × PLLN [with 64 <= PLLN <= 432] - * \endcode - * - * \param source - * Source select for PLL and for plli2s. If you are using - * HSE you must enable it first (see enableHse()). - * - * \param pllM - * Division factor for the main PLL (PLL) and - * audio PLL (PLLI2S) input clock (with 2 <= pllM <= 63). - * The software has to set these bits correctly to ensure - * that frequency of selected source divided by pllM - * is in ranges from 1 to 2 MHz. - * - * \param pllN - * Main PLL (PLL) multiplication factor for VCO (with 64 <= pllN <= 432). - * The software has to set these bits correctly to ensure - * that the VCO output frequency is - * - 336 MHz for ST32F4. Core will run at 168 MHz. - * - 240 MHz for ST32F2. Core will run at 120 MHz. - * - * Example: - * - */ - [[deprecated("Use PllFactors as argument instead")]] static bool - enablePll(PllSource source, uint8_t pllM, uint16_t pllN, - %% if target.family in ["l4", "g0", "g4"] - uint8_t pllR, - %% else - uint8_t pllP, - %% endif - uint32_t waitCycles = 2048) - { - PllFactors pllFactors{ - .pllM = pllM, - .pllN = pllN, - %% if target.family in ["l4", "g0", "g4"] - .pllR = pllR, - %% else - .pllP = pllP, - %% endif - }; - return enablePll(source, pllFactors, waitCycles); - } -%% elif target.family in ["l1"] - [[deprecated("Use PllFactors as argument instead")]] static bool - enablePll(PllSource source, PllMultiplier pllMul, uint8_t pllDiv, - uint32_t waitCycles = 2048) - { - PllFactors pllFactors{ - .pllMul = pllMul, - .pllDiv = pllDiv, - }; - return enablePll(source, pllFactors, waitCycles); - } -%% elif target.family in ["f0", "f1", "f3"] - [[deprecated("Use PllFactors as argument instead")]] static bool - enablePll(PllSource source, - uint8_t pllMul, - %% if pllprediv2 - uint8_t pllPrediv, uint8_t pllPrediv2, - %% elif pllprediv - uint8_t pllPrediv, - %% endif - uint32_t waitCycles = 2048) - { - PllFactors pllFactors{ - .pllMul = pllMul, - %% if pllprediv2 - .pllPrediv = pllPrediv, - .pllPrediv2 = pllPrediv2, - %% elif pllprediv - .pllPrediv = pllPrediv, - %% endif - }; - return enablePll(source, pllFactors, waitCycles); - } -%% endif %% if target.family == "l0" static inline bool isHsiPredivider4Active() @@ -689,6 +604,48 @@ public: setHsiPredivider4Enabled(bool divideBy4, uint32_t waitCycles = 2048); %% endif +%% if target["family"] == "f4" + /** + * Input clock for I2S Pll is the HSE or HSI divided by M. + * + * f(VCO clock) = f(PLLI2S clock input) × (PLLI2SN / PLLM) + * + * f(PLL I2S clock output) = f(VCO clock) / PLLI2SR + */ + struct PllI2sFactors + { + /** + * @brief multiplication factor for VCO + * + * Caution: The software has to set these bits correctly + * to ensure that the VCO output frequency + * is between 100 and 432 MHz. + * + * 50 <= `pllN` <= 432 + */ + const uint16_t pllN; + + /** + * @brief division factor for I2S clocks + * + * Caution: The I2Ss requires a frequency lower than + * or equal to 192 MHz to work correctly. + * + * 2 <= `pllR` <= 7 + */ + const uint16_t pllR; + }; + + /** + * @brief Configure factors for I2S pll and enable. + * + * @param pllFactors See `PllI2sFactors` + * @param waitCycles Number of cycles to wait for pll to become stable. Defaults to 2048. + */ + static bool + enablePllI2s(const PllI2sFactors& pllFactors, uint32_t waitCycles = 2048); +%% endif + // sinks static bool enableSystemClock(SystemClockSource src, uint32_t waitCycles = 2048); From e8b8e36b8951df0ec7cd4395b95aa51a098fd466 Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Mon, 28 Feb 2022 23:55:09 +0100 Subject: [PATCH 04/28] [board] Disco-F469: MicroSD card connector --- src/modm/board/disco_f469ni/board.hpp.in | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/modm/board/disco_f469ni/board.hpp.in b/src/modm/board/disco_f469ni/board.hpp.in index ba3163135b..fd01492b21 100644 --- a/src/modm/board/disco_f469ni/board.hpp.in +++ b/src/modm/board/disco_f469ni/board.hpp.in @@ -198,6 +198,20 @@ using Uart = Usart3; /// @} } +namespace microsd +{ +/// @ingroup modm_board_disco_f469ni +/// @{ +using Detect = GpioInputC12; +using Cmd = GpioD2; +using Clk = GpioC12; +using D0 = GpioC8; +using D1 = GpioC9; +using D2 = GpioC10; +using D3 = GpioC11; +/// @} +} + /// @ingroup modm_board_disco_f469ni /// @{ using LoggerDevice = modm::IODeviceWrapper< stlink::Uart, modm::IOBuffer::BlockIfFull >; From 5a040e0146cca4384eba4d283f628fd1d6753799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Ledneczki?= Date: Thu, 29 Apr 2021 00:00:00 +0000 Subject: [PATCH 05/28] [stm32] I2S master implementation with DMA Co-authored-by: Raphael Lehmann --- README.md | 20 +++ src/modm/platform/i2s/stm32/i2s_base.hpp.in | 110 +++++++++++++ src/modm/platform/i2s/stm32/i2s_hal.hpp.in | 94 +++++++++++ .../platform/i2s/stm32/i2s_hal_impl.hpp.in | 118 ++++++++++++++ src/modm/platform/i2s/stm32/i2s_master.hpp.in | 152 ++++++++++++++++++ src/modm/platform/i2s/stm32/module.lb | 79 +++++++++ 6 files changed, 573 insertions(+) create mode 100644 src/modm/platform/i2s/stm32/i2s_base.hpp.in create mode 100644 src/modm/platform/i2s/stm32/i2s_hal.hpp.in create mode 100644 src/modm/platform/i2s/stm32/i2s_hal_impl.hpp.in create mode 100644 src/modm/platform/i2s/stm32/i2s_master.hpp.in create mode 100644 src/modm/platform/i2s/stm32/module.lb diff --git a/README.md b/README.md index eab59d0927..259412ccc7 100644 --- a/README.md +++ b/README.md @@ -325,6 +325,26 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✅ ✅ +I2S +✅ +✅ +✅ +✅ +✅ +✅ +✅ +✅ +✅ +✅ +✅ +✕ +○ +✕ +✕ +✕ +✕ +✕ + I2C ✅ ✅ diff --git a/src/modm/platform/i2s/stm32/i2s_base.hpp.in b/src/modm/platform/i2s/stm32/i2s_base.hpp.in new file mode 100644 index 0000000000..0ba9735354 --- /dev/null +++ b/src/modm/platform/i2s/stm32/i2s_base.hpp.in @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2013, Kevin Läufer + * Copyright (c) 2013-2017, Niklas Hauser + * Copyright (c) 2014, Daniel Krebs + * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2021, Marton Ledneczki + * Copyright (c) 2021-2022, Raphael Lehmann + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_STM32_I2S_BASE_HPP +#define MODM_STM32_I2S_BASE_HPP + +#include +#include "../device.hpp" +#include + +namespace modm +{ + +namespace platform +{ + +/** + * Base class for the I2S classes + * + * Provides some common enum that do not depend on the specific I2S. + * + * @author Marton Ledneczki + * @ingroup modm_platform_i2s + */ +class I2sBase +{ +public: + enum class + Interrupt : uint32_t + { + RxBufferNotEmpty = SPI_CR2_RXNEIE, + TxBufferEmpty = SPI_CR2_TXEIE, + Error = SPI_CR2_ERRIE, + RxDmaEnable = SPI_CR2_RXDMAEN, + TxDmaEnable = SPI_CR2_TXDMAEN, + }; + MODM_FLAGS32(Interrupt); + + enum class + InterruptFlag : uint32_t + { + TxBufferEmpty = SPI_SR_TXE, + RxBufferNotEmpty = SPI_SR_RXNE, + CrcError = SPI_SR_CRCERR, + ModeFaultError = SPI_SR_MODF, + OverrunError = SPI_SR_OVR, + Busy = SPI_SR_BSY, + }; + MODM_FLAGS32(InterruptFlag); + + enum class + MasterSelection : uint32_t + { + Slave = 0b0, ///< Configure I2S as Slave + Master = SPI_I2SCFGR_I2SCFG_1, ///< Configure I2S as Master + All = Master, + }; + + enum class + DirectionSelection : uint32_t + { + Transmitter = 0b0, ///< Configure I2S as Transmitter + Receiver = SPI_I2SCFGR_I2SCFG_0, ///< COnfigure I2S as Receiver + All = Receiver, + }; + + enum class + BitDepth : uint32_t + { + Sixteen = 0b0, + Twentyfour = SPI_I2SCFGR_DATLEN_0, + Thirtytwo = SPI_I2SCFGR_DATLEN_1, + All = Thirtytwo, + }; + + enum class + MasterClockOutput : uint32_t + { + Disabled = 0b0, + Enabled = SPI_I2SPR_MCKOE, + All = Enabled, + }; + + enum class + OddFactor : uint32_t + { + Disabled = 0, + Enabled = SPI_I2SPR_ODD, + All = Enabled, + }; +}; + +} // namespace platform + +} // namespace modm + +#endif // MODM_STM32_I2S_BASE_HPP diff --git a/src/modm/platform/i2s/stm32/i2s_hal.hpp.in b/src/modm/platform/i2s/stm32/i2s_hal.hpp.in new file mode 100644 index 0000000000..b2a6d953c5 --- /dev/null +++ b/src/modm/platform/i2s/stm32/i2s_hal.hpp.in @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2013, Kevin Läufer + * Copyright (c) 2013-2018, Niklas Hauser + * Copyright (c) 2014, Daniel Krebs + * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2021, Marton Ledneczki + * Copyright (c) 2021-2022, Raphael Lehmann + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_STM32_I2S_HAL{{ id }}_HPP +#define MODM_STM32_I2S_HAL{{ id }}_HPP + +#include "i2s_base.hpp" + +namespace modm +{ + +namespace platform +{ + +/** + * Serial peripheral interface (I2S{{ id }}) + * + * TODO: Very basic implementation that exposes more hardware features than + * the regular Spi classes. + * + * @author Marton Ledneczki + * @ingroup modm_platform_i2s modm_platform_i2s_{{id}} + */ +class I2sHal{{ id }} : public I2sBase +{ +public: + /// TODO: Enables the clock, resets the hardware and sets the SPE bit + static void + enable(); + + /// Disables the hw module (by disabling its clock line) + static void + disable(); + + /** + * Initialize I2s Peripheral + * + * TODO: Enables clocks + */ + static void + initialize( OddFactor oddFactor, uint8_t i2s_div, + MasterSelection masterSelection = MasterSelection::Master, + DirectionSelection directionSelection = DirectionSelection::Transmitter, + BitDepth bitDepth = BitDepth::Sixteen, + MasterClockOutput masterClockOutput = MasterClockOutput::Enabled); + + static void + start(); + + static void + stop(); + + static void + write(uint16_t sample); + + static void + enableDma(bool enable = true); + + static void + enableInterruptVector(bool enable, uint32_t priority); + + static void + enableInterrupt(Interrupt_t interrupt); + + static void + disableInterrupt(Interrupt_t interrupt); + + static InterruptFlag_t + getInterruptFlags(); + + static void + acknowledgeInterruptFlag(InterruptFlag_t flags); +}; + +} // namespace platform + +} // namespace modm + +#include "i2s_hal_{{ id }}_impl.hpp" + +#endif // MODM_STM32_I2S_HAL{{ id }}_HPP diff --git a/src/modm/platform/i2s/stm32/i2s_hal_impl.hpp.in b/src/modm/platform/i2s/stm32/i2s_hal_impl.hpp.in new file mode 100644 index 0000000000..4cbd2876ab --- /dev/null +++ b/src/modm/platform/i2s/stm32/i2s_hal_impl.hpp.in @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2013, Kevin Läufer + * Copyright (c) 2013-2017, Niklas Hauser + * Copyright (c) 2014, Daniel Krebs + * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2021, Marton Ledneczki + * Copyright (c) 2021-2022, Raphael Lehmann + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_STM32_I2S_HAL{{ id }}_HPP +# error "Don't include this file directly, use 'i2s_hal{{ id }}.hpp' instead!" +#endif + +#include + +void inline +modm::platform::I2sHal{{ id }}::enable() +{ + Rcc::enable(); + SPI{{ id }}->CR1 = 0; + SPI{{ id }}->I2SCFGR = SPI_I2SCFGR_I2SMOD; +} + +void inline +modm::platform::I2sHal{{ id }}::disable() +{ + SPI{{ id }}->I2SCFGR = 0; + Rcc::disable(); +} + +void inline +modm::platform::I2sHal{{ id }}::initialize( OddFactor oddFactor, uint8_t i2s_div, + MasterSelection masterSelection, + DirectionSelection directionSelection, + BitDepth bitDepth, + MasterClockOutput masterClockOutput) +{ + enable(); + // disable peripheral, and clear all fields except I2S mode bit + SPI{{ id }}->I2SCFGR = SPI_I2SCFGR_I2SMOD; + SPI{{ id }}->I2SPR = 0x0002; // = reset value acording to reference manual + + // set parameters + SPI{{ id }}->I2SPR |= static_cast(masterClockOutput) | + static_cast(oddFactor) | + i2s_div; + SPI{{ id }}->I2SCFGR |= static_cast(masterSelection) | + static_cast(directionSelection) | + static_cast(bitDepth); +} + +void inline +modm::platform::I2sHal3::start() +{ + SPI{{ id }}->I2SCFGR |= SPI_I2SCFGR_I2SE; +} + +void inline +modm::platform::I2sHal3::enableDma(bool enable) +{ + if (enable) { + SPI{{ id }}->CR2 |= SPI_CR2_TXDMAEN; + } + else { + SPI{{ id }}->CR2 &= ~SPI_CR2_TXDMAEN; + } +} + +void inline +modm::platform::I2sHal{{ id }}::write(uint16_t sample) +{ + SPI{{ id }}->DR = sample; +} + +void inline +modm::platform::I2sHal{{ id }}::enableInterruptVector(bool enable, uint32_t priority) +{ + if (enable) { + // Set priority for the interrupt vector + NVIC_SetPriority(SPI{{ id }}_IRQn, priority); + // register IRQ at the NVIC + NVIC_EnableIRQ(SPI{{ id }}_IRQn); + } + else { + NVIC_DisableIRQ(SPI{{ id }}_IRQn); + } +} + +void inline +modm::platform::I2sHal{{ id }}::enableInterrupt(Interrupt_t interrupt) +{ + SPI{{ id }}->CR2 |= interrupt.value; +} + +void inline +modm::platform::I2sHal{{ id }}::disableInterrupt(Interrupt_t interrupt) +{ + SPI{{ id }}->CR2 &= ~interrupt.value; +} + +modm::platform::I2sHal{{ id }}::InterruptFlag_t inline +modm::platform::I2sHal{{ id }}::getInterruptFlags() +{ + return InterruptFlag_t(SPI{{ id }}->SR); +} + +void inline +modm::platform::I2sHal{{ id }}::acknowledgeInterruptFlag(InterruptFlag_t flags) +{ + SPI{{ id }}->SR = flags.value; +} \ No newline at end of file diff --git a/src/modm/platform/i2s/stm32/i2s_master.hpp.in b/src/modm/platform/i2s/stm32/i2s_master.hpp.in new file mode 100644 index 0000000000..94602a79d7 --- /dev/null +++ b/src/modm/platform/i2s/stm32/i2s_master.hpp.in @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2021, Marton Ledneczki + * Copyright (c) 2021-2022, Raphael Lehmann + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_STM32_I2S_MASTER{{ id }}_HPP +#define MODM_STM32_I2S_MASTER{{ id }}_HPP + +#include + +#include +#include +#include +#include +#include "i2s_hal_{{ id }}.hpp" + +namespace modm::platform +{ + +/** + * Inter-IC Sound (I2C{{ id }}). + * + * TODO: say something about the nature of implementation + * + * @tparam DmaChannelTX DMA channel for sending + * + * @author Marton Ledneczki + * @author Raphael Lehmann + * @ingroup modm_platform_i2s modm_platform_i2s_{{id}} + */ +template +class I2sMaster{{ id }} : public modm::I2sMaster +{ + struct Dma { + using TxChannel = typename DmaChannelTx::template RequestMapping::Channel; + + static constexpr DmaBase::Request TxRequest = DmaChannelTx::template RequestMapping::Request; + }; + +private: + using Hal = I2sHal{{ id }}; + +public: + template< class... Signals > + static void + connect() + { + using Connector = GpioConnector; + using Ck = typename Connector::template GetSignal; + using Mck = typename Connector::template GetSignal; + using Sd = typename Connector::template GetSignal; + using Ws = typename Connector::template GetSignal; + + // Connector::disconnect(); + Ck::setOutput(Gpio::OutputType::PushPull); + Mck::setOutput(Gpio::OutputType::PushPull); + Sd::setOutput(Gpio::OutputType::PushPull); + Ws::setOutput(Gpio::OutputType::PushPull); + Connector::connect(); + } + + template< class SystemClock, frequency_t samplerate, percent_t tolerance=pct(0.019) > + static inline void + initialize(DmaBase::Priority dmaPriority = DmaBase::Priority::High) + { + constexpr float prescaler = static_cast(SystemClock::I2sPll) / (samplerate*16*2*8); + constexpr uint8_t i2s_div = static_cast(prescaler/2); + constexpr uint8_t odd_factor = ((prescaler - static_cast(i2s_div*2)) >= 0.5) ? 1 : 0; + constexpr float real_samplerate = static_cast(SystemClock::I2sPll) / + (16*2*8*(2*i2s_div+odd_factor)); + static_assert(i2s_div > 1, "I2S{{ id }}: i2s_div can not be 1 or 0!"); + modm::PeripheralDriver::assertBaudrateInTolerance(); + + Hal::initialize(odd_factor ? Hal::OddFactor::Enabled : Hal::OddFactor::Disabled, + i2s_div); + + dmaError = false; + Dma::TxChannel::configure( + DmaBase::DataTransferDirection::MemoryToPeripheral, + DmaBase::MemoryDataSize::Bit16, + DmaBase::PeripheralDataSize::Bit16, + DmaBase::MemoryIncrementMode::Increment, + DmaBase::PeripheralIncrementMode::Fixed, + dmaPriority, + DmaBase::CircularMode::Disabled); + Dma::TxChannel::setPeripheralAddress(SPI{{ id }}_BASE + 0x0c); + Dma::TxChannel::setTransferErrorIrqHandler(handleDmaTransferError); + Dma::TxChannel::enableInterruptVector(); + Dma::TxChannel::enableInterrupt(DmaBase::InterruptEnable::TransferError | + DmaBase::InterruptEnable::TransferComplete); + Dma::TxChannel::template setPeripheralRequest(); + Hal::enableDma(true); + } + + static inline void + setTxBufferAddress(uintptr_t address, std::size_t length) + { + Dma::TxChannel::setMemoryAddress(address); + Dma::TxChannel::setDataLength(length); + } + + static inline void + setTransferCompleteIrqHandler(DmaBase::IrqHandler handleDmaTransferComplete) + { + Dma::TxChannel::setTransferCompleteIrqHandler(handleDmaTransferComplete); + } + + static inline void + start() + { + Hal::start(); + Dma::TxChannel::start(); + } + + static inline void + startDma() + { + Dma::TxChannel::start(); + } + + static inline void + stop() + { + //Hal::stop(); + Dma::TxChannel::stop(); + } + + // end documentation inherited + +private: + static void handleDmaTransferError() + { + Hal::enableDma(false); + Dma::TxChannel::stop(); + dmaError = true; + } + + static inline bool dmaError { false }; +public: + static bool hasDmaError() { return dmaError; } +}; + +} // namespace modm::platform + +#endif // MODM_STM32_I2S_MASTER{{ id }}_HPP diff --git a/src/modm/platform/i2s/stm32/module.lb b/src/modm/platform/i2s/stm32/module.lb new file mode 100644 index 0000000000..1d2faec8ae --- /dev/null +++ b/src/modm/platform/i2s/stm32/module.lb @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016-2018, Niklas Hauser +# Copyright (c) 2017, Fabian Greif +# Copyright (c) 2021, Marton Ledneczki +# Copyright (c) 2022, Raphael Lehmann +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + +def get_properties(env): + device = env[":target"] + driver = device.get_driver("i2s") + properties = {} + properties["target"] = device.identifier + properties["features"] = driver["feature"] if "feature" in driver else [] + return properties + +class Instance(Module): + def __init__(self, driver, instance): + self.instance = int(instance) + self.driver = driver + + def init(self, module): + module.name = str(self.instance) + module.description = "Instance {}".format(self.instance) + + def prepare(self, module, options): + module.depends( + ":platform:i2s", + ":platform:dma", + ) + return True + + def build(self, env): + properties = get_properties(env) + properties["id"] = self.instance + + env.substitutions = properties + env.outbasepath = "modm/src/modm/platform/i2s" + + env.template("i2s_hal.hpp.in", "i2s_hal_{}.hpp".format(self.instance)) + env.template("i2s_hal_impl.hpp.in", "i2s_hal_{}_impl.hpp".format(self.instance)) + env.template("i2s_master.hpp.in", "i2s_master_{}.hpp".format(self.instance)) + + +def init(module): + module.name = ":platform:i2s" + module.description = "Inter-IC Sound (I2S)" + +def prepare(module, options): + device = options[":target"] + if not device.has_driver("i2s:stm32*"): + return False + + module.depends( + ":architecture:register", + ":architecture:i2s", + ":cmsis:device", + ":math:algorithm", + ":platform:gpio", + ":platform:rcc") + + for driver in device.get_all_drivers("i2s:stm32*"): + for instance in driver["instance"]: + module.add_submodule(Instance(driver, instance)) + + return True + +def build(env): + env.substitutions = get_properties(env) + env.outbasepath = "modm/src/modm/platform/i2s" + + env.template("i2s_base.hpp.in") From d51569badf0dfbef2fc26f65a41858f5517f1706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Ledneczki?= Date: Sun, 9 May 2021 00:00:00 +0000 Subject: [PATCH 06/28] =?UTF-8?q?[driver]=20Add=20CS43L22=20audio=20DAC=20?= =?UTF-8?q?(I2S=20+=20I=C2=B2C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modm/driver/dac/cs43l22.hpp | 338 +++++++++++++++++++++++++++ src/modm/driver/dac/cs43l22.lb | 42 ++++ src/modm/driver/dac/cs43l22_impl.hpp | 148 ++++++++++++ 3 files changed, 528 insertions(+) create mode 100644 src/modm/driver/dac/cs43l22.hpp create mode 100644 src/modm/driver/dac/cs43l22.lb create mode 100644 src/modm/driver/dac/cs43l22_impl.hpp diff --git a/src/modm/driver/dac/cs43l22.hpp b/src/modm/driver/dac/cs43l22.hpp new file mode 100644 index 0000000000..c12b416c78 --- /dev/null +++ b/src/modm/driver/dac/cs43l22.hpp @@ -0,0 +1,338 @@ +/* + * Copyright (c) 2021, Marton Ledneczki + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_CS43L22_HPP +#define MODM_CS43L22_HPP + +#include + +namespace modm +{ + +/// @ingroup modm_driver_cs43l22 +struct cs43l22 +{ + enum class + Register : uint8_t + { + ChipIdRevision = 0x01, + PowerControl1 = 0x02, + PowerControl2 = 0x04, + ClockingControl = 0x05, + InterfaceControl1 = 0x06, + InterfaceControl2 = 0x07, + PassthroughASelect = 0x08, + PassthroughBSelect = 0x09, + AnalogZcAndSrSettings = 0x0A, + PassthroughGansControl = 0x0C, + PlaybackControl1 = 0x0D, + MiscellaneousControls = 0x0E, + PlaybackControl2 = 0x0F, + PassthroughAVolume = 0x14, + PassthroughBVolume = 0x15, + PcmAVolume = 0x1A, + PcmBVolume = 0x1B, + BeepFrequencyAndOnTime = 0x1C, + BeepVolumeAndOffTime = 0x1D, + BeepAndToneConfiguration = 0x1E, + ToneControl = 0x1F, + MasterVolumeControlA = 0x20, + MasterVolumeControlB = 0x21, + HeadphoneVolumeControlA = 0x22, + HeadphoneVolumeControlB = 0x23, + SpeakerVolumeControlA = 0x24, + SpeakerVolumeControlB = 0x25, + PcmChannelSwap = 0x26, + LimiterControl1MinMaxThresholds = 0x27, + LimiterControl2ReleaseRate = 0x28, + LimiterAttackRate = 0x29, + Status = 0x2E, + VpBatteryLevel = 0x30, + SpeakerStatus = 0x31, + ChargePumpFrequency = 0x34 + }; + + enum class + ChipIdRevision : uint8_t + { + REVID0 = Bit0, + REVID1 = Bit1, + REVID2 = Bit2, + + CHIPID0 = Bit3, + CHIPID1 = Bit4, + CHIPID2 = Bit5, + CHIPID3 = Bit6, + CHIPID4 = Bit7 + }; + MODM_FLAGS8(ChipIdRevision); + + enum class + ChipId : uint8_t + { + CS43L22 = int(ChipIdRevision::CHIPID4) | int(ChipIdRevision::CHIPID3) | int(ChipIdRevision::CHIPID2) + }; + typedef Configuration< ChipIdRevision_t, ChipId, (Bit7 | Bit6 | Bit5 | Bit4 | Bit3) > ChipId_t; + + enum class + RevisionId : uint8_t + { + A0 = 0, + A1 = int(ChipIdRevision::REVID0), + B0 = int(ChipIdRevision::REVID1), + B1 = int(ChipIdRevision::REVID1) | int(ChipIdRevision::REVID0) + }; + typedef Configuration< ChipIdRevision_t, RevisionId, (Bit2 | Bit1 | Bit0) > RevisionId_t; + + enum class + PowerControl1 : uint8_t + { + PDN0 = Bit0, + PDN1 = Bit1, + PDN2 = Bit2, + PDN3 = Bit3, + PDN4 = Bit4, + PDN5 = Bit5, + PDN6 = Bit6, + PDN7 = Bit7, + }; + MODM_FLAGS8(PowerControl1); + + enum class + Power : uint8_t + { + Down = int(PowerControl1::PDN0), + Up = int(PowerControl1::PDN7) | + int(PowerControl1::PDN4) | + int(PowerControl1::PDN3) | + int(PowerControl1::PDN2) | + int(PowerControl1::PDN1) + }; + typedef Configuration< PowerControl1_t, Power, 0xFF > Power_t; + + enum class + PowerControl2 : uint8_t + { + PDN_SPKA0 = Bit0, + PDN_SPKA1 = Bit1, + + PDN_SPKB0 = Bit2, + PDN_SPKB1 = Bit3, + + PDN_HPA0 = Bit4, + PDN_HPA1 = Bit5, + + PDN_HPB0 = Bit6, + PDN_HPB1 = Bit7, + }; + MODM_FLAGS8(PowerControl2); + + enum class + ChannelPower : uint8_t + { + OnWhenSelectorPinLo = 0, + OnWhenSelectorPinHi = 1, + OnAlways = 2, + OffAlways = 3 + }; + typedef Configuration< PowerControl2_t, ChannelPower, 0b11, 0> ChannelPowerSpeakerA_t; + typedef Configuration< PowerControl2_t, ChannelPower, 0b11, 2> ChannelPowerSpeakerB_t; + typedef Configuration< PowerControl2_t, ChannelPower, 0b11, 4> ChannelPowerHeadphoneA_t; + typedef Configuration< PowerControl2_t, ChannelPower, 0b11, 6> ChannelPowerHeadphoneB_t; + + enum class + ClockingControl : uint8_t + { + MCLKDIV2 = Bit0, + + RATIO0 = Bit1, + RATIO1 = Bit2, + + VIDEOCLK = Bit3, + GROUP_32K = Bit4, + + SPEED0 = Bit5, + SPEED1 = Bit6, + + AUTO_DETECT = Bit7 + }; + MODM_FLAGS8(ClockingControl); + + enum class + InterfaceControl1 : uint8_t + { + AWL0 = Bit0, + AWL1 = Bit1, + + DACDIF0 = Bit2, + DACDIF1 = Bit3, + + DSP = Bit4, + INV_SCLK = Bit6, + MASTER = Bit7 // Master (output only), otherwise Slave (input only) + }; + MODM_FLAGS8(InterfaceControl1); + + enum class + Role + { + Slave = 0, + Master = int(InterfaceControl1::MASTER) + }; + typedef Configuration< InterfaceControl1_t, Role, Bit7 > Role_t; + + enum class + DacInterfaceFormat : uint8_t + { + LeftJustified = 0, + I2sPhillipsStandard = int(InterfaceControl1::DACDIF0), + RightJustified = int(InterfaceControl1::DACDIF1) + }; + typedef Configuration< InterfaceControl1_t, DacInterfaceFormat, (Bit2 | Bit3) > DacInterfaceFormat_t; + + enum class + MasterVolumeControl : uint8_t + { + MSTVOL0 = Bit0, + MSTVOL1 = Bit1, + MSTVOL2 = Bit2, + MSTVOL3 = Bit3, + MSTVOL4 = Bit4, + MSTVOL5 = Bit5, + MSTVOL6 = Bit6, + MSTVOL7 = Bit7, + }; + MODM_FLAGS8(MasterVolumeControl); + typedef Value< MasterVolumeControl_t, 8 > MasterVol_t; + + enum class + AnalogZcAndSrSettings : uint8_t + { + ANLGZCA = Bit0, + ANLGSFTA = Bit1, + ANLGZCB = Bit2, + ANLGSFTB = Bit2 + }; + MODM_FLAGS8(AnalogZcAndSrSettings); + + enum class + SoftRamp : uint8_t + { + Disabled = 0, + Enabled = 1 + }; + typedef Configuration< AnalogZcAndSrSettings_t, SoftRamp, 0b1, 1> AnalogSoftRampA_t; + typedef Configuration< AnalogZcAndSrSettings_t, SoftRamp, 0b1, 3> AnalogSoftRampB_t; + + enum class + ZeroCrossing : uint8_t + { + Disabled = 0, + Enabled = 1 + }; + typedef Configuration< AnalogZcAndSrSettings_t, ZeroCrossing, 0b1, 0> AnalogZeroCrossingA_t; + typedef Configuration< AnalogZcAndSrSettings_t, ZeroCrossing, 0b1, 2> AnalogZeroCrossingB_t; + + enum class + MiscellaneousControls + { + DIGZC = Bit0, + DIGSFT = Bit1, + DEEMPH = Bit2, + FREEZE = Bit3, + PASSAMUTE = Bit4, + PASSBMUTE = Bit5, + PASSTHRUA = Bit6, + PASSTHRUB = Bit7 + }; + MODM_FLAGS8(MiscellaneousControls); + + enum class + LimiterControl1MinMaxThresholds + { + LIMZCDIS = Bit0, + LIMSRDIS = Bit1, + + CUSH0 = Bit2, + CUSH1 = Bit3, + CUSH2 = Bit4, + + LMAX0 = Bit5, + LMAX1 = Bit6, + LMAX2 = Bit7 + }; + MODM_FLAGS8(LimiterControl1MinMaxThresholds); + + using RegisterValue_t = FlagsGroup< ChipIdRevision_t, PowerControl1_t, PowerControl2_t, + ClockingControl_t, InterfaceControl1_t, + MasterVolumeControl_t, AnalogZcAndSrSettings_t, + MiscellaneousControls_t, LimiterControl1MinMaxThresholds_t >; + + typedef int16_t centiBel_t; + static constexpr centiBel_t MaxVolume = 120; + static constexpr centiBel_t MinVolume = -1020; +}; // struct cs43l22 + + +/** + * Cirrus Logic CS43L22, Portable Audio DAC with Integrated Class D Speaker Driver + * + * @tparam I2cMaster I²C interface + * + * @author Marton Ledneczki + * @ingroup modm_driver_cs43l22 + */ +template +class Cs43l22 : public cs43l22, public modm::I2cDevice +{ +public: + Cs43l22(uint8_t i2cAddress); + + /// Initialize device, call before using any other function + ResumableResult + initialize(); + + /** + * @note max volume is specified by cs43l22::MaxVolume + * and min volume by cs43l22::MinVolume + */ + ResumableResult + setMasterVolume(centiBel_t vol); + + ResumableResult + setMasterVolumeRelative(centiBel_t rel_vol); + +private: + uint8_t tx_buffer[2]; + uint8_t rx_buffer; + centiBel_t volume; + bool success; + + ResumableResult + writeRegister(Register reg, RegisterValue_t value); + + ResumableResult + readRegister(Register reg); + + ResumableResult + getMasterVolume(); + + void + regToCentibel(uint8_t reg); + +}; + +} // namespace modm + +#include "cs43l22_impl.hpp" + +#endif // MODM_CS43L22_HPP + diff --git a/src/modm/driver/dac/cs43l22.lb b/src/modm/driver/dac/cs43l22.lb new file mode 100644 index 0000000000..2284a42b51 --- /dev/null +++ b/src/modm/driver/dac/cs43l22.lb @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2021, Marton Ledneczki +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + + +def init(module): + module.name = ":driver:cs43l22" + module.description = """\ +# CS43L22, Portable Audio DAC with Integrated Class D Speaker Driver + +The CS43L22 is low power stereo digital/analog converter (DAC) with +integrated headphone and Class D speaker amplifiers that operates from a +low voltage analog and digital core. The DAC output path includes a +digital signal processing engine with various fixed-function controls, +such as tone and volume control, and includes de-emphasis, limiting +functions and beep generator that delivers tones selectable across +a range of two octaves. The stereo headphone amplifier is powered +from a separate positive supply and the integrated charge pump provides +a negative supply allowing for a ground-centered analog output to eliminate +the need for DC blocking capacitors. The Class D stereo speaker does not +require an external filter and can be powered directly from a battery. +""" + +def prepare(module, options): + module.depends( + ":architecture:register", + ":architecture:i2c.device", + ":processing:resumable") + return True + +def build(env): + env.outbasepath = "modm/src/modm/driver/dac" + env.copy("cs43l22.hpp") + env.copy("cs43l22_impl.hpp") diff --git a/src/modm/driver/dac/cs43l22_impl.hpp b/src/modm/driver/dac/cs43l22_impl.hpp new file mode 100644 index 0000000000..111aeae955 --- /dev/null +++ b/src/modm/driver/dac/cs43l22_impl.hpp @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2021, Marton Ledneczki + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_CS43L22_HPP +# error "Don't include this file directly, use 'cs43l22.hpp' instead!" +#endif + +namespace modm +{ + +template +Cs43l22::Cs43l22(uint8_t i2cAddress) + : I2cDevice(i2cAddress) +{ + volume = -300; +} + +template +ResumableResult +Cs43l22::initialize() +{ + RF_BEGIN(); + // Verify the chip ID + success = RF_CALL(readRegister(Register::ChipIdRevision)); + if (!success || (ChipId_t::get(static_cast(rx_buffer)) != ChipId::CS43L22)) + { + RF_RETURN(false); + } + RF_CALL(writeRegister(Register::PowerControl1, Power_t(Power::Down))); + RF_CALL(writeRegister(Register::PowerControl2, ChannelPowerHeadphoneA_t(ChannelPower::OnAlways) | + ChannelPowerHeadphoneB_t(ChannelPower::OnAlways) | + ChannelPowerSpeakerA_t(ChannelPower::OffAlways) | + ChannelPowerSpeakerB_t(ChannelPower::OffAlways))); + RF_CALL(writeRegister(Register::ClockingControl, ClockingControl::AUTO_DETECT)); + RF_CALL(writeRegister(Register::InterfaceControl1, DacInterfaceFormat_t(DacInterfaceFormat::I2sPhillipsStandard) | + Role_t(Role::Slave))); + RF_CALL(setMasterVolume(volume)); + RF_CALL(writeRegister(Register::PowerControl1, Power_t(Power::Up))); + /* Additional configuration for the CODEC. These configurations are done to reduce + the time needed for the Codec to power off. If these configurations are removed, + then a long delay should be added between powering off the Codec and switching + off the I2S peripheral MCLK clock (which is the operating clock for Codec). + If this delay is not inserted, then the codec will not shut down properly and + it results in high noise after shut down. */ + RF_CALL(writeRegister(Register::AnalogZcAndSrSettings, AnalogSoftRampA_t(SoftRamp::Disabled) | + AnalogSoftRampB_t(SoftRamp::Disabled) | + AnalogZeroCrossingA_t(ZeroCrossing::Disabled) | + AnalogZeroCrossingB_t(ZeroCrossing::Disabled))); + /* Disable the digital soft ramp */ + RF_CALL(writeRegister(Register::MiscellaneousControls, MiscellaneousControls_t(0x00))); + /* Disable the limiter attack level */ + RF_CALL(writeRegister(Register::LimiterControl1MinMaxThresholds, LimiterControl1MinMaxThresholds_t(0x00))); + RF_END_RETURN(success); +} + +template +ResumableResult +Cs43l22::writeRegister(Register reg, RegisterValue_t value) +{ + RF_BEGIN(); + tx_buffer[0] = static_cast(reg); + tx_buffer[1] = value.value; + this->transaction.configureWrite(tx_buffer, 2); + RF_END_RETURN_CALL(this->runTransaction()); +} + +template +ResumableResult +Cs43l22::readRegister(Register reg) +{ + RF_BEGIN(); + rx_buffer = static_cast(reg); + // First, set the internal address pointer + // of the DAC to the requested register + this->transaction.configureWrite(&rx_buffer, 1); + RF_CALL(this->runTransaction()); + this->transaction.configureRead(&rx_buffer, 1); + RF_END_RETURN_CALL(this->runTransaction()); +} + +template +ResumableResult +Cs43l22::setMasterVolume(centiBel_t vol) +{ + RF_BEGIN(); + { + volume = vol; + if (volume > MaxVolume) + volume = MaxVolume; + else if (volume < MinVolume) + volume = MinVolume; + volume /= 5; + if (volume < -128) + volume += 256; + } + RF_CALL(writeRegister(Register::MasterVolumeControlA, static_cast(volume))); + RF_CALL(writeRegister(Register::MasterVolumeControlB, static_cast(volume))); + RF_END_RETURN(true); +} + +template +void +Cs43l22::regToCentibel(uint8_t reg) +{ + volume = reg; + if (volume <= 24 and volume >= 0) + volume *= 5; + else if (volume <= 52 and volume > 24) + volume = MinVolume; + else if (volume <= 127 and volume > 52) + volume = (256-volume)*(-5); + else + { + volume |= 0xFF00; + volume *= 5; + } +} + +template +ResumableResult +Cs43l22::setMasterVolumeRelative(centiBel_t rel_vol) +{ + RF_BEGIN(); + if (RF_CALL(getMasterVolume())) + { + regToCentibel(rx_buffer); + volume += rel_vol; + RF_RETURN_CALL(setMasterVolume(volume)); + } + RF_END_RETURN(false); +} + +template +ResumableResult +Cs43l22::getMasterVolume() +{ + return readRegister(Register::MasterVolumeControlA); +} + +} // namespace modm From 3bb021d4908e6b40651ea829fc68b630ac92b965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Ledneczki?= Date: Tue, 4 May 2021 00:00:00 +0000 Subject: [PATCH 07/28] [board] Adapt STM32F4-Disco BSP for I2S DAC CS43L22 --- src/modm/board/disco_f407vg/board.hpp | 36 ++++++++++++++++++++------- src/modm/board/disco_f407vg/module.lb | 3 +++ 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/modm/board/disco_f407vg/board.hpp b/src/modm/board/disco_f407vg/board.hpp index da703b7abf..a96d7a4ac3 100644 --- a/src/modm/board/disco_f407vg/board.hpp +++ b/src/modm/board/disco_f407vg/board.hpp @@ -18,6 +18,7 @@ #include #include #include +#include using namespace modm::platform; @@ -47,6 +48,11 @@ struct SystemClock static constexpr uint32_t Spi5 = Apb2; static constexpr uint32_t Spi6 = Apb2; + static constexpr uint32_t I2s2 = Spi2; + static constexpr uint32_t I2s3 = Spi3; + + static constexpr uint32_t I2sPll = 86_MHz; + static constexpr uint32_t Usart1 = Apb2; static constexpr uint32_t Usart2 = Apb1; static constexpr uint32_t Usart3 = Apb1; @@ -84,12 +90,17 @@ struct SystemClock { Rcc::enableExternalCrystal(); // 8MHz const Rcc::PllFactors pllFactors{ - .pllM = 4, // 8MHz / M=4 -> 2MHz - .pllN = 168, // 2MHz * N=168 -> 336MHz + .pllM = 8, // 8MHz / M=8 -> 1MHz + .pllN = 336, // 1MHz * N=336 -> 336MHz .pllP = 2, // 336MHz / P=2 -> 168MHz = F_cpu .pllQ = 7 // 336MHz / Q=7 -> 48MHz = F_usb }; Rcc::enablePll(Rcc::PllSource::ExternalCrystal, pllFactors); + const Rcc::PllI2sFactors pllI2sFactors{ + .pllN = 258, // 1 MHz * N=258 -> 258 MHz + .pllR = 3 // 258 MHz / R=3 -> 86 MHz + }; + Rcc::enablePllI2s(pllI2sFactors); // set flash latency for 168MHz Rcc::setFlashLatency(); // switch system clock to PLL output @@ -144,12 +155,16 @@ using Mclk = GpioOutputC7; // I2S3_MCK using Sclk = GpioOutputC10; // I2S3_SCK using Sdin = GpioOutputC12; // I2S3_SD +using DmaTx = Dma1::Channel5; + using Reset = GpioOutputD4; // Audio_RST using Scl = GpioB6; // Audio_SCL using Sda = GpioB9; // Audio_SDA using I2cMaster = I2cMaster1; -//using I2sMaster = I2sMaster3; +using I2sMaster = I2sMaster3; + +static constexpr uint8_t I2CAddress = 0x4a; // (0x94 >> 1) /// @} } @@ -205,19 +220,22 @@ initializeLis3() lis3::SpiMaster::setDataMode(lis3::SpiMaster::DataMode::Mode3); } -/// not supported yet, due to missing I2S driver +template< modm::frequency_t samplerate=48_kHz, modm::percent_t tolerance=modm::pct(0.019) > inline void initializeCs43() { -// cs43::Lrck::connect(cs43::I2sMaster::Ws); -// cs43::Mclk::connect(cs43::I2sMaster::Mck); -// cs43::Sclk::connect(cs43::I2sMaster::Ck); -// cs43::Sdin::connect(cs43::I2sMaster::Sd); - + cs43::I2sMaster::connect(); + cs43::I2sMaster::initialize(); cs43::Reset::setOutput(modm::Gpio::High); cs43::I2cMaster::connect(); cs43::I2cMaster::initialize(); + + cs43::Reset::setOutput(modm::Gpio::Low); + modm::delay_ms(2); + cs43::Reset::setOutput(modm::Gpio::High); + } /// not supported yet, due to missing I2S driver diff --git a/src/modm/board/disco_f407vg/module.lb b/src/modm/board/disco_f407vg/module.lb index f5adb050d1..1de551d802 100644 --- a/src/modm/board/disco_f407vg/module.lb +++ b/src/modm/board/disco_f407vg/module.lb @@ -26,11 +26,14 @@ def prepare(module, options): module.depends( ":architecture:clock", ":driver:lis3dsh", + ":driver:cs43l22", ":platform:clock", ":platform:core", + ":platform:dma", ":platform:gpio", ":platform:i2c:1", ":platform:spi:1", + ":platform:i2s:3", ":platform:usb:fs") return True From ef6baeec66131d3f9b067e30b4c7322b466f761b Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Sat, 30 Oct 2021 00:00:00 +0000 Subject: [PATCH 08/28] [example] STM32F4-Disco: CS43L22 I2S DAC example --- examples/stm32f4_discovery/audio_i2s/main.cpp | 135 ++++++++++++++++++ .../stm32f4_discovery/audio_i2s/project.xml | 13 ++ 2 files changed, 148 insertions(+) create mode 100644 examples/stm32f4_discovery/audio_i2s/main.cpp create mode 100644 examples/stm32f4_discovery/audio_i2s/project.xml diff --git a/examples/stm32f4_discovery/audio_i2s/main.cpp b/examples/stm32f4_discovery/audio_i2s/main.cpp new file mode 100644 index 0000000000..f289395250 --- /dev/null +++ b/examples/stm32f4_discovery/audio_i2s/main.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2021, Raphael Lehmann + * Copyright (c) 2021, Christopher Durand + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// Set the log level +#undef MODM_LOG_LEVEL +#define MODM_LOG_LEVEL modm::log::DEBUG + +// Create an IODeviceWrapper around the Uart Peripheral we want to use +modm::IODeviceWrapper< Usart2, modm::IOBuffer::BlockIfFull > loggerDevice; + +// Set all four logger streams to use the UART +modm::log::Logger modm::log::debug(loggerDevice); +modm::log::Logger modm::log::info(loggerDevice); +modm::log::Logger modm::log::warning(loggerDevice); +modm::log::Logger modm::log::error(loggerDevice); + +using namespace Board; + +template +constexpr auto computeSinTable(uint8_t cycles=1) +{ + std::array data{}; + constexpr auto HalfOutput = std::numeric_limits::max() / 2; // 16 bit full scale + for (size_t i = 0; i < data.size(); ++i) { + constexpr auto pi = std::numbers::pi_v; + data[i] = HalfOutput * (1 + arm_sin_f32(i * (2*pi / data.size() * cycles))); + } + return data; +} + +constexpr std::size_t bufferSize = 960; // 480 +auto bufferA = computeSinTable(1); +auto bufferB = computeSinTable(2); +volatile bool bufferA_ready{true}; +volatile bool bufferB_ready{true}; + +void +transferCompleteIrqHandler() +{ + LedGreen::reset(); + + if (bufferA_ready) { + cs43::I2sMaster::setTxBufferAddress(uintptr_t(bufferA.data()), bufferSize); + bufferA_ready = false; + } + else if (bufferB_ready) { + cs43::I2sMaster::setTxBufferAddress(uintptr_t(bufferB.data()), bufferSize); + bufferB_ready = false; + } + else { + LedRed::toggle(); + //MODM_LOG_ERROR << "No buffer ready for DMA :(" << modm::endl; + } + cs43::I2sMaster::startDma(); + + LedGreen::set(); +} + +int +main() +{ + Board::initialize(); + + // initialize Uart2 for MODM_LOG_* + Usart2::connect(); + Usart2::initialize(); + + MODM_LOG_INFO << "Audio demo using CS43L22 I2S DAC on STM32F4-DSICOVERY" << modm::endl; + + Dma1::enable(); + Dma2::enable(); + + Board::initializeCs43(); + + modm::Cs43l22 audioDac{cs43::I2CAddress}; + if (!RF_CALL_BLOCKING(audioDac.initialize())) { + MODM_LOG_ERROR << "Unable to initialize CS43L22 audio DAC" << modm::endl; + } + if (!RF_CALL_BLOCKING(audioDac.setMasterVolume(-600))) { + MODM_LOG_ERROR << "Unable to set master volume of CS43L22 audio DAC" << modm::endl; + } + + cs43::I2sMaster::setTransferCompleteIrqHandler(transferCompleteIrqHandler); + cs43::I2sMaster::setTxBufferAddress(uintptr_t(bufferA.data()), bufferSize); + cs43::I2sMaster::start(); + + modm::PeriodicTimer tmr{500ms}; + uint8_t counter{3}; + + while (true) + { + if (!bufferA_ready) { + bufferA = computeSinTable(counter++); + bufferA_ready = true; + } + if (!bufferB_ready) { + bufferB = computeSinTable(counter++); + bufferB_ready = true; + } + if (counter > 100) { + counter = 3; + } + + if (tmr.execute()) { + LedBlue::toggle(); + } + + if (cs43::I2sMaster::hasDmaError()) { + MODM_LOG_ERROR << "I2S DMA Error :(" << modm::endl; + } + } + + return 0; +} diff --git a/examples/stm32f4_discovery/audio_i2s/project.xml b/examples/stm32f4_discovery/audio_i2s/project.xml new file mode 100644 index 0000000000..e2f2dafc58 --- /dev/null +++ b/examples/stm32f4_discovery/audio_i2s/project.xml @@ -0,0 +1,13 @@ + + modm:disco-f407vg + + + + + modm:debug + modm:build:scons + modm:platform:uart:2 + modm:processing:timer + modm:cmsis:dsp:fast_math + + From 9d7cae52dc7b891d07dfc89bffa5e28dd99e5a0f Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Tue, 1 Mar 2022 00:15:49 +0100 Subject: [PATCH 09/28] =?UTF-8?q?[driver]=20Add=20MA12070P=20audio=20DAC?= =?UTF-8?q?=20(I2S=20+=20I=C2=B2C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modm/driver/dac/ma12070p.hpp | 354 ++++++++++++++++++++++++++ src/modm/driver/dac/ma12070p.lb | 37 +++ src/modm/driver/dac/ma12070p_impl.hpp | 179 +++++++++++++ 3 files changed, 570 insertions(+) create mode 100644 src/modm/driver/dac/ma12070p.hpp create mode 100644 src/modm/driver/dac/ma12070p.lb create mode 100644 src/modm/driver/dac/ma12070p_impl.hpp diff --git a/src/modm/driver/dac/ma12070p.hpp b/src/modm/driver/dac/ma12070p.hpp new file mode 100644 index 0000000000..44ff1914e7 --- /dev/null +++ b/src/modm/driver/dac/ma12070p.hpp @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2021 - 2022, Raphael Lehmann + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_MA12070P_HPP +#define MODM_MA12070P_HPP + +#include +#include + +namespace modm +{ + +/// @ingroup modm_driver_ma12070p +struct ma12070p +{ + enum class + Register : uint8_t + { + PowerModeControl = 0x00, + ThresholdForPowerModeChange12 = 0x01, + ThresholdForPowerModeChange21 = 0x02, + ThresholdForPowerModeChange23 = 0x03, + ThresholdForPowerModeChange32 = 0x04, + DisableAmplifier = 0x07, + SoftClippingAndOcpLatching = 0x0a, + SelectPowerModeProfile = 0x1d, + PowerModeProfileConfiguration = 0x1e, + OcpLatchClear = 0x20, + AudioInputMode = 0x25, + DcProtection = 0x26, + AudioInputModeOverwrite = 0x27, + ErrorHandlerClear = 0x2d, + PcmFormat = 0x35, + PcmConfiguration = 0x36, + LimiterConfiguration = 0x35, + MuteAndLimiterMux = 0x36, + MasterVolumeDbInteger = 0x40, + MasterVolumeDbFract = 0x41, + Ch0LVolumeDbInteger = 0x42, + Ch0RVolumeDbInteger = 0x43, + Ch1LVolumeDbInteger = 0x44, + Ch1RVolumeDbInteger = 0x45, + ChxxVolumeDbFract = 0x46, + Ch0LLimiterDbfsInteger = 0x47, + Ch0RLimiterDbfsInteger = 0x48, + Ch1LLimiterDbfsInteger = 0x49, + Ch1RLimiterDbfsInteger = 0x4a, + ChxxLimiterDbfsFract = 0x4b, + VolumeClippingIndicator = 0x7e, + MonitorCh0FreqPower = 0x60, + MonitorCh0 = 0x61, + MonitorCh0ModulationIndex = 0x62, + MonitorCh1FreqPower = 0x64, + MonitorCh1 = 0x65, + MonitorCh1ModulationIndex = 0x66, + ErrorAccumulated = 0x6d, + MonitorMsel = 0x75, + Error = 0x7c, + VlpMonitor = 0x7e, + }; + + + enum class + PcmWordFormat : uint8_t + { + I2s = 0b000, + LeftJustifed = 0b001, /// default + RightJustifed16b = 0b100, + RightJustifed18b = 0b101, + RightJustifed20b = 0b110, + RightJustifed24b = 0b111, + }; + + enum class + ClockPolarity : uint8_t + { + FallingEdge = 0b0, /// Capture data at the falling edge of the serial clock signal SCK + RisingEdge = 0b1, /// Capture data at the rising edge of the serial clock signal SCK + }; + + enum class + FrameSize : uint8_t + { + Bits64 = (0b00 << 3), + Bits48 = (0b01 << 3), + Bits32 = (0b10 << 3), + //Reserved = (0b11 << 3), + }; + + enum class + WordSelectPolarity : uint8_t + { + Low = (0b0 << 1), /// First word of a simultaneously sampled PCM data pair is transmitted while word select (WS) is low. + High = (0b1 << 1), /// First word of a simultaneously sampled PCM data pair is transmitted while word select (WS) is high. + }; + + enum class + DataOrder : uint8_t + { + MsbFirst = (0b0 << 2), + LsbFirst = (0b1 << 2), + }; + + enum class + RightLeftOrder : uint8_t + { + LeftFirst = (0b0 << 5), + RightFirst = (0b1 << 5), + }; + + enum class + LimiterTiming : uint8_t + { + Slow = 0b00, + Normal = 0b01, + Fast = 0b10, + }; + + struct I2sAndVlpConfig + { + const PcmWordFormat pcmWordFormat = PcmWordFormat::LeftJustifed; + const ClockPolarity clockPolarity = ClockPolarity::FallingEdge; + const FrameSize frameSize = FrameSize::Bits64; + const WordSelectPolarity wordSelectPolarity = WordSelectPolarity::Low; + const DataOrder dataOrder = DataOrder::MsbFirst; + const RightLeftOrder rightLeftOrder = RightLeftOrder::LeftFirst; + const bool useVlp = true; + const bool useLimiter = true; + const LimiterTiming limiterReleaseTime = LimiterTiming::Normal; + const LimiterTiming limiterAttackTime = LimiterTiming::Normal; + }; + + enum class + VlpMonitor : uint8_t + { + LimiterCh0L = Bit0, + LimiterCh0R = Bit1, + LimiterCh1L = Bit2, + LimiterCh1R = Bit3, + ClippingCh0L = Bit4, + ClippingCh0R = Bit5, + ClippingCh1L = Bit6, + ClippingCh1R = Bit7, + }; + MODM_FLAGS8(VlpMonitor); + +#if MODM_HAS_IOSTREAM + friend IOStream& + operator << (IOStream& os, const VlpMonitor_t& c) + { + os << "VlpMonitor("; + if(c & VlpMonitor::LimiterCh0L) + os << "LimiterCh0L "; + if(c & VlpMonitor::LimiterCh0R) + os << "LimiterCh0R "; + if(c & VlpMonitor::LimiterCh1L) + os << "LimiterCh1L "; + if(c & VlpMonitor::LimiterCh1R) + os << "LimiterCh1R "; + if(c & VlpMonitor::ClippingCh0L) + os << "ClippingCh0L "; + if(c & VlpMonitor::ClippingCh0R) + os << "ClippingCh0R "; + if(c & VlpMonitor::ClippingCh1L) + os << "ClippingCh1L "; + if(c & VlpMonitor::ClippingCh1R) + os << "ClippingCh1R "; + os << ")"; + return os; + } +#endif + + enum class + ErrorRegister : uint8_t + { + FlyingCapOverVolt = Bit0, + OverCurrent = Bit1, + Pll = Bit2, + PvddUnderVolt = Bit3, + OverTempWarning = Bit4, + OverTempError = Bit5, + PinToPinLowImpedance = Bit6, + DcProtection = Bit7, + }; + MODM_FLAGS8(ErrorRegister); + +#if MODM_HAS_IOSTREAM + friend IOStream& + operator << (IOStream& os, const ErrorRegister_t& c) + { + os << "ErrorRegister("; + if(c & ErrorRegister::FlyingCapOverVolt) + os << "FlyingCapOverVolt "; + if(c & ErrorRegister::OverCurrent) + os << "OverCurrent "; + if(c & ErrorRegister::Pll) + os << "Pll "; + if(c & ErrorRegister::PvddUnderVolt) + os << "PvddUnderVolt "; + if(c & ErrorRegister::OverTempWarning) + os << "OverTempWarning "; + if(c & ErrorRegister::OverTempError) + os << "OverTempError "; + if(c & ErrorRegister::PinToPinLowImpedance) + os << "PinToPinLowImpedance "; + if(c & ErrorRegister::DcProtection) + os << "DcProtection "; + os << ")"; + return os; + } +#endif + + + // quarter_decibel type + enum class + quarter_decibel_t : int16_t + {}; + + template + static constexpr quarter_decibel_t + quarter_decibel(T value) + { + return quarter_decibel_t(int16_t(value * 4ul)); + } + + static constexpr float + quarterDecibelToFloat(quarter_decibel_t value) + { + return int16_t(value) / 4.f; + } + + static constexpr quarter_decibel_t MaxVolume = static_cast(24*4); + static constexpr quarter_decibel_t MinVolume = static_cast(-144*4); + // static constexpr quarter_decibel_t MaxVolume = quarter_decibel(24); + // static constexpr quarter_decibel_t MinVolume = quarter_decibel(-144); +}; // struct ma12070p + +constexpr ma12070p::quarter_decibel_t +operator-(ma12070p::quarter_decibel_t v) +{ + return static_cast(-static_cast(v)); +} + +namespace literals +{ + constexpr ma12070p::quarter_decibel_t + operator""_q_db(long double value) + { + return ma12070p::quarter_decibel(value); + } + + constexpr ma12070p::quarter_decibel_t + operator""_q_db(unsigned long long int value) + { + return ma12070p::quarter_decibel(value); + } +} + +/** + * Infineon MA12070P, Portable Audio DAC with Integrated Class D Speaker Driver + * + * @tparam I2cMaster I2C interface + * + * @author Raphael Lehmann + * @ingroup modm_driver_ma12070p + */ +template +class Ma12070p : public ma12070p, public modm::I2cDevice +{ +public: + /** + * Constructor. + * + * \param i2cAddress The I²C address depends on the AD1 and AD0 pins: + * AD1=0, AD0=0 -> 0x20 + * AD1=0, AD0=1 -> 0x21 + * AD1=1, AD0=0 -> 0x22 + * AD1=1, AD0=1 -> 0x23 + */ + Ma12070p(uint8_t i2cAddress); + + /// Initialize device, call before using any other function + ResumableResult + initialize(); + + /// Configure I2S input format and the volume and limiter processor (VLP) + ResumableResult + configureI2sAndVlp(I2sAndVlpConfig config); + + /// Set the limiter treshold for individual channels (-144db to +24db) + ResumableResult + setLimiterTreshold(quarter_decibel_t ch0l, quarter_decibel_t ch0r, quarter_decibel_t ch1l = -144, quarter_decibel_t ch1r = -144); + + /// Set the master volume (-144db to +24db) + ResumableResult + setMasterVolume(quarter_decibel_t volume); + + /// Set the volume for individual channels (-144db to +24db) + ResumableResult + setChannelVolume(quarter_decibel_t ch0l, quarter_decibel_t ch0r, quarter_decibel_t ch1l = -144, quarter_decibel_t ch1r = -144); + + /// Read if limiters are active or if clipping occurs on the VLP output signals + ResumableResult> + readVlpMonitor(); + + /// Disable amplifier channels + /// true means disabled + ResumableResult + disableAmplifier(bool ch0, bool ch1); + + /// Enable DC protection. Default on. + ResumableResult + enableDcProtection(bool enable = true); + + /// Read error monitor register. Gives the live status of every potential error source. + ResumableResult> + readErrors(); + + /// Read error monitor register. + /// Gives the accumulated status of every potential error source. + /// This register should be cleared by using `clearErrorHandler()`. + ResumableResult> + readAccumulatedErrors(); + + /// Clears the error handler + ResumableResult + clearErrorHandler(); + +private: + uint8_t tx_buffer[6]; + uint8_t rx_buffer; + bool success; + + ResumableResult + writeRegister(Register reg, uint8_t value); + + ResumableResult> + readRegister(Register reg); +}; + +} // namespace modm + +#include "ma12070p_impl.hpp" + +#endif // MODM_MA12070P_HPP + diff --git a/src/modm/driver/dac/ma12070p.lb b/src/modm/driver/dac/ma12070p.lb new file mode 100644 index 0000000000..3455489440 --- /dev/null +++ b/src/modm/driver/dac/ma12070p.lb @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2021 - 2022, Raphael Lehmann +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + + +def init(module): + module.name = ":driver:ma12070p" + module.description = """\ +# MA12070P Filterless and High-Efficiency Audio Amplifier with I2S Digital Input + +The MA12070P is a super-efficient audio power amplifier based on proprietary +multi-level switching technology. It supports a 4-26V supply voltage range, +allowing it to be used in many different applications. + +Datasheet: https://www.infineon.com/dgdl/Infineon-MA12070P-DS-v01_00-EN.pdf?fileId=5546d46264a8de7e0164b761f2f261e4 + +""" + +def prepare(module, options): + module.depends( + ":architecture:register", + ":architecture:i2c.device", + ":processing:resumable") + return True + +def build(env): + env.outbasepath = "modm/src/modm/driver/dac" + env.copy("ma12070p.hpp") + env.copy("ma12070p_impl.hpp") diff --git a/src/modm/driver/dac/ma12070p_impl.hpp b/src/modm/driver/dac/ma12070p_impl.hpp new file mode 100644 index 0000000000..058bf22a6f --- /dev/null +++ b/src/modm/driver/dac/ma12070p_impl.hpp @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2021 - 2022, Raphael Lehmann + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_MA12070P_HPP +# error "Don't include this file directly, use 'ma12070p.hpp' instead!" +#endif + +namespace { +template +auto +resumableResultOptionalTfromOptionalUint(modm::ResumableResult> value) +-> modm::ResumableResult> +{ + if (value.getResult()) { + return modm::ResumableResult>(value.getState(), static_cast>(value.getResult())); + } + else { + return modm::ResumableResult>(value.getState(), static_cast>(std::nullopt)); + } +} +} + +namespace modm +{ + +template +Ma12070p::Ma12070p(uint8_t i2cAddress) + : I2cDevice(i2cAddress) +{ +} + +template +ResumableResult +Ma12070p::initialize() +{ + RF_BEGIN(); + RF_END_RETURN(true); +} + +template +ResumableResult +Ma12070p::writeRegister(Register reg, uint8_t value) +{ + RF_BEGIN(); + tx_buffer[0] = static_cast(reg); + tx_buffer[1] = value; + this->transaction.configureWrite(tx_buffer, 2); + RF_END_RETURN_CALL(this->runTransaction()); +} + +template +ResumableResult> +Ma12070p::readRegister(Register reg) +{ + RF_BEGIN(); + tx_buffer[0] = static_cast(reg); + this->transaction.configureWriteRead(&tx_buffer[0], 1, &rx_buffer, 1); + success = RF_CALL(this->runTransaction()); + if (!success) { + RF_RETURN(std::nullopt); + } + RF_END_RETURN(rx_buffer); +} + +template +ResumableResult +Ma12070p::configureI2sAndVlp(I2sAndVlpConfig c) +{ + RF_BEGIN(); + tx_buffer[0] = uint8_t(Register::LimiterConfiguration); // and the following Register::MuteAndLimiterMux + tx_buffer[1] /* register 0x35 */ = (uint8_t(c.limiterReleaseTime) << 6) | + (uint8_t(c.limiterAttackTime) << 4) | + (uint8_t(c.useVlp) << 3) | + (uint8_t(c.pcmWordFormat) << 0); + tx_buffer[2] /* register 0x36 */ = + (uint16_t(c.useLimiter) << 6) | + (uint8_t(c.rightLeftOrder) << 5) | + (uint8_t(c.frameSize) << 3) | + (uint8_t(c.dataOrder) << 2) | + (uint8_t(c.wordSelectPolarity) << 1) | + (uint8_t(c.clockPolarity) << 0); + this->transaction.configureWrite(tx_buffer, 3); + RF_END_RETURN_CALL(this->runTransaction()); +} + +template +ResumableResult +Ma12070p::setLimiterTreshold(quarter_decibel_t ch0l, quarter_decibel_t ch0r, quarter_decibel_t ch1l, quarter_decibel_t ch1r) +{ + RF_BEGIN(); + tx_buffer[0] = uint8_t(Register::Ch0LVolumeDbInteger); + tx_buffer[1] = uint16_t(ch0l) >> 2; + tx_buffer[2] = uint16_t(ch0r) >> 2; + tx_buffer[3] = uint16_t(ch1l) >> 2; + tx_buffer[4] = uint16_t(ch1r) >> 2; + tx_buffer[5] = (uint16_t(ch0l) & 0b11) | ((uint16_t(ch0r) & 0b11) << 2) | ((uint16_t(ch1l) & 0b11) << 4) | ((uint16_t(ch1r) & 0b11) << 6); + this->transaction.configureWrite(tx_buffer, 6); + RF_END_RETURN_CALL(this->runTransaction()); +} + +template +ResumableResult +Ma12070p::setMasterVolume(quarter_decibel_t volume) +{ + RF_BEGIN(); + tx_buffer[0] = uint8_t(Register::MasterVolumeDbInteger); + tx_buffer[1] = uint16_t(volume) >> 2; + tx_buffer[2] = (uint16_t(volume) & 0b11); + this->transaction.configureWrite(tx_buffer, 3); + RF_END_RETURN_CALL(this->runTransaction()); +} + +template +ResumableResult +Ma12070p::setChannelVolume(quarter_decibel_t ch0l, quarter_decibel_t ch0r, quarter_decibel_t ch1l, quarter_decibel_t ch1r) +{ + RF_BEGIN(); + tx_buffer[0] = uint8_t(Register::Ch0LLimiterDbfsInteger); + tx_buffer[1] = uint16_t(ch0l) >> 2; + tx_buffer[2] = uint16_t(ch0r) >> 2; + tx_buffer[3] = uint16_t(ch1l) >> 2; + tx_buffer[4] = uint16_t(ch1r) >> 2; + tx_buffer[5] = (uint16_t(ch0l) & 0b11) | ((uint16_t(ch0r) & 0b11) << 2) | ((uint16_t(ch1l) & 0b11) << 4) | ((uint16_t(ch1r) & 0b11) << 6); + this->transaction.configureWrite(tx_buffer, 6); + RF_END_RETURN_CALL(this->runTransaction()); +} + +template +ResumableResult> +Ma12070p::readVlpMonitor() +{ + return resumableResultOptionalTfromOptionalUint(readRegister(Register::VlpMonitor)); +} + +template +ResumableResult +Ma12070p::disableAmplifier(bool ch0, bool ch1) +{ + return writeRegister(Register::DisableAmplifier, (uint8_t(ch0) << 7) | (uint8_t(ch1) << 6)); +} + +template +ResumableResult +Ma12070p::enableDcProtection(bool enable) +{ + return writeRegister(Register::DcProtection, (uint8_t(enable) << 2)); +} + +template +ResumableResult> +Ma12070p::readErrors() +{ + return resumableResultOptionalTfromOptionalUint(readRegister(Register::Error)); +} + +template +ResumableResult> +Ma12070p::readAccumulatedErrors() +{ + return resumableResultOptionalTfromOptionalUint(readRegister(Register::ErrorAccumulated)); +} + +template +ResumableResult +Ma12070p::clearErrorHandler() +{ + return writeRegister(Register::ErrorHandlerClear, (0b1 << 2)); +} + + +} // namespace modm From d31ea26c18739a24a72fb9a1d8f7b853c70f60ed Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Tue, 1 Mar 2022 00:16:23 +0100 Subject: [PATCH 10/28] WIP [example] STM32F469-Disco: MA12070P I2S DAC example --- .../audio_ma12070p/main.cpp | 79 +++++++++++++++++++ .../audio_ma12070p/project.xml | 13 +++ 2 files changed, 92 insertions(+) create mode 100644 examples/stm32f469_discovery/audio_ma12070p/main.cpp create mode 100644 examples/stm32f469_discovery/audio_ma12070p/project.xml diff --git a/examples/stm32f469_discovery/audio_ma12070p/main.cpp b/examples/stm32f469_discovery/audio_ma12070p/main.cpp new file mode 100644 index 0000000000..3f8d3c1601 --- /dev/null +++ b/examples/stm32f469_discovery/audio_ma12070p/main.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2022, Raphael Lehmann + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +using namespace Board; +using namespace modm::literals; + +using I2cMaster = modm::platform::I2cMaster1; +using Ma12070p = modm::Ma12070p; + +constexpr uint8_t ma12070pAddressI2c = 0x20; +Ma12070p ma12070p{ma12070pAddressI2c}; + +int +main() +{ + Board::initialize(); + Board::initializeTouchscreen(); // for I2c + + MODM_LOG_INFO << "Audio MA12070P demo on ST Discovery F469NI" << modm::endl; + + while (!RF_CALL_BLOCKING(ma12070p.initialize())) { + MODM_LOG_ERROR << "Unable to initialize MA12070P" << modm::endl; + } + + + modm::ma12070p::I2sAndVlpConfig config { + .pcmWordFormat = modm::ma12070p::PcmWordFormat::RightJustifed16b, + .clockPolarity = modm::ma12070p::ClockPolarity::FallingEdge, + .frameSize = modm::ma12070p::FrameSize::Bits32, + .wordSelectPolarity = modm::ma12070p::WordSelectPolarity::High, + .rightLeftOrder = modm::ma12070p::RightLeftOrder::LeftFirst, + .useVlp = true, + .useLimiter = true, + }; + while (!RF_CALL_BLOCKING(ma12070p.configureI2sAndVlp(config))) { + MODM_LOG_ERROR << "Unable to configure I2S ansd VLP settings of MA12070P" << modm::endl; + } + + while (!RF_CALL_BLOCKING(ma12070p.setMasterVolume(-20_q_db))) { + MODM_LOG_ERROR << "Unable to set master volume of MA12070P" << modm::endl; + } + + std::optional vlpMonitor; + std::optional errorRegister; + + while (true) + { + vlpMonitor = RF_CALL_BLOCKING(ma12070p.readVlpMonitor()); + if(!vlpMonitor) { + MODM_LOG_ERROR << "Unable to read VLP monitor register" << modm::endl; + } + else { + MODM_LOG_ERROR << *vlpMonitor << modm::endl; + } + errorRegister = RF_CALL_BLOCKING(ma12070p.readAccumulatedErrors()); + if(!errorRegister) { + MODM_LOG_ERROR << "Unable to read accumulated error register" << modm::endl; + } + else { + MODM_LOG_ERROR << *errorRegister << modm::endl; + } + } + + return 0; +} diff --git a/examples/stm32f469_discovery/audio_ma12070p/project.xml b/examples/stm32f469_discovery/audio_ma12070p/project.xml new file mode 100644 index 0000000000..4ca5df8023 --- /dev/null +++ b/examples/stm32f469_discovery/audio_ma12070p/project.xml @@ -0,0 +1,13 @@ + + modm:disco-f469ni + + + + + modm:processing:protothread + modm:processing:timer + modm:platform:i2c:1 + modm:build:scons + modm:driver:ma12070p + + From 1a8494bb083e8ab2582d2d011400dc65abcece71 Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Wed, 2 Mar 2022 01:41:36 +0100 Subject: [PATCH 11/28] fixup_example_cs43l22 --- examples/stm32f4_discovery/audio_i2s/main.cpp | 54 +++++++++++++++---- .../stm32f4_discovery/audio_i2s/project.xml | 1 + 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/examples/stm32f4_discovery/audio_i2s/main.cpp b/examples/stm32f4_discovery/audio_i2s/main.cpp index f289395250..093974e7c8 100644 --- a/examples/stm32f4_discovery/audio_i2s/main.cpp +++ b/examples/stm32f4_discovery/audio_i2s/main.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Raphael Lehmann + * Copyright (c) 2021-2022, Raphael Lehmann * Copyright (c) 2021, Christopher Durand * * This file is part of the modm project. @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -49,7 +50,7 @@ constexpr auto computeSinTable(uint8_t cycles=1) return data; } -constexpr std::size_t bufferSize = 960; // 480 +constexpr std::size_t bufferSize = 960; auto bufferA = computeSinTable(1); auto bufferB = computeSinTable(2); volatile bool bufferA_ready{true}; @@ -61,11 +62,11 @@ transferCompleteIrqHandler() LedGreen::reset(); if (bufferA_ready) { - cs43::I2sMaster::setTxBufferAddress(uintptr_t(bufferA.data()), bufferSize); + cs43::I2sMaster::setTxBuffer(uintptr_t(bufferA.data()), bufferSize); bufferA_ready = false; } else if (bufferB_ready) { - cs43::I2sMaster::setTxBufferAddress(uintptr_t(bufferB.data()), bufferSize); + cs43::I2sMaster::setTxBuffer(uintptr_t(bufferB.data()), bufferSize); bufferB_ready = false; } else { @@ -77,6 +78,40 @@ transferCompleteIrqHandler() LedGreen::set(); } +class VolumeThread : public modm::pt::Protothread +{ +public: + VolumeThread(modm::Cs43l22& audioDac) : audioDac{audioDac} + { + RF_CALL_BLOCKING(audioDac.setMasterVolume(volume)); + } + + bool + update() + { + PT_BEGIN(); + while (true) { + if (Board::Button::read()) { + volume += 50; + if (volume > modm::cs43l22::MaxVolume) { + volume = modm::cs43l22::MinVolume; + } + MODM_LOG_INFO.printf("Volume: %2.1fdb\n", volume/10.f); + PT_CALL(audioDac.setMasterVolume(volume)); + timeout.restart(); + PT_WAIT_UNTIL(timeout.isExpired()); + } + PT_YIELD(); + } + PT_END(); + } + +private: + modm::Cs43l22& audioDac; + modm::cs43l22::centiBel_t volume = (modm::cs43l22::MinVolume + modm::cs43l22::MaxVolume) / 2; + modm::ShortTimeout timeout{500ms}; +}; + int main() { @@ -87,9 +122,9 @@ main() Usart2::initialize(); MODM_LOG_INFO << "Audio demo using CS43L22 I2S DAC on STM32F4-DSICOVERY" << modm::endl; + MODM_LOG_INFO << "Press the 'user' button to increate volume." << modm::endl; Dma1::enable(); - Dma2::enable(); Board::initializeCs43(); @@ -97,12 +132,11 @@ main() if (!RF_CALL_BLOCKING(audioDac.initialize())) { MODM_LOG_ERROR << "Unable to initialize CS43L22 audio DAC" << modm::endl; } - if (!RF_CALL_BLOCKING(audioDac.setMasterVolume(-600))) { - MODM_LOG_ERROR << "Unable to set master volume of CS43L22 audio DAC" << modm::endl; - } + + VolumeThread volumeThread{audioDac}; cs43::I2sMaster::setTransferCompleteIrqHandler(transferCompleteIrqHandler); - cs43::I2sMaster::setTxBufferAddress(uintptr_t(bufferA.data()), bufferSize); + cs43::I2sMaster::setTxBuffer(uintptr_t(bufferA.data()), bufferSize); cs43::I2sMaster::start(); modm::PeriodicTimer tmr{500ms}; @@ -129,6 +163,8 @@ main() if (cs43::I2sMaster::hasDmaError()) { MODM_LOG_ERROR << "I2S DMA Error :(" << modm::endl; } + + volumeThread.update(); } return 0; diff --git a/examples/stm32f4_discovery/audio_i2s/project.xml b/examples/stm32f4_discovery/audio_i2s/project.xml index e2f2dafc58..f70f941a26 100644 --- a/examples/stm32f4_discovery/audio_i2s/project.xml +++ b/examples/stm32f4_discovery/audio_i2s/project.xml @@ -8,6 +8,7 @@ modm:build:scons modm:platform:uart:2 modm:processing:timer + modm:processing:protothread modm:cmsis:dsp:fast_math From 9f243a2423924393bfd9d52e97441c4aa524ca3c Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Wed, 2 Mar 2022 01:41:57 +0100 Subject: [PATCH 12/28] fixup_platform_i2s --- README.md | 2 ++ src/modm/platform/i2s/stm32/i2s_base.hpp.in | 4 ---- src/modm/platform/i2s/stm32/i2s_hal.hpp.in | 4 ---- src/modm/platform/i2s/stm32/i2s_hal_impl.hpp.in | 8 ++------ src/modm/platform/i2s/stm32/i2s_master.hpp.in | 6 +++--- src/modm/platform/i2s/stm32/module.lb | 2 -- 6 files changed, 7 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 259412ccc7..f37f8bf52b 100644 --- a/README.md +++ b/README.md @@ -338,12 +338,14 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✅ ✅ ✕ +✕ ○ ✕ ✕ ✕ ✕ ✕ +✕ I2C ✅ diff --git a/src/modm/platform/i2s/stm32/i2s_base.hpp.in b/src/modm/platform/i2s/stm32/i2s_base.hpp.in index 0ba9735354..ab19585dcb 100644 --- a/src/modm/platform/i2s/stm32/i2s_base.hpp.in +++ b/src/modm/platform/i2s/stm32/i2s_base.hpp.in @@ -1,8 +1,4 @@ /* - * Copyright (c) 2013, Kevin Läufer - * Copyright (c) 2013-2017, Niklas Hauser - * Copyright (c) 2014, Daniel Krebs - * Copyright (c) 2020, Mike Wolfram * Copyright (c) 2021, Marton Ledneczki * Copyright (c) 2021-2022, Raphael Lehmann * diff --git a/src/modm/platform/i2s/stm32/i2s_hal.hpp.in b/src/modm/platform/i2s/stm32/i2s_hal.hpp.in index b2a6d953c5..9950551f4f 100644 --- a/src/modm/platform/i2s/stm32/i2s_hal.hpp.in +++ b/src/modm/platform/i2s/stm32/i2s_hal.hpp.in @@ -1,8 +1,4 @@ /* - * Copyright (c) 2013, Kevin Läufer - * Copyright (c) 2013-2018, Niklas Hauser - * Copyright (c) 2014, Daniel Krebs - * Copyright (c) 2020, Mike Wolfram * Copyright (c) 2021, Marton Ledneczki * Copyright (c) 2021-2022, Raphael Lehmann * diff --git a/src/modm/platform/i2s/stm32/i2s_hal_impl.hpp.in b/src/modm/platform/i2s/stm32/i2s_hal_impl.hpp.in index 4cbd2876ab..4bd30c4e37 100644 --- a/src/modm/platform/i2s/stm32/i2s_hal_impl.hpp.in +++ b/src/modm/platform/i2s/stm32/i2s_hal_impl.hpp.in @@ -1,8 +1,4 @@ /* - * Copyright (c) 2013, Kevin Läufer - * Copyright (c) 2013-2017, Niklas Hauser - * Copyright (c) 2014, Daniel Krebs - * Copyright (c) 2020, Mike Wolfram * Copyright (c) 2021, Marton Ledneczki * Copyright (c) 2021-2022, Raphael Lehmann * @@ -57,13 +53,13 @@ modm::platform::I2sHal{{ id }}::initialize( OddFactor oddFactor, uint8_t i2s_div } void inline -modm::platform::I2sHal3::start() +modm::platform::I2sHal{{ id }}::start() { SPI{{ id }}->I2SCFGR |= SPI_I2SCFGR_I2SE; } void inline -modm::platform::I2sHal3::enableDma(bool enable) +modm::platform::I2sHal{{ id }}::enableDma(bool enable) { if (enable) { SPI{{ id }}->CR2 |= SPI_CR2_TXDMAEN; diff --git a/src/modm/platform/i2s/stm32/i2s_master.hpp.in b/src/modm/platform/i2s/stm32/i2s_master.hpp.in index 94602a79d7..2ceed0c2df 100644 --- a/src/modm/platform/i2s/stm32/i2s_master.hpp.in +++ b/src/modm/platform/i2s/stm32/i2s_master.hpp.in @@ -56,13 +56,13 @@ public: using Ck = typename Connector::template GetSignal; using Mck = typename Connector::template GetSignal; using Sd = typename Connector::template GetSignal; - using Ws = typename Connector::template GetSignal; + using Ws = typename Connector::template GetSignal; // Connector::disconnect(); Ck::setOutput(Gpio::OutputType::PushPull); Mck::setOutput(Gpio::OutputType::PushPull); Sd::setOutput(Gpio::OutputType::PushPull); - Ws::setOutput(Gpio::OutputType::PushPull); + Ws::setOutput(Gpio::OutputType::PushPull); Connector::connect(); } @@ -100,7 +100,7 @@ public: } static inline void - setTxBufferAddress(uintptr_t address, std::size_t length) + setTxBuffer(uintptr_t address, std::size_t length) { Dma::TxChannel::setMemoryAddress(address); Dma::TxChannel::setDataLength(length); diff --git a/src/modm/platform/i2s/stm32/module.lb b/src/modm/platform/i2s/stm32/module.lb index 1d2faec8ae..70e7f1cb30 100644 --- a/src/modm/platform/i2s/stm32/module.lb +++ b/src/modm/platform/i2s/stm32/module.lb @@ -1,8 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # -# Copyright (c) 2016-2018, Niklas Hauser -# Copyright (c) 2017, Fabian Greif # Copyright (c) 2021, Marton Ledneczki # Copyright (c) 2022, Raphael Lehmann # From 5251e28ecebf701a792e74c1724abc16f0118e2d Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Wed, 2 Mar 2022 01:45:23 +0100 Subject: [PATCH 13/28] fixup_architecture_i2s --- .../architecture/interface/i2s_master.hpp | 3 +- src/modm/platform/i2s/stm32/i2s_base.hpp.in | 70 +++++++++++-------- src/modm/platform/i2s/stm32/i2s_hal.hpp.in | 28 +++----- .../platform/i2s/stm32/i2s_hal_impl.hpp.in | 8 ++- src/modm/platform/i2s/stm32/i2s_master.hpp.in | 21 ++++-- 5 files changed, 74 insertions(+), 56 deletions(-) diff --git a/src/modm/architecture/interface/i2s_master.hpp b/src/modm/architecture/interface/i2s_master.hpp index bc33f0c13e..e6c3f266ec 100644 --- a/src/modm/architecture/interface/i2s_master.hpp +++ b/src/modm/architecture/interface/i2s_master.hpp @@ -14,7 +14,6 @@ #define MODM_INTERFACE_I2S_MASTER_HPP #include -#include "i2s.hpp" namespace modm { @@ -66,7 +65,7 @@ class I2sMaster : public ::modm::PeripheralDriver /** * @brief Set transfer complete handler function * - * The transfer complete handler should set the next tx buffer using + * The transfer complete handler should set the next tx buffer using * `setTxBuffer()` and then restart the DMA transfer using `startDma()`. */ static void diff --git a/src/modm/platform/i2s/stm32/i2s_base.hpp.in b/src/modm/platform/i2s/stm32/i2s_base.hpp.in index ab19585dcb..d0a1717406 100644 --- a/src/modm/platform/i2s/stm32/i2s_base.hpp.in +++ b/src/modm/platform/i2s/stm32/i2s_base.hpp.in @@ -17,10 +17,7 @@ #include "../device.hpp" #include -namespace modm -{ - -namespace platform +namespace modm::platform { /** @@ -40,8 +37,8 @@ public: RxBufferNotEmpty = SPI_CR2_RXNEIE, TxBufferEmpty = SPI_CR2_TXEIE, Error = SPI_CR2_ERRIE, - RxDmaEnable = SPI_CR2_RXDMAEN, - TxDmaEnable = SPI_CR2_TXDMAEN, + RxDmaEnable = SPI_CR2_RXDMAEN, + TxDmaEnable = SPI_CR2_TXDMAEN, }; MODM_FLAGS32(Interrupt); @@ -60,47 +57,64 @@ public: enum class MasterSelection : uint32_t { - Slave = 0b0, ///< Configure I2S as Slave - Master = SPI_I2SCFGR_I2SCFG_1, ///< Configure I2S as Master - All = Master, + Slave = 0b0, /// Configure I2S as Slave + Master = SPI_I2SCFGR_I2SCFG_1, /// Configure I2S as Master }; - enum class - DirectionSelection : uint32_t - { - Transmitter = 0b0, ///< Configure I2S as Transmitter - Receiver = SPI_I2SCFGR_I2SCFG_0, ///< COnfigure I2S as Receiver - All = Receiver, - }; + enum class + DirectionSelection : uint32_t + { + Transmitter = 0b0, /// Configure I2S as Transmitter + Receiver = SPI_I2SCFGR_I2SCFG_0, /// Configure I2S as Receiver + }; + /// Data length to be transferred (with channel length options for 16-bit data length) + /// For all data lengths except 16 bit the channel length is fixed to 32 bit. enum class BitDepth : uint32_t { - Sixteen = 0b0, - Twentyfour = SPI_I2SCFGR_DATLEN_0, - Thirtytwo = SPI_I2SCFGR_DATLEN_1, - All = Thirtytwo, + SixteenWithChannel16 = (0b00 << SPI_I2SCFGR_DATLEN_Pos) | (0b0 << SPI_I2SCFGR_CHLEN_Pos), /// 16 bit data length with 16 bit channel length + SixteenWithChannel32 = (0b00 << SPI_I2SCFGR_DATLEN_Pos) | (0b1 << SPI_I2SCFGR_CHLEN_Pos), /// 16 bit data length with 32 bit channel length + Twentyfour = (0b01 << SPI_I2SCFGR_DATLEN_Pos), /// 24 bit data length with 32 bit channel length + Thirtytwo = (0b10 << SPI_I2SCFGR_DATLEN_Pos), /// 32 bit data length with 32 bit channel length }; + /// Master clock output enable enum class MasterClockOutput : uint32_t { - Disabled = 0b0, - Enabled = SPI_I2SPR_MCKOE, - All = Enabled, + Disabled = 0b0, + Enabled = SPI_I2SPR_MCKOE, }; enum class OddFactor : uint32_t { - Disabled = 0, - Enabled = SPI_I2SPR_ODD, - All = Enabled, + Disabled = 0, + Enabled = SPI_I2SPR_ODD, + }; + + /// I2S standard selection (with frame synchronization options for PCM) + enum class + I2sStandard : uint32_t + { + Philips = (0b00 << SPI_I2SCFGR_I2SSTD_Pos), /// I2S Philips + MsbJustified = (0b01 << SPI_I2SCFGR_I2SSTD_Pos), /// MSB justified standard (left justified) + LsbJustified = (0b10 << SPI_I2SCFGR_I2SSTD_Pos), /// LSB justified standard (right justified) + PcmWithShortFrameSync = (0b11 << SPI_I2SCFGR_I2SSTD_Pos) | (0b0 << SPI_I2SCFGR_PCMSYNC_Pos), /// PCM with short frame synchronization + PcmWithLongFrameSync = (0b11 << SPI_I2SCFGR_I2SSTD_Pos) | (0b1 << SPI_I2SCFGR_PCMSYNC_Pos), /// PCM with long frame synchronization + }; + + /// Steady state clock polarity + enum class + ClockPolarity : uint32_t + { + Low = 0, /// clock steady state is low level + High = SPI_I2SCFGR_CKPOL, /// clock steady state is high level }; }; -} // namespace platform +} // namespace modm::platform -} // namespace modm #endif // MODM_STM32_I2S_BASE_HPP diff --git a/src/modm/platform/i2s/stm32/i2s_hal.hpp.in b/src/modm/platform/i2s/stm32/i2s_hal.hpp.in index 9950551f4f..c1bce2a65c 100644 --- a/src/modm/platform/i2s/stm32/i2s_hal.hpp.in +++ b/src/modm/platform/i2s/stm32/i2s_hal.hpp.in @@ -15,25 +15,23 @@ #include "i2s_base.hpp" -namespace modm -{ - -namespace platform +namespace modm::platform { /** - * Serial peripheral interface (I2S{{ id }}) + * Serial peripheral interface in I2S mode (I2S{{ id }}) * - * TODO: Very basic implementation that exposes more hardware features than + * Very basic implementation that exposes more hardware features than * the regular Spi classes. * * @author Marton Ledneczki + * @author Raphael Lehmann * @ingroup modm_platform_i2s modm_platform_i2s_{{id}} */ class I2sHal{{ id }} : public I2sBase { public: - /// TODO: Enables the clock, resets the hardware and sets the SPE bit + /// Enables the RCC clock, resets the hardware and configures mode to I2S static void enable(); @@ -41,17 +39,15 @@ public: static void disable(); - /** - * Initialize I2s Peripheral - * - * TODO: Enables clocks - */ + /// Initialize I2s peripheral static void initialize( OddFactor oddFactor, uint8_t i2s_div, MasterSelection masterSelection = MasterSelection::Master, DirectionSelection directionSelection = DirectionSelection::Transmitter, - BitDepth bitDepth = BitDepth::Sixteen, - MasterClockOutput masterClockOutput = MasterClockOutput::Enabled); + BitDepth bitDepth = BitDepth::SixteenWithChannel16, + MasterClockOutput masterClockOutput = MasterClockOutput::Enabled, + I2sStandard i2sStandard = I2sStandard::Philips, + ClockPolarity clockPolarity = ClockPolarity::Low); static void start(); @@ -81,9 +77,7 @@ public: acknowledgeInterruptFlag(InterruptFlag_t flags); }; -} // namespace platform - -} // namespace modm +} // namespace modm::platform #include "i2s_hal_{{ id }}_impl.hpp" diff --git a/src/modm/platform/i2s/stm32/i2s_hal_impl.hpp.in b/src/modm/platform/i2s/stm32/i2s_hal_impl.hpp.in index 4bd30c4e37..5f8905e25a 100644 --- a/src/modm/platform/i2s/stm32/i2s_hal_impl.hpp.in +++ b/src/modm/platform/i2s/stm32/i2s_hal_impl.hpp.in @@ -36,7 +36,9 @@ modm::platform::I2sHal{{ id }}::initialize( OddFactor oddFactor, uint8_t i2s_div MasterSelection masterSelection, DirectionSelection directionSelection, BitDepth bitDepth, - MasterClockOutput masterClockOutput) + MasterClockOutput masterClockOutput, + I2sStandard i2sStandard, + ClockPolarity clockPolarity) { enable(); // disable peripheral, and clear all fields except I2S mode bit @@ -49,7 +51,9 @@ modm::platform::I2sHal{{ id }}::initialize( OddFactor oddFactor, uint8_t i2s_div i2s_div; SPI{{ id }}->I2SCFGR |= static_cast(masterSelection) | static_cast(directionSelection) | - static_cast(bitDepth); + static_cast(bitDepth) | + static_cast(i2sStandard) | + static_cast(clockPolarity); } void inline diff --git a/src/modm/platform/i2s/stm32/i2s_master.hpp.in b/src/modm/platform/i2s/stm32/i2s_master.hpp.in index 2ceed0c2df..17a0deb1e4 100644 --- a/src/modm/platform/i2s/stm32/i2s_master.hpp.in +++ b/src/modm/platform/i2s/stm32/i2s_master.hpp.in @@ -27,8 +27,6 @@ namespace modm::platform /** * Inter-IC Sound (I2C{{ id }}). * - * TODO: say something about the nature of implementation - * * @tparam DmaChannelTX DMA channel for sending * * @author Marton Ledneczki @@ -36,7 +34,7 @@ namespace modm::platform * @ingroup modm_platform_i2s modm_platform_i2s_{{id}} */ template -class I2sMaster{{ id }} : public modm::I2sMaster +class I2sMaster{{ id }} : public modm::I2sMaster, public I2sBase { struct Dma { using TxChannel = typename DmaChannelTx::template RequestMapping::Channel; @@ -58,7 +56,6 @@ public: using Sd = typename Connector::template GetSignal; using Ws = typename Connector::template GetSignal; - // Connector::disconnect(); Ck::setOutput(Gpio::OutputType::PushPull); Mck::setOutput(Gpio::OutputType::PushPull); Sd::setOutput(Gpio::OutputType::PushPull); @@ -66,9 +63,13 @@ public: Connector::connect(); } - template< class SystemClock, frequency_t samplerate, percent_t tolerance=pct(0.019) > + template static inline void - initialize(DmaBase::Priority dmaPriority = DmaBase::Priority::High) + initialize(BitDepth bitDepth = BitDepth::SixteenWithChannel16, + MasterClockOutput masterClockOutput = MasterClockOutput::Enabled, + I2sStandard i2sStandard = I2sStandard::Philips, + ClockPolarity clockPolarity = ClockPolarity::Low, + DmaBase::Priority dmaPriority = DmaBase::Priority::High) { constexpr float prescaler = static_cast(SystemClock::I2sPll) / (samplerate*16*2*8); constexpr uint8_t i2s_div = static_cast(prescaler/2); @@ -79,7 +80,13 @@ public: modm::PeripheralDriver::assertBaudrateInTolerance(); Hal::initialize(odd_factor ? Hal::OddFactor::Enabled : Hal::OddFactor::Disabled, - i2s_div); + i2s_div, + Hal::MasterSelection::Master, + Hal::DirectionSelection::Transmitter, + bitDepth, + masterClockOutput, + i2sStandard, + clockPolarity); dmaError = false; Dma::TxChannel::configure( From cb87fe02239ec45a18dc3bacce54d73735efc206 Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Wed, 2 Mar 2022 01:49:13 +0100 Subject: [PATCH 14/28] fixup_rcc --- src/modm/platform/clock/stm32/rcc.hpp.in | 85 ++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/src/modm/platform/clock/stm32/rcc.hpp.in b/src/modm/platform/clock/stm32/rcc.hpp.in index 99787cfd40..3d574b2455 100644 --- a/src/modm/platform/clock/stm32/rcc.hpp.in +++ b/src/modm/platform/clock/stm32/rcc.hpp.in @@ -593,6 +593,91 @@ public: enablePllSai(const PllSaiFactors& pllFactors, uint32_t waitCycles = 2048); %% endif + // DEPRECATE: 2022q1 +%% if target.family in ["f2", "f4", "f7", "l4", "g0", "g4"] + /** + * Enable PLL. + * + * \code + * VCO input frequency = PLL input clock frequency / PLLM [with 2 <= PLLM <= 63] + * VCO output frequency = VCO input frequency × PLLN [with 64 <= PLLN <= 432] + * \endcode + * + * \param source + * Source select for PLL and for plli2s. If you are using + * HSE you must enable it first (see enableHse()). + * + * \param pllM + * Division factor for the main PLL (PLL) and + * audio PLL (PLLI2S) input clock (with 2 <= pllM <= 63). + * The software has to set these bits correctly to ensure + * that frequency of selected source divided by pllM + * is in ranges from 1 to 2 MHz. + * + * \param pllN + * Main PLL (PLL) multiplication factor for VCO (with 64 <= pllN <= 432). + * The software has to set these bits correctly to ensure + * that the VCO output frequency is + * - 336 MHz for ST32F4. Core will run at 168 MHz. + * - 240 MHz for ST32F2. Core will run at 120 MHz. + * + * Example: + * + */ + [[deprecated("Use PllFactors as argument instead")]] static bool + enablePll(PllSource source, uint8_t pllM, uint16_t pllN, + %% if target.family in ["l4", "g0", "g4"] + uint8_t pllR, + %% else + uint8_t pllP, + %% endif + uint32_t waitCycles = 2048) + { + PllFactors pllFactors{ + .pllM = pllM, + .pllN = pllN, + %% if target.family in ["l4", "g0", "g4"] + .pllR = pllR, + %% else + .pllP = pllP, + %% endif + }; + return enablePll(source, pllFactors, waitCycles); + } +%% elif target.family in ["l1"] + [[deprecated("Use PllFactors as argument instead")]] static bool + enablePll(PllSource source, PllMultiplier pllMul, uint8_t pllDiv, + uint32_t waitCycles = 2048) + { + PllFactors pllFactors{ + .pllMul = pllMul, + .pllDiv = pllDiv, + }; + return enablePll(source, pllFactors, waitCycles); + } +%% elif target.family in ["f0", "f1", "f3"] + [[deprecated("Use PllFactors as argument instead")]] static bool + enablePll(PllSource source, + uint8_t pllMul, + %% if pllprediv2 + uint8_t pllPrediv, uint8_t pllPrediv2, + %% elif pllprediv + uint8_t pllPrediv, + %% endif + uint32_t waitCycles = 2048) + { + PllFactors pllFactors{ + .pllMul = pllMul, + %% if pllprediv2 + .pllPrediv = pllPrediv, + .pllPrediv2 = pllPrediv2, + %% elif pllprediv + .pllPrediv = pllPrediv, + %% endif + }; + return enablePll(source, pllFactors, waitCycles); + } +%% endif %% if target.family == "l0" static inline bool isHsiPredivider4Active() From db6e309db5a0a25269c7e2defcb8d1566eaa0a45 Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Mon, 7 Mar 2022 22:49:44 +0100 Subject: [PATCH 15/28] fixup_ma12070p --- README.md | 13 +++++++------ src/modm/driver/dac/ma12070p.hpp | 6 +++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index f37f8bf52b..c129245111 100644 --- a/README.md +++ b/README.md @@ -705,46 +705,47 @@ you specific needs. LSM6DS33 LTC2984 +MA12070P MAX6966 MAX7219 MCP23x17 -MCP2515 +MCP2515 MCP7941x MMC5603 NOKIA5110 NRF24 TFT-DISPLAY -PAT9125EL +PAT9125EL PCA8574 PCA9535 PCA9548A PCA9685 SH1106 -SIEMENS-S65 +SIEMENS-S65 SIEMENS-S75 SK6812 SK9822 SSD1306 ST7586S -STTS22H +STTS22H STUSB4500 SX1276 TCS3414 TCS3472 TLC594x -TMP102 +TMP102 TMP12x TMP175 TOUCH2046 VL53L0 VL6180 -WS2812 +WS2812 diff --git a/src/modm/driver/dac/ma12070p.hpp b/src/modm/driver/dac/ma12070p.hpp index 44ff1914e7..79de1c891b 100644 --- a/src/modm/driver/dac/ma12070p.hpp +++ b/src/modm/driver/dac/ma12070p.hpp @@ -230,7 +230,7 @@ struct ma12070p { return quarter_decibel_t(int16_t(value * 4ul)); } - + static constexpr float quarterDecibelToFloat(quarter_decibel_t value) { @@ -278,8 +278,8 @@ class Ma12070p : public ma12070p, public modm::I2cDevice public: /** * Constructor. - * - * \param i2cAddress The I²C address depends on the AD1 and AD0 pins: + * + * \param i2cAddress The I²C address depends on the AD1 and AD0 pins: * AD1=0, AD0=0 -> 0x20 * AD1=0, AD0=1 -> 0x21 * AD1=1, AD0=0 -> 0x22 From 7b42b834327ef0188570eeffe9fea043064ace19 Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Mon, 7 Mar 2022 22:55:46 +0100 Subject: [PATCH 16/28] fixup_cs43l22 --- README.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index c129245111..8c2e4ec807 100644 --- a/README.md +++ b/README.md @@ -669,82 +669,83 @@ you specific needs. BNO055 CAT24AA +CS43L22 DRV832X DS1302 DS1631 DS18B20 -EA-DOG +EA-DOG Encoder Input Encoder Input BitBang Encoder Output BitBang FT245 FT6x06 -Gpio Sampler +Gpio Sampler HCLAx HD44780 HMC58x HMC6343 HX711 -I2C-EEPROM +I2C-EEPROM ILI9341 IS31FL3733 ITG3200 L3GD20 LAN8720A -LAWICEL +LAWICEL LIS302DL LIS3DSH LIS3MDL LM75 LP503x -LSM303A +LSM303A LSM6DS33 LTC2984 MA12070P MAX6966 MAX7219 -MCP23x17 +MCP23x17 MCP2515 MCP7941x MMC5603 NOKIA5110 NRF24 -TFT-DISPLAY +TFT-DISPLAY PAT9125EL PCA8574 PCA9535 PCA9548A PCA9685 -SH1106 +SH1106 SIEMENS-S65 SIEMENS-S75 SK6812 SK9822 SSD1306 -ST7586S +ST7586S STTS22H STUSB4500 SX1276 TCS3414 TCS3472 -TLC594x +TLC594x TMP102 TMP12x TMP175 TOUCH2046 VL53L0 -VL6180 +VL6180 WS2812 From cc6cb20f12419e845721cc622214af163d8410e1 Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Tue, 8 Mar 2022 00:05:58 +0100 Subject: [PATCH 17/28] fixup_disco_f407vg --- src/modm/board/disco_f407vg/board.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/modm/board/disco_f407vg/board.hpp b/src/modm/board/disco_f407vg/board.hpp index a96d7a4ac3..fe435971cf 100644 --- a/src/modm/board/disco_f407vg/board.hpp +++ b/src/modm/board/disco_f407vg/board.hpp @@ -226,7 +226,10 @@ initializeCs43() { cs43::I2sMaster::connect(); - cs43::I2sMaster::initialize(); + cs43::I2sMaster::initialize( + cs43::I2sMaster::BitDepth::SixteenWithChannel16, + cs43::I2sMaster::MasterClockOutput::Enabled, + cs43::I2sMaster::I2sStandard::Philips); cs43::Reset::setOutput(modm::Gpio::High); cs43::I2cMaster::connect(); From 9d38a46c232a2525a64a435e26eb32cd4838324b Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Tue, 8 Mar 2022 00:34:58 +0100 Subject: [PATCH 18/28] fixup_example_ma12070p --- .../audio_ma12070p/main.cpp | 87 +++++++++++++++++++ .../audio_ma12070p/project.xml | 7 +- 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/examples/stm32f469_discovery/audio_ma12070p/main.cpp b/examples/stm32f469_discovery/audio_ma12070p/main.cpp index 3f8d3c1601..9ee2f0ea9a 100644 --- a/examples/stm32f469_discovery/audio_ma12070p/main.cpp +++ b/examples/stm32f469_discovery/audio_ma12070p/main.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2022, Raphael Lehmann + * Copyright (c) 2021, Christopher Durand * * This file is part of the modm project. * @@ -9,26 +10,98 @@ */ // ---------------------------------------------------------------------------- +#include #include #include #include #include +#include +#include #include using namespace Board; using namespace modm::literals; +// I²C pins shared with touchscreen: +using Scl = GpioB8; +using Sda = GpioB9; using I2cMaster = modm::platform::I2cMaster1; using Ma12070p = modm::Ma12070p; +using I2sWs = GpioOutputB12; +using I2sMck = GpioOutputC6; +using I2sCk = GpioOutputB13; +using I2sData = GpioOutputC1; +using DmaTx = Dma1::Channel4; +using I2sMaster = modm::platform::I2sMaster2; + + +struct I2sSystemClock +{ + static constexpr uint32_t I2sPll = 86_MHz; + + static void + enable() + { + const Rcc::PllI2sFactors pllI2sFactors{ + .pllN = 258, // 1 MHz * N=258 -> 258 MHz + .pllR = 3 // 258 MHz / R=3 -> 86 MHz + }; + Rcc::enablePllI2s(pllI2sFactors); + } +}; + constexpr uint8_t ma12070pAddressI2c = 0x20; Ma12070p ma12070p{ma12070pAddressI2c}; +template +constexpr auto computeSinTable(uint8_t cycles=1) +{ + std::array data{}; + constexpr auto HalfOutput = std::numeric_limits::max() / 2; // 16 bit full scale + for (size_t i = 0; i < data.size(); ++i) { + constexpr auto pi = std::numbers::pi_v; + data[i] = HalfOutput * (1 + arm_sin_f32(i * (2*pi / data.size() * cycles))); + } + return data; +} + +constexpr std::size_t bufferSize = 960; +auto bufferA = computeSinTable(1); +auto bufferB = computeSinTable(2); +volatile bool bufferA_ready{true}; +volatile bool bufferB_ready{true}; + +void +transferCompleteIrqHandler() +{ + LedGreen::reset(); + + if (bufferA_ready) { + I2sMaster::setTxBuffer(uintptr_t(bufferA.data()), bufferSize); + bufferA_ready = false; + } + else if (bufferB_ready) { + I2sMaster::setTxBuffer(uintptr_t(bufferB.data()), bufferSize); + bufferB_ready = false; + } + else { + LedRed::toggle(); + //MODM_LOG_ERROR << "No buffer ready for DMA :(" << modm::endl; + } + I2sMaster::startDma(); + + LedGreen::set(); +} + int main() { Board::initialize(); + I2sSystemClock::enable(); Board::initializeTouchscreen(); // for I2c + Dma1::enable(); + Dma2::enable(); MODM_LOG_INFO << "Audio MA12070P demo on ST Discovery F469NI" << modm::endl; @@ -57,6 +130,16 @@ main() std::optional vlpMonitor; std::optional errorRegister; + I2sMaster::connect(); + I2sMaster::initialize( + I2sMaster::BitDepth::SixteenWithChannel16, + I2sMaster::MasterClockOutput::Enabled, + I2sMaster::I2sStandard::Philips); + + I2sMaster::setTransferCompleteIrqHandler(transferCompleteIrqHandler); + I2sMaster::setTxBuffer(uintptr_t(bufferA.data()), bufferSize); + I2sMaster::start(); + while (true) { vlpMonitor = RF_CALL_BLOCKING(ma12070p.readVlpMonitor()); @@ -73,6 +156,10 @@ main() else { MODM_LOG_ERROR << *errorRegister << modm::endl; } + + if (I2sMaster::hasDmaError()) { + MODM_LOG_ERROR << "I2S DMA Error :(" << modm::endl; + } } return 0; diff --git a/examples/stm32f469_discovery/audio_ma12070p/project.xml b/examples/stm32f469_discovery/audio_ma12070p/project.xml index 4ca5df8023..e321f5fa85 100644 --- a/examples/stm32f469_discovery/audio_ma12070p/project.xml +++ b/examples/stm32f469_discovery/audio_ma12070p/project.xml @@ -4,10 +4,11 @@ - modm:processing:protothread - modm:processing:timer - modm:platform:i2c:1 modm:build:scons + modm:cmsis:dsp:fast_math modm:driver:ma12070p + modm:platform:i2c:1 + modm:platform:i2s:2 + modm:processing:timer From eacad10c1200ea9ae2e0ef3d90f9836afcbe10a6 Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Tue, 8 Mar 2022 01:05:11 +0100 Subject: [PATCH 19/28] fixup platform i2s: disable stm32h7 --- src/modm/platform/i2s/stm32/module.lb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modm/platform/i2s/stm32/module.lb b/src/modm/platform/i2s/stm32/module.lb index 70e7f1cb30..ffac2d9599 100644 --- a/src/modm/platform/i2s/stm32/module.lb +++ b/src/modm/platform/i2s/stm32/module.lb @@ -53,6 +53,9 @@ def init(module): def prepare(module, options): device = options[":target"] + if device.identifier.family in ["h7"]: + # STM32H7 has a completely different SPI IP + return False if not device.has_driver("i2s:stm32*"): return False From 001c09a951613f48ec82f8d73e16b3c0fcaed345 Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Tue, 8 Mar 2022 01:18:37 +0100 Subject: [PATCH 20/28] fixup platform i2s: stm32f410 has no PllI2s --- src/modm/platform/clock/stm32/module.lb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modm/platform/clock/stm32/module.lb b/src/modm/platform/clock/stm32/module.lb index 805764ee70..ac215b6985 100644 --- a/src/modm/platform/clock/stm32/module.lb +++ b/src/modm/platform/clock/stm32/module.lb @@ -77,7 +77,7 @@ def build(env): (target["family"] == "l4" and target["name"][0] in ["p", "q", "r", "s"]) properties["pllsai_p_usb"] = (target["family"] == "f7") or \ ((target["family"] == "f4") and target["name"] in ["46", "69", "79"]) - properties["plli2s"] = (target["family"] == "f4") + properties["plli2s"] = (target["family"] == "f4" and target["name"] not in ["10"]) properties["cfgr1"] = ("CDCFGR1" if target.name in ["a0", "a3", "b0", "b3"] else "D1CFGR") \ if target.family == "h7" else "CFGR" properties["d1"] = ("CD" if target.name in ["a0", "a3", "b0", "b3"] else "D1") \ From 9b7d3e8cc4ad44f53cf5fab924c7cb14a67f3f3c Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Fri, 18 Mar 2022 12:46:29 +0100 Subject: [PATCH 21/28] fixup driver ma12070p --- src/modm/driver/dac/ma12070p.hpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/modm/driver/dac/ma12070p.hpp b/src/modm/driver/dac/ma12070p.hpp index 79de1c891b..4e88c2469d 100644 --- a/src/modm/driver/dac/ma12070p.hpp +++ b/src/modm/driver/dac/ma12070p.hpp @@ -249,6 +249,19 @@ operator-(ma12070p::quarter_decibel_t v) return static_cast(-static_cast(v)); } +constexpr ma12070p::quarter_decibel_t +operator+(ma12070p::quarter_decibel_t v1, ma12070p::quarter_decibel_t v2) +{ + return static_cast(static_cast(v1) + static_cast(v2)); +} + +constexpr ma12070p::quarter_decibel_t +operator+=(ma12070p::quarter_decibel_t& lhs, ma12070p::quarter_decibel_t rhs) +{ + lhs = static_cast(static_cast(lhs) + static_cast(rhs)); + return lhs; +} + namespace literals { constexpr ma12070p::quarter_decibel_t From a4cffe61f30fd6fc617b0ddf43f5f66d80c76644 Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Fri, 18 Mar 2022 12:46:44 +0100 Subject: [PATCH 22/28] fixup example stm32f469_discovery/audio_ma12070p --- .../audio_ma12070p/main.cpp | 170 +++++++++++++----- .../audio_ma12070p/project.xml | 4 +- 2 files changed, 132 insertions(+), 42 deletions(-) diff --git a/examples/stm32f469_discovery/audio_ma12070p/main.cpp b/examples/stm32f469_discovery/audio_ma12070p/main.cpp index 9ee2f0ea9a..164359ad30 100644 --- a/examples/stm32f469_discovery/audio_ma12070p/main.cpp +++ b/examples/stm32f469_discovery/audio_ma12070p/main.cpp @@ -13,8 +13,10 @@ #include #include #include +#include #include -#include +#include +#include #include #include #include @@ -22,10 +24,14 @@ using namespace Board; using namespace modm::literals; -// I²C pins shared with touchscreen: -using Scl = GpioB8; -using Sda = GpioB9; -using I2cMaster = modm::platform::I2cMaster1; +// // I²C pins shared with touchscreen: +// using Scl = GpioB8; +// using Sda = GpioB9; +// using I2cMaster = modm::platform::I2cMaster1; + +using Scl = Board::D3; +using Sda = Board::D4; +using I2cMaster = modm::platform::BitBangI2cMaster; using Ma12070p = modm::Ma12070p; using I2sWs = GpioOutputB12; @@ -35,6 +41,8 @@ using I2sData = GpioOutputC1; using DmaTx = Dma1::Channel4; using I2sMaster = modm::platform::I2sMaster2; +using Enable = GpioInverted; +using Mute = GpioInverted; struct I2sSystemClock { @@ -51,9 +59,6 @@ struct I2sSystemClock } }; -constexpr uint8_t ma12070pAddressI2c = 0x20; -Ma12070p ma12070p{ma12070pAddressI2c}; - template constexpr auto computeSinTable(uint8_t cycles=1) { @@ -94,72 +99,155 @@ transferCompleteIrqHandler() LedGreen::set(); } -int -main() +class Ma12070pThread : public modm::pt::Protothread { - Board::initialize(); - I2sSystemClock::enable(); - Board::initializeTouchscreen(); // for I2c - Dma1::enable(); - Dma2::enable(); +public: + Ma12070pThread() : ma12070p{ma12070pAddressI2c} + {} - MODM_LOG_INFO << "Audio MA12070P demo on ST Discovery F469NI" << modm::endl; + bool + update() + { + PT_BEGIN(); + + Enable::set(); + timeout.restart(); + PT_WAIT_UNTIL(timeout.isExpired()); + + /// initialize() is actually empty + // while (!PT_CALL(ma12070p.initialize())) { + // MODM_LOG_ERROR << "Unable to initialize MA12070P" << modm::endl; + // } + + MODM_LOG_INFO << "Configuring I2S and VLP settings of MA12070P..." << modm::endl; + while (!PT_CALL(ma12070p.configureI2sAndVlp(config))) { + MODM_LOG_ERROR << "Unable to configure I2S and VLP settings of MA12070P" << modm::endl; + timeout.restart(); + PT_WAIT_UNTIL(timeout.isExpired()); + } - while (!RF_CALL_BLOCKING(ma12070p.initialize())) { - MODM_LOG_ERROR << "Unable to initialize MA12070P" << modm::endl; - } + MODM_LOG_INFO.printf("Setting MA12070P volume to %2.1fdb...\n", modm::ma12070p::quarterDecibelToFloat(volume)); + while (!PT_CALL(ma12070p.setMasterVolume(volume))) { + MODM_LOG_ERROR << "Unable to set master volume of MA12070P" << modm::endl; + timeout.restart(); + PT_WAIT_UNTIL(timeout.isExpired()); + } + MODM_LOG_INFO << "Unmute..." << modm::endl; + Mute::reset(); + + while (true) { + if (Board::Button::read()) { + volume += 3_q_db; + if (volume > modm::ma12070p::MaxVolume) { + volume = modm::ma12070p::MinVolume; + } + MODM_LOG_INFO.printf("Volume: %2.1fdb\n", modm::ma12070p::quarterDecibelToFloat(volume)); + if (!PT_CALL(ma12070p.setMasterVolume(volume))) { + MODM_LOG_ERROR << "Unable to set master volume of MA12070P" << modm::endl; + } + timeout.restart(); + PT_WAIT_UNTIL(timeout.isExpired()); + } + + if (timer.execute()) { + vlpMonitor = PT_CALL(ma12070p.readVlpMonitor()); + if(!vlpMonitor) { + MODM_LOG_ERROR << "Unable to read VLP monitor register" << modm::endl; + } + else { + MODM_LOG_ERROR << *vlpMonitor << modm::endl; + } + errorRegister = PT_CALL(ma12070p.readAccumulatedErrors()); + if(!errorRegister) { + MODM_LOG_ERROR << "Unable to read accumulated error register" << modm::endl; + } + else { + MODM_LOG_ERROR << *errorRegister << modm::endl; + } + } + + PT_YIELD(); + } + PT_END(); + } - modm::ma12070p::I2sAndVlpConfig config { +private: + static constexpr uint8_t ma12070pAddressI2c = 0x20; + static constexpr modm::ma12070p::I2sAndVlpConfig config = { .pcmWordFormat = modm::ma12070p::PcmWordFormat::RightJustifed16b, .clockPolarity = modm::ma12070p::ClockPolarity::FallingEdge, .frameSize = modm::ma12070p::FrameSize::Bits32, .wordSelectPolarity = modm::ma12070p::WordSelectPolarity::High, .rightLeftOrder = modm::ma12070p::RightLeftOrder::LeftFirst, - .useVlp = true, - .useLimiter = true, + .useVlp = false, + .useLimiter = false, }; - while (!RF_CALL_BLOCKING(ma12070p.configureI2sAndVlp(config))) { - MODM_LOG_ERROR << "Unable to configure I2S ansd VLP settings of MA12070P" << modm::endl; - } - - while (!RF_CALL_BLOCKING(ma12070p.setMasterVolume(-20_q_db))) { - MODM_LOG_ERROR << "Unable to set master volume of MA12070P" << modm::endl; - } + Ma12070p ma12070p; std::optional vlpMonitor; std::optional errorRegister; + modm::ma12070p::quarter_decibel_t volume = modm::ma12070p::MinVolume; + + modm::ShortTimeout timeout{500ms}; + modm::ShortPeriodicTimer timer{1s}; +}; + +int +main() +{ + Board::initialize(); + I2sSystemClock::enable(); + I2cMaster::connect(I2cMaster::PullUps::Internal); + I2cMaster::initialize(); + Dma1::enable(); + Dma2::enable(); + Enable::setOutput(); + Enable::reset(); + Mute::setOutput(); + Mute::set(); + + + MODM_LOG_INFO << "Audio MA12070P demo on ST Discovery F469NI" << modm::endl; + I2sMaster::connect(); I2sMaster::initialize( I2sMaster::BitDepth::SixteenWithChannel16, I2sMaster::MasterClockOutput::Enabled, I2sMaster::I2sStandard::Philips); - I2sMaster::setTransferCompleteIrqHandler(transferCompleteIrqHandler); I2sMaster::setTxBuffer(uintptr_t(bufferA.data()), bufferSize); I2sMaster::start(); + Ma12070pThread ma12070pThread{}; + + modm::PeriodicTimer tmr{500ms}; + uint8_t counter{3}; + while (true) { - vlpMonitor = RF_CALL_BLOCKING(ma12070p.readVlpMonitor()); - if(!vlpMonitor) { - MODM_LOG_ERROR << "Unable to read VLP monitor register" << modm::endl; + if (!bufferA_ready) { + bufferA = computeSinTable(counter++); + bufferA_ready = true; } - else { - MODM_LOG_ERROR << *vlpMonitor << modm::endl; + if (!bufferB_ready) { + bufferB = computeSinTable(counter++); + bufferB_ready = true; } - errorRegister = RF_CALL_BLOCKING(ma12070p.readAccumulatedErrors()); - if(!errorRegister) { - MODM_LOG_ERROR << "Unable to read accumulated error register" << modm::endl; - } - else { - MODM_LOG_ERROR << *errorRegister << modm::endl; + if (counter > 100) { + counter = 3; } + ma12070pThread.update(); + if (I2sMaster::hasDmaError()) { MODM_LOG_ERROR << "I2S DMA Error :(" << modm::endl; } + + if (tmr.execute()) { + LedBlue::toggle(); + } } return 0; diff --git a/examples/stm32f469_discovery/audio_ma12070p/project.xml b/examples/stm32f469_discovery/audio_ma12070p/project.xml index e321f5fa85..ecbf49bc93 100644 --- a/examples/stm32f469_discovery/audio_ma12070p/project.xml +++ b/examples/stm32f469_discovery/audio_ma12070p/project.xml @@ -7,8 +7,10 @@ modm:build:scons modm:cmsis:dsp:fast_math modm:driver:ma12070p - modm:platform:i2c:1 + + modm:platform:i2c.bitbang modm:platform:i2s:2 modm:processing:timer + modm:processing:protothread From 7406d7decb5bfbd06706568f74e948d94596249b Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Tue, 22 Mar 2022 17:16:16 +0100 Subject: [PATCH 23/28] fixup audio_ma12070p: move to nucleo-f429 dev board --- .../audio_ma12070p/main.cpp | 14 +++++++------- .../audio_ma12070p/project.xml | 7 +++---- 2 files changed, 10 insertions(+), 11 deletions(-) rename examples/{stm32f469_discovery => nucleo_f429zi}/audio_ma12070p/main.cpp (94%) rename examples/{stm32f469_discovery => nucleo_f429zi}/audio_ma12070p/project.xml (58%) diff --git a/examples/stm32f469_discovery/audio_ma12070p/main.cpp b/examples/nucleo_f429zi/audio_ma12070p/main.cpp similarity index 94% rename from examples/stm32f469_discovery/audio_ma12070p/main.cpp rename to examples/nucleo_f429zi/audio_ma12070p/main.cpp index 164359ad30..54da31500e 100644 --- a/examples/stm32f469_discovery/audio_ma12070p/main.cpp +++ b/examples/nucleo_f429zi/audio_ma12070p/main.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include #include #include @@ -29,15 +29,15 @@ using namespace modm::literals; // using Sda = GpioB9; // using I2cMaster = modm::platform::I2cMaster1; -using Scl = Board::D3; -using Sda = Board::D4; -using I2cMaster = modm::platform::BitBangI2cMaster; +using Scl = GpioB8; +using Sda = GpioB9; +using I2cMaster = modm::platform::I2cMaster1; using Ma12070p = modm::Ma12070p; using I2sWs = GpioOutputB12; using I2sMck = GpioOutputC6; using I2sCk = GpioOutputB13; -using I2sData = GpioOutputC1; +using I2sData = GpioOutputB15; using DmaTx = Dma1::Channel4; using I2sMaster = modm::platform::I2sMaster2; @@ -198,7 +198,7 @@ main() { Board::initialize(); I2sSystemClock::enable(); - I2cMaster::connect(I2cMaster::PullUps::Internal); + I2cMaster::connect(I2cMaster::PullUps::Internal); I2cMaster::initialize(); Dma1::enable(); Dma2::enable(); @@ -208,7 +208,7 @@ main() Mute::set(); - MODM_LOG_INFO << "Audio MA12070P demo on ST Discovery F469NI" << modm::endl; + MODM_LOG_INFO << "Audio MA12070P demo on NUcleo-F429ZI" << modm::endl; I2sMaster::connect(); diff --git a/examples/stm32f469_discovery/audio_ma12070p/project.xml b/examples/nucleo_f429zi/audio_ma12070p/project.xml similarity index 58% rename from examples/stm32f469_discovery/audio_ma12070p/project.xml rename to examples/nucleo_f429zi/audio_ma12070p/project.xml index ecbf49bc93..db53bbf53c 100644 --- a/examples/stm32f469_discovery/audio_ma12070p/project.xml +++ b/examples/nucleo_f429zi/audio_ma12070p/project.xml @@ -1,14 +1,13 @@ - modm:disco-f469ni + modm:nucleo-f429zi - + modm:build:scons modm:cmsis:dsp:fast_math modm:driver:ma12070p - - modm:platform:i2c.bitbang + modm:platform:i2c:1 modm:platform:i2s:2 modm:processing:timer modm:processing:protothread From a816af39ae41accd38f80fbb70ae5c1552ca2dae Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Tue, 22 Mar 2022 18:37:32 +0100 Subject: [PATCH 24/28] fixup example ma12070p: adapt changed I2sMaster API --- examples/nucleo_f429zi/audio_ma12070p/main.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/examples/nucleo_f429zi/audio_ma12070p/main.cpp b/examples/nucleo_f429zi/audio_ma12070p/main.cpp index 54da31500e..903ce52f59 100644 --- a/examples/nucleo_f429zi/audio_ma12070p/main.cpp +++ b/examples/nucleo_f429zi/audio_ma12070p/main.cpp @@ -212,10 +212,14 @@ main() I2sMaster::connect(); - I2sMaster::initialize( - I2sMaster::BitDepth::SixteenWithChannel16, - I2sMaster::MasterClockOutput::Enabled, - I2sMaster::I2sStandard::Philips); + constexpr I2sMaster::I2sConfig config{ + .samplerate = 48_kHz, + .tolerance = 1_pct, + .bitDepth = I2sMaster::BitDepth::SixteenWithChannel16, + .masterClockOutput = I2sMaster::MasterClockOutput::Enabled, + .i2sStandard = I2sMaster::I2sStandard::Philips, + }; + I2sMaster::initialize(); I2sMaster::setTransferCompleteIrqHandler(transferCompleteIrqHandler); I2sMaster::setTxBuffer(uintptr_t(bufferA.data()), bufferSize); I2sMaster::start(); From 3ead27ed8890935de0a9d7edef9ecdfe01491dd8 Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Tue, 22 Mar 2022 18:39:31 +0100 Subject: [PATCH 25/28] fixup platform/i2s: fix prescaler calculation, i2s config is now template argument (struct) --- src/modm/platform/i2s/stm32/i2s_base.hpp.in | 2 +- src/modm/platform/i2s/stm32/i2s_master.hpp.in | 52 +++++++++++++------ 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/modm/platform/i2s/stm32/i2s_base.hpp.in b/src/modm/platform/i2s/stm32/i2s_base.hpp.in index d0a1717406..fbf8d4137d 100644 --- a/src/modm/platform/i2s/stm32/i2s_base.hpp.in +++ b/src/modm/platform/i2s/stm32/i2s_base.hpp.in @@ -13,7 +13,7 @@ #ifndef MODM_STM32_I2S_BASE_HPP #define MODM_STM32_I2S_BASE_HPP -#include +#include #include "../device.hpp" #include diff --git a/src/modm/platform/i2s/stm32/i2s_master.hpp.in b/src/modm/platform/i2s/stm32/i2s_master.hpp.in index 17a0deb1e4..d4b58c5af2 100644 --- a/src/modm/platform/i2s/stm32/i2s_master.hpp.in +++ b/src/modm/platform/i2s/stm32/i2s_master.hpp.in @@ -25,7 +25,9 @@ namespace modm::platform { /** - * Inter-IC Sound (I2C{{ id }}). + * Inter-IC Sound (I2S) master driver + * + * @warning Only data output (e.g. to headphone/speaker DAC) functionality is currently implemented * * @tparam DmaChannelTX DMA channel for sending * @@ -63,30 +65,48 @@ public: Connector::connect(); } - template + struct I2sConfig + { + frequency_t samplerate = 48_kHz; + percent_t tolerance = pct(0.019); + BitDepth bitDepth = BitDepth::SixteenWithChannel16; + MasterClockOutput masterClockOutput = MasterClockOutput::Enabled; + I2sStandard i2sStandard = I2sStandard::Philips; + ClockPolarity clockPolarity = ClockPolarity::Low; + DmaBase::Priority dmaPriority = DmaBase::Priority::High; + }; + static constexpr I2sConfig DefaultI2sConfig{}; + + template static inline void - initialize(BitDepth bitDepth = BitDepth::SixteenWithChannel16, - MasterClockOutput masterClockOutput = MasterClockOutput::Enabled, - I2sStandard i2sStandard = I2sStandard::Philips, - ClockPolarity clockPolarity = ClockPolarity::Low, - DmaBase::Priority dmaPriority = DmaBase::Priority::High) + initialize() { - constexpr float prescaler = static_cast(SystemClock::I2sPll) / (samplerate*16*2*8); + // RM0090, p.908: + // F_S = I2SxCLK / [(16*2)*((2*I2SDIV)+ODD)*8)] when the channel frame is 16-bit wide and MCKOE bit set + // F_S = I2SxCLK / [(32*2)*((2*I2SDIV)+ODD)*4)] when the channel frame is 32-bit wide and MCKOE bit set + // F_S = I2SxCLK / [(16*2)*((2*I2SDIV)+ODD))] when the channel frame is 16-bit wide and MCKOE bit cleared + // F_S = I2SxCLK / [(32*2)*((2*I2SDIV)+ODD))] when the channel frame is 32-bit wide and MCKOE bit cleared + + constexpr uint8_t bits_per_channel = (c.bitDepth == BitDepth::SixteenWithChannel16) ? 16 : 32; + constexpr uint8_t mckoe_factor = (c.masterClockOutput == MasterClockOutput::Disabled) ? 1 + : ((c.bitDepth == BitDepth::SixteenWithChannel16) ? 8 : 4); + constexpr uint16_t bits_per_cycle = bits_per_channel * 2/*channels*/ * mckoe_factor; + constexpr float prescaler = static_cast(SystemClock::I2sPll) / (c.samplerate * bits_per_cycle); constexpr uint8_t i2s_div = static_cast(prescaler/2); constexpr uint8_t odd_factor = ((prescaler - static_cast(i2s_div*2)) >= 0.5) ? 1 : 0; constexpr float real_samplerate = static_cast(SystemClock::I2sPll) / - (16*2*8*(2*i2s_div+odd_factor)); + (bits_per_cycle * ((2 * i2s_div) + odd_factor)); static_assert(i2s_div > 1, "I2S{{ id }}: i2s_div can not be 1 or 0!"); - modm::PeripheralDriver::assertBaudrateInTolerance(); + modm::PeripheralDriver::assertBaudrateInTolerance(); Hal::initialize(odd_factor ? Hal::OddFactor::Enabled : Hal::OddFactor::Disabled, i2s_div, Hal::MasterSelection::Master, Hal::DirectionSelection::Transmitter, - bitDepth, - masterClockOutput, - i2sStandard, - clockPolarity); + c.bitDepth, + c.masterClockOutput, + c.i2sStandard, + c.clockPolarity); dmaError = false; Dma::TxChannel::configure( @@ -95,9 +115,9 @@ public: DmaBase::PeripheralDataSize::Bit16, DmaBase::MemoryIncrementMode::Increment, DmaBase::PeripheralIncrementMode::Fixed, - dmaPriority, + c.dmaPriority, DmaBase::CircularMode::Disabled); - Dma::TxChannel::setPeripheralAddress(SPI{{ id }}_BASE + 0x0c); + Dma::TxChannel::setPeripheralAddress(uintptr_t(&SPI{{ id }}->DR)); Dma::TxChannel::setTransferErrorIrqHandler(handleDmaTransferError); Dma::TxChannel::enableInterruptVector(); Dma::TxChannel::enableInterrupt(DmaBase::InterruptEnable::TransferError | From 2c85e4fa251dab2ed519783593af467c5b8bf593 Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Tue, 22 Mar 2022 18:40:26 +0100 Subject: [PATCH 26/28] fixup dma: enable double buffer also for 'stm32-mux-stream' IP --- src/modm/platform/dma/stm32/dma_hal.hpp.in | 6 ++++-- src/modm/platform/dma/stm32/module.lb | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/modm/platform/dma/stm32/dma_hal.hpp.in b/src/modm/platform/dma/stm32/dma_hal.hpp.in index dfb76de319..ad869cbcbb 100644 --- a/src/modm/platform/dma/stm32/dma_hal.hpp.in +++ b/src/modm/platform/dma/stm32/dma_hal.hpp.in @@ -257,7 +257,7 @@ public: setMemoryAddress2(uintptr_t address) { DMA_Channel_TypeDef *Base = (DMA_Channel_TypeDef *) CHANNEL_BASE; -%% if dmaType in ["stm32-stream-channel"] +%% if dmaType in ["stm32-stream-channel", "stm32-mux-stream"] Base->M1AR = address; %% endif } @@ -271,8 +271,10 @@ public: isPrimaryBufferActive() { DMA_Channel_TypeDef *Base = (DMA_Channel_TypeDef *) CHANNEL_BASE; -%% if dmaType in ["stm32-stream-channel"] +%% if dmaType in ["stm32-stream-channel", "stm32-mux-stream"] return (Base->CR & DMA_SxCR_CT); +%% else + return true; %% endif } %% endif diff --git a/src/modm/platform/dma/stm32/module.lb b/src/modm/platform/dma/stm32/module.lb index 974df2f25c..3e457cdfe1 100644 --- a/src/modm/platform/dma/stm32/module.lb +++ b/src/modm/platform/dma/stm32/module.lb @@ -186,7 +186,7 @@ def build(env): properties["dmaType"] = dma["type"] properties["dmaSignals"] = signal_names properties["dmaController"] = controller - properties["doubleBuffer"] = (dma["type"] in ["stm32-stream-channel"]) ## TODO: which other types support double buffer mode? + properties["doubleBuffer"] = (dma["type"] in ["stm32-stream-channel", "stm32-mux-stream"]) properties['channel_count'] = { "min" : min(controller, key=lambda c: c["min_channel"])["min_channel"], From 4859b64f4a881b739f6ee18b4a5f67d5b0860638 Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Tue, 22 Mar 2022 18:41:17 +0100 Subject: [PATCH 27/28] fixup driver ma12070p --- src/modm/driver/dac/ma12070p.cpp | 61 ++++++++++++++++++++ src/modm/driver/dac/ma12070p.hpp | 79 +++++--------------------- src/modm/driver/dac/ma12070p.lb | 6 ++ src/modm/driver/dac/ma12070p_impl.hpp | 2 +- src/modm/driver/dac/ma12070p_io.hpp.in | 36 ++++++++++++ 5 files changed, 117 insertions(+), 67 deletions(-) create mode 100644 src/modm/driver/dac/ma12070p.cpp create mode 100644 src/modm/driver/dac/ma12070p_io.hpp.in diff --git a/src/modm/driver/dac/ma12070p.cpp b/src/modm/driver/dac/ma12070p.cpp new file mode 100644 index 0000000000..130413015d --- /dev/null +++ b/src/modm/driver/dac/ma12070p.cpp @@ -0,0 +1,61 @@ +// coding: utf-8 +/* + * Copyright (c) 2022, Raphael Lehmann + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include "ma12070p.hpp" + +modm::IOStream& +modm::operator << (modm::IOStream& os, const ma12070p::VlpMonitor_t& c) +{ + os << "VlpMonitor("; + if(c & ma12070p::VlpMonitor::LimiterCh0L) + os << "LimiterCh0L "; + if(c & ma12070p::VlpMonitor::LimiterCh0R) + os << "LimiterCh0R "; + if(c & ma12070p::VlpMonitor::LimiterCh1L) + os << "LimiterCh1L "; + if(c & ma12070p::VlpMonitor::LimiterCh1R) + os << "LimiterCh1R "; + if(c & ma12070p::VlpMonitor::ClippingCh0L) + os << "ClippingCh0L "; + if(c & ma12070p::VlpMonitor::ClippingCh0R) + os << "ClippingCh0R "; + if(c & ma12070p::VlpMonitor::ClippingCh1L) + os << "ClippingCh1L "; + if(c & ma12070p::VlpMonitor::ClippingCh1R) + os << "ClippingCh1R "; + os << ")"; + return os; +} + +modm::IOStream& +modm::operator << (modm::IOStream& os, const ma12070p::ErrorRegister_t& c) +{ + os << "ErrorRegister("; + if(c & ma12070p::ErrorRegister::FlyingCapOverVolt) + os << "FlyingCapOverVolt "; + if(c & ma12070p::ErrorRegister::OverCurrent) + os << "OverCurrent "; + if(c & ma12070p::ErrorRegister::Pll) + os << "Pll "; + if(c & ma12070p::ErrorRegister::PvddUnderVolt) + os << "PvddUnderVolt "; + if(c & ma12070p::ErrorRegister::OverTempWarning) + os << "OverTempWarning "; + if(c & ma12070p::ErrorRegister::OverTempError) + os << "OverTempError "; + if(c & ma12070p::ErrorRegister::PinToPinLowImpedance) + os << "PinToPinLowImpedance "; + if(c & ma12070p::ErrorRegister::DcProtection) + os << "DcProtection "; + os << ")"; + return os; +} diff --git a/src/modm/driver/dac/ma12070p.hpp b/src/modm/driver/dac/ma12070p.hpp index 4e88c2469d..fbd163c35c 100644 --- a/src/modm/driver/dac/ma12070p.hpp +++ b/src/modm/driver/dac/ma12070p.hpp @@ -126,16 +126,16 @@ struct ma12070p struct I2sAndVlpConfig { - const PcmWordFormat pcmWordFormat = PcmWordFormat::LeftJustifed; - const ClockPolarity clockPolarity = ClockPolarity::FallingEdge; - const FrameSize frameSize = FrameSize::Bits64; - const WordSelectPolarity wordSelectPolarity = WordSelectPolarity::Low; - const DataOrder dataOrder = DataOrder::MsbFirst; - const RightLeftOrder rightLeftOrder = RightLeftOrder::LeftFirst; - const bool useVlp = true; - const bool useLimiter = true; - const LimiterTiming limiterReleaseTime = LimiterTiming::Normal; - const LimiterTiming limiterAttackTime = LimiterTiming::Normal; + PcmWordFormat pcmWordFormat = PcmWordFormat::LeftJustifed; + ClockPolarity clockPolarity = ClockPolarity::FallingEdge; + FrameSize frameSize = FrameSize::Bits64; + WordSelectPolarity wordSelectPolarity = WordSelectPolarity::Low; + DataOrder dataOrder = DataOrder::MsbFirst; + RightLeftOrder rightLeftOrder = RightLeftOrder::LeftFirst; + bool useVlp = true; + bool useLimiter = true; + LimiterTiming limiterReleaseTime = LimiterTiming::Normal; + LimiterTiming limiterAttackTime = LimiterTiming::Normal; }; enum class @@ -152,32 +152,6 @@ struct ma12070p }; MODM_FLAGS8(VlpMonitor); -#if MODM_HAS_IOSTREAM - friend IOStream& - operator << (IOStream& os, const VlpMonitor_t& c) - { - os << "VlpMonitor("; - if(c & VlpMonitor::LimiterCh0L) - os << "LimiterCh0L "; - if(c & VlpMonitor::LimiterCh0R) - os << "LimiterCh0R "; - if(c & VlpMonitor::LimiterCh1L) - os << "LimiterCh1L "; - if(c & VlpMonitor::LimiterCh1R) - os << "LimiterCh1R "; - if(c & VlpMonitor::ClippingCh0L) - os << "ClippingCh0L "; - if(c & VlpMonitor::ClippingCh0R) - os << "ClippingCh0R "; - if(c & VlpMonitor::ClippingCh1L) - os << "ClippingCh1L "; - if(c & VlpMonitor::ClippingCh1R) - os << "ClippingCh1R "; - os << ")"; - return os; - } -#endif - enum class ErrorRegister : uint8_t { @@ -192,33 +166,6 @@ struct ma12070p }; MODM_FLAGS8(ErrorRegister); -#if MODM_HAS_IOSTREAM - friend IOStream& - operator << (IOStream& os, const ErrorRegister_t& c) - { - os << "ErrorRegister("; - if(c & ErrorRegister::FlyingCapOverVolt) - os << "FlyingCapOverVolt "; - if(c & ErrorRegister::OverCurrent) - os << "OverCurrent "; - if(c & ErrorRegister::Pll) - os << "Pll "; - if(c & ErrorRegister::PvddUnderVolt) - os << "PvddUnderVolt "; - if(c & ErrorRegister::OverTempWarning) - os << "OverTempWarning "; - if(c & ErrorRegister::OverTempError) - os << "OverTempError "; - if(c & ErrorRegister::PinToPinLowImpedance) - os << "PinToPinLowImpedance "; - if(c & ErrorRegister::DcProtection) - os << "DcProtection "; - os << ")"; - return os; - } -#endif - - // quarter_decibel type enum class quarter_decibel_t : int16_t @@ -308,9 +255,9 @@ class Ma12070p : public ma12070p, public modm::I2cDevice ResumableResult configureI2sAndVlp(I2sAndVlpConfig config); - /// Set the limiter treshold for individual channels (-144db to +24db) + /// Set the limiter threshold for individual channels (-144db to +24db) ResumableResult - setLimiterTreshold(quarter_decibel_t ch0l, quarter_decibel_t ch0r, quarter_decibel_t ch1l = -144, quarter_decibel_t ch1r = -144); + setLimiterThreshold(quarter_decibel_t ch0l, quarter_decibel_t ch0r, quarter_decibel_t ch1l = -144, quarter_decibel_t ch1r = -144); /// Set the master volume (-144db to +24db) ResumableResult @@ -361,7 +308,7 @@ class Ma12070p : public ma12070p, public modm::I2cDevice } // namespace modm +#include "ma12070p_io.hpp" #include "ma12070p_impl.hpp" #endif // MODM_MA12070P_HPP - diff --git a/src/modm/driver/dac/ma12070p.lb b/src/modm/driver/dac/ma12070p.lb index 3455489440..5c6f647594 100644 --- a/src/modm/driver/dac/ma12070p.lb +++ b/src/modm/driver/dac/ma12070p.lb @@ -32,6 +32,12 @@ def prepare(module, options): return True def build(env): + env.substitutions = { + "has_io": env.has_module("modm:io"), + } env.outbasepath = "modm/src/modm/driver/dac" env.copy("ma12070p.hpp") env.copy("ma12070p_impl.hpp") + env.template("ma12070p_io.hpp.in") + if env.has_module("modm:io"): + env.copy("ma12070p.cpp") diff --git a/src/modm/driver/dac/ma12070p_impl.hpp b/src/modm/driver/dac/ma12070p_impl.hpp index 058bf22a6f..2f9823f532 100644 --- a/src/modm/driver/dac/ma12070p_impl.hpp +++ b/src/modm/driver/dac/ma12070p_impl.hpp @@ -93,7 +93,7 @@ Ma12070p::configureI2sAndVlp(I2sAndVlpConfig c) template ResumableResult -Ma12070p::setLimiterTreshold(quarter_decibel_t ch0l, quarter_decibel_t ch0r, quarter_decibel_t ch1l, quarter_decibel_t ch1r) +Ma12070p::setLimiterThreshold(quarter_decibel_t ch0l, quarter_decibel_t ch0r, quarter_decibel_t ch1l, quarter_decibel_t ch1r) { RF_BEGIN(); tx_buffer[0] = uint8_t(Register::Ch0LVolumeDbInteger); diff --git a/src/modm/driver/dac/ma12070p_io.hpp.in b/src/modm/driver/dac/ma12070p_io.hpp.in new file mode 100644 index 0000000000..506e647b75 --- /dev/null +++ b/src/modm/driver/dac/ma12070p_io.hpp.in @@ -0,0 +1,36 @@ +// coding: utf-8 +/* + * Copyright (c) 2022, Raphael Lehmann + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_MA12070P_IO_HPP +#define MODM_MA12070P_IO_HPP + +#include "ma12070p.hpp" + +%% if has_io + +#include + +namespace modm +{ + +// Output operators +IOStream& +operator << (IOStream& os, const ma12070p::VlpMonitor_t& c); + +IOStream& +operator << (IOStream& os, const ma12070p::ErrorRegister_t& c); + +} + +%% endif + +#endif // MODM_MA12070P_IO_HPP From 812d3542aba1c22ba9b3ae7b747f6560c6c52ef8 Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Tue, 22 Mar 2022 18:41:32 +0100 Subject: [PATCH 28/28] fixup driver cs43l22 --- src/modm/driver/dac/cs43l22.hpp | 41 ++++++++++++++++----------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/modm/driver/dac/cs43l22.hpp b/src/modm/driver/dac/cs43l22.hpp index c12b416c78..ab4468b89d 100644 --- a/src/modm/driver/dac/cs43l22.hpp +++ b/src/modm/driver/dac/cs43l22.hpp @@ -80,7 +80,7 @@ struct cs43l22 { CS43L22 = int(ChipIdRevision::CHIPID4) | int(ChipIdRevision::CHIPID3) | int(ChipIdRevision::CHIPID2) }; - typedef Configuration< ChipIdRevision_t, ChipId, (Bit7 | Bit6 | Bit5 | Bit4 | Bit3) > ChipId_t; + using ChipId_t = Configuration< ChipIdRevision_t, ChipId, (Bit7 | Bit6 | Bit5 | Bit4 | Bit3) >; enum class RevisionId : uint8_t @@ -90,7 +90,7 @@ struct cs43l22 B0 = int(ChipIdRevision::REVID1), B1 = int(ChipIdRevision::REVID1) | int(ChipIdRevision::REVID0) }; - typedef Configuration< ChipIdRevision_t, RevisionId, (Bit2 | Bit1 | Bit0) > RevisionId_t; + using RevisionId_t = Configuration< ChipIdRevision_t, RevisionId, (Bit2 | Bit1 | Bit0) >; enum class PowerControl1 : uint8_t @@ -116,7 +116,7 @@ struct cs43l22 int(PowerControl1::PDN2) | int(PowerControl1::PDN1) }; - typedef Configuration< PowerControl1_t, Power, 0xFF > Power_t; + using Power_t = Configuration< PowerControl1_t, Power, 0xFF >; enum class PowerControl2 : uint8_t @@ -143,10 +143,10 @@ struct cs43l22 OnAlways = 2, OffAlways = 3 }; - typedef Configuration< PowerControl2_t, ChannelPower, 0b11, 0> ChannelPowerSpeakerA_t; - typedef Configuration< PowerControl2_t, ChannelPower, 0b11, 2> ChannelPowerSpeakerB_t; - typedef Configuration< PowerControl2_t, ChannelPower, 0b11, 4> ChannelPowerHeadphoneA_t; - typedef Configuration< PowerControl2_t, ChannelPower, 0b11, 6> ChannelPowerHeadphoneB_t; + using ChannelPowerSpeakerA_t = Configuration< PowerControl2_t, ChannelPower, 0b11, 0>; + using ChannelPowerSpeakerB_t = Configuration< PowerControl2_t, ChannelPower, 0b11, 2>; + using ChannelPowerHeadphoneA_t = Configuration< PowerControl2_t, ChannelPower, 0b11, 4>; + using ChannelPowerHeadphoneB_t = Configuration< PowerControl2_t, ChannelPower, 0b11, 6>; enum class ClockingControl : uint8_t @@ -187,7 +187,7 @@ struct cs43l22 Slave = 0, Master = int(InterfaceControl1::MASTER) }; - typedef Configuration< InterfaceControl1_t, Role, Bit7 > Role_t; + using Role_t = Configuration< InterfaceControl1_t, Role, Bit7 >; enum class DacInterfaceFormat : uint8_t @@ -196,7 +196,7 @@ struct cs43l22 I2sPhillipsStandard = int(InterfaceControl1::DACDIF0), RightJustified = int(InterfaceControl1::DACDIF1) }; - typedef Configuration< InterfaceControl1_t, DacInterfaceFormat, (Bit2 | Bit3) > DacInterfaceFormat_t; + using DacInterfaceFormat_t = Configuration< InterfaceControl1_t, DacInterfaceFormat, (Bit2 | Bit3) >; enum class MasterVolumeControl : uint8_t @@ -211,7 +211,7 @@ struct cs43l22 MSTVOL7 = Bit7, }; MODM_FLAGS8(MasterVolumeControl); - typedef Value< MasterVolumeControl_t, 8 > MasterVol_t; + using MasterVol_t = Value< MasterVolumeControl_t, 8 >; enum class AnalogZcAndSrSettings : uint8_t @@ -229,8 +229,8 @@ struct cs43l22 Disabled = 0, Enabled = 1 }; - typedef Configuration< AnalogZcAndSrSettings_t, SoftRamp, 0b1, 1> AnalogSoftRampA_t; - typedef Configuration< AnalogZcAndSrSettings_t, SoftRamp, 0b1, 3> AnalogSoftRampB_t; + using AnalogSoftRampA_t = Configuration< AnalogZcAndSrSettings_t, SoftRamp, 0b1, 1>; + using AnalogSoftRampB_t = Configuration< AnalogZcAndSrSettings_t, SoftRamp, 0b1, 3>; enum class ZeroCrossing : uint8_t @@ -238,8 +238,8 @@ struct cs43l22 Disabled = 0, Enabled = 1 }; - typedef Configuration< AnalogZcAndSrSettings_t, ZeroCrossing, 0b1, 0> AnalogZeroCrossingA_t; - typedef Configuration< AnalogZcAndSrSettings_t, ZeroCrossing, 0b1, 2> AnalogZeroCrossingB_t; + using AnalogZeroCrossingA_t = Configuration< AnalogZcAndSrSettings_t, ZeroCrossing, 0b1, 0>; + using AnalogZeroCrossingB_t = Configuration< AnalogZcAndSrSettings_t, ZeroCrossing, 0b1, 2>; enum class MiscellaneousControls @@ -276,7 +276,7 @@ struct cs43l22 MasterVolumeControl_t, AnalogZcAndSrSettings_t, MiscellaneousControls_t, LimiterControl1MinMaxThresholds_t >; - typedef int16_t centiBel_t; + using centiBel_t = int16_t; static constexpr centiBel_t MaxVolume = 120; static constexpr centiBel_t MinVolume = -1020; }; // struct cs43l22 @@ -300,10 +300,10 @@ class Cs43l22 : public cs43l22, public modm::I2cDevice ResumableResult initialize(); - /** - * @note max volume is specified by cs43l22::MaxVolume - * and min volume by cs43l22::MinVolume - */ + /** + * @note max volume is specified by cs43l22::MaxVolume + * and min volume by cs43l22::MinVolume + */ ResumableResult setMasterVolume(centiBel_t vol); @@ -330,9 +330,8 @@ class Cs43l22 : public cs43l22, public modm::I2cDevice }; -} // namespace modm +} // namespace modm #include "cs43l22_impl.hpp" #endif // MODM_CS43L22_HPP -