Программирование Arduino-робота через микрофон


Наушники проигрывают пару звуковых тона в микрофон робота, используя аудио частотную манипуляцию (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? akumpf@gmail.com
//  - - - -
//  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; c<expectedLength; c++) sum += tmpProg[c];
            // --
            if(inByte == sum){
              for(int c=0; c<expectedLength; c++){
                botProg[c]   = tmpProg[c];
                botProgLen   = expectedLength;
                botProgIndex = 0;
              }
              setEyeColor(CRGB_GREEN);
              
            }else{
              index = -3;
              setEyeColor(CRGB_RED);
              Serial.print("Bad ChkSum ");Serial.print(inByte, DEC); Serial.print(" != "); Serial.println(sum, DEC);
            }
          }
         }
        }
        // --
      }
    }
    // --
  }else{
    // Discard any serial data we may receive...
    if(HWSERIAL.available() > 0) 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, а также про клоны, оригиналы и совместимость
КМБ для начинающих ардуинщиков
Состав стартера (точка входа для начинающих ардуинщиков)
  • 0
  • 10 декабря 2015, 10:38
  • admin

Комментарии (0)

RSS свернуть / развернуть

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.