diff --git a/README.md b/README.md
index eab59d0927..8c2e4ec807 100644
--- a/README.md
+++ b/README.md
@@ -325,6 +325,28 @@ Please [discover modm's peripheral drivers for your specific device][discover].
✅ |
✅ |
+I2S |
+✅ |
+✅ |
+✅ |
+✅ |
+✅ |
+✅ |
+✅ |
+✅ |
+✅ |
+✅ |
+✅ |
+✕ |
+✕ |
+○ |
+✕ |
+✕ |
+✕ |
+✕ |
+✕ |
+✕ |
+
I2C |
✅ |
✅ |
@@ -647,82 +669,84 @@ 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 |
MCP2515 |
-
MCP7941x |
MMC5603 |
NOKIA5110 |
NRF24 |
+
TFT-DISPLAY |
PAT9125EL |
-
PCA8574 |
PCA9535 |
PCA9548A |
PCA9685 |
+
SH1106 |
SIEMENS-S65 |
-
SIEMENS-S75 |
SK6812 |
SK9822 |
SSD1306 |
+
ST7586S |
STTS22H |
-
STUSB4500 |
SX1276 |
TCS3414 |
TCS3472 |
+
TLC594x |
TMP102 |
-
TMP12x |
TMP175 |
TOUCH2046 |
VL53L0 |
+
VL6180 |
WS2812 |
-
diff --git a/examples/nucleo_f429zi/audio_ma12070p/main.cpp b/examples/nucleo_f429zi/audio_ma12070p/main.cpp
new file mode 100644
index 0000000000..903ce52f59
--- /dev/null
+++ b/examples/nucleo_f429zi/audio_ma12070p/main.cpp
@@ -0,0 +1,258 @@
+/*
+ * Copyright (c) 2022, 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
+
+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 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 = GpioOutputB15;
+using DmaTx = Dma1::Channel4;
+using I2sMaster = modm::platform::I2sMaster2;
+
+using Enable = GpioInverted;
+using Mute = GpioInverted;
+
+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);
+ }
+};
+
+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();
+}
+
+class Ma12070pThread : public modm::pt::Protothread
+{
+public:
+ Ma12070pThread() : ma12070p{ma12070pAddressI2c}
+ {}
+
+ 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());
+ }
+
+ 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();
+ }
+
+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 = false,
+ .useLimiter = false,
+ };
+
+ 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 NUcleo-F429ZI" << modm::endl;
+
+
+ I2sMaster::connect();
+ 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();
+
+ Ma12070pThread ma12070pThread{};
+
+ 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;
+ }
+
+ ma12070pThread.update();
+
+ if (I2sMaster::hasDmaError()) {
+ MODM_LOG_ERROR << "I2S DMA Error :(" << modm::endl;
+ }
+
+ if (tmr.execute()) {
+ LedBlue::toggle();
+ }
+ }
+
+ return 0;
+}
diff --git a/examples/nucleo_f429zi/audio_ma12070p/project.xml b/examples/nucleo_f429zi/audio_ma12070p/project.xml
new file mode 100644
index 0000000000..db53bbf53c
--- /dev/null
+++ b/examples/nucleo_f429zi/audio_ma12070p/project.xml
@@ -0,0 +1,15 @@
+
+ modm:nucleo-f429zi
+
+
+
+
+ modm:build:scons
+ modm:cmsis:dsp:fast_math
+ modm:driver:ma12070p
+ modm:platform:i2c:1
+ modm:platform:i2s:2
+ modm:processing:timer
+ modm:processing:protothread
+
+
diff --git a/examples/stm32f4_discovery/audio_i2s/main.cpp b/examples/stm32f4_discovery/audio_i2s/main.cpp
new file mode 100644
index 0000000000..093974e7c8
--- /dev/null
+++ b/examples/stm32f4_discovery/audio_i2s/main.cpp
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2021-2022, 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
+
+#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;
+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::setTxBuffer(uintptr_t(bufferA.data()), bufferSize);
+ bufferA_ready = false;
+ }
+ else if (bufferB_ready) {
+ cs43::I2sMaster::setTxBuffer(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();
+}
+
+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()
+{
+ 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;
+ MODM_LOG_INFO << "Press the 'user' button to increate volume." << modm::endl;
+
+ Dma1::enable();
+
+ Board::initializeCs43*samplerate=*/48_kHz, /*tolerance=*/0.02_pct>();
+
+ modm::Cs43l22 audioDac{cs43::I2CAddress};
+ if (!RF_CALL_BLOCKING(audioDac.initialize())) {
+ MODM_LOG_ERROR << "Unable to initialize CS43L22 audio DAC" << modm::endl;
+ }
+
+ VolumeThread volumeThread{audioDac};
+
+ cs43::I2sMaster::setTransferCompleteIrqHandler(transferCompleteIrqHandler);
+ cs43::I2sMaster::setTxBuffer(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;
+ }
+
+ volumeThread.update();
+ }
+
+ 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..f70f941a26
--- /dev/null
+++ b/examples/stm32f4_discovery/audio_i2s/project.xml
@@ -0,0 +1,14 @@
+
+ modm:disco-f407vg
+
+
+
+
+ modm:debug
+ modm:build:scons
+ modm:platform:uart:2
+ modm:processing:timer
+ modm:processing:protothread
+ modm:cmsis:dsp:fast_math
+
+
diff --git a/src/modm/architecture/interface/i2s_master.hpp b/src/modm/architecture/interface/i2s_master.hpp
new file mode 100644
index 0000000000..e6c3f266ec
--- /dev/null
+++ b/src/modm/architecture/interface/i2s_master.hpp
@@ -0,0 +1,102 @@
+/*
+ * 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
+
+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())
diff --git a/src/modm/board/disco_f407vg/board.hpp b/src/modm/board/disco_f407vg/board.hpp
index da703b7abf..fe435971cf 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,25 @@ 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::I2sMaster::BitDepth::SixteenWithChannel16,
+ cs43::I2sMaster::MasterClockOutput::Enabled,
+ cs43::I2sMaster::I2sStandard::Philips);
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
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 >;
diff --git a/src/modm/driver/dac/cs43l22.hpp b/src/modm/driver/dac/cs43l22.hpp
new file mode 100644
index 0000000000..ab4468b89d
--- /dev/null
+++ b/src/modm/driver/dac/cs43l22.hpp
@@ -0,0 +1,337 @@
+/*
+ * 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)
+ };
+ using ChipId_t = Configuration< ChipIdRevision_t, ChipId, (Bit7 | Bit6 | Bit5 | Bit4 | Bit3) >;
+
+ enum class
+ RevisionId : uint8_t
+ {
+ A0 = 0,
+ A1 = int(ChipIdRevision::REVID0),
+ B0 = int(ChipIdRevision::REVID1),
+ B1 = int(ChipIdRevision::REVID1) | int(ChipIdRevision::REVID0)
+ };
+ using RevisionId_t = Configuration< ChipIdRevision_t, RevisionId, (Bit2 | Bit1 | Bit0) >;
+
+ 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)
+ };
+ using Power_t = Configuration< PowerControl1_t, Power, 0xFF >;
+
+ 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
+ };
+ 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
+ {
+ 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)
+ };
+ using Role_t = Configuration< InterfaceControl1_t, Role, Bit7 >;
+
+ enum class
+ DacInterfaceFormat : uint8_t
+ {
+ LeftJustified = 0,
+ I2sPhillipsStandard = int(InterfaceControl1::DACDIF0),
+ RightJustified = int(InterfaceControl1::DACDIF1)
+ };
+ using DacInterfaceFormat_t = Configuration< InterfaceControl1_t, DacInterfaceFormat, (Bit2 | Bit3) >;
+
+ enum class
+ MasterVolumeControl : uint8_t
+ {
+ MSTVOL0 = Bit0,
+ MSTVOL1 = Bit1,
+ MSTVOL2 = Bit2,
+ MSTVOL3 = Bit3,
+ MSTVOL4 = Bit4,
+ MSTVOL5 = Bit5,
+ MSTVOL6 = Bit6,
+ MSTVOL7 = Bit7,
+ };
+ MODM_FLAGS8(MasterVolumeControl);
+ using MasterVol_t = Value< MasterVolumeControl_t, 8 >;
+
+ 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
+ };
+ using AnalogSoftRampA_t = Configuration< AnalogZcAndSrSettings_t, SoftRamp, 0b1, 1>;
+ using AnalogSoftRampB_t = Configuration< AnalogZcAndSrSettings_t, SoftRamp, 0b1, 3>;
+
+ enum class
+ ZeroCrossing : uint8_t
+ {
+ Disabled = 0,
+ Enabled = 1
+ };
+ using AnalogZeroCrossingA_t = Configuration< AnalogZcAndSrSettings_t, ZeroCrossing, 0b1, 0>;
+ using AnalogZeroCrossingB_t = Configuration< AnalogZcAndSrSettings_t, ZeroCrossing, 0b1, 2>;
+
+ 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 >;
+
+ using centiBel_t = int16_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
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
new file mode 100644
index 0000000000..fbd163c35c
--- /dev/null
+++ b/src/modm/driver/dac/ma12070p.hpp
@@ -0,0 +1,314 @@
+/*
+ * 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
+ {
+ 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
+ VlpMonitor : uint8_t
+ {
+ LimiterCh0L = Bit0,
+ LimiterCh0R = Bit1,
+ LimiterCh1L = Bit2,
+ LimiterCh1R = Bit3,
+ ClippingCh0L = Bit4,
+ ClippingCh0R = Bit5,
+ ClippingCh1L = Bit6,
+ ClippingCh1R = Bit7,
+ };
+ MODM_FLAGS8(VlpMonitor);
+
+ enum class
+ ErrorRegister : uint8_t
+ {
+ FlyingCapOverVolt = Bit0,
+ OverCurrent = Bit1,
+ Pll = Bit2,
+ PvddUnderVolt = Bit3,
+ OverTempWarning = Bit4,
+ OverTempError = Bit5,
+ PinToPinLowImpedance = Bit6,
+ DcProtection = Bit7,
+ };
+ MODM_FLAGS8(ErrorRegister);
+
+ // 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));
+}
+
+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
+ 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 threshold for individual channels (-144db to +24db)
+ ResumableResult
+ 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
+ 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_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
new file mode 100644
index 0000000000..5c6f647594
--- /dev/null
+++ b/src/modm/driver/dac/ma12070p.lb
@@ -0,0 +1,43 @@
+#!/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.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
new file mode 100644
index 0000000000..2f9823f532
--- /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::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);
+ 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
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
diff --git a/src/modm/platform/clock/stm32/module.lb b/src/modm/platform/clock/stm32/module.lb
index 32387af17f..ac215b6985 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" 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") \
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..3d574b2455 100644
--- a/src/modm/platform/clock/stm32/rcc.hpp.in
+++ b/src/modm/platform/clock/stm32/rcc.hpp.in
@@ -689,6 +689,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);
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..ad869cbcbb 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,40 @@ 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", "stm32-mux-stream"]
+ 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", "stm32-mux-stream"]
+ return (Base->CR & DMA_SxCR_CT);
+%% else
+ return true;
+%% 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..3e457cdfe1 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", "stm32-mux-stream"])
properties['channel_count'] = {
"min" : min(controller, key=lambda c: c["min_channel"])["min_channel"],
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..fbf8d4137d
--- /dev/null
+++ b/src/modm/platform/i2s/stm32/i2s_base.hpp.in
@@ -0,0 +1,120 @@
+/*
+ * 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::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
+ };
+
+ 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
+ {
+ 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,
+ };
+
+ enum class
+ OddFactor : uint32_t
+ {
+ 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 modm::platform
+
+
+#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..c1bce2a65c
--- /dev/null
+++ b/src/modm/platform/i2s/stm32/i2s_hal.hpp.in
@@ -0,0 +1,84 @@
+/*
+ * 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::platform
+{
+
+/**
+ * Serial peripheral interface in I2S mode (I2S{{ id }})
+ *
+ * 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:
+ /// Enables the RCC clock, resets the hardware and configures mode to I2S
+ static void
+ enable();
+
+ /// Disables the hw module (by disabling its clock line)
+ static void
+ disable();
+
+ /// Initialize I2s peripheral
+ static void
+ initialize( OddFactor oddFactor, uint8_t i2s_div,
+ MasterSelection masterSelection = MasterSelection::Master,
+ DirectionSelection directionSelection = DirectionSelection::Transmitter,
+ BitDepth bitDepth = BitDepth::SixteenWithChannel16,
+ MasterClockOutput masterClockOutput = MasterClockOutput::Enabled,
+ I2sStandard i2sStandard = I2sStandard::Philips,
+ ClockPolarity clockPolarity = ClockPolarity::Low);
+
+ 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 modm::platform
+
+#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..5f8905e25a
--- /dev/null
+++ b/src/modm/platform/i2s/stm32/i2s_hal_impl.hpp.in
@@ -0,0 +1,118 @@
+/*
+ * 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,
+ I2sStandard i2sStandard,
+ ClockPolarity clockPolarity)
+{
+ 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) |
+ static_cast(i2sStandard) |
+ static_cast(clockPolarity);
+}
+
+void inline
+modm::platform::I2sHal{{ id }}::start()
+{
+ SPI{{ id }}->I2SCFGR |= SPI_I2SCFGR_I2SE;
+}
+
+void inline
+modm::platform::I2sHal{{ id }}::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..d4b58c5af2
--- /dev/null
+++ b/src/modm/platform/i2s/stm32/i2s_master.hpp.in
@@ -0,0 +1,179 @@
+/*
+ * 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 (I2S) master driver
+ *
+ * @warning Only data output (e.g. to headphone/speaker DAC) functionality is currently implemented
+ *
+ * @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, public I2sBase
+{
+ 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;
+
+ Ck::setOutput(Gpio::OutputType::PushPull);
+ Mck::setOutput(Gpio::OutputType::PushPull);
+ Sd::setOutput(Gpio::OutputType::PushPull);
+ Ws::setOutput(Gpio::OutputType::PushPull);
+ Connector::connect();
+ }
+
+ 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()
+ {
+ // 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) /
+ (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();
+
+ Hal::initialize(odd_factor ? Hal::OddFactor::Enabled : Hal::OddFactor::Disabled,
+ i2s_div,
+ Hal::MasterSelection::Master,
+ Hal::DirectionSelection::Transmitter,
+ c.bitDepth,
+ c.masterClockOutput,
+ c.i2sStandard,
+ c.clockPolarity);
+
+ dmaError = false;
+ Dma::TxChannel::configure(
+ DmaBase::DataTransferDirection::MemoryToPeripheral,
+ DmaBase::MemoryDataSize::Bit16,
+ DmaBase::PeripheralDataSize::Bit16,
+ DmaBase::MemoryIncrementMode::Increment,
+ DmaBase::PeripheralIncrementMode::Fixed,
+ c.dmaPriority,
+ DmaBase::CircularMode::Disabled);
+ Dma::TxChannel::setPeripheralAddress(uintptr_t(&SPI{{ id }}->DR));
+ 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
+ setTxBuffer(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..ffac2d9599
--- /dev/null
+++ b/src/modm/platform/i2s/stm32/module.lb
@@ -0,0 +1,80 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# 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 device.identifier.family in ["h7"]:
+ # STM32H7 has a completely different SPI IP
+ return False
+ 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")