Pull to refresh
183.78
FirstVDS
Виртуальные серверы в ДЦ в Москве

Ряд способов для передачи голоса на расстояние с помощью ESP32/Arduino

Reading time9 min
Views19K

Источник картинки: silenthollywood.com

Использование микроконтроллеров ESP32/Arduino позволяет создавать достаточно любопытные проекты, среди которых особняком стоят проекты для передачи голоса. Например, создать собственную рацию, которая может быть весьма тонко настроена на программном уровне. В этой статье мы обзорно рассмотрим несколько известных способов для реализации подобной задумки.

В одном из проектов был продемонстрирован достаточно любопытный подход: возможность выбора одного из двух способов передачи данных:

Использование широковещательной рассылки UDP


Широковещательная рассылка UDP — очень простой механизм. Вы отправляете UDP-пакет на специальный IP-адрес, и ваш маршрутизатор передаёт этот пакет всем другим устройствам в вашей сети.

Мы можем безопасно отправлять до 1436 байт в пакете UDP, поэтому, если мы семплируем на частоте 16 кГц и используем 8-битные сэмплы, это около 90 мс аудиоданных. Таким образом, нам нужно отправить около 11 пакетов в секунду. Это вполне соответствует возможностям ESP32.

Большим преимуществом использования широковещательного UDP является то, что нам не нужно знать о наших одноранговых узлах, мы можем просто передать сообщение, и любой, кто его слушает, получит его.

Однако у UDP есть и некоторые недостатки:

  • Доставка UDP-пакетов — это только максимальные усилия — нет никакой гарантии, что кто-то получит отправленный вами пакет.
  • Также нет гарантии порядка пакетов — кто-то может получить отправленные вами пакеты в совершенно случайном порядке.

В рассмотренном проекте эти две проблемы проигнорированы. С широковещательными пакетами мы обычно остаёмся в одной сети, поэтому мы, вполне вероятно, не потеряем слишком много пакетов, и наши пакеты, возможно, также будут поступать в правильном порядке. Если они этого не сделают, то мы просто получим немного шума и искажения звука.

Другим большим преимуществом широковещательной передачи UDP является то, что вы можете получать пакеты на свой настольный компьютер или телефон, поэтому было бы довольно легко создавать дополнительных клиентов, не основанных на ESP32.

Использование ESP-NOW


ESP-NOW — это протокол, разработанный Esppresif, который позволяет нескольким устройствам ESP взаимодействовать друг с другом без необходимости использования Wi-Fi.

Это даёт нам большое преимущество перед опцией UDP, заключающееся в том, что нам не нужна сеть Wi-Fi, чтобы наша рация работала.

Недостатком ESP-NOW является гораздо меньший размер пакета — 250 байт. Это означает, что нам нужно отправлять пакеты 64 раза в секунду.

У нас также есть все те же недостатки, что и у UDP — доставка пакетов осуществляется с максимальной эффективностью и нет гарантий, в каком порядке будут приходить пакеты.
Выбор одного из двух режимов осуществляется с помощью комментирования / раскомментирования строки 45 в файле Config:

// Which transport do you want to use? ESP_NOW or UDP?
// comment out this line to use UDP
// #define USE_ESP_NOW

Единственным минусом использования решения на базе радиомодуля ESP32 является некоторая ограниченность радиуса действия устройства, так как wi-fi модуль ESP32 не предназначен изначально до передачи на большие расстояния. Да, я понимаю, вы можете сказать, что с использованием отражателя или тарелки — расстояние можно существенно увеличить: мне известны подобные эксперименты, где радиус действия увеличили вплоть до 10 км. Однако подобное решение всё-таки существенно выбивается за рамки и требует достаточно точной настройки друг на друга как приёмника, так и передатчика.

Альтернативным способом передачи голосового потока является использование известных дальнобойных плат радиосвязи NRF24L01+PA+LNA, которые позволяют (при соответствующем падении скорости передачи с ростом дальности) увеличить радиус, вплоть до более чем 1 километра.



Для этого нам надо будет использовать библиотеку RF24Audio — это поистине библиотека «для ленивых людей», так как сам скетч полноценной рации не содержит даже и 10 строк:

#include <RF24.h>
#include <SPI.h>
#include <RF24Audio.h>

RF24 radio(7,8);  // Set radio up using pins 7 (CE) 8 (CS)
RF24Audio rfAudio(radio,1); // Set up the audio using the radio, and set to radio number 0.
                                // Setting the radio number is only important if one-to-one communication is desired
    // in a multi-node radio group. See the privateBroadcast() function.

void setup() {   

  rfAudio.begin();  // The only thing to do is initialize the library.

}

void loop() {
  
  // Audio playback and button handling is all managed internally.
  // In this example, the radio is controlled by external buttons, so there is nothing to do here
  
}

Чтобы работать с ним, нам необходимо:

  • микрофон подключить к пину А0;
  • кнопку переключателя, которая переключает режимы, для осуществления передачи следует соединить с пином А1;
  • кнопка прибавления громкости должна быть подключена к пину A2;
  • убавление громкости к пину А3;
  • кстати сказать, библиотека предоставляет достаточно интересную функцию, которая позволяет включать удалённо передающее устройство! То есть не совсем включать, а переводить его в режим «прослушки, что вокруг происходит» ;). Для этого следует использовать кнопку, присоединённую к пину A4.

Да, на больших расстояниях качество передачи будет наверняка падать, однако, как могут подтвердить олдфаги, заставшие ещё времена модемной связи с интернетом, скорость в 250 кбит/сек (на расстоянии в 1100 м, которое заявляется максимальным для этого радиомодуля) является более чем достаточной для передачи хорошо слышимого голоса.

Следует отметить, что если кто-то соберётся экспериментировать с этим передатчиком, то стоит понимать, радиомодуль nrf является весьма чувствительным к качеству питания (напряжение и сила тока), поэтому питать его от стандартного пина Arduino совершенно недостаточно.

В идеале следует использовать специальные модули для питания:



Если же это недоступно, то тебе следует использовать любой другой модуль, например, регулятор/стабилизатор напряжения (так называемый DC-DC модуль). Например, MT3608:



Аппаратная часть проекта на основе этой идеи хорошо разобрана здесь, если кому интересно.

Сейчас я специально не останавливаюсь подробно на аппаратной части подобных затей, так как это выйдет далеко за пределы статьи, если описывать всё подробно. Поэтому я привёл выше только ссылки, которые помогут вам разобраться, если эта тема вас заинтересует.

Говоря о способах передачи голоса на расстояние, я совершенно не могу пройти мимо одного из проектов, поскольку он «реализует мои потаённые желания» и одну нереализованную задумку из далёкой молодости — передача голоса с помощью лазерного луча.

В своё время мне очень хотелось создать нечто подобное, так как в те времена, когда динозавры были зеленее, а сотовой связи не было, мы с одним товарищем жили в прямой видимости друг от друга, на расстоянии порядка 100 метров, и подобный проект был бы как нельзя кстати. Единственная проблема, что в те времена не существовало ещё Arduino :)

Приведённую ниже затею можно взять в качестве основы, так как основная задумка, положенная в основу проекта, заключается в использовании ШИМ-модуляции на 11 пине, для воспроизведения аудио (ссылка на скетч):
Код передатчика
/*
 * speaker_pcm
 *
 * Plays 8-bit PCM audio on pin 11 using pulse-width modulation (PWM).
 * For Arduino with Atmega168 at 16 MHz.
 *
 * Uses two timers. The first changes the sample value 8000 times a second.
 * The second holds pin 11 high for 0-255 ticks out of a 256-tick cycle,
 * depending on sample value. The second timer repeats 62500 times per second
 * (16000000 / 256), much faster than the playback rate (8000 Hz), so
 * it almost sounds halfway decent, just really quiet on a PC speaker.
 *
 * Takes over Timer 1 (16-bit) for the 8000 Hz timer. This breaks PWM
 * (analogWrite()) for Arduino pins 9 and 10. Takes Timer 2 (8-bit)
 * for the pulse width modulation, breaking PWM for pins 11 & 3.
 *
 * References:
 *     https://www.uchobby.com/index.php/2007/11/11/arduino-sound-part-1/
 *     https://www.atmel.com/dyn/resources/prod_documents/doc2542.pdf
 *     https://www.evilmadscientist.com/article.php/avrdac
 *     https://gonium.net/md/2006/12/27/i-will-think-before-i-code/
 *     https://fly.cc.fer.hr/GDM/articles/sndmus/speaker2.html
 *     https://www.gamedev.net/reference/articles/article442.asp
 *
 * Michael Smith <michael@hurts.ca>
 */

#include <stdint.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>

#define SAMPLE_RATE 8000

/*
 * The audio data needs to be unsigned, 8-bit, 8000 Hz, and small enough
 * to fit in flash. 10000-13000 samples is about the limit.
 *
 * sounddata.h should look like this:
 *     const int sounddata_length=10000;
 *     const unsigned char sounddata_data[] PROGMEM = { ..... };
 *
 * You can use wav2c from GBA CSS:
 *     https://thieumsweb.free.fr/english/gbacss.html
 * Then add "PROGMEM" in the right place. I hacked it up to dump the samples
 * as unsigned rather than signed, but it shouldn't matter.
 *
 * https://musicthing.blogspot.com/2005/05/tiny-music-makers-pt-4-mac-startup.html
 * mplayer -ao pcm macstartup.mp3
 * sox audiodump.wav -v 1.32 -c 1 -r 8000 -u -1 macstartup-8000.wav
 * sox macstartup-8000.wav macstartup-cut.wav trim 0 10000s
 * wav2c macstartup-cut.wav sounddata.h sounddata
 *
 * (starfox) nb. under sox 12.18 (distributed in CentOS 5), i needed to run
 * the following command to convert my wav file to the appropriate format:
 * sox audiodump.wav -c 1 -r 8000 -u -b macstartup-8000.wav
 */

#include "sounddata.h"

int ledPin = 13;
int speakerPin = 11; // Can be either 3 or 11, two PWM outputs connected to Timer 2
volatile uint16_t sample;
byte lastSample;


void stopPlayback()
{
    // Disable playback per-sample interrupt.
    TIMSK1 &= ~_BV(OCIE1A);

    // Disable the per-sample timer completely.
    TCCR1B &= ~_BV(CS10);

    // Disable the PWM timer.
    TCCR2B &= ~_BV(CS10);

    digitalWrite(speakerPin, LOW);
}

// This is called at 8000 Hz to load the next sample.
ISR(TIMER1_COMPA_vect) {
    if (sample >= sounddata_length) {
        if (sample == sounddata_length + lastSample) {
            stopPlayback();
        }
        else {
            if(speakerPin==11){
                // Ramp down to zero to reduce the click at the end of playback.
                OCR2A = sounddata_length + lastSample - sample;
            } else {
                OCR2B = sounddata_length + lastSample - sample;                
            }
        }
    }
    else {
        if(speakerPin==11){
            OCR2A = pgm_read_byte(&sounddata_data[sample]);
        } else {
            OCR2B = pgm_read_byte(&sounddata_data[sample]);            
        }
    }

    ++sample;
}

void startPlayback()
{
    pinMode(speakerPin, OUTPUT);

    // Set up Timer 2 to do pulse width modulation on the speaker
    // pin.

    // Use internal clock (datasheet p.160)
    ASSR &= ~(_BV(EXCLK) | _BV(AS2));

    // Set fast PWM mode  (p.157)
    TCCR2A |= _BV(WGM21) | _BV(WGM20);
    TCCR2B &= ~_BV(WGM22);

    if(speakerPin==11){
        // Do non-inverting PWM on pin OC2A (p.155)
        // On the Arduino this is pin 11.
        TCCR2A = (TCCR2A | _BV(COM2A1)) & ~_BV(COM2A0);
        TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0));
        // No prescaler (p.158)
        TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);

        // Set initial pulse width to the first sample.
        OCR2A = pgm_read_byte(&sounddata_data[0]);
    } else {
        // Do non-inverting PWM on pin OC2B (p.155)
        // On the Arduino this is pin 3.
        TCCR2A = (TCCR2A | _BV(COM2B1)) & ~_BV(COM2B0);
        TCCR2A &= ~(_BV(COM2A1) | _BV(COM2A0));
        // No prescaler (p.158)
        TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);

        // Set initial pulse width to the first sample.
        OCR2B = pgm_read_byte(&sounddata_data[0]);
    }





    // Set up Timer 1 to send a sample every interrupt.

    cli();

    // Set CTC mode (Clear Timer on Compare Match) (p.133)
    // Have to set OCR1A *after*, otherwise it gets reset to 0!
    TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12);
    TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));

    // No prescaler (p.134)
    TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);

    // Set the compare register (OCR1A).
    // OCR1A is a 16-bit register, so we have to do this with
    // interrupts disabled to be safe.
    OCR1A = F_CPU / SAMPLE_RATE;    // 16e6 / 8000 = 2000

    // Enable interrupt when TCNT1 == OCR1A (p.136)
    TIMSK1 |= _BV(OCIE1A);

    lastSample = pgm_read_byte(&sounddata_data[sounddata_length-1]);
    sample = 0;
    sei();
}


void setup()
{
    pinMode(ledPin, OUTPUT);
    digitalWrite(ledPin, HIGH);
    startPlayback();
}

void loop()
{
    while (true);
}
То есть, другими словами, он берёт заранее заготовленный массив данных (подключаемый как библиотека sounddata.h) и воспроизводит его. Выглядит конечно «зело эффектно»:

Чтобы использовать его как рацию, в качестве источника данных следует использовать микрофон, что потребует соответствующей доработки скетча. Так что заинтересовавшемуся читателю будет над чем поработать ;)

Хотя, честно говоря, требуется для этого не так уж много – обеспечить модуляцию лазерного луча. Другими словами, мерцать его в такт голосу. Например, захватывать сигнал с аналогового пина ардуино с подключенным к нему микрофоном и выводить на другом аналоговом пине, к которому подключен лазер (это на стороне передатчика). На стороне приёмника – обратное преобразование: снимать сигнал с аналогового пина и выводить на другом аналоговом пине, к которому подключено устройство воспроизведения – колонка, динамик и т. д. Понятно, что качество звучания будет оставлять желать лучшего – но для коммуникации сгодится ;)

Нечто примерно подобное было продемонстрировано вот в этой самоделке, только без участия ардуино (просто как принцип действия, который можно взять за основу):

Рассмотренные способы не охватывают все вероятные аппаратно-программные комбинации для передачи голоса, однако их вполне можно использовать в своих проектах, а вариант передачи с помощью радиомодуля nrf — вообще с минимальным вмешательством в код.

В заключение, если говорить об интересных дальнобойных способах передачи информации (хотя это уже и не относится напрямую к передаче голоса) — следует упомянуть ещё один, который вряд ли подойдёт для передачи голоса ввиду его низкой скорости, однако вполне может быть использован для передачи текстовых сообщений — LoRa.

LoRa (Long Range) — запатентованная проприетарная технология модуляции маломощной глобальной сети. Среди её особенностей в рамках нашей задачи можно выделить:

  • Дальность передачи существенно выше, чем у других способов (10-15 км. Есть даже прецеденты передачи на 200! км).
  • Высокая проникающая способность в городских условиях.
  • Низкая пропускная способность (сотни бит/с – десятки кбит/сек). Да, «фоточки» уже не покачаешь. Если только текстовые сообщения.

В США есть целая сеть любителей на базе модулей LoRa — эдакая «сеть Судного Дня», которая позволяет обмениваться информацией внутри сети, даже если «цивилизация перестала существовать» :)

Если экспериментировать с LoRa, то имеет смысл это делать с модулями TTGO LoRa32 SX1276 OLED, которые построены на основе ESP32 и имеют кроме модуля LoRa в своём составе — ещё и OLED дисплей, что может быть достаточно полезно в целом. Например, вот тут есть подробная инструкция по соединению двух таких модулей и исходный код для этого.

Таким образом, можно сказать, что есть множество способов передачи голосовой информации на расстояние и выбор конкретного из них базируется только на конкретной поставленной задаче и аппаратных возможностях того железа, которое будет обеспечивать выбранный тип решения.

А какой способ предпочитаете вы?


НЛО прилетело и оставило здесь промокод для читателей нашего блога:

15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.
Tags:
Hubs:
Total votes 12: ↑12 and ↓0+12
Comments9

Articles

Information

Website
firstvds.ru
Registered
Founded
Employees
51–100 employees
Location
Россия
Representative
FirstJohn