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(); + + 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")