diff --git a/src/modm/architecture/interface/i2s.hpp b/src/modm/architecture/interface/i2s.hpp new file mode 100644 index 0000000000..f986ab53af --- /dev/null +++ b/src/modm/architecture/interface/i2s.hpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2009-2012, Fabian Greif + * Copyright (c) 2010, Martin Rosekeit + * Copyright (c) 2012-2015, Niklas Hauser + * Copyright (c) 2013, Sascha Schade + * Copyright (c) 2021, Marton Ledneczki + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_INTERFACE_I2S_HPP +#define MODM_INTERFACE_I2S_HPP + +#include + +namespace modm +{ + +/// @ingroup modm_architecture_i2s +struct I2s +{ + /// The signature of the configuration function. + using ConfigurationHandler = void(*)(); +}; + +} // namespace modm + +#endif // MODM_INTERFACE_I2S_HPP diff --git a/src/modm/architecture/interface/i2s_master.hpp b/src/modm/architecture/interface/i2s_master.hpp new file mode 100644 index 0000000000..8471b07242 --- /dev/null +++ b/src/modm/architecture/interface/i2s_master.hpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2009-2012, Fabian Greif + * Copyright (c) 2010, Martin Rosekeit + * Copyright (c) 2012-2017, Niklas Hauser + * Copyright (c) 2013, Sascha Schade + * Copyright (c) 2021, Marton Lednczki + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_INTERFACE_I2S_MASTER_HPP +#define MODM_INTERFACE_I2S_MASTER_HPP + +#include +#include "i2s.hpp" + +namespace modm +{ + +/** + * Interface for a I2S Master + * + * @author Marton Ledneczki + * @ingroup modm_architecture_i2s + */ +class I2sMaster : public ::modm::PeripheralDriver, public I2s +{ +#ifdef __DOXYGEN__ +public: + /** + * Connect GPIOs to the peripheral and configure them. + * + * This configures the CK (serial clock), MCK (master clock), + * SD (serial data) and WS (word select) signals and connects them. + * + * @tparam Signals + * One CK, SD, WS, MCK signal are required + * and can be passed out-of-order. + */ + template< class... Signals > + static void + connect(); + + /** + * 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(); + +#endif +}; + +} // namespace modm + +#endif // MODM_INTERFACE_SPI_MASTER_HPP diff --git a/src/modm/architecture/module.lb b/src/modm/architecture/module.lb index dbadd79fe0..52e6d9eeb9 100644 --- a/src/modm/architecture/module.lb +++ b/src/modm/architecture/module.lb @@ -230,6 +230,21 @@ class I2cMultiplexer(Module): env.copy("interface/i2c_multiplexer.hpp") # ----------------------------------------------------------------------------- +class I2s(Module): + def init(self, module): + module.name = "i2s" + module.description = "Inter-IC Sound (I2S)" + + def prepare(self, module, options): + module.depends(":processing:resumable") + return True + + def build(self, env): + env.outbasepath = "modm/src/modm/architecture" + env.copy("interface/i2s.hpp") + env.copy("interface/i2s_master.hpp") +# ----------------------------------------------------------------------------- + class Interrupt(Module): def init(self, module): module.name = "interrupt" @@ -381,6 +396,7 @@ def prepare(module, options): module.add_submodule(I2c()) module.add_submodule(I2cDevice()) module.add_submodule(I2cMultiplexer()) + module.add_submodule(I2s()) module.add_submodule(Interrupt()) module.add_submodule(Memory()) module.add_submodule(OneWire()) diff --git a/src/modm/board/disco_f407vg/board.hpp b/src/modm/board/disco_f407vg/board.hpp index e99e4af4ec..df9e7dcdd2 100644 --- a/src/modm/board/disco_f407vg/board.hpp +++ b/src/modm/board/disco_f407vg/board.hpp @@ -45,6 +45,11 @@ struct SystemClock { static constexpr uint32_t Spi5 = Apb2; static constexpr uint32_t Spi6 = Apb2; + static constexpr uint32_t I2s2 = Spi2; + static constexpr uint32_t I2s3 = Spi3; + + static constexpr uint32_t I2sPll = 86_MHz; + static constexpr uint32_t Usart1 = Apb2; static constexpr uint32_t Usart2 = Apb1; static constexpr uint32_t Usart3 = Apb1; @@ -82,12 +87,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, 2048); // set flash latency for 168MHz Rcc::setFlashLatency(); // switch system clock to PLL output @@ -143,7 +153,7 @@ using Scl = GpioB6; // Audio_SCL using Sda = GpioB9; // Audio_SDA using I2cMaster = I2cMaster1; -//using I2sMaster = I2sMaster3; +using I2sMaster = I2sMaster3; } @@ -202,15 +212,18 @@ initializeLis3() inline void initializeCs43() { -// cs43::Lrck::connect(cs43::I2sMaster::Ws); -// cs43::Mclk::connect(cs43::I2sMaster::Mck); -// cs43::Sclk::connect(cs43::I2sMaster::Ck); -// cs43::Sdin::connect(cs43::I2sMaster::Sd); - + cs43::I2sMaster::connect(); + cs43::I2sMaster::initialize(); cs43::Reset::setOutput(modm::Gpio::High); cs43::I2cMaster::connect(); cs43::I2cMaster::initialize(); + + cs43::Reset::setOutput(modm::Gpio::Low); + modm::delay_ms(2); + cs43::Reset::setOutput(modm::Gpio::High); + } /// not supported yet, due to missing I2S driver diff --git a/src/modm/board/disco_f407vg/module.lb b/src/modm/board/disco_f407vg/module.lb index f5adb050d1..899215e49f 100644 --- a/src/modm/board/disco_f407vg/module.lb +++ b/src/modm/board/disco_f407vg/module.lb @@ -31,6 +31,7 @@ def prepare(module, options): ":platform:gpio", ":platform:i2c:1", ":platform:spi:1", + ":platform:i2s:3", ":platform:usb:fs") return True diff --git a/src/modm/driver/dac/cs43l22.hpp b/src/modm/driver/dac/cs43l22.hpp new file mode 100644 index 0000000000..123dd8ecf0 --- /dev/null +++ b/src/modm/driver/dac/cs43l22.hpp @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2021, Marton Ledneczki + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_CS43L22_HPP +#define MODM_CS43L22_HPP + +#include +#include + +namespace modm +{ + +/// @ingroup modm_driver_cs43l22 +struct cs43l22 +{ + enum class + Register : uint8_t + { + ChipIdRevision = 0x01, + PowerControl1 = 0x02, + PowerControl2 = 0x04, + ClockingControl = 0x05, + InterfaceControl1 = 0x06, + InterfaceControl2 = 0x07, + PassthroughASelect = 0x08, + PassthroughBSelect = 0x09, + AnalogZcAndSrSettings = 0x0A, + PassthroughGansControl = 0x0C, + PlaybackControl1 = 0x0D, + MiscellaneousControls = 0x0E, + PlaybackControl2 = 0x0F, + PassthroughAVolume = 0x14, + PassthroughBVolume = 0x15, + PcmAVolume = 0x1A, + PcmBVolume = 0x1B, + BeepFrequencyAndOnTime = 0x1C, + BeepVolumeAndOffTime = 0x1D, + BeepAndToneConfiguration = 0x1E, + ToneControl = 0x1F, + MasterVolumeControlA = 0x20, + MasterVolumeControlB = 0x21, + HeadphoneVolumeControlA = 0x22, + HeadphoneVolumeControlB = 0x23, + SpeakerVolumeControlA = 0x24, + SpeakerVolumeControlB = 0x25, + PcmChannelSwap = 0x26, + LimiterControl1MinMaxThresholds = 0x27, + LimiterControl2ReleaseRate = 0x28, + LimiterAttackRate = 0x29, + Status = 0x2E, + VpBatteryLevel = 0x30, + SpeakerStatus = 0x31, + ChargePumpFrequency = 0x34 + }; + + enum class + ChipIdRevision : uint8_t + { + REVID0 = Bit0, + REVID1 = Bit1, + REVID2 = Bit2, + + CHIPID0 = Bit3, + CHIPID1 = Bit4, + CHIPID2 = Bit5, + CHIPID3 = Bit6, + CHIPID4 = Bit7 + }; + MODM_FLAGS8(ChipIdRevision); + + enum class + ChipId : uint8_t + { + CS43L22 = int(ChipIdRevision::CHIPID4) | int(ChipIdRevision::CHIPID3) | int(ChipIdRevision::CHIPID2) + }; + typedef Configuration< ChipIdRevision_t, ChipId, (Bit7 | Bit6 | Bit5 | Bit4 | Bit3) > ChipId_t; + + enum class + RevisionId : uint8_t + { + A0 = 0, + A1 = int(ChipIdRevision::REVID0), + B0 = int(ChipIdRevision::REVID1), + B1 = int(ChipIdRevision::REVID1) | int(ChipIdRevision::REVID0) + }; + typedef Configuration< ChipIdRevision_t, RevisionId, (Bit2 | Bit1 | Bit0) > RevisionId_t; + + enum class + PowerControl1 : uint8_t + { + PDN0 = Bit0, + PDN1 = Bit1, + PDN2 = Bit2, + PDN3 = Bit3, + PDN4 = Bit4, + PDN5 = Bit5, + PDN6 = Bit6, + PDN7 = Bit7, + }; + MODM_FLAGS8(PowerControl1); + + enum class + Power : uint8_t + { + Down = int(PowerControl1::PDN0), + Up = int(PowerControl1::PDN7) | + int(PowerControl1::PDN4) | + int(PowerControl1::PDN3) | + int(PowerControl1::PDN2) | + int(PowerControl1::PDN1) + }; + typedef Configuration< PowerControl1_t, Power, 0xFF > Power_t; + + enum class + PowerControl2 : uint8_t + { + PDN_SPKA0 = Bit0, + PDN_SPKA1 = Bit1, + + PDN_SPKB0 = Bit2, + PDN_SPKB1 = Bit3, + + PDN_HPA0 = Bit4, + PDN_HPA1 = Bit5, + + PDN_HPB0 = Bit6, + PDN_HPB1 = Bit7, + }; + MODM_FLAGS8(PowerControl2); + + enum class + ChannelPower : uint8_t + { + OnWhenSelectorPinLo = 0, + OnWhenSelectorPinHi = 1, + OnAlways = 2, + OffAlways = 3 + }; + typedef Configuration< PowerControl2_t, ChannelPower, 0b11, 0> ChannelPowerSpeakerA_t; + typedef Configuration< PowerControl2_t, ChannelPower, 0b11, 2> ChannelPowerSpeakerB_t; + typedef Configuration< PowerControl2_t, ChannelPower, 0b11, 4> ChannelPowerHeadphoneA_t; + typedef Configuration< PowerControl2_t, ChannelPower, 0b11, 6> ChannelPowerHeadphoneB_t; + + enum class + ClockingControl : uint8_t + { + MCLKDIV2 = Bit0, + + RATIO0 = Bit1, + RATIO1 = Bit2, + + VIDEOCLK = Bit3, + GROUP_32K = Bit4, + + SPEED0 = Bit5, + SPEED1 = Bit6, + + AUTO_DETECT = Bit7 + }; + MODM_FLAGS8(ClockingControl); + + enum class + InterfaceControl1 : uint8_t + { + AWL0 = Bit0, + AWL1 = Bit1, + + DACDIF0 = Bit2, + DACDIF1 = Bit3, + + DSP = Bit4, + INV_SCLK = Bit6, + MASTER = Bit7 // Master (output only), otherwise Slave (input only) + }; + MODM_FLAGS8(InterfaceControl1); + + enum class + Role + { + Slave = 0, + Master = int(InterfaceControl1::MASTER) + }; + typedef Configuration< InterfaceControl1_t, Role, Bit7 > Role_t; + + enum class + DacInterfaceFormat : uint8_t + { + LeftJustified = 0, + I2sPhillipsStandard = int(InterfaceControl1::DACDIF0), + RightJustified = int(InterfaceControl1::DACDIF1) + }; + typedef Configuration< InterfaceControl1_t, DacInterfaceFormat, (Bit2 | Bit3) > DacInterfaceFormat_t; + + enum class + MasterVolumeControl : uint8_t + { + MSTVOL0 = Bit0, + MSTVOL1 = Bit1, + MSTVOL2 = Bit2, + MSTVOL3 = Bit3, + MSTVOL4 = Bit4, + MSTVOL5 = Bit5, + MSTVOL6 = Bit6, + MSTVOL7 = Bit7, + }; + MODM_FLAGS8(MasterVolumeControl); + typedef Value< MasterVolumeControl_t, 8 > MasterVol_t; + + enum class + AnalogZcAndSrSettings : uint8_t + { + ANLGZCA = Bit0, + ANLGSFTA = Bit1, + ANLGZCB = Bit2, + ANLGSFTB = Bit2 + }; + MODM_FLAGS8(AnalogZcAndSrSettings); + + enum class + SoftRamp : uint8_t + { + Disabled = 0, + Enabled = 1 + }; + typedef Configuration< AnalogZcAndSrSettings_t, SoftRamp, 0b1, 1> AnalogSoftRampA_t; + typedef Configuration< AnalogZcAndSrSettings_t, SoftRamp, 0b1, 3> AnalogSoftRampB_t; + + enum class + ZeroCrossing : uint8_t + { + Disabled = 0, + Enabled = 1 + }; + typedef Configuration< AnalogZcAndSrSettings_t, ZeroCrossing, 0b1, 0> AnalogZeroCrossingA_t; + typedef Configuration< AnalogZcAndSrSettings_t, ZeroCrossing, 0b1, 2> AnalogZeroCrossingB_t; + + enum class + MiscellaneousControls + { + DIGZC = Bit0, + DIGSFT = Bit1, + DEEMPH = Bit2, + FREEZE = Bit3, + PASSAMUTE = Bit4, + PASSBMUTE = Bit5, + PASSTHRUA = Bit6, + PASSTHRUB = Bit7 + }; + MODM_FLAGS8(MiscellaneousControls); + + enum class + LimiterControl1MinMaxThresholds + { + LIMZCDIS = Bit0, + LIMSRDIS = Bit1, + + CUSH0 = Bit2, + CUSH1 = Bit3, + CUSH2 = Bit4, + + LMAX0 = Bit5, + LMAX1 = Bit6, + LMAX2 = Bit7 + }; + MODM_FLAGS8(LimiterControl1MinMaxThresholds); + + using RegisterValue_t = FlagsGroup< ChipIdRevision_t, PowerControl1_t, PowerControl2_t, + ClockingControl_t, InterfaceControl1_t, + MasterVolumeControl_t, AnalogZcAndSrSettings_t, + MiscellaneousControls_t, LimiterControl1MinMaxThresholds_t >; + + typedef int16_t centiBel_t; + static constexpr centiBel_t MaxVolume = 120; + static constexpr centiBel_t MinVolume = -1020; +}; // struct cs43l22 + + +/** + * Cirrus Logic CS43L22, Portable Audio DAC with Integrated Class D Speaker Driver + * + * @tparam I2cMaster I2C interface + * @tparam I2sMaster I2S interface + * + * @author Marton Ledneczki + * @ingroup modm_driver_cs43l22 + */ +template +class CS43L22 : public cs43l22, public modm::I2cDevice +{ +public: + CS43L22(uint8_t i2cAddress); + + /// Initialize device, call before using any other function + ResumableResult + initialize(); + + /** + * @note max volume is specified by cs43l22::MaxVolume + * and min volume by cs43l22::MinVolume + */ + ResumableResult + setMasterVolume(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..d425dc6597 --- /dev/null +++ b/src/modm/driver/dac/cs43l22.lb @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2021, Marton Ledneczki +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + + +def init(module): + module.name = ":driver:cs43l22" + module.description = """\ +# CS43L22, Portable Audio DAC with Integrated Class D Speaker Driver + +The CS43L22 is low power stereo digital/analog converter (DAC) with +integrated headphone and Class D speaker amplifiers that operates from a +low voltage analog and digital core. The DAC output path includes a +digital signal processing engine with various fixed-function controls, +such as tone and volume control, and includes de-emphasis, limiting +functions and beep generator that delivers tones selectable across +a range of two octaves. The stereo headphone amplifier is powered +from a separate positive supply and the integrated charge pump provides +a negative supply allowing for a ground-centered analog output to eliminate +the need for DC blocking capacitors. The Class D stereo speaker does not +require an external filter and can be powered directly from a battery. +""" + +def prepare(module, options): + module.depends( + ":architecture:register", + ":architecture:i2c.device", + ":architecture:i2s", + ":processing:resumable") + return True + +def build(env): + env.outbasepath = "modm/src/modm/driver/dac" + env.copy("cs43l22.hpp") + env.copy("cs43l22_impl.hpp") diff --git a/src/modm/driver/dac/cs43l22_impl.hpp b/src/modm/driver/dac/cs43l22_impl.hpp new file mode 100644 index 0000000000..32551fb264 --- /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 \ No newline at end of file diff --git a/src/modm/platform/clock/stm32/rcc.cpp.in b/src/modm/platform/clock/stm32/rcc.cpp.in index 137c013398..9bb8caa8b4 100644 --- a/src/modm/platform/clock/stm32/rcc.cpp.in +++ b/src/modm/platform/clock/stm32/rcc.cpp.in @@ -290,6 +290,29 @@ modm::platform::Rcc::enablePll(PllSource source, const PllFactors& pllFactors, u %%endif } +%% if target["family"] == "f4" +bool +modm::platform::Rcc::enablePllI2s(const PllI2sFactors& pllFactors, uint32_t waitCycles) +{ + // Read reserved and don't care values and clear all other values + uint32_t tmp = RCC->PLLI2SCFGR & ~(RCC_PLLI2SCFGR_PLLI2SN | RCC_PLLI2SCFGR_PLLI2SR); + // set PLL divider and multiplier + tmp |= (((uint32_t) pllFactors.pllN) << RCC_PLLI2SCFGR_PLLI2SN_Pos) & RCC_PLLI2SCFGR_PLLI2SN; + tmp |= (((uint32_t) pllFactors.pllR) << RCC_PLLI2SCFGR_PLLI2SR_Pos) & RCC_PLLI2SCFGR_PLLI2SR; + RCC->PLLI2SCFGR = tmp; + + // enable I2S PLL + RCC->CR |= RCC_CR_PLLI2SON; + // wait till I2S PLL gets ready or time is up + while ((RCC->CR & RCC_CR_PLLI2SRDY) == 0) + { + if (not --waitCycles) + return false; + } + return true; +} +%% endif + %% if pllsai_p_usb bool modm::platform::Rcc::enablePllSai(const PllSaiFactors& pllFactors, uint32_t waitCycles) diff --git a/src/modm/platform/clock/stm32/rcc.hpp.in b/src/modm/platform/clock/stm32/rcc.hpp.in index 6783758208..59beff0cb4 100644 --- a/src/modm/platform/clock/stm32/rcc.hpp.in +++ b/src/modm/platform/clock/stm32/rcc.hpp.in @@ -559,6 +559,39 @@ public: setHsiPredivider4Enabled(bool divideBy4, uint32_t waitCycles = 2048); %% endif +%% if target["family"] == "f4" + /** TODO: comments + * \code + * input clock is the HSE or HSI divided by M + * f(VCO clock) = f(PLLI2S clock input) × (PLLI2SN / PLLM) + * f(PLL I2S clock output) = f(VCO clock) / PLLI2SR + * \endcode + * + * + * \param pllN + * PLLI2S multiplication factor for VCO + * Caution: The software has to set these bits correctly + * to ensure that the VCO output frequency + * is between 100 and 432 MHz. + * 50 <= PLLI2SN <= 432 + * + * \param pllR + * PLLI2S division factor for I2S clocks + * Caution: The I2Ss requires a frequency lower than + * or equal to 192 MHz to work correctly. + * 2 <= PLLR <= 7 + */ + struct PllI2sFactors + { + const uint16_t pllN; + const uint16_t pllR; + }; + + /* TODO: comments */ + static bool + enablePllI2s(const PllI2sFactors& pllFactors, uint32_t waitCycles); +%% endif + // sinks static bool enableSystemClock(SystemClockSource src, uint32_t waitCycles = 2048); diff --git a/src/modm/platform/i2s/stm32/i2s_base.hpp.in b/src/modm/platform/i2s/stm32/i2s_base.hpp.in new file mode 100644 index 0000000000..c301e0fe51 --- /dev/null +++ b/src/modm/platform/i2s/stm32/i2s_base.hpp.in @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2013, Kevin Läufer + * Copyright (c) 2013-2017, Niklas Hauser + * Copyright (c) 2014, Daniel Krebs + * Copyright (c) 2020, Mike Wolfram, + * Copyright (c) 2021, Marton Ledneczki + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_STM32_I2S_BASE_HPP +#define MODM_STM32_I2S_BASE_HPP + +#include +#include "../device.hpp" +#include + +namespace modm +{ + +namespace platform +{ + +/** + * Base class for the I2S classes + * + * Provides some common enum that do not depend on the specific I2S. + * + * @author Marton Ledneczki + * @ingroup modm_platform_i2s + */ +class I2sBase +{ +public: + enum class + Interrupt : uint32_t + { + RxBufferNotEmpty = SPI_CR2_RXNEIE, + TxBufferEmpty = SPI_CR2_TXEIE, + Error = SPI_CR2_ERRIE, + RxDmaEnable = SPI_CR2_RXDMAEN, + TxDmaEnable = SPI_CR2_TXDMAEN, + }; + MODM_FLAGS32(Interrupt); + + enum class + InterruptFlag : uint32_t + { + TxBufferEmpty = SPI_SR_TXE, + RxBufferNotEmpty = SPI_SR_RXNE, + CrcError = SPI_SR_CRCERR, + ModeFaultError = SPI_SR_MODF, + OverrunError = SPI_SR_OVR, + Busy = SPI_SR_BSY, + }; + MODM_FLAGS32(InterruptFlag); + + enum class + MasterSelection : uint32_t + { + Slave = 0b0, ///< Configure I2S as Slave + Master = SPI_I2SCFGR_I2SCFG_1, ///< Configure I2S as Master + All = Master, + }; + + enum class + DirectionSelection : uint32_t + { + Transmitter = 0b0, ///< Configure I2S as Transmitter + Receiver = SPI_I2SCFGR_I2SCFG_0, ///< COnfigure I2S as Receiver + All = Receiver, + }; + + enum class + BitDepth : uint32_t + { + Sixteen = 0b0, + Twentyfour = SPI_I2SCFGR_DATLEN_0, + Thirtytwo = SPI_I2SCFGR_DATLEN_1, + All = Thirtytwo, + }; + + enum class + MasterClockOutput : uint32_t + { + Disabled = 0b0, + Enabled = SPI_I2SPR_MCKOE, + All = Enabled, + }; + + enum class + OddFactor : uint32_t + { + Disabled = 0, + Enabled = SPI_I2SPR_ODD, + All = Enabled, + }; +}; + +} // namespace platform + +} // namespace modm + +#endif // MODM_STM32_I2S_BASE_HPP diff --git a/src/modm/platform/i2s/stm32/i2s_hal.hpp.in b/src/modm/platform/i2s/stm32/i2s_hal.hpp.in new file mode 100644 index 0000000000..dea4d22193 --- /dev/null +++ b/src/modm/platform/i2s/stm32/i2s_hal.hpp.in @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2013, Kevin Läufer + * Copyright (c) 2013-2018, Niklas Hauser + * Copyright (c) 2014, Daniel Krebs + * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2021, Marton Ledneczki + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_STM32_I2S_HAL{{ id }}_HPP +#define MODM_STM32_I2S_HAL{{ id }}_HPP + +#include "i2s_base.hpp" + +namespace modm +{ + +namespace platform +{ + +/** + * Serial peripheral interface (I2S{{ id }}) + * + * TODO: Very basic implementation that exposes more hardware features than + * the regular Spi classes. + * + * @author Marton Ledneczki + * @ingroup modm_platform_i2s modm_platform_i2s_{{id}} + */ +class I2sHal{{ id }} : public I2sBase +{ +public: + /// TODO: Enables the clock, resets the hardware and sets the SPE bit + static void + enable(); + + /// Disables the hw module (by disabling its clock line) + static void + disable(); + + /** + * Initialize I2s Peripheral + * + * TODO: Enables clocks + */ + static void + initialize( OddFactor oddFactor, uint8_t i2s_div, + MasterSelection masterSelection = MasterSelection::Master, + DirectionSelection directionSelection = DirectionSelection::Transmitter, + BitDepth bitDepth = BitDepth::Sixteen, + MasterClockOutput masterClockOutput = MasterClockOutput::Enabled); + + static void + write(uint16_t sample); + + static void + enableInterruptVector(bool enable, uint32_t priority); + + static void + enableInterrupt(Interrupt_t interrupt); + + static void + disableInterrupt(Interrupt_t interrupt); + + static InterruptFlag_t + getInterruptFlags(); + + static void + acknowledgeInterruptFlag(InterruptFlag_t flags); +}; + +} // namespace platform + +} // namespace modm + +#include "i2s_hal_{{ id }}_impl.hpp" + +#endif // MODM_STM32_I2S_HAL{{ id }}_HPP diff --git a/src/modm/platform/i2s/stm32/i2s_hal_impl.hpp.in b/src/modm/platform/i2s/stm32/i2s_hal_impl.hpp.in new file mode 100644 index 0000000000..9683d76b93 --- /dev/null +++ b/src/modm/platform/i2s/stm32/i2s_hal_impl.hpp.in @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2013, Kevin Läufer + * Copyright (c) 2013-2017, Niklas Hauser + * Copyright (c) 2014, Daniel Krebs + * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2021, Marton Ledneczki + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_STM32_I2S_HAL{{ id }}_HPP +# error "Don't include this file directly, use 'i2s_hal{{ id }}.hpp' instead!" +#endif +#include + +void inline +modm::platform::I2sHal{{ id }}::enable() +{ + Rcc::enable(); + SPI{{ id }}->I2SCFGR |= SPI_I2SCFGR_I2SMOD | SPI_I2SCFGR_I2SE; // I2S Enable +} + +void inline +modm::platform::I2sHal{{ id }}::disable() +{ + SPI{{ id }}->I2SCFGR &= ~(uint32_t)SPI_I2SCFGR_I2SE; + Rcc::disable(); +} + +void inline +modm::platform::I2sHal{{ id }}::initialize( OddFactor oddFactor, uint8_t i2s_div, + MasterSelection masterSelection, + DirectionSelection directionSelection, + BitDepth bitDepth, + MasterClockOutput masterClockOutput) +{ + enable(); + // disable peripheral, and clear fields to be set + SPI{{ id }}->I2SCFGR &= ~(uint32_t)(SPI_I2SCFGR_I2SE | SPI_I2SCFGR_I2SCFG | SPI_I2SCFGR_PCMSYNC | + SPI_I2SCFGR_I2SSTD | SPI_I2SCFGR_CKPOL | SPI_I2SCFGR_DATLEN | + SPI_I2SCFGR_CHLEN); + SPI{{ id }}->I2SPR &= ~(uint32_t)(SPI_I2SPR_MCKOE | SPI_I2SPR_ODD | SPI_I2SPR_I2SDIV); + // set parameters + SPI{{ id }}->I2SCFGR |= static_cast(masterSelection) | + static_cast(directionSelection) | + static_cast(bitDepth); + SPI{{ id }}->I2SPR |= static_cast(masterClockOutput) | + static_cast(oddFactor) | + i2s_div; + // re-enable peripheral + SPI{{ id }}->I2SCFGR |= SPI_I2SCFGR_I2SE; +} + +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..5f2d46eb00 --- /dev/null +++ b/src/modm/platform/i2s/stm32/i2s_master.hpp.in @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2009-2011, Fabian Greif + * Copyright (c) 2010, Martin Rosekeit + * Copyright (c) 2011-2017, Niklas Hauser + * Copyright (c) 2012, Georgi Grinshpun + * Copyright (c) 2013, Kevin Läufer + * Copyright (c) 2014, Sascha Schade + * Copyright (C) 2021, Marton Ledneczki + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_STM32_I2S_MASTER{{ id }}_HPP +#define MODM_STM32_I2S_MASTER{{ id }}_HPP + +#include + +#include +#include +#include +#include "i2s_hal_{{ id }}.hpp" + +namespace modm +{ + +namespace platform +{ + +/** + * Inter-IC Sound (I2C{{ id }}). + * + * TODO: say something about the nature of implementation + * + * @author Marton Ledneczki + * @ingroup modm_platform_i2s modm_platform_i2s_{{id}} + */ +class I2sMaster{{ id }} : public modm::I2sMaster +{ +public: + using Hal = I2sHal{{ id }}; + + + template< template class... Signals > + static void + connect() + { + using Connector = GpioConnector; + using Ck = typename Connector::template GetSignal; + using Mck = typename Connector::template GetSignal; + using Sd = typename Connector::template GetSignal; + using Ws = typename Connector::template GetSignal; + + // Connector::disconnect(); + Ck::setOutput(Gpio::OutputType::PushPull); + Mck::setOutput(Gpio::OutputType::PushPull); + Sd::setOutput(Gpio::OutputType::PushPull); + Ws::setOutput(Gpio::OutputType::PushPull); + Connector::connect(); + } + + template< class SystemClock, frequency_t samplerate, percent_t tolerance=pct(0.019) > + static inline void + initialize() + { + constexpr float prescaler = static_cast(SystemClock::I2sPll) / (samplerate*16*2*8); + constexpr uint8_t i2s_div = static_cast(prescaler/2); + constexpr uint8_t odd_factor = ((prescaler - static_cast(i2s_div*2)) >= 0.5) ? 1 : 0; + constexpr float real_samplerate = static_cast(SystemClock::I2sPll) / + (16*2*8*(2*i2s_div+odd_factor)); + static_assert(i2s_div > 1, "I2S{{ id }}: i2s_div can not be 1 or 0!"); + static_assert(abs(samplerate - real_samplerate)/static_cast(samplerate) < pct2f(tolerance), + "I2S{{ id }}: Exceeding specified tolerance, when determining prescelar."); + Hal::initialize(odd_factor ? Hal::OddFactor::Enabled : Hal::OddFactor::Disabled, + i2s_div); + } + + // end documentation inherited +}; + +} // namespace platform + +} // namespace modm + +#endif // MODM_STM32_I2S_MASTER{{ id }}_HPP diff --git a/src/modm/platform/i2s/stm32/module.lb b/src/modm/platform/i2s/stm32/module.lb new file mode 100644 index 0000000000..57174b6f60 --- /dev/null +++ b/src/modm/platform/i2s/stm32/module.lb @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016-2018, Niklas Hauser +# Copyright (c) 2017, Fabian Greif +# Copyright (c) 2021, Marton Ledneczki +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + +def get_properties(env): + device = env[":target"] + driver = device.get_driver("i2s") + properties = {} + properties["target"] = device.identifier + properties["features"] = driver["feature"] if "feature" in driver else [] + return properties + +class Instance(Module): + def __init__(self, driver, instance): + self.instance = int(instance) + self.driver = driver + + def init(self, module): + module.name = str(self.instance) + module.description = "Instance {}".format(self.instance) + + def prepare(self, module, options): + module.depends(":platform:i2s") + return True + + def build(self, env): + properties = get_properties(env) + properties["id"] = self.instance + + env.substitutions = properties + env.outbasepath = "modm/src/modm/platform/i2s" + + env.template("i2s_hal.hpp.in", "i2s_hal_{}.hpp".format(self.instance)) + env.template("i2s_hal_impl.hpp.in", "i2s_hal_{}_impl.hpp".format(self.instance)) + env.template("i2s_master.hpp.in", "i2s_master_{}.hpp".format(self.instance)) + #env.template("i2s_master.cpp.in", "i2s_master_{}.cpp".format(self.instance)) + if env.has_module(":platform:dma"): + pass + #env.template("i2s_master_dma.hpp.in", "i2s_master_{}_dma.hpp".format(self.instance)) + #env.template("i2s_master_dma_impl.hpp.in", "i2s_master_{}_dma_impl.hpp".format(self.instance)) + + +def init(module): + module.name = ":platform:i2s" + module.description = "Inter-IC Sound (I2S)" + +def prepare(module, options): + device = options[":target"] + if not device.has_driver("i2s:stm32*"): + return False + + module.depends( + ":architecture:register", + ":architecture:i2s", + ":cmsis:device", + ":math:algorithm", + ":platform:gpio", + ":platform:rcc") + + for driver in device.get_all_drivers("i2s:stm32*"): + for instance in driver["instance"]: + module.add_submodule(Instance(driver, instance)) + + return True + +def build(env): + env.substitutions = get_properties(env) + env.outbasepath = "modm/src/modm/platform/i2s" + + env.template("i2s_base.hpp.in")