initial commit

This commit is contained in:
2023-02-10 10:34:41 +01:00
commit fcad3a98b6
44 changed files with 1392 additions and 0 deletions

46
lib/README Normal file
View File

@@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

View File

@@ -0,0 +1,5 @@
{
"build": {
"flags": "-Ofast"
}
}

View File

@@ -0,0 +1,32 @@
#include "ADCSampler.h"
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
ADCSampler::ADCSampler(adc_unit_t adcUnit, adc1_channel_t adcChannel, const i2s_config_t &i2s_config) : I2SSampler(I2S_NUM_0, i2s_config)
{
m_adcUnit = adcUnit;
m_adcChannel = adcChannel;
}
void ADCSampler::configureI2S()
{
//init ADC pad
i2s_set_adc_mode(m_adcUnit, m_adcChannel);
// enable the adc
i2s_adc_enable(m_i2sPort);
}
int ADCSampler::read(int16_t *samples, int count)
{
// read from i2s
size_t bytes_read = 0;
i2s_read(m_i2sPort, samples, sizeof(int16_t) * count, &bytes_read, portMAX_DELAY);
int samples_read = bytes_read / sizeof(int16_t);
for (int i = 0; i < samples_read; i++)
{
samples[i] = (2048 - (uint16_t(samples[i]) & 0xfff)) * 15;
}
return samples_read;
}
#endif

View File

@@ -0,0 +1,18 @@
#pragma once
#include <driver/adc.h>
#include "I2SSampler.h"
class ADCSampler : public I2SSampler
{
private:
adc_unit_t m_adcUnit;
adc1_channel_t m_adcChannel;
protected:
void configureI2S();
public:
ADCSampler(adc_unit_t adc_unit, adc1_channel_t adc_channel, const i2s_config_t &i2s_config);
virtual int read(int16_t *samples, int count);
};

View File

@@ -0,0 +1,52 @@
#include "I2SMEMSSampler.h"
#include "soc/i2s_reg.h"
I2SMEMSSampler::I2SMEMSSampler(
i2s_port_t i2s_port,
i2s_pin_config_t &i2s_pins,
i2s_config_t i2s_config,
int raw_samples_size,
bool fixSPH0645) : I2SSampler(i2s_port, i2s_config)
{
m_i2sPins = i2s_pins;
m_fixSPH0645 = fixSPH0645;
m_raw_samples_size = raw_samples_size;
m_raw_samples = (int32_t *)malloc(sizeof(int32_t) * raw_samples_size);
}
I2SMEMSSampler::~I2SMEMSSampler()
{
free(m_raw_samples);
}
void I2SMEMSSampler::configureI2S()
{
if (m_fixSPH0645)
{
// FIXES for SPH0645
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
REG_SET_BIT(I2S_TIMING_REG(m_i2sPort), BIT(9));
REG_SET_BIT(I2S_CONF_REG(m_i2sPort), I2S_RX_MSB_SHIFT);
#endif
}
i2s_set_pin(m_i2sPort, &m_i2sPins);
}
int I2SMEMSSampler::read(int16_t *samples, int count)
{
// read from i2s
size_t bytes_read = 0;
if (count>m_raw_samples_size)
{
count = m_raw_samples_size; // Buffer is too small
}
i2s_read(m_i2sPort, m_raw_samples, sizeof(int32_t) * count, &bytes_read, portMAX_DELAY);
int samples_read = bytes_read / sizeof(int32_t);
for (int i = 0; i < samples_read; i++)
{
int32_t temp = m_raw_samples[i] >> 11;
samples[i] = (temp > INT16_MAX) ? INT16_MAX : (temp < -INT16_MAX) ? -INT16_MAX : (int16_t)temp;
}
return samples_read;
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include "I2SSampler.h"
class I2SMEMSSampler : public I2SSampler
{
private:
i2s_pin_config_t m_i2sPins;
bool m_fixSPH0645;
int32_t *m_raw_samples;
int m_raw_samples_size;
protected:
void configureI2S();
public:
I2SMEMSSampler(
i2s_port_t i2s_port,
i2s_pin_config_t &i2s_pins,
i2s_config_t i2s_config,
int raw_samples_size,
bool fixSPH0645 = false);
~I2SMEMSSampler();
virtual int read(int16_t *samples, int count);
};

View File

@@ -0,0 +1,21 @@
#include "I2SSampler.h"
#include "driver/i2s.h"
I2SSampler::I2SSampler(i2s_port_t i2sPort, const i2s_config_t &i2s_config) : m_i2sPort(i2sPort), m_i2s_config(i2s_config)
{
}
void I2SSampler::start()
{
//install and start i2s driver
i2s_driver_install(m_i2sPort, &m_i2s_config, 0, NULL);
// set up the I2S configuration from the subclass
configureI2S();
}
void I2SSampler::stop()
{
// stop the i2S driver
i2s_driver_uninstall(m_i2sPort);
}

View File

@@ -0,0 +1,28 @@
#pragma once
#include <freertos/FreeRTOS.h>
#include <driver/i2s.h>
/**
* Base Class for both the ADC and I2S sampler
**/
class I2SSampler
{
protected:
i2s_port_t m_i2sPort = I2S_NUM_0;
i2s_config_t m_i2s_config;
virtual void configureI2S() = 0;
virtual void processI2SData(void *samples, size_t count){
// nothing to do for the default case
};
public:
I2SSampler(i2s_port_t i2sPort, const i2s_config_t &i2sConfig);
void start();
virtual int read(int16_t *samples, int count) = 0;
void stop();
int sample_rate()
{
return m_i2s_config.sample_rate;
}
};

View File

@@ -0,0 +1,5 @@
{
"build": {
"flags": "-Ofast"
}
}

View File

@@ -0,0 +1,30 @@
#include "DACOutput.h"
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
void DACOutput::start(int sample_rate)
{
// i2s config for writing both channels of I2S
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN),
.sample_rate = sample_rate,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S_MSB),
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 2,
.dma_buf_len = 1024,
.use_apll = false,
.tx_desc_auto_clear = true,
.fixed_mclk = 0};
//install and start i2s driver
i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
// enable the DAC channels
i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN);
// clear the DMA buffers
i2s_zero_dma_buffer(I2S_NUM_0);
i2s_start(I2S_NUM_0);
}
#endif

View File

@@ -0,0 +1,20 @@
#pragma once
#include <freertos/FreeRTOS.h>
#include <driver/i2s.h>
#include "Output.h"
/**
* Base Class for both the ADC and I2S sampler
**/
class DACOutput : public Output
{
public:
DACOutput(i2s_port_t i2s_port) : Output(i2s_port) {}
void start(int sample_rate);
virtual int16_t process_sample(int16_t sample)
{
// DAC needs unsigned 16 bit samples
return sample + 32768;
}
};

View File

@@ -0,0 +1,35 @@
#include "I2SOutput.h"
I2SOutput::I2SOutput(i2s_port_t i2s_port, i2s_pin_config_t &i2s_pins) : Output(i2s_port), m_i2s_pins(i2s_pins)
{
}
void I2SOutput::start(int sample_rate)
{
// i2s config for writing both channels of I2S
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = sample_rate,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0)
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S),
#else
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S),
#endif
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 2,
.dma_buf_len = 1024,
.use_apll = false,
.tx_desc_auto_clear = true,
.fixed_mclk = 0};
//install and start i2s driver
i2s_driver_install(m_i2s_port, &i2s_config, 0, NULL);
// set up the i2s pins
i2s_set_pin(m_i2s_port, &m_i2s_pins);
// clear the DMA buffers
i2s_zero_dma_buffer(m_i2s_port);
i2s_start(m_i2s_port);
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include "Output.h"
/**
* Base Class for both the ADC and I2S sampler
**/
class I2SOutput : public Output
{
private:
i2s_pin_config_t m_i2s_pins;
public:
I2SOutput(i2s_port_t i2s_port, i2s_pin_config_t &i2s_pins);
void start(int sample_rate);
};

View File

@@ -0,0 +1,51 @@
#include "Output.h"
#include <esp_log.h>
#include <driver/i2s.h>
static const char *TAG = "OUT";
// number of frames to try and send at once (a frame is a left and right sample)
const int NUM_FRAMES_TO_SEND = 256;
Output::Output(i2s_port_t i2s_port) : m_i2s_port(i2s_port)
{
// this will contain the prepared samples for sending to the I2S device
m_frames = (int16_t *)malloc(2 * sizeof(int16_t) * NUM_FRAMES_TO_SEND);
}
Output::~Output()
{
free(m_frames);
}
void Output::stop()
{
// stop the i2S driver
i2s_stop(m_i2s_port);
i2s_driver_uninstall(m_i2s_port);
}
void Output::write(int16_t *samples, int count)
{
int sample_index = 0;
while (sample_index < count)
{
int samples_to_send = 0;
for (int i = 0; i < NUM_FRAMES_TO_SEND && sample_index < count; i++)
{
int sample = process_sample(samples[sample_index]);
m_frames[i * 2] = sample; // left channel
m_frames[i * 2 + 1] = sample; // right channel
samples_to_send++;
sample_index++;
}
// write data to the i2s peripheral
size_t bytes_written = 0;
i2s_write(m_i2s_port, m_frames, samples_to_send * sizeof(int16_t) * 2, &bytes_written, portMAX_DELAY);
if (bytes_written != samples_to_send * sizeof(int16_t) * 2)
{
ESP_LOGE(TAG, "Did not write all bytes");
}
}
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include <freertos/FreeRTOS.h>
#include <driver/i2s.h>
/**
* Base Class for both the DAC and I2S output
**/
class Output
{
private:
int16_t *m_frames;
protected:
i2s_port_t m_i2s_port = I2S_NUM_0;
public:
Output(i2s_port_t i2s_port);
~Output();
virtual void start(int sample_rate) = 0;
void stop();
// override this in derived classes to turn the sample into
// something the output device expects - for the default case
// this is simply a pass through
virtual int16_t process_sample(int16_t sample) { return sample; }
void write(int16_t *samples, int count);
};

View File

@@ -0,0 +1,99 @@
#pragma once
#include <Arduino.h>
#include <freertos/FreeRTOS.h>
/**
* @brief Circular buffer for 8 bit unsigned PCM samples
*
*/
class OutputBuffer
{
private:
// how many samples should we buffer before outputting data?
int m_number_samples_to_buffer;
// where are we reading from
int m_read_head;
// where are we writing to
int m_write_head;
// keep track of how many samples we have
int m_available_samples;
// the total size of the buffer
int m_buffer_size;
// are we currently buffering samples?
bool m_buffering;
// the sample buffer
uint8_t *m_buffer;
// thread safety
SemaphoreHandle_t m_semaphore;
public:
OutputBuffer(int number_samples_to_buffer) : m_number_samples_to_buffer(number_samples_to_buffer)
{
// create a semaphore and make it available for locking
m_semaphore = xSemaphoreCreateBinary();
xSemaphoreGive(m_semaphore);
// set reading and writing to the beginning of the buffer
m_read_head = 0;
m_write_head = 0;
m_available_samples = 0;
// we'll start off buffering data as we have no samples yet
m_buffering = true;
// make sufficient space for the bufferring and incoming data
m_buffer_size = 3 * number_samples_to_buffer;
m_buffer = (uint8_t *)malloc(m_buffer_size);
memset(m_buffer, 0, m_buffer_size);
if (!m_buffer)
{
Serial.println("Failed to allocate buffer");
}
}
// we're adding samples that are 8 bit as they are coming from the transport
void add_samples(const uint8_t *samples, int count)
{
xSemaphoreTake(m_semaphore, portMAX_DELAY);
// copy the samples into the buffer wrapping around as needed
for (int i = 0; i < count; i++)
{
m_buffer[m_write_head] = samples[i];
m_write_head = (m_write_head + 1) % m_buffer_size;
}
m_available_samples += count;
xSemaphoreGive(m_semaphore);
}
// convert the samples to 16 bit as they are going to the output
void remove_samples(int16_t *samples, int count)
{
xSemaphoreTake(m_semaphore, portMAX_DELAY);
for (int i = 0; i < count; i++)
{
samples[i] = 0;
// if we have no samples and we aren't already buffering then we need to start buffering
if (m_available_samples == 0 && !m_buffering)
{
Serial.println("Buffering");
m_buffering = true;
samples[i] = 0;
}
// are we buffering?
if (m_buffering && m_available_samples < m_number_samples_to_buffer)
{
// just return 0 as we don't have enough samples yet
samples[i] = 0;
}
else
{
// we've buffered enough samples so no need to buffer anymore
m_buffering = false;
// just send back the samples we've got and move the read head forward
int16_t sample = m_buffer[m_read_head];
samples[i] = (sample - 128) << 5;
m_read_head = (m_read_head + 1) % m_buffer_size;
m_available_samples--;
}
}
xSemaphoreGive(m_semaphore);
}
};

View File

@@ -0,0 +1,26 @@
#include "Arduino.h"
#include "GenericDevBoardIndicatorLed.h"
#ifdef LED_BUILTIN
const uint8_t BUILT_IN_LED = LED_BUILTIN;
#else
const uint8_t BUILT_IN_LED = GPIO_NUM_2;
#endif
GenericDevBoardIndicatorLed::GenericDevBoardIndicatorLed()
{
pinMode(BUILT_IN_LED, OUTPUT);
}
// we don't really have any colors so just use the built in LED
void GenericDevBoardIndicatorLed::set_led_rgb(uint32_t color)
{
if (color == 0)
{
digitalWrite(BUILT_IN_LED, LOW);
}
else
{
digitalWrite(BUILT_IN_LED, HIGH);
}
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include "IndicatorLed.h"
class GenericDevBoardIndicatorLed : public IndicatorLed
{
protected:
void set_led_rgb(uint32_t color);
public:
GenericDevBoardIndicatorLed();
};

View File

@@ -0,0 +1,34 @@
#include <Arduino.h>
#include <freertos/FreeRTOS.h>
#include "IndicatorLed.h"
void update_indicator_task(void *param)
{
IndicatorLed *indicator = reinterpret_cast<IndicatorLed *>(param);
while (true)
{
if (indicator->m_is_flashing)
{
indicator->set_led_rgb(indicator->m_flash_color);
vTaskDelay(100);
}
indicator->set_led_rgb(indicator->m_default_color);
vTaskDelay(100);
}
}
void IndicatorLed::begin()
{
TaskHandle_t task_handle;
xTaskCreate(update_indicator_task, "Indicator LED Task", 4096, this, 0, &task_handle);
}
void IndicatorLed::set_is_flashing(bool is_flashing, uint32_t flash_color)
{
m_is_flashing = is_flashing;
m_flash_color = flash_color;
}
void IndicatorLed::set_default_color(uint32_t color)
{
m_default_color = color;
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include <stdint.h>
class IndicatorLed
{
private:
bool m_is_flashing = false;
uint32_t m_default_color = 0;
uint32_t m_flash_color = 0;
protected:
virtual void set_led_rgb(uint32_t color) = 0;
public:
void begin();
void set_is_flashing(bool is_flashing, uint32_t flash_color);
void set_default_color(uint32_t color);
friend void update_indicator_task(void *param);
};

View File

@@ -0,0 +1,14 @@
#include "TinyPICOIndicatorLed.h"
#ifdef ARDUINO_TINYPICO
TinyPICOIndicatorLed::TinyPICOIndicatorLed()
{
m_tp = new TinyPICO();
}
void TinyPICOIndicatorLed::set_led_rgb(uint32_t color)
{
m_tp->DotStar_SetPixelColor(color);
}
#endif

View File

@@ -0,0 +1,21 @@
#pragma once
#include "IndicatorLed.h"
#ifdef ARDUINO_TINYPICO
#include <TinyPICO.h>
class TinyPICO;
class TinyPICOIndicatorLed : public IndicatorLed
{
private:
TinyPICO *m_tp = NULL;
protected:
void set_led_rgb(uint32_t color);
public:
TinyPICOIndicatorLed();
};
#endif

View File

@@ -0,0 +1,72 @@
#include <Arduino.h>
#include <WiFi.h>
#include <esp_now.h>
#include <esp_wifi.h>
#include "OutputBuffer.h"
#include "EspNowTransport.h"
const int MAX_ESP_NOW_PACKET_SIZE = 250;
const uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
static EspNowTransport *instance = NULL;
void receiveCallback(const uint8_t *macAddr, const uint8_t *data, int dataLen)
{
// annoyingly we can't pass an param into this so we need to do a bit of hack to access the EspNowTransport instance
int header_size = instance->m_header_size;
// first m_header_size bytes of m_buffer are the expected header
if ((dataLen > header_size) && (dataLen<=MAX_ESP_NOW_PACKET_SIZE) && (memcmp(data,instance->m_buffer,header_size) == 0))
{
instance->m_output_buffer->add_samples(data + header_size, dataLen - header_size);
}
}
bool EspNowTransport::begin()
{
// Set Wifi channel
esp_wifi_set_promiscuous(true);
esp_wifi_set_channel(m_wifi_channel, WIFI_SECOND_CHAN_NONE);
esp_wifi_set_promiscuous(false);
esp_err_t result = esp_now_init();
if (result == ESP_OK)
{
Serial.println("ESPNow Init Success");
esp_now_register_recv_cb(receiveCallback);
}
else
{
Serial.printf("ESPNow Init failed: %s\n", esp_err_to_name(result));
return false;
}
// this will broadcast a message to everyone in range
esp_now_peer_info_t peerInfo = {};
memcpy(&peerInfo.peer_addr, broadcastAddress, 6);
if (!esp_now_is_peer_exist(broadcastAddress))
{
result = esp_now_add_peer(&peerInfo);
if (result != ESP_OK)
{
Serial.printf("Failed to add broadcast peer: %s\n", esp_err_to_name(result));
return false;
}
}
return true;
}
EspNowTransport::EspNowTransport(OutputBuffer *output_buffer, uint8_t wifi_channel) : Transport(output_buffer, MAX_ESP_NOW_PACKET_SIZE)
{
instance = this;
m_wifi_channel = wifi_channel;
}
void EspNowTransport::send()
{
esp_err_t result = esp_now_send(broadcastAddress, m_buffer, m_index + m_header_size);
if (result != ESP_OK)
{
Serial.printf("Failed to send: %s\n", esp_err_to_name(result));
}
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include "Transport.h"
class OutputBuffer;
class EspNowTransport: public Transport {
private:
uint8_t m_wifi_channel;
protected:
void send();
public:
EspNowTransport(OutputBuffer *output_buffer, uint8_t wifi_channel);
virtual bool begin() override;
friend void receiveCallback(const uint8_t *macAddr, const uint8_t *data, int dataLen);
};

View File

@@ -0,0 +1,46 @@
#include "Arduino.h"
#include "Transport.h"
Transport::Transport(OutputBuffer *output_buffer, size_t buffer_size)
{
m_output_buffer = output_buffer;
m_buffer_size = buffer_size;
m_buffer = (uint8_t *)malloc(m_buffer_size);
m_index = 0;
m_header_size = 0;
}
void Transport::add_sample(int16_t sample)
{
m_buffer[m_index+m_header_size] = (sample + 32768) >> 8;
m_index++;
// have we reached a full packet?
if ((m_index+m_header_size) == m_buffer_size)
{
send();
m_index = 0;
}
}
void Transport::flush()
{
if (m_index >0 )
{
send();
m_index = 0;
}
}
int Transport::set_header(const int header_size, const uint8_t *header)
{
if ((header_size<m_buffer_size) && (header))
{
m_header_size = header_size;
memcpy(m_buffer, header, header_size);
return 0;
}
else
{
return -1;
}
}

View File

@@ -0,0 +1,26 @@
#pragma once
#include <stdlib.h>
#include <stdint.h>
class OutputBuffer;
class Transport
{
protected:
// audio buffer for samples we need to send
uint8_t *m_buffer = NULL;
int m_buffer_size = 0;
int m_index = 0;
int m_header_size;
OutputBuffer *m_output_buffer = NULL;
virtual void send() = 0;
public:
Transport(OutputBuffer *output_buffer, size_t buffer_size);
int set_header(const int header_size, const uint8_t *header);
void add_sample(int16_t sample);
void flush();
virtual bool begin() = 0;
};

View File

@@ -0,0 +1,37 @@
#include <Arduino.h>
#include <AsyncUDP.h>
#include "UdpTransport.h"
#include "OutputBuffer.h"
const int MAX_UDP_SIZE = 1436;
UdpTransport::UdpTransport(OutputBuffer *output_buffer) : Transport(output_buffer, MAX_UDP_SIZE)
{
}
unsigned long last_packet;
bool UdpTransport::begin()
{
udp = new AsyncUDP();
last_packet = millis();
if (udp->listen(8192))
{
udp->onPacket([this](AsyncUDPPacket packet)
{
// our packets contain unsigned 8 bit PCM samples
// so we can push them straight into the output buffer
if ((packet.length() > this->m_header_size) && (packet.length() <= MAX_UDP_SIZE) && (memcmp(packet.data(), this->m_buffer, this->m_header_size) == 0))
{
this->m_output_buffer->add_samples(packet.data() + m_header_size, packet.length() - m_header_size);
}
});
return true;
}
Serial.println("Failed to listen");
return false;
}
void UdpTransport::send()
{
udp->broadcast(m_buffer, m_index);
}

View File

@@ -0,0 +1,19 @@
#pragma once
#include "Transport.h"
class OutputBuffer;
class AsyncUDP;
class UdpTransport : public Transport
{
private:
AsyncUDP *udp;
protected:
void send();
public:
UdpTransport(OutputBuffer *output_buffer);
bool begin() override;
};