Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8f66fcc
[architecture] I2sMaster created
ledneczki Apr 28, 2021
e158b11
[dma] Support double buffer (only stm32-stream-channel)
rleh Oct 29, 2021
294b380
[rcc] Add support for I2S pll on STM32F4
ledneczki Apr 18, 2021
e8b8e36
[board] Disco-F469: MicroSD card connector
rleh Feb 28, 2022
5a040e0
[stm32] I2S master implementation with DMA
ledneczki Apr 29, 2021
d51569b
[driver] Add CS43L22 audio DAC (I2S + I²C)
ledneczki May 9, 2021
3bb021d
[board] Adapt STM32F4-Disco BSP for I2S DAC CS43L22
ledneczki May 4, 2021
ef6baee
[example] STM32F4-Disco: CS43L22 I2S DAC example
rleh Oct 30, 2021
9d7cae5
[driver] Add MA12070P audio DAC (I2S + I²C)
rleh Feb 28, 2022
d31ea26
WIP [example] STM32F469-Disco: MA12070P I2S DAC example
rleh Feb 28, 2022
1a8494b
fixup_example_cs43l22
rleh Mar 2, 2022
9f243a2
fixup_platform_i2s
rleh Mar 2, 2022
5251e28
fixup_architecture_i2s
rleh Mar 2, 2022
cb87fe0
fixup_rcc
rleh Mar 2, 2022
db6e309
fixup_ma12070p
rleh Mar 7, 2022
7b42b83
fixup_cs43l22
rleh Mar 7, 2022
cc6cb20
fixup_disco_f407vg
rleh Mar 7, 2022
9d38a46
fixup_example_ma12070p
rleh Mar 7, 2022
eacad10
fixup platform i2s: disable stm32h7
rleh Mar 8, 2022
001c09a
fixup platform i2s: stm32f410 has no PllI2s
rleh Mar 8, 2022
9b7d3e8
fixup driver ma12070p
rleh Mar 18, 2022
a4cffe6
fixup example stm32f469_discovery/audio_ma12070p
rleh Mar 18, 2022
7406d7d
fixup audio_ma12070p: move to nucleo-f429 dev board
rleh Mar 22, 2022
a816af3
fixup example ma12070p: adapt changed I2sMaster API
rleh Mar 22, 2022
3ead27e
fixup platform/i2s: fix prescaler calculation, i2s config is now temp…
rleh Mar 22, 2022
2c85e4f
fixup dma: enable double buffer also for 'stm32-mux-stream' IP
rleh Mar 22, 2022
4859b64
fixup driver ma12070p
rleh Mar 22, 2022
812d354
fixup driver cs43l22
rleh Mar 22, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,26 @@ Please [discover modm's peripheral drivers for your specific device][discover].
<td align="center">✅</td>
<td align="center">✅</td>
</tr><tr>
<td align="left">I2S</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td align="center">✕</td>
<td align="center">○</td>
<td align="center">✕</td>
<td align="center">✕</td>
<td align="center">✕</td>
<td align="center">✕</td>
<td align="center">✕</td>
</tr><tr>
<td align="left">I<sup>2</sup>C</td>
<td align="center">✅</td>
<td align="center">✅</td>
Expand Down
79 changes: 79 additions & 0 deletions examples/stm32f469_discovery/audio_ma12070p/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2022, Raphael Lehmann
*
* This file is part of the modm project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
// ----------------------------------------------------------------------------

#include <modm/board.hpp>
#include <modm/processing.hpp>
#include <modm/driver/dac/ma12070p.hpp>
#include <modm/platform/i2c/i2c_master_1.hpp>
#include <optional>

using namespace Board;
using namespace modm::literals;

using I2cMaster = modm::platform::I2cMaster1;
using Ma12070p = modm::Ma12070p<I2cMaster>;

constexpr uint8_t ma12070pAddressI2c = 0x20;
Ma12070p ma12070p{ma12070pAddressI2c};

int
main()
{
Board::initialize();
Board::initializeTouchscreen(); // for I2c

MODM_LOG_INFO << "Audio MA12070P demo on ST Discovery F469NI" << modm::endl;

while (!RF_CALL_BLOCKING(ma12070p.initialize())) {
MODM_LOG_ERROR << "Unable to initialize MA12070P" << modm::endl;
}


modm::ma12070p::I2sAndVlpConfig config {
.pcmWordFormat = modm::ma12070p::PcmWordFormat::RightJustifed16b,
.clockPolarity = modm::ma12070p::ClockPolarity::FallingEdge,
.frameSize = modm::ma12070p::FrameSize::Bits32,
.wordSelectPolarity = modm::ma12070p::WordSelectPolarity::High,
.rightLeftOrder = modm::ma12070p::RightLeftOrder::LeftFirst,
.useVlp = true,
.useLimiter = true,
};
while (!RF_CALL_BLOCKING(ma12070p.configureI2sAndVlp(config))) {
MODM_LOG_ERROR << "Unable to configure I2S ansd VLP settings of MA12070P" << modm::endl;
}

while (!RF_CALL_BLOCKING(ma12070p.setMasterVolume(-20_q_db))) {
MODM_LOG_ERROR << "Unable to set master volume of MA12070P" << modm::endl;
}

std::optional<modm::ma12070p::VlpMonitor_t> vlpMonitor;
std::optional<modm::ma12070p::ErrorRegister_t> errorRegister;

while (true)
{
vlpMonitor = RF_CALL_BLOCKING(ma12070p.readVlpMonitor());
if(!vlpMonitor) {
MODM_LOG_ERROR << "Unable to read VLP monitor register" << modm::endl;
}
else {
MODM_LOG_ERROR << *vlpMonitor << modm::endl;
}
errorRegister = RF_CALL_BLOCKING(ma12070p.readAccumulatedErrors());
if(!errorRegister) {
MODM_LOG_ERROR << "Unable to read accumulated error register" << modm::endl;
}
else {
MODM_LOG_ERROR << *errorRegister << modm::endl;
}
}

return 0;
}
13 changes: 13 additions & 0 deletions examples/stm32f469_discovery/audio_ma12070p/project.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<library>
<extends>modm:disco-f469ni</extends>
<options>
<option name="modm:build:build.path">../../../build/stm32f469_discovery/audio_ma12070p</option>
</options>
<modules>
<module>modm:processing:protothread</module>
<module>modm:processing:timer</module>
<module>modm:platform:i2c:1</module>
<module>modm:build:scons</module>
<module>modm:driver:ma12070p</module>
</modules>
</library>
135 changes: 135 additions & 0 deletions examples/stm32f4_discovery/audio_i2s/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright (c) 2021, Raphael Lehmann
* Copyright (c) 2021, Christopher Durand
*
* This file is part of the modm project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
// ----------------------------------------------------------------------------

#include <array>
#include <cmath>
#include <limits>
#include <modm/board.hpp>
#include <modm/debug/logger.hpp>
#include <modm/driver/dac/cs43l22.hpp>
#include <modm/io/iostream.hpp>
#include <modm/processing/timer.hpp>
#include <numbers>

#include <arm_math.h>

// 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<typename T, std::size_t length>
constexpr auto computeSinTable(uint8_t cycles=1)
{
std::array<T, length> data{};
constexpr auto HalfOutput = std::numeric_limits<T>::max() / 2; // 16 bit full scale
for (size_t i = 0; i < data.size(); ++i) {
constexpr auto pi = std::numbers::pi_v<float>;
data[i] = HalfOutput * (1 + arm_sin_f32(i * (2*pi / data.size() * cycles)));
}
return data;
}

constexpr std::size_t bufferSize = 960; // 480
auto bufferA = computeSinTable<uint16_t, bufferSize>(1);
auto bufferB = computeSinTable<uint16_t, bufferSize>(2);
volatile bool bufferA_ready{true};
volatile bool bufferB_ready{true};

void
transferCompleteIrqHandler()
{
LedGreen::reset();

if (bufferA_ready) {
cs43::I2sMaster::setTxBufferAddress(uintptr_t(bufferA.data()), bufferSize);
bufferA_ready = false;
}
else if (bufferB_ready) {
cs43::I2sMaster::setTxBufferAddress(uintptr_t(bufferB.data()), bufferSize);
bufferB_ready = false;
}
else {
LedRed::toggle();
//MODM_LOG_ERROR << "No buffer ready for DMA :(" << modm::endl;
}
cs43::I2sMaster::startDma();

LedGreen::set();
}

int
main()
{
Board::initialize();

// initialize Uart2 for MODM_LOG_*
Usart2::connect<GpioOutputA2::Tx>();
Usart2::initialize<Board::SystemClock, 115200_Bd>();

MODM_LOG_INFO << "Audio demo using CS43L22 I2S DAC on STM32F4-DSICOVERY" << modm::endl;

Dma1::enable();
Dma2::enable();

Board::initializeCs43</*samplerate=*/48_kHz, /*tolerance=*/0.02_pct>();

modm::Cs43l22<cs43::I2cMaster> audioDac{cs43::I2CAddress};
if (!RF_CALL_BLOCKING(audioDac.initialize())) {
MODM_LOG_ERROR << "Unable to initialize CS43L22 audio DAC" << modm::endl;
}
if (!RF_CALL_BLOCKING(audioDac.setMasterVolume(-600))) {
MODM_LOG_ERROR << "Unable to set master volume of CS43L22 audio DAC" << modm::endl;
}

cs43::I2sMaster::setTransferCompleteIrqHandler(transferCompleteIrqHandler);
cs43::I2sMaster::setTxBufferAddress(uintptr_t(bufferA.data()), bufferSize);
cs43::I2sMaster::start();

modm::PeriodicTimer tmr{500ms};
uint8_t counter{3};

while (true)
{
if (!bufferA_ready) {
bufferA = computeSinTable<uint16_t, bufferSize>(counter++);
bufferA_ready = true;
}
if (!bufferB_ready) {
bufferB = computeSinTable<uint16_t, bufferSize>(counter++);
bufferB_ready = true;
}
if (counter > 100) {
counter = 3;
}

if (tmr.execute()) {
LedBlue::toggle();
}

if (cs43::I2sMaster::hasDmaError()) {
MODM_LOG_ERROR << "I2S DMA Error :(" << modm::endl;
}
}

return 0;
}
13 changes: 13 additions & 0 deletions examples/stm32f4_discovery/audio_i2s/project.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<library>
<extends>modm:disco-f407vg</extends>
<options>
<option name="modm:build:build.path">../../../build/stm32f4_discovery/audio_i2s</option>
</options>
<modules>
<module>modm:debug</module>
<module>modm:build:scons</module>
<module>modm:platform:uart:2</module>
<module>modm:processing:timer</module>
<module>modm:cmsis:dsp:fast_math</module>
</modules>
</library>
103 changes: 103 additions & 0 deletions src/modm/architecture/interface/i2s_master.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright (c) 2021, Marton Lednczki
* Copyright (c) 2022, Raphael Lehmann
*
* This file is part of the modm project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
// ----------------------------------------------------------------------------

#ifndef MODM_INTERFACE_I2S_MASTER_HPP
#define MODM_INTERFACE_I2S_MASTER_HPP

#include <modm/processing/resumable.hpp>
#include "i2s.hpp"

namespace modm
{

/**
* Interface for a I2S Master
*
* @author Marton Ledneczki
* @ingroup modm_architecture_i2s
*/
class I2sMaster : public ::modm::PeripheralDriver
Comment on lines +21 to +27
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we somehow include in the name that this is an output device only? This interface does not describe a generic I²S master which could also receive audio or operate in time-multiplexed half-duplex mode. The terms master / slave do not imply a certain audio transfer direction, they only describe which device generates the clock.
If someone wanted to also implement input in the future there would be a naming issue.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to not break compatibility when (later) implementing I2S full-duplex/input functionality I would like to keep the class name, but add ONLY OUTPUT FUNCTIONALITY CURRENTLY IMPLEMENTED to the description.
(Slave (=clock receiver) would probably need another interface, but master with input you would probably just add more function/parameters to the current interface.)
What do you think?

{
#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
Loading