Наушники проигрывают пару звуковых тона в микрофон робота, используя аудио частотную манипуляцию (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 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, а также про клоны, оригиналы и совместимость
КМБ для начинающих ардуинщиков
Состав стартера (точка входа для начинающих ардуинщиков)
