Наушники проигрывают пару звуковых тона в микрофон робота, используя аудио частотную манипуляцию (AFSK — Audio Frequency Shift Keying). Затем, используется операционный усилитель для усиления сигнала, а затем используется микросхема LM567 для декодирования аудио сигналов.
Автор выбрал две частоты для передачи 1 и 0 (12345 Гц и 9876 Гц). Кроме численной красоты — эти частоты достаточно далеко разнесены друг от друга, чтобы было легко обнаружить разницу.
Сигнал с аудио декодера поступает на последовательный порт контроллера Arduino (на скорости 300 бод), который осуществляет соответствующее управление роботом.
Чтобы избежать ложных команд, данные содержит дополнительные маркеры, контроль длины и контрольной суммы.
Схема для передачи данных через микрофон
Скетч: AudioSerial_BotVer4.ino
// -- // This is all open souce. Use it, modify it, share it, enjoy! // questions? [email protected] // - - - - // Canny the Robot: Programming with Headphones Arduino code // Designed to run on Teensy 3.1, at 24MHz (a bit slow, but seems to work best for low baud serial) // -- #include <Servo.h> #include <TimerOne.h> #ifndef cbi #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #endif #ifndef sbi #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) #endif #define setDebugLightOn() digitalWrite(PIN_OUT_DEBUG, HIGH) //sbi(PORTC, 5) #define setDebugLightOff() digitalWrite(PIN_OUT_DEBUG, LOW) //cbi(PORTC, 5) #define setOut0_HIGH() digitalWrite(PIN_OUT_PIEZO, HIGH) //sbi(PORTC,7) #define setOut0_LOW() digitalWrite(PIN_OUT_PIEZO, LOW) //cbi(PORTC,7) // ----------------------------------------------- const int PIN_OUT_DEBUG = 13; const int PIN_OUT_SERVO_1 = 14; const int PIN_OUT_SERVO_2 = 15; const int PIN_OUT_PIEZO = 17; const int PIN_OUT_LED_R = 12; const int PIN_OUT_LED_G = 11; const int PIN_OUT_LED_B = 10; const int PIN_IN_LIGHT = 22; const int PIN_IN_BUTTON = 2; const byte CRGB_OFF = B00000000; const byte CRGB_RED = B00110000; const byte CRGB_GREEN = B00001100; const byte CRGB_BLUE = B00000011; const byte CRGB_ORANGE = B00111100; const byte CRGB_PURPLE = B00110011; const byte CRGB_CYAN = B00001111; const byte CRGB_WHITE = B00111111; const byte CMD_EYECOLOR = B00000000; const byte CMD_EYEBROW = B01000000; const byte CMD_PLAYTONE = B10000000; const byte CMD_DELAY = B11000000; Servo servo1; Servo servo2; #define HWSERIAL Serial1 #define SERIAL_BAUD 300 // -- #define BOT_PROG_MAX_LEN 254 byte botProg[BOT_PROG_MAX_LEN]; int botProgLen = 0; int botProgIndex = 0; byte tmpProg[BOT_PROG_MAX_LEN]; // ----------------------------------------------- void setup(){ Serial.begin(19200); HWSERIAL.begin(SERIAL_BAUD, SERIAL_8N1); // -- pinMode(PIN_OUT_DEBUG, OUTPUT); pinMode(PIN_OUT_LED_R, OUTPUT); pinMode(PIN_OUT_LED_G, OUTPUT); pinMode(PIN_OUT_LED_B, OUTPUT); pinMode(PIN_OUT_PIEZO, OUTPUT); pinMode(PIN_OUT_SERVO_1,OUTPUT); pinMode(PIN_OUT_SERVO_2,OUTPUT); // -- pinMode(PIN_IN_BUTTON, INPUT); pinMode(PIN_IN_LIGHT, INPUT); // -- initEyebrows(); // -- Timer1.initialize(25); // microseconds Timer1.attachInterrupt(ISR_TIMER1); // -- allOff(); // -- Serial.println("> Ready!"); //-- int l = 0; // -- botProg[l++] = CMD_EYECOLOR |CRGB_BLUE; // first program command must be eye color // -- botProg[l++] = CMD_EYEBROW |16; botProg[l++] = CMD_DELAY |7; // -- // Funkytown is the default song ;) // -- botProg[l++] = CMD_PLAYTONE |36; botProg[l++] = CMD_DELAY | 6; // 1/8th note -1 botProg[l++] = CMD_PLAYTONE | 0; botProg[l++] = CMD_DELAY | 1; // 1/8th note botProg[l++] = CMD_PLAYTONE |36; botProg[l++] = CMD_DELAY | 6; // 1/8th note -1 botProg[l++] = CMD_PLAYTONE | 0; botProg[l++] = CMD_DELAY | 1; // 1/8th note botProg[l++] = CMD_PLAYTONE |34; botProg[l++] = CMD_DELAY | 6; // 1/8th note -1 botProg[l++] = CMD_PLAYTONE | 0; botProg[l++] = CMD_DELAY | 1; // 1/8th note botProg[l++] = CMD_PLAYTONE |36; botProg[l++] = CMD_DELAY | 6; // 1/8th note -1 botProg[l++] = CMD_PLAYTONE | 0; botProg[l++] = CMD_DELAY | 1; // 1/8th note botProg[l++] = CMD_PLAYTONE | 0; botProg[l++] = CMD_DELAY | 7; // 1/8th note -1 botProg[l++] = CMD_PLAYTONE |31; botProg[l++] = CMD_DELAY | 6; // 1/8th note -1 botProg[l++] = CMD_PLAYTONE | 0; botProg[l++] = CMD_DELAY | 1; // 1/8th note botProg[l++] = CMD_PLAYTONE | 0; botProg[l++] = CMD_DELAY | 7; // 1/8th note -1 botProg[l++] = CMD_PLAYTONE |31; botProg[l++] = CMD_DELAY | 6; // 1/8th note -1 botProg[l++] = CMD_PLAYTONE | 0; botProg[l++] = CMD_DELAY | 1; // 1/8th note botProg[l++] = CMD_PLAYTONE |36; botProg[l++] = CMD_DELAY | 6; // 1/8th note -1 botProg[l++] = CMD_PLAYTONE | 0; botProg[l++] = CMD_DELAY | 1; // 1/8th note botProg[l++] = CMD_PLAYTONE |41; botProg[l++] = CMD_DELAY | 6; // 1/8th note -1 botProg[l++] = CMD_PLAYTONE | 0; botProg[l++] = CMD_DELAY | 1; // 1/8th note botProg[l++] = CMD_PLAYTONE |40; botProg[l++] = CMD_DELAY | 6; // 1/8th note -1 botProg[l++] = CMD_PLAYTONE | 0; botProg[l++] = CMD_DELAY | 1; // 1/8th note botProg[l++] = CMD_PLAYTONE |36; botProg[l++] = CMD_DELAY | 6; // 1/8th note -1 botProg[l++] = CMD_PLAYTONE | 0; botProg[l++] = CMD_DELAY | 1; // 1/8th note // -- botProgLen = l; // -- showAsIdle(); } // ----------------------------------------------- boolean playing = false; boolean wasListening = false; const int LIGHT_THRESH = 112; void loop(){ int inByte; int dd = 35; // -- int lightVal = analogRead(PIN_IN_LIGHT); if(lightVal < LIGHT_THRESH && !wasListening){ // putting headphones on stops the playing immediately. playing = false; wasListening = true; allOff(); // -- setEyeColor(CRGB_OFF); // -- setEyebrows(90+24); setOutputNote(16); delay(dd); setOutputNote(17); delay(dd); setOutputNote(19); delay(dd); setOutputNote(21); delay(dd); setOutputNote(23); delay(dd); setOutputNote(24); delay(dd); setOutputNote(26); delay(dd); setOutputNote(28); delay(dd*2); setOutputNote(0); delay(250); // -- // LISTEN/PARSE INCOMING SERIAL DATA... boolean listening = true; int index = -3; int expectedLength = 0; while(listening && analogRead(PIN_IN_LIGHT) < LIGHT_THRESH){ if(HWSERIAL.available() > 0){ inByte = HWSERIAL.read(); //Serial.print(" Rx:"); Serial.print(inByte, DEC); Serial.print(","); Serial.println((char)inByte); // -- switch(index){ case -3: { if(inByte == 'T'){ index++; setEyeColor(CRGB_ORANGE); }else{ index = -3; setEyeColor(CRGB_RED); Serial.print("Err. Waiting for start 'T' != ");Serial.print(inByte, DEC); Serial.print(","); Serial.println((char)inByte); } break; } case -2: { if(inByte == 'x'){ index++; }else{ index = -3; setEyeColor(CRGB_RED); Serial.print("Err@i=");Serial.print(index);Serial.print(", saw:");Serial.print(inByte, DEC); Serial.print(","); Serial.println((char)inByte); } break; } case -1: { if(inByte < BOT_PROG_MAX_LEN && inByte > 0){ expectedLength = inByte; index++; Serial.print("Rx: len="); Serial.println(inByte); }else{ index = -3; setEyeColor(CRGB_RED); Serial.print("Err@i=");Serial.print(index);Serial.print(", saw:");Serial.print(inByte, DEC); Serial.print(","); Serial.println((char)inByte); } break; } case 0: { int cmd = (inByte>>6)&0x03; if(cmd == 0){ tmpProg[index++] = inByte; }else{ index = -3; setEyeColor(CRGB_RED); Serial.println("Err. First command must be eye color."); Serial.print("Err@i=");Serial.print(index);Serial.print(", saw:");Serial.print(inByte, DEC); Serial.print(","); Serial.println((char)inByte); } break; } default: { if(index < expectedLength){ tmpProg[index++] = inByte; //Serial.print(" Rx:"); Serial.print(inByte, DEC); Serial.print(","); Serial.println((char)inByte); }else{ // Verify checksum and copy over new program if valid. byte sum = 0; for(int c=0; c0) inByte = HWSERIAL.read(); // -- if(wasListening){ allOff(); // -- setOutputNote(28); delay(dd); setOutputNote(26); delay(dd); setOutputNote(24); delay(dd); setOutputNote(23); delay(dd); setOutputNote(21); delay(dd); setOutputNote(19); delay(dd); setOutputNote(17); delay(dd); setOutputNote(16); delay(dd*2); setOutputNote(0); // -- showAsIdle(); delay(250); wasListening = false; } // check to see if we should start playing; is button pressed and not yet playing? if(!digitalRead(PIN_IN_BUTTON) && !playing){ playing = true; botProgIndex = 0; Serial.println("Playing..."); } if(playing){ if(botProgIndex >= botProgLen){ // done playing. playing = false; allOff(); showAsIdle(); // -- Serial.println(botProgLen); Serial.println("Done."); delay(500); }else{ // play the program! byte pByte = botProg[botProgIndex++]; int cmd = (pByte>>6)&0x03; int data = pByte&0x3F; switch(cmd){ case 0: { // EYE COLOR setEyeColor(data); break; } case 1: { // EYEBROW ANGLE setEyebrows(90-32+data); break; } case 2: { // TONE setOutputNote(data); break; } case 3: { // DELAY delay((data+1)*25); // 16 = quarter note; 16*25 = 400ms for a quarter note. Note that data gets a +1 so that 63->64 as a whole note. break; } } } } } } // ----------------------------------------------- // COMBINED ACTIONS // -- void allOff(void){ setEyeColor(CRGB_OFF); setEyebrows(90); setOutputFreq(0); } void showAsIdle(void){ setEyeColor(botProg[0]); } // ----------------------------------------------- // EYES // -- void setEyeColor(byte crgb){ int r = (crgb>>4)&0x03; int g = (crgb>>2)&0x03; int b = (crgb>>0)&0x03; // -- if(r > 0) digitalWrite(PIN_OUT_LED_R, HIGH); else digitalWrite(PIN_OUT_LED_R, LOW); // -- if(g > 0) digitalWrite(PIN_OUT_LED_G, HIGH); else digitalWrite(PIN_OUT_LED_G, LOW); // -- if(b > 0) digitalWrite(PIN_OUT_LED_B, HIGH); else digitalWrite(PIN_OUT_LED_B, LOW); } // ----------------------------------------------- // EYEBROWS // -- void initEyebrows(){ servo1.attach(PIN_OUT_SERVO_1); servo2.attach(PIN_OUT_SERVO_2); setEyebrows(90); } void setEyebrows(int angle){ servo1.write(angle); servo2.write(185-angle); } // ----------------------------------------------- // AUDIO OUTPUT // -- // Designed to run at 40kHz (max audio tone freq = 20kHz)... return as quickly as possible. unsigned int heartbeatCount = 0; char isrLEDCount = 0; // -- unsigned int outputPeriod_0 = 2; unsigned int outputOnFor_0 = 0; unsigned int outputCounter_0 = 0; // -- void ISR_TIMER1(void){ // ISR(TIMER1_COMPA_vect){ // Heartbeat debug light... if(heartbeatCount < 4000 && isrLEDCount == 0) setDebugLightOn(); if(heartbeatCount > 12000 && heartbeatCount < 18000 && isrLEDCount == 0) setDebugLightOn(); // -- isrLEDCount++; if(isrLEDCount > 5) isrLEDCount = 0; // -- heartbeatCount++; if(heartbeatCount > 40000) heartbeatCount = 0; // -- // OUT 0 if(outputCounter_0 < outputOnFor_0) setOut0_HIGH(); else setOut0_LOW(); // -- outputCounter_0++; if(outputCounter_0 >= outputPeriod_0) outputCounter_0 = 0; // -- setDebugLightOff(); } float noteFreqs[64] = {65.41, 69.3, 73.42, 77.78, 82.41, 87.31, 92.5, 98, 103.83, 110, 116.54, 123.47, 130.81, 138.59, 146.83, 155.56, 164.81, 174.61, 185, 196, 207.65, 220, 233.08, 246.94, 261.63, 277.18, 293.66, 311.13, 329.63, 349.23, 369.99, 392, 415.3, 440, 466.16, 493.88, 523.25, 554.37, 587.33, 622.25, 659.25, 698.46, 739.99, 783.99, 830.61, 880, 932.33, 987.77, 1046.5, 1108.73, 1174.66, 1244.51, 1318.51, 1396.91, 1479.98, 1567.98, 1661.22, 1760, 1864.66, 1975.53, 2093, 2217.46, 2349.32, 2489.02}; void setOutputNote(int n){ if(n==0){ setOutputFreq(0); }else{ n = max(1, min(63, n)); setOutputFreq(noteFreqs[n]); } } void setOutputFreq(int f){ unsigned int fBound = min(20000, max(1, f)); int set_onFor = 0; int set_period = 2; if(f > 0){ float t = 4000.0/fBound; set_onFor = t*5; set_period = t*10; } // -- outputOnFor_0 = set_onFor; outputPeriod_0 = set_period; }
Ссылки
Canny the Robot: Programming With Headphones
AudioSerial Headphone Programmer
По теме
Ардуино что это и зачем?
Почему Arduino побеждает и почему он здесь, чтобы остаться?
Arduino, термины, начало работы
Разновидности плат Arduino, а также про клоны, оригиналы и совместимость
КМБ для начинающих ардуинщиков
Состав стартера (точка входа для начинающих ардуинщиков)