From 4826ca82a21cebb9f3f67976fae221e94253b3fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Ledneczki?= Date: Sun, 18 Apr 2021 14:59:28 +0200 Subject: [PATCH 1/6] [I2S] Setup PLL for the I2S peripheral Functions are added to be able to set up the I2S PLL at startup. PLL multipliers are taken over from ST's example: STM32F4-Discovery_FW_V1.1.0/Project/Audio_playback_and_record/src/system_stm32f4xx.c --- src/modm/board/disco_f407vg/board.hpp | 9 ++++++-- src/modm/platform/clock/stm32/rcc.cpp.in | 23 +++++++++++++++++++++ src/modm/platform/clock/stm32/rcc.hpp.in | 26 ++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/modm/board/disco_f407vg/board.hpp b/src/modm/board/disco_f407vg/board.hpp index e99e4af4ec..adcd859190 100644 --- a/src/modm/board/disco_f407vg/board.hpp +++ b/src/modm/board/disco_f407vg/board.hpp @@ -82,12 +82,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(Rcc::PllSource::ExternalCrystal, pllI2sFactors, 2048); // set flash latency for 168MHz Rcc::setFlashLatency(); // switch system clock to PLL output diff --git a/src/modm/platform/clock/stm32/rcc.cpp.in b/src/modm/platform/clock/stm32/rcc.cpp.in index 137c013398..b77c27f07a 100644 --- a/src/modm/platform/clock/stm32/rcc.cpp.in +++ b/src/modm/platform/clock/stm32/rcc.cpp.in @@ -290,6 +290,29 @@ modm::platform::Rcc::enablePll(PllSource source, const PllFactors& pllFactors, u %%endif } +%% if target["family"] == "f4" +bool +modm::platform::Rcc::enablePllI2s(PllSource, 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 modm::platform::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 6783758208..54c1ce7053 100644 --- a/src/modm/platform/clock/stm32/rcc.hpp.in +++ b/src/modm/platform/clock/stm32/rcc.hpp.in @@ -559,6 +559,32 @@ public: setHsiPredivider4Enabled(bool divideBy4, uint32_t waitCycles = 2048); %% endif +%% if target["family"] == "f4" + /** TODO: comments + * input clock 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 + * + * \param pllN + * PLLI2S multiplication factor for VCO + * 50 <= PLLI2SN <= 432 + * + * \param pllR + * PLLI2S division factor for I2S clocks + * Caution: The I2Ss requires a frequency lower than or equal to 192 MHz to work correctly. + * 2 <= PLLR <= 7 + */ + struct PllI2sFactors + { + const uint16_t pllN; + const uint16_t pllR; + }; + + /* TODO: comments */ + static bool + enablePllI2s(PllSource, const PllI2sFactors& pllFactors, uint32_t waitCycles); +%% endif + // sinks static bool enableSystemClock(SystemClockSource src, uint32_t waitCycles = 2048); From 6f8fc647c35410add7ff4cc0eef440ddd66c8ac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Ledneczki?= Date: Wed, 21 Apr 2021 20:03:08 +0200 Subject: [PATCH 2/6] [I2S] Setup basic I2S driver template package --- src/modm/architecture/interface/i2s.hpp | 33 ++++ .../architecture/interface/i2s_master.hpp | 164 ++++++++++++++++++ src/modm/architecture/module.lb | 16 ++ src/modm/board/disco_f407vg/module.lb | 1 + src/modm/platform/i2s/stm32/i2s_master.hpp.in | 74 ++++++++ src/modm/platform/i2s/stm32/module.lb | 80 +++++++++ 6 files changed, 368 insertions(+) create mode 100644 src/modm/architecture/interface/i2s.hpp create mode 100644 src/modm/architecture/interface/i2s_master.hpp 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/src/modm/architecture/interface/i2s.hpp b/src/modm/architecture/interface/i2s.hpp new file mode 100644 index 0000000000..f986ab53af --- /dev/null +++ b/src/modm/architecture/interface/i2s.hpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2009-2012, Fabian Greif + * Copyright (c) 2010, Martin Rosekeit + * Copyright (c) 2012-2015, Niklas Hauser + * Copyright (c) 2013, Sascha Schade + * 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_INTERFACE_I2S_HPP +#define MODM_INTERFACE_I2S_HPP + +#include + +namespace modm +{ + +/// @ingroup modm_architecture_i2s +struct I2s +{ + /// The signature of the configuration function. + using ConfigurationHandler = void(*)(); +}; + +} // namespace modm + +#endif // MODM_INTERFACE_I2S_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..c0d9613f34 --- /dev/null +++ b/src/modm/architecture/interface/i2s_master.hpp @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2009-2012, Fabian Greif + * Copyright (c) 2010, Martin Rosekeit + * Copyright (c) 2012-2017, Niklas Hauser + * Copyright (c) 2013, Sascha Schade + * Copyright (c) 2021, Marton Lednczki + * + * 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, public I2s +{ +#ifdef __DOXYGEN__ +public: + /** + * 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 and WS signal are required + * and can be passed out-of-order. + */ + template< class... Signals > + static void + connect(); + + /** + * Initializes the hardware and sets the baudrate. + * + * @tparam SystemClock + * the currently active system clock + * @tparam baudrate + * the desired baudrate in Hz + * @tparam tolerance + * the allowed relative tolerance for the resulting baudrate + */ + template< class SystemClock, baudrate_t baudrate, percent_t tolerance=5_pct > + static void + initialize(); + + /// Sets a new data mode. + static void + setDataMode(DataMode mode); + + /// Sets a new data order. + static void + setDataOrder(DataOrder order); + + /** + * Request access to the spi master within a context. + * You may acquire the spi master multiple times within the same context. + * + * The configuration handler will only be called when aquiring the spi + * master for the first time (if it is not a `nullptr`). + * + * @warning Aquires must be balanced with releases of the **same** context! + * @warning Aquires are persistent even after calling `initialize()`! + * + * @return `0` if another context is using the spi master, otherwise + * `>0` as the number of times this context acquired the master. + */ + static uint8_t + acquire(void *ctx, ConfigurationHandler handler = nullptr); + + /** + * Release access to the spi master within a context. + * + * @warning Releases must be balanced with acquires of the **same** context! + * @warning Releases are persistent even after calling `initialize()`! + * + * @return `0` if nothing can be released anymore (for any context) + * `>0` as the number of times this context can still release the master. + */ + static uint8_t + release(void *ctx); + + /** + * Swap a single byte and wait for completion. + * + * @param data + * data to be sent + * @return received data + */ + static uint8_t + transferBlocking(uint8_t data); + + /** + * Set the data buffers and length with options and starts a transfer. + * This may be hardware accelerated (DMA or Interrupt), but not guaranteed. + * + * @param[in] tx + * pointer to transmit buffer, set to `nullptr` to send dummy bytes + * @param[out] rx + * pointer to receive buffer, set to `nullptr` to discard received bytes + * @param length + * number of bytes to be shifted out + */ + static void + transferBlocking(const uint8_t *tx, uint8_t *rx, std::size_t length); + + /** + * Swap a single byte and wait for completion non-blocking!. + * + * You must call this inside a Protothread or Resumable + * using `PT_CALL` or `RF_CALL` respectively. + * @warning These methods differ from Resumables by lacking context protection! + * You must ensure that only one driver is accessing this resumable function + * by using `acquire(ctx)` and `release(ctx)`. + * + * @param data + * data to be sent + * @return received data + */ + static modm::ResumableResult + transfer(uint8_t data); + + /** + * Set the data buffers and length with options and + * starts a non-blocking transfer. + * This may be hardware accelerated (DMA or Interrupt), but not guaranteed. + * + * You must call this inside a Protothread or Resumable + * using `PT_CALL` or `RF_CALL` respectively. + * @warning These methods differ from Resumables by lacking context protection! + * You must ensure that only one driver is accessing this resumable function + * by using `acquire(ctx)` and `release(ctx)`. + * + * @param[in] tx + * pointer to transmit buffer, set to `nullptr` to send dummy bytes + * @param[out] rx + * pointer to receive buffer, set to `nullptr` to discard received bytes + * @param length + * number of bytes to be shifted out + */ + static modm::ResumableResult + transfer(const uint8_t *tx, uint8_t *rx, std::size_t length); +#endif +}; + +} // namespace modm + +#endif // MODM_INTERFACE_SPI_MASTER_HPP diff --git a/src/modm/architecture/module.lb b/src/modm/architecture/module.lb index dbadd79fe0..52e6d9eeb9 100644 --- a/src/modm/architecture/module.lb +++ b/src/modm/architecture/module.lb @@ -230,6 +230,21 @@ 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.hpp") + env.copy("interface/i2s_master.hpp") +# ----------------------------------------------------------------------------- + class Interrupt(Module): def init(self, module): module.name = "interrupt" @@ -381,6 +396,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()) diff --git a/src/modm/board/disco_f407vg/module.lb b/src/modm/board/disco_f407vg/module.lb index f5adb050d1..899215e49f 100644 --- a/src/modm/board/disco_f407vg/module.lb +++ b/src/modm/board/disco_f407vg/module.lb @@ -31,6 +31,7 @@ def prepare(module, options): ":platform:gpio", ":platform:i2c:1", ":platform:spi:1", + ":platform:i2s:3", ":platform:usb:fs") return True 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..6967584ac9 --- /dev/null +++ b/src/modm/platform/i2s/stm32/i2s_master.hpp.in @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2009-2011, Fabian Greif + * Copyright (c) 2010, Martin Rosekeit + * Copyright (c) 2011-2017, Niklas Hauser + * Copyright (c) 2012, Georgi Grinshpun + * Copyright (c) 2013, Kevin Läufer + * Copyright (c) 2014, Sascha Schade + * 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_STM32_I2S_MASTER{{ id }}_HPP +#define MODM_STM32_I2S_MASTER{{ id }}_HPP + +#include +#include +#include +//#include "i2s_hal_{{ id }}.hpp" TODO + +namespace modm +{ + +namespace platform +{ + +/** + * Inter-IC Sound (I2C{{ id }}). + * + * TODO: say something about the nature of implementation + * + * @author Marton Ledneczki + * @ingroup modm_platform_i2s modm_platform_i2s_{{id}} + */ +class I2sMaster{{ id }} : public modm::I2sMaster +{ +public: + //using Hal = I2sHal{{ id }}; TODO + + //using DataSize = Hal::DataSize; TODO + +public: + template< 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(); + } + + + // end documentation inherited +}; + +} // namespace platform + +} // namespace modm + +#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..5976537556 --- /dev/null +++ b/src/modm/platform/i2s/stm32/module.lb @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016-2018, Niklas Hauser +# Copyright (c) 2017, Fabian Greif +# 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 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") + 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)) + #env.template("i2s_master.cpp.in", "i2s_master_{}.cpp".format(self.instance)) + if env.has_module(":platform:dma"): + pass + #env.template("i2s_master_dma.hpp.in", "i2s_master_{}_dma.hpp".format(self.instance)) + #env.template("i2s_master_dma_impl.hpp.in", "i2s_master_{}_dma_impl.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 4162b243477e705e6d911a10bed0b1af62326cd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Ledneczki?= Date: Thu, 22 Apr 2021 19:05:44 +0200 Subject: [PATCH 3/6] [I2S] Add pin configuration for I2S DAC Remove not used parameter from I2S PLL initializer function. Pin setup for the I2S DAC can be done by calling Board::initializeCs43() --- src/modm/board/disco_f407vg/board.hpp | 10 ++++------ src/modm/platform/clock/stm32/rcc.cpp.in | 2 +- src/modm/platform/clock/stm32/rcc.hpp.in | 19 +++++++++++++------ 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/modm/board/disco_f407vg/board.hpp b/src/modm/board/disco_f407vg/board.hpp index adcd859190..eedc9f2f9b 100644 --- a/src/modm/board/disco_f407vg/board.hpp +++ b/src/modm/board/disco_f407vg/board.hpp @@ -92,7 +92,7 @@ struct SystemClock { .pllN = 258, // 1 MHz * N=258 -> 258 MHz .pllR = 3 // 258 MHz / R=3 -> 86 MHz }; - Rcc::enablePllI2s(Rcc::PllSource::ExternalCrystal, pllI2sFactors, 2048); + Rcc::enablePllI2s(pllI2sFactors, 2048); // set flash latency for 168MHz Rcc::setFlashLatency(); // switch system clock to PLL output @@ -148,7 +148,7 @@ using Scl = GpioB6; // Audio_SCL using Sda = GpioB9; // Audio_SDA using I2cMaster = I2cMaster1; -//using I2sMaster = I2sMaster3; +using I2sMaster = I2sMaster3; } @@ -207,10 +207,8 @@ initializeLis3() 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::Reset::setOutput(modm::Gpio::High); diff --git a/src/modm/platform/clock/stm32/rcc.cpp.in b/src/modm/platform/clock/stm32/rcc.cpp.in index b77c27f07a..9bb8caa8b4 100644 --- a/src/modm/platform/clock/stm32/rcc.cpp.in +++ b/src/modm/platform/clock/stm32/rcc.cpp.in @@ -292,7 +292,7 @@ modm::platform::Rcc::enablePll(PllSource source, const PllFactors& pllFactors, u %% if target["family"] == "f4" bool -modm::platform::Rcc::enablePllI2s(PllSource, const PllI2sFactors& pllFactors, uint32_t waitCycles) +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); diff --git a/src/modm/platform/clock/stm32/rcc.hpp.in b/src/modm/platform/clock/stm32/rcc.hpp.in index 54c1ce7053..59beff0cb4 100644 --- a/src/modm/platform/clock/stm32/rcc.hpp.in +++ b/src/modm/platform/clock/stm32/rcc.hpp.in @@ -561,18 +561,25 @@ public: %% if target["family"] == "f4" /** TODO: comments + * \code * input clock 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 + * \endcode + * * * \param pllN - * PLLI2S multiplication factor for VCO - * 50 <= PLLI2SN <= 432 + * PLLI2S 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 <= PLLI2SN <= 432 * * \param pllR - * PLLI2S division factor for I2S clocks - * Caution: The I2Ss requires a frequency lower than or equal to 192 MHz to work correctly. - * 2 <= PLLR <= 7 + * PLLI2S division factor for I2S clocks + * Caution: The I2Ss requires a frequency lower than + * or equal to 192 MHz to work correctly. + * 2 <= PLLR <= 7 */ struct PllI2sFactors { @@ -582,7 +589,7 @@ public: /* TODO: comments */ static bool - enablePllI2s(PllSource, const PllI2sFactors& pllFactors, uint32_t waitCycles); + enablePllI2s(const PllI2sFactors& pllFactors, uint32_t waitCycles); %% endif // sinks From bed748a5103c4a001d084723e78ad2686ac80c50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Ledneczki?= Date: Wed, 28 Apr 2021 20:01:27 +0200 Subject: [PATCH 4/6] [I2S] Add first draft of I2S master configuration Clock divider for the I2S peripheral is done compile time. --- src/modm/board/disco_f407vg/board.hpp | 7 +- src/modm/platform/i2s/stm32/i2s_base.hpp.in | 109 ++++++++++++++++++ src/modm/platform/i2s/stm32/i2s_hal.hpp.in | 66 +++++++++++ .../platform/i2s/stm32/i2s_hal_impl.hpp.in | 57 +++++++++ src/modm/platform/i2s/stm32/i2s_master.hpp.in | 24 +++- src/modm/platform/i2s/stm32/module.lb | 6 +- 6 files changed, 260 insertions(+), 9 deletions(-) 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 diff --git a/src/modm/board/disco_f407vg/board.hpp b/src/modm/board/disco_f407vg/board.hpp index eedc9f2f9b..c5673ac4ea 100644 --- a/src/modm/board/disco_f407vg/board.hpp +++ b/src/modm/board/disco_f407vg/board.hpp @@ -45,6 +45,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; @@ -209,7 +214,7 @@ initializeCs43() { cs43::I2sMaster::connect(); - + cs43::I2sMaster::initialize(); cs43::Reset::setOutput(modm::Gpio::High); cs43::I2cMaster::connect(); 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..c301e0fe51 --- /dev/null +++ b/src/modm/platform/i2s/stm32/i2s_base.hpp.in @@ -0,0 +1,109 @@ +/* + * 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 + * + * 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..a82cc7e3a2 --- /dev/null +++ b/src/modm/platform/i2s/stm32/i2s_hal.hpp.in @@ -0,0 +1,66 @@ +/* + * 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 + * + * 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); +}; + +} // 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..cae32a26c9 --- /dev/null +++ b/src/modm/platform/i2s/stm32/i2s_hal_impl.hpp.in @@ -0,0 +1,57 @@ +/* + * 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 + * + * 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 }}->I2SCFGR |= SPI_I2SCFGR_I2SMOD | SPI_I2SCFGR_I2SE; // I2S Enable +} + +void inline +modm::platform::I2sHal{{ id }}::disable() +{ + SPI{{ id }}->I2SCFGR &= ~(uint32_t)SPI_I2SCFGR_I2SE; + 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 fields to be set + SPI{{ id }}->I2SCFGR &= ~(uint32_t)(SPI_I2SCFGR_I2SE | SPI_I2SCFGR_I2SCFG | SPI_I2SCFGR_PCMSYNC | + SPI_I2SCFGR_I2SSTD | SPI_I2SCFGR_CKPOL | SPI_I2SCFGR_DATLEN | + SPI_I2SCFGR_CHLEN); + SPI{{ id }}->I2SPR &= ~(uint32_t)(SPI_I2SPR_MCKOE | SPI_I2SPR_ODD | SPI_I2SPR_I2SDIV); + // set parameters + SPI{{ id }}->I2SCFGR |= static_cast(masterSelection) | + static_cast(directionSelection) | + static_cast(bitDepth); + SPI{{ id }}->I2SPR |= static_cast(masterClockOutput) | + static_cast(oddFactor) | + i2s_div; + // re-enable peripheral + SPI{{ id }}->I2SCFGR |= SPI_I2SCFGR_I2SE; +} \ 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 index 6967584ac9..d434713f4d 100644 --- a/src/modm/platform/i2s/stm32/i2s_master.hpp.in +++ b/src/modm/platform/i2s/stm32/i2s_master.hpp.in @@ -18,10 +18,12 @@ #ifndef MODM_STM32_I2S_MASTER{{ id }}_HPP #define MODM_STM32_I2S_MASTER{{ id }}_HPP +#include + #include #include #include -//#include "i2s_hal_{{ id }}.hpp" TODO +#include "i2s_hal_{{ id }}.hpp" namespace modm { @@ -40,11 +42,9 @@ namespace platform class I2sMaster{{ id }} : public modm::I2sMaster { public: - //using Hal = I2sHal{{ id }}; TODO + using Hal = I2sHal{{ id }}; - //using DataSize = Hal::DataSize; TODO -public: template< template class... Signals > static void connect() @@ -63,7 +63,21 @@ public: Connector::connect(); } - + template< class SystemClock, frequency_t samplerate, percent_t tolerance=pct(0.019) > + static inline void + initialize() + { + 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!"); + static_assert(abs(samplerate - real_samplerate)/static_cast(samplerate) < pct2f(tolerance), + "I2S{{ id }}: Exceeding specified tolerance, when determining prescelar."); + Hal::initialize(odd_factor ? Hal::OddFactor::Enabled : Hal::OddFactor::Disabled, + i2s_div); + } // end documentation inherited }; diff --git a/src/modm/platform/i2s/stm32/module.lb b/src/modm/platform/i2s/stm32/module.lb index 5976537556..57174b6f60 100644 --- a/src/modm/platform/i2s/stm32/module.lb +++ b/src/modm/platform/i2s/stm32/module.lb @@ -40,8 +40,8 @@ class Instance(Module): 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_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)) #env.template("i2s_master.cpp.in", "i2s_master_{}.cpp".format(self.instance)) if env.has_module(":platform:dma"): @@ -77,4 +77,4 @@ def build(env): env.substitutions = get_properties(env) env.outbasepath = "modm/src/modm/platform/i2s" - #env.template("i2s_base.hpp.in") + env.template("i2s_base.hpp.in") From 96db91a19b54cda073a84d85be9d5351ad3b29d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Ledneczki?= Date: Tue, 4 May 2021 20:46:46 +0200 Subject: [PATCH 5/6] [I2S] Add audio DAC driver and interrupt driven I2S I2S master driver can be used with the TXE (transmit buffer empty) to transmit audio samples. The DAC driver is not that flexible at this moment. It just provides enough config that it can output audio on the headphone output. Register field definition is also not complete. I just added that much that is currently enough to make it work. --- .../architecture/interface/i2s_master.hpp | 108 +----- src/modm/board/disco_f407vg/board.hpp | 5 + src/modm/driver/dac/cs43l22.hpp | 330 ++++++++++++++++++ src/modm/driver/dac/cs43l22.lb | 43 +++ src/modm/driver/dac/cs43l22_impl.hpp | 97 +++++ src/modm/platform/i2s/stm32/i2s_hal.hpp.in | 18 + .../platform/i2s/stm32/i2s_hal_impl.hpp.in | 44 +++ src/modm/platform/i2s/stm32/i2s_master.hpp.in | 1 + 8 files changed, 544 insertions(+), 102 deletions(-) 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/architecture/interface/i2s_master.hpp b/src/modm/architecture/interface/i2s_master.hpp index c0d9613f34..8471b07242 100644 --- a/src/modm/architecture/interface/i2s_master.hpp +++ b/src/modm/architecture/interface/i2s_master.hpp @@ -39,7 +39,7 @@ class I2sMaster : public ::modm::PeripheralDriver, public I2s * SD (serial data) and WS (word select) signals and connects them. * * @tparam Signals - * One CK, SD and WS signal are required + * One CK, SD, WS, MCK signal are required * and can be passed out-of-order. */ template< class... Signals > @@ -47,115 +47,19 @@ class I2sMaster : public ::modm::PeripheralDriver, public I2s connect(); /** - * Initializes the hardware and sets the baudrate. + * Initializes the hardware and sets the samplerate. * * @tparam SystemClock * the currently active system clock - * @tparam baudrate - * the desired baudrate in Hz + * @tparam samplerate + * the desired sample rate in Hz * @tparam tolerance - * the allowed relative tolerance for the resulting baudrate + * the allowed relative tolerance for the resulting samplerate */ - template< class SystemClock, baudrate_t baudrate, percent_t tolerance=5_pct > + template< class SystemClock, frequency_t samplerate, percent_t tolerance=pct(0.019) > static void initialize(); - /// Sets a new data mode. - static void - setDataMode(DataMode mode); - - /// Sets a new data order. - static void - setDataOrder(DataOrder order); - - /** - * Request access to the spi master within a context. - * You may acquire the spi master multiple times within the same context. - * - * The configuration handler will only be called when aquiring the spi - * master for the first time (if it is not a `nullptr`). - * - * @warning Aquires must be balanced with releases of the **same** context! - * @warning Aquires are persistent even after calling `initialize()`! - * - * @return `0` if another context is using the spi master, otherwise - * `>0` as the number of times this context acquired the master. - */ - static uint8_t - acquire(void *ctx, ConfigurationHandler handler = nullptr); - - /** - * Release access to the spi master within a context. - * - * @warning Releases must be balanced with acquires of the **same** context! - * @warning Releases are persistent even after calling `initialize()`! - * - * @return `0` if nothing can be released anymore (for any context) - * `>0` as the number of times this context can still release the master. - */ - static uint8_t - release(void *ctx); - - /** - * Swap a single byte and wait for completion. - * - * @param data - * data to be sent - * @return received data - */ - static uint8_t - transferBlocking(uint8_t data); - - /** - * Set the data buffers and length with options and starts a transfer. - * This may be hardware accelerated (DMA or Interrupt), but not guaranteed. - * - * @param[in] tx - * pointer to transmit buffer, set to `nullptr` to send dummy bytes - * @param[out] rx - * pointer to receive buffer, set to `nullptr` to discard received bytes - * @param length - * number of bytes to be shifted out - */ - static void - transferBlocking(const uint8_t *tx, uint8_t *rx, std::size_t length); - - /** - * Swap a single byte and wait for completion non-blocking!. - * - * You must call this inside a Protothread or Resumable - * using `PT_CALL` or `RF_CALL` respectively. - * @warning These methods differ from Resumables by lacking context protection! - * You must ensure that only one driver is accessing this resumable function - * by using `acquire(ctx)` and `release(ctx)`. - * - * @param data - * data to be sent - * @return received data - */ - static modm::ResumableResult - transfer(uint8_t data); - - /** - * Set the data buffers and length with options and - * starts a non-blocking transfer. - * This may be hardware accelerated (DMA or Interrupt), but not guaranteed. - * - * You must call this inside a Protothread or Resumable - * using `PT_CALL` or `RF_CALL` respectively. - * @warning These methods differ from Resumables by lacking context protection! - * You must ensure that only one driver is accessing this resumable function - * by using `acquire(ctx)` and `release(ctx)`. - * - * @param[in] tx - * pointer to transmit buffer, set to `nullptr` to send dummy bytes - * @param[out] rx - * pointer to receive buffer, set to `nullptr` to discard received bytes - * @param length - * number of bytes to be shifted out - */ - static modm::ResumableResult - transfer(const uint8_t *tx, uint8_t *rx, std::size_t length); #endif }; diff --git a/src/modm/board/disco_f407vg/board.hpp b/src/modm/board/disco_f407vg/board.hpp index c5673ac4ea..df9e7dcdd2 100644 --- a/src/modm/board/disco_f407vg/board.hpp +++ b/src/modm/board/disco_f407vg/board.hpp @@ -219,6 +219,11 @@ initializeCs43() 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/driver/dac/cs43l22.hpp b/src/modm/driver/dac/cs43l22.hpp new file mode 100644 index 0000000000..9397c74f27 --- /dev/null +++ b/src/modm/driver/dac/cs43l22.hpp @@ -0,0 +1,330 @@ +/* + * 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 +#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 centiBels_t; + static constexpr centiBels_t MaxVolume = 120; + static constexpr centiBels_t MinVolume = -1020; +}; // struct cs43l22 + + +/** + * Cirrus Logic CS43L22, Portable Audio DAC with Integrated Class D Speaker Driver + * + * @tparam I2cMaster I2C interface + * @tparam I2sMaster I2S 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(centiBels_t vol); + +private: + uint8_t tx_buffer[2]; + uint8_t rx_buffer; + bool success; + + ResumableResult + writeRegister(Register reg, RegisterValue_t value); + + ResumableResult + readRegister(Register 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..d425dc6597 --- /dev/null +++ b/src/modm/driver/dac/cs43l22.lb @@ -0,0 +1,43 @@ +#!/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", + ":architecture:i2s", + ":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..daff4bf65c --- /dev/null +++ b/src/modm/driver/dac/cs43l22_impl.hpp @@ -0,0 +1,97 @@ +/* + * 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) {} + +template +ResumableResult +CS43L22::initialize() +{ + RF_BEGIN(); + // Verify the chip ID + success = RF_CALL_BLOCKING(readRegister(Register::ChipIdRevision)); + if (!success || (ChipId_t::get(static_cast(rx_buffer)) != ChipId::CS43L22)) + { + RF_RETURN(false); + } + RF_CALL_BLOCKING(writeRegister(Register::PowerControl1, Power_t(Power::Down))); + RF_CALL_BLOCKING(writeRegister(Register::PowerControl2, ChannelPowerHeadphoneA_t(ChannelPower::OnAlways) | + ChannelPowerHeadphoneB_t(ChannelPower::OnAlways) | + ChannelPowerSpeakerA_t(ChannelPower::OffAlways) | + ChannelPowerSpeakerB_t(ChannelPower::OffAlways))); + RF_CALL_BLOCKING(writeRegister(Register::ClockingControl, ClockingControl::AUTO_DETECT)); + RF_CALL_BLOCKING(writeRegister(Register::InterfaceControl1, DacInterfaceFormat_t(DacInterfaceFormat::I2sPhillipsStandard) | + Role_t(Role::Slave))); + RF_CALL_BLOCKING(setMasterVolume(-30)); + RF_CALL_BLOCKING(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_BLOCKING(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_BLOCKING(writeRegister(Register::MiscellaneousControls, MiscellaneousControls_t(0x00))); + /* Disable the limiter attack level */ + RF_CALL_BLOCKING(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); + this->transaction.configureRead(&rx_buffer, 1); + RF_END_RETURN_CALL(this->runTransaction()); +} + +template +ResumableResult +CS43L22::setMasterVolume(centiBels_t vol) +{ + RF_BEGIN(); + if (vol > MaxVolume) + vol = MaxVolume; + else if (vol < MinVolume) + vol = MinVolume; + vol /= 5; + RF_CALL_BLOCKING(writeRegister(Register::MasterVolumeControlA, static_cast(vol))); + RF_CALL_BLOCKING(writeRegister(Register::MasterVolumeControlB, static_cast(vol))); + RF_END_RETURN(true); +} + +} // namespace modm \ No newline at end of file diff --git a/src/modm/platform/i2s/stm32/i2s_hal.hpp.in b/src/modm/platform/i2s/stm32/i2s_hal.hpp.in index a82cc7e3a2..dea4d22193 100644 --- a/src/modm/platform/i2s/stm32/i2s_hal.hpp.in +++ b/src/modm/platform/i2s/stm32/i2s_hal.hpp.in @@ -55,6 +55,24 @@ public: DirectionSelection directionSelection = DirectionSelection::Transmitter, BitDepth bitDepth = BitDepth::Sixteen, MasterClockOutput masterClockOutput = MasterClockOutput::Enabled); + + static void + write(uint16_t sample); + + 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 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 cae32a26c9..9683d76b93 100644 --- a/src/modm/platform/i2s/stm32/i2s_hal_impl.hpp.in +++ b/src/modm/platform/i2s/stm32/i2s_hal_impl.hpp.in @@ -54,4 +54,48 @@ modm::platform::I2sHal{{ id }}::initialize( OddFactor oddFactor, uint8_t i2s_div i2s_div; // re-enable peripheral SPI{{ id }}->I2SCFGR |= SPI_I2SCFGR_I2SE; +} + +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 index d434713f4d..5f2d46eb00 100644 --- a/src/modm/platform/i2s/stm32/i2s_master.hpp.in +++ b/src/modm/platform/i2s/stm32/i2s_master.hpp.in @@ -78,6 +78,7 @@ public: Hal::initialize(odd_factor ? Hal::OddFactor::Enabled : Hal::OddFactor::Disabled, i2s_div); } + // end documentation inherited }; From dab81d6e8fdd9fddf803ffa504b015e0630010ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Ledneczki?= Date: Sun, 9 May 2021 16:01:58 +0200 Subject: [PATCH 6/6] [I2S] Fix audio DAC driver Several fixes have been applied to the driver, like removal of blocking calls. ANother fix is applied to the volume setting. The volume can be also set by specifying relative steps. --- src/modm/driver/dac/cs43l22.hpp | 18 ++++-- src/modm/driver/dac/cs43l22_impl.hpp | 89 ++++++++++++++++++++++------ 2 files changed, 84 insertions(+), 23 deletions(-) diff --git a/src/modm/driver/dac/cs43l22.hpp b/src/modm/driver/dac/cs43l22.hpp index 9397c74f27..123dd8ecf0 100644 --- a/src/modm/driver/dac/cs43l22.hpp +++ b/src/modm/driver/dac/cs43l22.hpp @@ -277,9 +277,9 @@ struct cs43l22 MasterVolumeControl_t, AnalogZcAndSrSettings_t, MiscellaneousControls_t, LimiterControl1MinMaxThresholds_t >; - typedef int16_t centiBels_t; - static constexpr centiBels_t MaxVolume = 120; - static constexpr centiBels_t MinVolume = -1020; + typedef int16_t centiBel_t; + static constexpr centiBel_t MaxVolume = 120; + static constexpr centiBel_t MinVolume = -1020; }; // struct cs43l22 @@ -307,11 +307,15 @@ class CS43L22 : public cs43l22, public modm::I2cDevice * and min volume by cs43l22::MinVolume */ ResumableResult - setMasterVolume(centiBels_t vol); + 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 @@ -320,6 +324,12 @@ class CS43L22 : public cs43l22, public modm::I2cDevice ResumableResult readRegister(Register reg); + ResumableResult + getMasterVolume(); + + void + regToCentibel(uint8_t reg); + }; } // namespace modm diff --git a/src/modm/driver/dac/cs43l22_impl.hpp b/src/modm/driver/dac/cs43l22_impl.hpp index daff4bf65c..32551fb264 100644 --- a/src/modm/driver/dac/cs43l22_impl.hpp +++ b/src/modm/driver/dac/cs43l22_impl.hpp @@ -18,7 +18,10 @@ namespace modm template CS43L22::CS43L22(uint8_t i2cAddress) - : I2cDevice(i2cAddress) {} + : I2cDevice(i2cAddress) +{ + volume = -300; +} template ResumableResult @@ -26,35 +29,35 @@ CS43L22::initialize() { RF_BEGIN(); // Verify the chip ID - success = RF_CALL_BLOCKING(readRegister(Register::ChipIdRevision)); + success = RF_CALL(readRegister(Register::ChipIdRevision)); if (!success || (ChipId_t::get(static_cast(rx_buffer)) != ChipId::CS43L22)) { RF_RETURN(false); } - RF_CALL_BLOCKING(writeRegister(Register::PowerControl1, Power_t(Power::Down))); - RF_CALL_BLOCKING(writeRegister(Register::PowerControl2, ChannelPowerHeadphoneA_t(ChannelPower::OnAlways) | + 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_BLOCKING(writeRegister(Register::ClockingControl, ClockingControl::AUTO_DETECT)); - RF_CALL_BLOCKING(writeRegister(Register::InterfaceControl1, DacInterfaceFormat_t(DacInterfaceFormat::I2sPhillipsStandard) | + RF_CALL(writeRegister(Register::ClockingControl, ClockingControl::AUTO_DETECT)); + RF_CALL(writeRegister(Register::InterfaceControl1, DacInterfaceFormat_t(DacInterfaceFormat::I2sPhillipsStandard) | Role_t(Role::Slave))); - RF_CALL_BLOCKING(setMasterVolume(-30)); - RF_CALL_BLOCKING(writeRegister(Register::PowerControl1, Power_t(Power::Up))); + 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_BLOCKING(writeRegister(Register::AnalogZcAndSrSettings, AnalogSoftRampA_t(SoftRamp::Disabled) | + 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_BLOCKING(writeRegister(Register::MiscellaneousControls, MiscellaneousControls_t(0x00))); + RF_CALL(writeRegister(Register::MiscellaneousControls, MiscellaneousControls_t(0x00))); /* Disable the limiter attack level */ - RF_CALL_BLOCKING(writeRegister(Register::LimiterControl1MinMaxThresholds, LimiterControl1MinMaxThresholds_t(0x00))); + RF_CALL(writeRegister(Register::LimiterControl1MinMaxThresholds, LimiterControl1MinMaxThresholds_t(0x00))); RF_END_RETURN(success); } @@ -75,23 +78,71 @@ 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(centiBels_t vol) +CS43L22::setMasterVolume(centiBel_t vol) { RF_BEGIN(); - if (vol > MaxVolume) - vol = MaxVolume; - else if (vol < MinVolume) - vol = MinVolume; - vol /= 5; - RF_CALL_BLOCKING(writeRegister(Register::MasterVolumeControlA, static_cast(vol))); - RF_CALL_BLOCKING(writeRegister(Register::MasterVolumeControlB, static_cast(vol))); + { + 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 \ No newline at end of file