diff --git a/examples/arduino-rx/arduino-rx.ino b/examples/arduino-rx/arduino-rx.ino index 37d81f5..74430aa 100644 --- a/examples/arduino-rx/arduino-rx.ino +++ b/examples/arduino-rx/arduino-rx.ino @@ -1,20 +1,52 @@ +// arduino-rx +// +// Sample sketch for receiving data using "ggwave" +// +// Tested with: +// - Arduino Nano RP2040 Connect +// +// The Arduino Nano RP2040 Connect board has a built-in microphone which is used +// in this example to capture audio data. +// +// The sketch optionally supports displaying the received "ggwave" data on an OLED display. +// Use the DISPLAY_OUTPUT macro to enable or disable this functionality. +// +// If you don't have a display, you can simply observe the decoded data in the serial monitor. +// +// If you want to perform a quick test, you can use the free "Waver" application: +// - Web: https://waver.ggerganov.com +// - Android: https://play.google.com/store/apps/details?id=com.ggerganov.Waver +// - iOS: https://apps.apple.com/us/app/waver-data-over-sound/id1543607865 +// +// Make sure to enable the "Fixed-length" option in "Waver"'s settings and set the number of +// bytes to be equal to "payloadLength" used in the sketch. Also, select a protocol that is +// listed as Rx in the current sketch. +// +// Demo: https://youtu.be/HiDpGvnxPLs +// +// Sketch: https://github.com/ggerganov/ggwave/tree/master/examples/arduino-rx +// + +// Uncoment this line to enable SSD1306 display output +//#define DISPLAY_OUTPUT 1 + #include #include +// Pin configuration const int kPinButton0 = 5; const int kPinSpeaker = 10; +// Audio capture configuration using TSample = int16_t; const size_t kSampleSize_bytes = sizeof(TSample); -// default number of output channels -const char channels = 1; - -// default PCM output sampleRate -const int sampleRate = 6000; -const int samplesPerFrame = 128; +const char channels = 1; +const int sampleRate = 6000; +const int samplesPerFrame = 128; +// Audio capture ring-buffer const int qpow = 9; const int qmax = 1 << qpow; @@ -22,17 +54,14 @@ volatile int qhead = 0; volatile int qtail = 0; volatile int qsize = 0; -// buffer to read samples into, each sample is 16-bits TSample sampleBuffer[qmax]; +// Error handling volatile int err = 0; -// global GGwave instance +// Global GGwave instance GGWave ggwave; -// uncoment this to enable SSD1306 display output -#define DISPLAY_OUTPUT 1 - #ifdef DISPLAY_OUTPUT #include @@ -54,7 +83,7 @@ Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); #endif -// helper function to output the generated GGWave waveform via a buzzer +// Helper function to output the generated GGWave waveform via a buzzer void send_text(GGWave & ggwave, uint8_t pin, const char * text, GGWave::TxProtocolId protocolId) { Serial.print(F("Sending text: ")); Serial.println(text); @@ -77,7 +106,7 @@ void send_text(GGWave & ggwave, uint8_t pin, const char * text, GGWave::TxProtoc void setup() { Serial.begin(57600); - while (!Serial); + //while (!Serial); pinMode(kPinSpeaker, OUTPUT); pinMode(kPinButton0, INPUT_PULLUP); @@ -110,30 +139,37 @@ void setup() { } #endif - Serial.println(F("Trying to create ggwave instance")); - - ggwave.setLogFile(nullptr); - + // Initialize "ggwave" { + Serial.println(F("Trying to initialize the ggwave instance")); + + ggwave.setLogFile(nullptr); + auto p = GGWave::getDefaultParameters(); + // Adjust the "ggwave" parameters to your needs. + // Make sure that the "payloadLength" parameter matches the one used on the transmitting side. 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.sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8; p.operatingMode = GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX | GGWAVE_OPERATING_MODE_USE_DSS | GGWAVE_OPERATING_MODE_TX_ONLY_TONES; + // Protocols to use for TX + // Remove the ones that you don't need to reduce memory usage 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_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); + // Protocols to use for RX + // Remove the ones that you don't need to reduce memory usage GGWave::Protocols::rx().disableAll(); GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_NORMAL, true); GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FAST, true); @@ -142,15 +178,15 @@ void setup() { GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FAST, true); GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FASTEST, true); - + // Initialize the ggwave instance and print the memory usage ggwave.prepare(p); - Serial.println(ggwave.heapSize()); + + Serial.print(F("Instance initialized successfully! Memory used: ")); + Serial.print(ggwave.heapSize()); + Serial.println(F(" bytes")); } - delay(1000); - - Serial.println(F("Instance initialized")); - + // Start capturing audio { // Configure the data receive callback PDM.onReceive(onPDMdata); @@ -159,10 +195,7 @@ void setup() { // Defaults to 20 on the BLE Sense and -10 on the Portenta Vision Shields //PDM.setGain(30); - // Initialize PDM with: - // - 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 + // Initialize PDM: if (!PDM.begin(channels, sampleRate)) { Serial.println(F("Failed to start PDM!")); while (1); @@ -178,8 +211,10 @@ void loop() { GGWave::TxRxData result; char resultLast[17]; + // Main loop .. while (true) { while (qsize >= samplesPerFrame) { + // We have enough captured samples - try to decode any "ggwave" data: auto tStart = millis(); ggwave.decode(sampleBuffer + qhead, samplesPerFrame*kSampleSize_bytes); @@ -191,15 +226,16 @@ 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/sampleRate seconds - // for example: samplesPerFrame = 128, sampleRate = 6000 => not more than 20 ms + // Print the time it took the last decode() call to complete. + // The time 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!")); } } + // Check if we have successfully decoded any data: nr = ggwave.rxTakeData(result); if (nr > 0) { Serial.println(tEnd - tStart); @@ -225,12 +261,16 @@ void loop() { } } + // This should never happen. + // If it does - there is something wrong with the audio capturing callback. + // For example, the microcontroller is not able to process the captured data in real-time. if (err > 0) { Serial.println(F("ERRROR")); Serial.println(err); err = 0; } + // If the button has been presse - transmit the last received data: int but0 = digitalRead(kPinButton0); if (but0 == LOW && but0Prev == HIGH) { Serial.println(F("Button 0 pressed - transmitting ..")); @@ -268,7 +308,7 @@ void onPDMdata() { const int nSamples = bytesAvailable/kSampleSize_bytes; if (qsize + nSamples > qmax) { - // if you hit this error, try to increase qmax + // If you hit this error, try to increase qmax err += 10; qhead = 0; @@ -282,7 +322,7 @@ void onPDMdata() { qsize += nSamples; if (qtail > qmax) { - // if you hit this error, qmax is probably not a multiple of the recorded samples + // If you hit this error, qmax is probably not a multiple of the recorded samples err += 1; } diff --git a/examples/arduino-tx/arduino-tx.ino b/examples/arduino-tx/arduino-tx.ino index 7b1bf79..466ee9c 100644 --- a/examples/arduino-tx/arduino-tx.ino +++ b/examples/arduino-tx/arduino-tx.ino @@ -1,5 +1,28 @@ +// arduino-tx +// +// Sample sketch for transmitting data using "ggwave" +// +// Tested with: +// - Arduino Uno R3 +// - Arduino Nano RP2040 Connect +// - NodeMCU-ESP32-S +// +// If you want to perform a quick test, you can use the free "Waver" application: +// - Web: https://waver.ggerganov.com +// - Android: https://play.google.com/store/apps/details?id=com.ggerganov.Waver +// - iOS: https://apps.apple.com/us/app/waver-data-over-sound/id1543607865 +// +// Make sure to enable the "Fixed-length" option in "Waver"'s settings and set the number of +// bytes to be equal to "payloadLength" used in the sketch. +// +// Demo: https://youtu.be/qbzKo3zbQcI +// +// Sketch: https://github.com/ggerganov/ggwave/tree/master/examples/arduino-tx +// + #include +// Pin configuration const int kPinLed0 = 13; const int kPinSpeaker = 10; const int kPinButton0 = 2; @@ -8,13 +31,13 @@ const int kPinButton1 = 4; const int samplesPerFrame = 128; const int sampleRate = 6000; -// global GGwave instance +// Global GGwave instance GGWave ggwave; char txt[64]; #define P(str) (strcpy_P(txt, PSTR(str)), txt) -// helper function to output the generated GGWave waveform via a buzzer +// Helper function to output the generated GGWave waveform via a buzzer void send_text(GGWave & ggwave, uint8_t pin, const char * text, GGWave::TxProtocolId protocolId) { Serial.print(F("Sending text: ")); Serial.println(text); @@ -44,26 +67,37 @@ void setup() { pinMode(kPinButton0, INPUT); pinMode(kPinButton1, INPUT); - Serial.println(F("Trying to create ggwave instance")); + // Initialize "ggwave" + { + Serial.println(F("Trying to initialize the ggwave instance")); - 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_U8; - p.operatingMode = GGWAVE_OPERATING_MODE_TX | GGWAVE_OPERATING_MODE_TX_ONLY_TONES | GGWAVE_OPERATING_MODE_USE_DSS; + ggwave.setLogFile(nullptr); - GGWave::Protocols::tx().only(GGWAVE_PROTOCOL_MT_FASTEST); - ggwave.prepare(p); - ggwave.setLogFile(nullptr); - Serial.println(ggwave.heapSize()); + auto p = GGWave::getDefaultParameters(); - Serial.println(F("Instance initialized")); + // Adjust the "ggwave" parameters to your needs. + // Make sure that the "payloadLength" parameter matches the one used on the transmitting side. + 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_U8; + p.operatingMode = GGWAVE_OPERATING_MODE_TX | GGWAVE_OPERATING_MODE_TX_ONLY_TONES | GGWAVE_OPERATING_MODE_USE_DSS; + + // Protocols to use for TX + GGWave::Protocols::tx().only(GGWAVE_PROTOCOL_MT_FASTEST); + + // Initialize the ggwave instance and print the memory usage + ggwave.prepare(p); + Serial.println(ggwave.heapSize()); + + Serial.println(F("Instance initialized successfully!")); + } } +// Button state int pressed = 0; bool isDown = false; diff --git a/examples/esp32-rx/esp32-rx.ino b/examples/esp32-rx/esp32-rx.ino index 0252ae6..9201ae1 100644 --- a/examples/esp32-rx/esp32-rx.ino +++ b/examples/esp32-rx/esp32-rx.ino @@ -1,23 +1,65 @@ +// esp32-rx +// +// Sample sketch for receiving data using "ggwave" +// +// Tested with: +// - NodeMCU-ESP32-S +// +// Tested microphones: +// - MAX9814 +// - KY-037 +// - KY-038 +// - WS Sound sensor +// +// The ESP32 microcontroller has a built-int 12-bit ADC which is used to digitalize the analog signal +// from the external microphone. +// +// The sketch optionally supports displaying the received "ggwave" data on an OLED display. +// Use the DISPLAY_OUTPUT macro to enable or disable this functionality. +// +// If you don't have a display, you can simply observe the decoded data in the serial monitor. +// +// If you want to perform a quick test, you can use the free "Waver" application: +// - Web: https://waver.ggerganov.com +// - Android: https://play.google.com/store/apps/details?id=com.ggerganov.Waver +// - iOS: https://apps.apple.com/us/app/waver-data-over-sound/id1543607865 +// +// Make sure to enable the "Fixed-length" option in "Waver"'s settings and set the number of +// bytes to be equal to "payloadLength" used in the sketch. Also, select a protocol that is +// listed as Rx in the current sketch. +// +// Demo: https://youtu.be/38JoMwdpH6I +// +// Sketch: https://github.com/ggerganov/ggwave/tree/master/examples/esp32-rx +// + +// Uncoment this line to enable SSD1306 display output +//#define DISPLAY_OUTPUT 1 + #include #include #include -// global GGwave instance +// Global GGwave instance GGWave ggwave; +// Audio capture configuration using TSample = int16_t; const size_t kSampleSize_bytes = sizeof(TSample); +// High sample rate - better quality, but more CPU/Memory usage const int sampleRate = 24000; const int samplesPerFrame = 512; -// switch to the following settings if only using MT protocols +// Low sample rate +// Only MT protocols will work in this mode //const int sampleRate = 12000; //const int samplesPerFrame = 256; TSample sampleBuffer[samplesPerFrame]; +// ADC configuration 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; @@ -37,9 +79,6 @@ const i2s_config_t adc_i2s_config = { .fixed_mclk = 0 }; -// uncoment this to enable SSD1306 display output -#define DISPLAY_OUTPUT 1 - #ifdef DISPLAY_OUTPUT #include @@ -64,7 +103,6 @@ Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); void setup() { Serial.begin(115200); while (!Serial); - Serial.println(F("GGWave test for ESP32")); #ifdef DISPLAY_OUTPUT { @@ -94,27 +132,34 @@ void setup() { } #endif + // Initialize "ggwave" { - Serial.println(F("Trying to create ggwave instance")); + Serial.println(F("Trying to initialize the ggwave instance")); ggwave.setLogFile(nullptr); auto p = GGWave::getDefaultParameters(); + // Adjust the "ggwave" parameters to your needs. + // Make sure that the "payloadLength" parameter matches the one used on the transmitting side. 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.sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8; p.operatingMode = GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX | GGWAVE_OPERATING_MODE_USE_DSS | GGWAVE_OPERATING_MODE_TX_ONLY_TONES; + // Protocols to use for TX + // Remove the ones that you don't need to reduce memory usage GGWave::Protocols::tx().disableAll(); //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); + // Protocols to use for RX + // Remove the ones that you don't need to reduce memory usage GGWave::Protocols::rx().disableAll(); //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_NORMAL, true); //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FAST, true); @@ -123,25 +168,25 @@ void setup() { //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FAST, true); GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FASTEST, true); + // Initialize the ggwave instance and print the memory usage ggwave.prepare(p); - delay(1000); - - Serial.print(F("Instance initialized! Memory used: ")); + Serial.print(F("Instance initialized successfully! Memory used: ")); Serial.print(ggwave.heapSize()); Serial.println(F(" bytes")); - - Serial.println(F("Trying to start I2S ADC")); } + // Start capturing audio { - //install and start i2s driver + 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 + // Init ADC pad i2s_set_adc_mode(adc_unit, adc_channel); - // enable the adc + // Enable the adc i2s_adc_enable(i2s_port); Serial.println(F("I2S ADC started")); @@ -149,12 +194,12 @@ void setup() { } void loop() { - static int nr = 0; - static int niter = 0; + int nr = 0; + int niter = 0; - static GGWave::TxRxData result; + GGWave::TxRxData result; - // read from i2s - the samples are 12-bit so we need to do some massaging to make them 16-bit + // 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); @@ -177,16 +222,19 @@ void loop() { s0 = s0 ^ s1; } - // use with serial plotter to observe real-time audio signal + // Use this with the serial plotter to observe real-time audio signal //for (int i = 0; i < samples_read; i++) { // Serial.println(sampleBuffer[i]); //} } + // Try to decode any "ggwave" data: auto tStart = millis(); + if (ggwave.decode(sampleBuffer, samplesPerFrame*kSampleSize_bytes) == false) { Serial.println("Failed to decode"); } + auto tEnd = millis(); if (++niter % 10 == 0) { @@ -199,6 +247,7 @@ void loop() { } } + // Check if we have successfully decoded any data: nr = ggwave.rxTakeData(result); if (nr > 0) { Serial.println(tEnd - tStart);