-
Notifications
You must be signed in to change notification settings - Fork 154
I2S driver #769
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
I2S driver #769
Changes from 10 commits
8f66fcc
e158b11
294b380
e8b8e36
5a040e0
d51569b
3bb021d
ef6baee
9d7cae5
d31ea26
1a8494b
9f243a2
5251e28
cb87fe0
db6e309
7b42b83
cc6cb20
9d38a46
eacad10
001c09a
9b7d3e8
a4cffe6
7406d7d
a816af3
3ead27e
2c85e4f
4859b64
812d354
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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; | ||
} |
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> |
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; | ||
} |
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> |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
{ | ||
#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 |
Uh oh!
There was an error while loading. Please reload this page.