Программирование 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? [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("[email protected]=");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("[email protected]=");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("[email protected]=");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= 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, а также про клоны, оригиналы и совместимость
КМБ для начинающих ардуинщиков
Состав стартера (точка входа для начинающих ардуинщиков)


Добавить комментарий

Arduino

Что такое Arduino?
Зачем мне Arduino?
Начало работы с Arduino
Для начинающих ардуинщиков
Радиодетали (точка входа для начинающих ардуинщиков)
Первые шаги с Arduino

Разделы

  1. Преимуществ нет, за исключением читабельности: тип bool обычно имеет размер 1 байт, как и uint8_t. Думаю, компилятор в обоих случаях…

  2. Добрый день! Я недавно начал изучать программирование под STM32 и ваши уроки просто бесценны! Хотел узнать зачем использовать переменную типа…

3D-печать AI Android Arduino Bluetooth CraftDuino DIY IDE iRobot Kinect LEGO OpenCV Open Source Python Raspberry Pi RoboCraft ROS swarm ИК автоматизация андроид балансировать бионика версия видео военный датчик дрон интерфейс камера кибервесна конкурс манипулятор машинное обучение наше нейронная сеть подводный пылесос работа распознавание робот робототехника светодиод сервомашинка собака управление ходить шаг за шагом шаговый двигатель шилд

OpenCV
Робототехника
Будущее за бионическими роботами?
Нейронная сеть - введение