4 листопада 2016 р.

7. Генерування звуків

Отже наш робот навчився повзати. Прийшов час дати йому голос. Очевидно, що той же ж R2D2 не в останню чергу став знаменитістю через свої особливі трелі і свисти. Наш робот має бути не гіршим.

Для синтезу складних звукових ефектів є багато недорогих модулів, які легко інтегрувати з Arduino. Включно з модулями, які можуть відтворювати MP3 файли із внутрішньої пам'яті або micro-SD карт.

В нас поки нічого такого нема. Але завжди можна знайти якийсь вихід.


Із старої зламаної китайської іграшки добули пищалку - думаю в кожній хаті, де є діти, таких знайдеться пару штук

Simple beeper - 1



Simple beeper - 2


До провідників припаяли шматки дроту від використаних бенгальських вогнів. Тепер їх стало зручно комутувати до виходів Arduino або до макетної плати. Треба буде ще їх зафіксувати шматками трубки-термоусадки..

Справа застопорилася на програмній частині. Сам по собі Arduino не вміє генерувати звуки (ну тобто красиві сигнали у вигляді синусоїд, які мають частоти чутні людським вухом). Тому для генерування звуку використовують побічний ефект Широтно-Імпульсної Модуляції (ШІМ або PWM) яку використовують для симулювання різних рівнів напруги на виходах Arduino.

https://www.arduino.cc/en/Tutorial/PWM
Ідея ШІМ в тому, щоб замість будування складних схем для пониження напруги, просто зменшується час, протягом якого подаються 5-вольтові імпульси. Якщо треба видати всі 5 вольт - подається безконечно широкий імпульс. Якщо треба подати напругу схожу на 4 вольта - 4/5 часу подається напруга 5 вольт, а решту - 0 вольт. Для створення напруги схожої на 2.5 вольт, ширина 5-вольтового імпульсу становить 50% часу періоду.

Таким чином, подаючи на вихід за допомогою analogWrite() сигнал різного рівня, можна генерувати коливання напруги, які при потраплянні на пищалку даватимуть звук. Цей звук буде дещо одноманітним, з досить металічним тембром, але ж в тому і є суть роботівського голосу!

В комплекті з Arduino надається бібліотека Tone, яка дозволяє дуже простим способом подавати на вихід звуковий сигнал певної частоти. Для цього достатньо написати таку команду:
    tone(<номер PWM виходу>, <частота ноти>)
Наприклад:
    tone(9, 440) ; // пищати ля першої октави на виході 9
Пищалку слід одним контактом підключити до виходу 9, а іншим - на землю (GND).

З використанням бібліотеки Tone є дві проблеми:
  1. Використання нот не дуже добре годиться для синтезування роботського пищання. Доведеться виписувати великі масиви нот дуже короткої тривалості.
  2. Використання бібліотеки псує роботу PWM на виходах 3 та 11. Як саме - в документації не сказано.

Блукання Інтернетом привело мене до ось цієї теми "Звуки R2D2 на Ардуіно (рос.)". Дописувачі створили просто суперову бібліотеку для генерування R2D2-подібних звуків. Зі всіх десятків вже готових варіантів, ми скомпілювали для нашого робота кілька звуків, які він має видавати в різних ситуаціях.
  • Привітання
  • Згода
  • Питання
  • Переляк

Як це звучить вживу:


Ядро бібліотеки дуже компактне і базується лише на двох функціях - тому ми ті функції просто перенесли у свій код. Текст пробної програмки подаю нижче. Пищіть собі на здоров'я.


// запам'ятовуємо номер виходу, до якого підключений динамік
int voicePin = 9;

void setup() {

    pinMode(voicePin, OUTPUT);

    // Привіт
    playTone(1600,100);
    playTone(2600,24);
    playTone(1900,92);
    delay(4);
    playFreqGlissando(2500, 3600, 1, 1900);
    playFreqGlissando(3600, 4200, 1, 1900);
    playFreqGlissando(4200, 3600, 1, 1900);
    playFreqGlissando(3600, 1900, 1, 1900);
    playFreqGlissando(550, 750, 1, 500);
    playFreqGlissando(750, 1200, 1, 500);
    playFreqGlissando(1200, 2000, 1, 500);
    playFreqGlissando(2000, 2100, 1, 300);
    delay(32);
    playTone(2350,36);
    playFreqGlissando(2350, 1600, 1, 500);
    delay(1000);

    // OK
    playFreqGlissando(2300, 1900, 1, 1100);
    delay(28);
    playFreqGlissando(1800, 1700, 1, 1000);
    delay(36);
    playFreqGlissando(2000, 2200, 1, 1100);
    delay(1000);

    // Питання
    playTone(2400,40);
    playTone(1000,32);
    delay(28);
    playFreqGlissando(1700, 2400, 1, 1000);
    playTone(2400,32);
    delay(28);
    playFreqGlissando(2200, 1700, 1, 800);
    playTone(1700,24);
    delay(28);
    playFreqGlissando(1900, 2700, 1, 800);
    playTone(2700,21);
    delay(28);
    playFreqGlissando(2200, 1700, 1, 800);
    playTone(1700,24);
    delay(28);
    playFreqGlissando(1900, 2700, 1, 800);
    playTone(2700,21);
    delay(1000);

    // Переляк
    playFreqGlissando(400, 550, 1, 14000);
    playFreqGlissando(550, 400, 1, 1200);
    playTone(2400,36);
    playFreqGlissando(1800, 2400, 1, 700);
    playTone(2400,36);
    delay(32);
    playFreqGlissando(2200, 1800, 1, 700);
    playFreqGlissando(1800, 1750, 1, 900);
    playTone(2350,36);
    playFreqGlissando(2350, 1200, 1, 700);
    delay(32);
    playFreqGlissando(700, 600, 1, 300);
    playFreqGlissando(600, 750, 1, 900);
}

void playTone(unsigned int toneFrequency, byte beats) {
    int tone = (1000000 / toneFrequency)/2; //recalculate frequency to pause value between pulses
    for (long i = 0; i < beats * 1000L; i += tone * 2) {
        digitalWrite(voicePin, HIGH);
        delayMicroseconds(tone);
        digitalWrite(voicePin, LOW);
        delayMicroseconds(tone);
    }
}

void playFreqGlissando( float freqFrom, float freqTo, float duty, float duration ) {

    duration=duration/8*3;

    int stepLength = 20; //30
    int i, numberOfSteps;
    float freqStep, freq;

    numberOfSteps  =  duration/stepLength;
    freqStep =  (freqTo - freqFrom)/numberOfSteps;

    freq = freqFrom;
    for ( i=0; i < numberOfSteps; i++ ) {
        //summer.playTone(freq, stepLength);
        playTone(freq, duty);
        freq = freq  + freqStep ;
    }
    playTone(freqTo, stepLength);

}

void loop() {

}

Немає коментарів:

Дописати коментар