diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index fa79075..8360768 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -90,6 +90,7 @@ else() add_subdirectory(arduino-rx) add_subdirectory(arduino-tx) add_subdirectory(arduino-tx-obsolete) + add_subdirectory(esp32-rx) endif() if (GGWAVE_SUPPORT_SDL2) diff --git a/examples/arduino-rx/arduino-rx.ino b/examples/arduino-rx/arduino-rx.ino index fbbf0ad..2557a2f 100644 --- a/examples/arduino-rx/arduino-rx.ino +++ b/examples/arduino-rx/arduino-rx.ino @@ -10,8 +10,8 @@ const size_t kSampleSize_bytes = sizeof(TSample); // default number of output channels const char channels = 1; -// default PCM output frequency -const int frequency = 6000; +// default PCM output sampleRate +const int sampleRate = 6000; const int samplesPerFrame = 128; const int qpow = 9; @@ -63,9 +63,9 @@ void setup() { auto p = GGWave::getDefaultParameters(); p.payloadLength = 16; - p.sampleRateInp = frequency; - p.sampleRateOut = frequency; - p.sampleRate = frequency; + p.sampleRateInp = sampleRate; + p.sampleRateOut = sampleRate; + p.sampleRate = sampleRate; p.samplesPerFrame = samplesPerFrame; p.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16; p.sampleFormatOut = GGWAVE_SAMPLE_FORMAT_I16; @@ -106,7 +106,7 @@ void setup() { // - one channel (mono mode) // - a 16 kHz sample rate for the Arduino Nano 33 BLE Sense // - a 32 kHz or 64 kHz sample rate for the Arduino Portenta Vision Shields - if (!PDM.begin(channels, frequency)) { + if (!PDM.begin(channels, sampleRate)) { Serial.println(F("Failed to start PDM!")); while (1); } @@ -131,10 +131,10 @@ void loop() { auto tEnd = millis(); if (++niter % 10 == 0) { // print the time it took the last decode() call to complete - // should be smaller than samplesPerFrame/frequency seconds - // for example: samplesPerFrame = 128, frequency = 6000 => not more than 20 ms + // should be smaller than samplesPerFrame/sampleRate seconds + // for example: samplesPerFrame = 128, sampleRate = 6000 => not more than 20 ms Serial.println(tEnd - tStart); - if (tEnd - tStart > 1000*(float(samplesPerFrame)/frequency)) { + if (tEnd - tStart > 1000*(float(samplesPerFrame)/sampleRate)) { Serial.println(F("Warning: decode() took too long to execute!")); } } @@ -156,7 +156,7 @@ void loop() { send_text(ggwave, kPinSpeaker, "hello", GGWAVE_PROTOCOL_MT_FASTEST); // resume microphone capture - if (!PDM.begin(channels, frequency)) { + if (!PDM.begin(channels, sampleRate)) { Serial.println(F("Failed to start PDM!")); while (1); } diff --git a/examples/esp32-rx/.gitignore b/examples/esp32-rx/.gitignore new file mode 100644 index 0000000..4211c4f --- /dev/null +++ b/examples/esp32-rx/.gitignore @@ -0,0 +1,6 @@ +ggwave +ggwave.cpp +fft.h +resampler.h +resampler.cpp +reed-solomon diff --git a/examples/esp32-rx/CMakeLists.txt b/examples/esp32-rx/CMakeLists.txt new file mode 100644 index 0000000..c4ee279 --- /dev/null +++ b/examples/esp32-rx/CMakeLists.txt @@ -0,0 +1,10 @@ +# +# esp32-rx + +configure_file(${CMAKE_SOURCE_DIR}/include/ggwave/ggwave.h ${CMAKE_CURRENT_SOURCE_DIR}/ggwave/ggwave.h COPYONLY) +configure_file(${CMAKE_SOURCE_DIR}/src/ggwave.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ggwave.cpp COPYONLY) +configure_file(${CMAKE_SOURCE_DIR}/src/fft.h ${CMAKE_CURRENT_SOURCE_DIR}/fft.h COPYONLY) +configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/gf.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/gf.hpp COPYONLY) +configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/rs.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/rs.hpp COPYONLY) +configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/poly.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/poly.hpp COPYONLY) +configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/LICENSE COPYONLY) diff --git a/examples/esp32-rx/esp32-rx.ino b/examples/esp32-rx/esp32-rx.ino new file mode 100644 index 0000000..5c8b6a0 --- /dev/null +++ b/examples/esp32-rx/esp32-rx.ino @@ -0,0 +1,156 @@ +#include "ggwave/ggwave.h" + +#include + +// global GGwave instance +GGWave ggwave; + +using TSample = int16_t; +const size_t kSampleSize_bytes = sizeof(TSample); + +const int sampleRate = 12000; +const int samplesPerFrame = 256; + +TSample sampleBuffer[samplesPerFrame]; + +const i2s_port_t i2s_port = I2S_NUM_0; +const adc_unit_t adc_unit = ADC_UNIT_1; +const adc1_channel_t adc_channel = ADC1_GPIO35_CHANNEL; + +// i2s config for using the internal ADC +const i2s_config_t adc_i2s_config = { + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN), + .sample_rate = sampleRate, + .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, + .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, + .communication_format = I2S_COMM_FORMAT_I2S_LSB, + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, + .dma_buf_count = 4, + .dma_buf_len = samplesPerFrame, + .use_apll = false, + .tx_desc_auto_clear = false, + .fixed_mclk = 0 +}; + +void setup() { + Serial.begin(115200); + while (!Serial); + Serial.println(F("GGWave test for ESP32")); + + { + Serial.println(F("Trying to create ggwave instance")); + + ggwave.setLogFile(nullptr); + + auto p = GGWave::getDefaultParameters(); + + p.payloadLength = 16; + p.sampleRateInp = sampleRate; + p.sampleRateOut = sampleRate; + p.sampleRate = sampleRate; + p.samplesPerFrame = samplesPerFrame; + p.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16; + p.sampleFormatOut = GGWAVE_SAMPLE_FORMAT_I16; + p.operatingMode = GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX | GGWAVE_OPERATING_MODE_USE_DSS | GGWAVE_OPERATING_MODE_TX_ONLY_TONES; + + GGWave::Protocols::tx().disableAll(); + //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_DT_NORMAL, true); + //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_DT_FAST, true); + //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_DT_FASTEST, true); + GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_NORMAL, true); + GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_FAST, true); + GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_FASTEST, true); + + GGWave::Protocols::rx().disableAll(); + //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_NORMAL, true); + //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FAST, true); + //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FASTEST, true); + GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_NORMAL, true); + GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FAST, true); + GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FASTEST, true); + + ggwave.prepare(p); + + delay(1000); + + Serial.print(F("Instance initialized! Memory used: ")); + Serial.print(ggwave.heapSize()); + Serial.println(F(" bytes")); + + Serial.println(F("Trying to start I2S ADC")); + } + + { + //install and start i2s driver + i2s_driver_install(i2s_port, &adc_i2s_config, 0, NULL); + + //init ADC pad + i2s_set_adc_mode(adc_unit, adc_channel); + + // enable the adc + i2s_adc_enable(i2s_port); + + Serial.println(F("I2S ADC started")); + } +} + +void loop() { + static int nr = 0; + static int niter = 0; + + static GGWave::TxRxData result; + + // read from i2s - the samples are 12-bit so we need to do some massaging to make them 16-bit + { + size_t bytes_read = 0; + i2s_read(i2s_port, sampleBuffer, sizeof(int16_t)*samplesPerFrame, &bytes_read, portMAX_DELAY); + + int samples_read = bytes_read / sizeof(int16_t); + if (samples_read != samplesPerFrame) { + Serial.println("Failed to read samples"); + return; + } + + for (int i = 0; i < samples_read; i += 2) { + auto & s0 = sampleBuffer[i]; + auto & s1 = sampleBuffer[i + 1]; + + s0 = s0 & 0x0fff; + s1 = s1 & 0x0fff; + + s0 = s0 ^ s1; + s1 = s0 ^ s1; + s0 = s0 ^ s1; + } + } + + auto tStart = millis(); + if (ggwave.decode(sampleBuffer, samplesPerFrame*kSampleSize_bytes) == false) { + Serial.println("Failed to decode"); + } + auto tEnd = millis(); + + if (++niter % 10 == 0) { + // print the time it took the last decode() call to complete + // should be smaller than samplesPerFrame/sampleRate seconds + // for example: samplesPerFrame = 128, sampleRate = 6000 => not more than 20 ms + Serial.println(tEnd - tStart); + if (tEnd - tStart > 1000*(float(samplesPerFrame)/sampleRate)) { + Serial.println(F("Warning: decode() took too long to execute!")); + } + } + + nr = ggwave.rxTakeData(result); + if (nr > 0) { + Serial.println(tEnd - tStart); + Serial.print(F("Received data with length ")); + Serial.print(nr); // should be equal to p.payloadLength + Serial.println(F(" bytes:")); + + Serial.println((char *) result.data()); + } + + //for (int i = 0; i < samples_read; i++) { + // Serial.println(samples[i]); + //} +}