firmware-base/vendor/sming/Sming/samples/Basic_Audio/app/application.cpp
2026-01-28 16:42:43 +01:00

270 lines
6.3 KiB
C++

/****
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
* Created 2015 by Skurydin Alexey
* http://github.com/SmingHub/Sming
* All files of the Sming Core are provided under the LGPL v3 license.
*
* application.cpp
*
* Basic audio sample
*
****/
#include <SmingCore.h>
#include <driver/i2s.h>
#include <ToneGenerator/DeltaSigma.h>
#include <Services/Profiling/MinMaxTimes.h>
#include <SignalGenerator.h>
// If using an external DAC, set this to 0 - you'll probably need to change other settings as well
#define ENABLE_DELTA_SIGMA
namespace
{
// Set this to 0 to output fixed values for easier checking on a scope or signal analyzer
//#define GENERATE_FIXED_VALUES
constexpr i2s_sample_t fixedSampleValue = {0xF55F0AA0};
// I2S sample rate to request from hardware
constexpr unsigned targetSampleRate = 44100;
// Target frequency to generate
constexpr float sineWaveFrequency = 440;
// Measure time taken to fill the I2S DMA buffers
Profiling::MicroTimes fillTime("Fill Time");
// Measure time taken between I2S callback (interrupt) and our task callback getting executed
Profiling::MicroTimes callbackLatency("Callback latency");
// Report status periodically
SimpleTimer statusTimer;
constexpr unsigned statusIntervalMs = 5000;
// One full sine-wave cycle
struct SineWaveTable {
std::unique_ptr<uint16_t[]> samples;
unsigned sampleCount = 0;
unsigned readPos = 0;
bool generate(float sampleRate, float frequency)
{
samples.reset();
readPos = 0;
sampleCount = round(sampleRate / frequency);
// Modify frequency to fit in exact number of samples
frequency = sampleRate / sampleCount;
Serial << _F("Generating sine wave table @ ") << frequency << _F(" Hz, ") << sampleCount << _F(" samples")
<< endl;
samples = std::make_unique<uint16_t[]>(sampleCount);
if(!samples) {
debug_e("Memory allocation failed");
return false;
}
SignalGenerator gen(eST_Sine, frequency);
for(unsigned i = 0; i < sampleCount; ++i) {
float t = i / (sampleCount * frequency);
float value = gen.getValue(t);
samples[i] = round(value * 32767);
}
return true;
}
uint16_t read()
{
uint16_t value = samples[readPos++];
if(readPos == sampleCount) {
readPos = 0;
}
return value;
}
};
SineWaveTable sineWaveTable;
#ifdef GENERATE_FIXED_VALUES
void writeFixedValues()
{
i2s_buffer_info_t info;
while(i2s_dma_write(&info, UINT_MAX)) {
memset(info.samples, 0, info.size);
// for(unsigned i = 0; i < info.size / sizeof(i2s_sample_t); ++i) {
// info.samples[i] = fixedSampleValue;
// }
}
}
#else
/*
* Outputs a 172.266Hz sine wave (256 samples at 44100 samples/sec)
*/
void writeSine()
{
unsigned frames_written = 0;
i2s_buffer_info_t info;
while(i2s_dma_write(&info, UINT_MAX)) {
unsigned frame_count = info.size / sizeof(i2s_sample_t);
for(unsigned i = 0; i < frame_count; ++i) {
uint32_t sample = sineWaveTable.read();
#ifdef ENABLE_DELTA_SIGMA
static DeltaSigma mod;
sample = mod.update(sample);
#endif
info.samples[i].u32 = sample;
}
frames_written += frame_count;
}
}
#endif // GENERATE_FIXED_VALUES
void fillBuffers()
{
callbackLatency.update();
fillTime.start();
#ifdef GENERATE_FIXED_VALUES
writeFixedValues();
#else
writeSine();
#endif
fillTime.update();
}
void checkReceive()
{
unsigned total = 0;
i2s_buffer_info_t info;
while(i2s_dma_read(&info, UINT_MAX)) {
total += info.size;
m_printHex("DATA", info.buffer, info.size);
}
debug_i("RX: %u bytes", total);
}
void IRAM_ATTR i2sCallback(void*, i2s_event_type_t event)
{
// For this sample, process the data in task context
switch(event) {
case I2S_EVENT_TX_DONE:
System.queueCallback(fillBuffers);
callbackLatency.start();
break;
case I2S_EVENT_RX_DONE:
System.queueCallback(checkReceive);
break;
default:; // ignore
}
}
void initialiseI2S()
{
i2s_config_t config;
memset(&config, 0, sizeof(config));
const i2s_module_config_t modcfg = {
.mode = I2S_MODE_MASTER,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = I2S_COMM_FORMAT_I2S_MSB,
.dma_buf_len = 128,
.dma_buf_count = 4,
.callback_threshold = 1,
};
/*
* Not clear exactly what this setting does.
* I think, additional bits written out.
*
* For example, 16-bit channel data + bits_mod=8 produces 24-bits per channel.
* Where do additional 8 bits come from? Scope says 0.
*/
config.bits_mod = 16;
config.tx = modcfg;
config.sample_rate = targetSampleRate;
// config.rx = modcfg;
// config.rx.mode = I2S_MODE_SLAVE;
// config.tx_desc_auto_clear = true;
// config.auto_start = true;
config.callback = i2sCallback;
config.param = nullptr; // If you want to pass a parameter to the callback routine
if(!i2s_driver_install(&config)) {
debug_e("i2s_driver_install failed");
return;
}
#ifdef ENABLE_DELTA_SIGMA
// We're doing delta mod so only need one pin
i2s_set_pins(I2S_PIN_DATA_OUT, true);
#else
// Real I2S DACs require channel select (assuming its stereo) and clock lines
i2s_set_pins(I2S_PIN_DATA_OUT | I2S_PIN_WS_OUT | I2S_PIN_BCK_OUT, true);
#endif
auto realSampleRate = i2s_get_real_rate();
Serial << _F("I2S initialised, rate = ") << realSampleRate << endl;
#ifndef GENERATE_FIXED_VALUES
/*
* Generate sine wave data using the actual sample rate, which may be
* different to that requested.
*/
if(!sineWaveTable.generate(realSampleRate, sineWaveFrequency)) {
return;
}
#endif
// Pre-fill the buffers
fillBuffers();
// Schedule a periodic status report
statusTimer
.initializeMs<statusIntervalMs>([]() {
Serial.println(fillTime);
fillTime.clear();
Serial.println(callbackLatency);
callbackLatency.clear();
})
.start();
// Test receive
// i2s_enable_loopback(true);
// and away we go
i2s_start();
}
} // namespace
void init()
{
/*
* Primary serial port pins are used by I2S.
* We're using the alternate pin mappings - see Serial.swap() below.
* Could use the second (output-only) UART instead
*/
// Serial.setPort(UART_ID_1);
Serial.begin(SERIAL_BAUD_RATE);
Serial.systemDebugOutput(true);
// See above - must call swap after port is initialised
Serial.swap();
// Network not required for this sample
#ifndef DISABLE_WIFI
WifiStation.enable(false, false);
WifiAccessPoint.enable(false, false);
#endif
// We could initialise I2S directly here, but not in any rush
System.onReady(initialiseI2S);
}