AVA Disco Mode
The robot received a set of Ikea Dioder LED strips for Christmas. Below is a quick video of AVA in disco mode. There will be a separate post on controlling these strips using an Arduino at some stage.
AVA Christmas
In keeping with the Christmas theme here is AVA singing a Christmas Carol using the EMIC 2 Speech Synthesizer. The Emic 2 Text-to-Speech Module is a multi-language voice synthesizer that converts a stream of digital text into speech. You can select either the Epson (default) or DECtalk parsers.
Note that the Emic 2 uses DECtalk version 5.0.E1. The Flame of Hope web site has a heap of DECtalk songs but they are for earlier versions of the DECtalk parser and require tweaking to work with the EMIC 2. Thanks to Ron on the Parallax forum for these tips on converting the text files:
- you often have to change an "L" to "LL"
- sometimes you need a space between any character preceding the "ey" phonetic symbol.
The following is the string used to produce the song in the video above, should you wish to try out your EMIC 2's singing ability.
"[:phone arpa speak on][:rate 190][:n2][:dv ap 200 sm 100 ri 100][R EY<200,17>N<100>DRAO<200,24>PS<100>AO<200>N<100>ROW<300,19>ZIX<200,17>Z<100>AE<150>N<100>D<50>WIH<300,12>SKRR<200,17>Z<100>AO<200>N<100>KIH<300,19>TAH<150,17>N<100>Z<50>_<300>BRAY<200>T<100>KAO<300,24>PRR<300>K EH<300,19>TEL<200,17>Z<100>AE<150>N<100>D<50>War<200,12>M<100>WUH<300,17>LL EH<200>N<100>MIH<300,19>TAH<150,17>N<100>Z<50>_<300>BRAW<200>N<100>PEY<300,24>PRR<300,22>PAE<300,17>KIH<300,19>JHIX<200,15>Z<100>TAY<200>D<100>AH<200,22>P<100>WIH<200,20>TH<100>STRIH<300,13>NX<200>Z<100>_<300>DHIY<200,12>Z<100>AR<300,13>AX<300,15>FYU<300,17>AH<200,18>V<100>MAY<300,20>FEY<300,22>VRR<300,24>EH<200,22>T<100>THIH<500,16>NX<300>Z<100>][:n0]"
To make it easy for our Arduino Mega 2650 to communicate with the EMIC2 we created a library called synthesizer.h. As you can see the Arduino connects to the EMIC 2 via one of its serial ports.
To use this class, I have created a robot class which looks like:
Then in the main.ino file all you need to do is:
You don't have to use C++ classes like I did but it makes it easier to maintain your code. You can also ignore the bits which refer to the motors and other sensors, I just leave it in here for completeness.
Arduino Code
To make it easy for our Arduino Mega 2650 to communicate with the EMIC2 we created a library called synthesizer.h. As you can see the Arduino connects to the EMIC 2 via one of its serial ports.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @file synthesizer.h | |
* @brief Speech Synthesizer class. | |
* @author David Such | |
*/ | |
#ifndef synthesizer_h | |
#define synthesizer_h | |
class Synthesizer | |
{ | |
public: | |
Synthesizer(byte rxPin, byte txPin): _emicSerial(rxPin, txPin) | |
{ | |
_rxPin = rxPin; | |
_txPin = txPin; | |
} | |
void begin() | |
{ | |
pinMode(_rxPin, INPUT); | |
pinMode(_txPin, OUTPUT); | |
_emicSerial.begin(9600); | |
_emicSerial.print('\n'); // Send a CR in case the system is already up | |
while (_emicSerial.read() != ':'); // When the Emic 2 has initialized and is ready, it will send a single ':' character, so wait here until we receive it | |
delay(10); // Short delay | |
_emicSerial.flush(); // Flush the receive buffer | |
_emicSerial.print("N8\n"); // Select Voice (0 to 8) | |
_emicSerial.print("V15\n"); // Set Volume (-48 to 18) | |
_emicSerial.print("L0\n"); // Set Language (0 to 2) | |
_emicSerial.print("P1\n"); // Select Parser (0 DECtalk, 1 Epson) | |
_emicSerial.print("W200\n"); // Set Speaking Rate (75 to 600) | |
} | |
void speakMessage(String msg) | |
{ | |
_emicSerial.print('\n'); | |
while (_emicSerial.read() != ':'); | |
_emicSerial.print('S'); | |
_emicSerial.print(msg); | |
_emicSerial.print('\n'); | |
} | |
void singSong() | |
{ | |
_emicSerial.print("P0\n"); | |
speakMessage("[:phone arpa speak on][:rate 190][:n2][:dv ap 200 sm 100 ri 100][R EY<200,17>N<100>DRAO<200,24>PS<100>AO<200>N<100>ROW<300,19>ZIX<200,17>Z<100>AE<150>N<100>D<50>WIH<300,12>SKRR<200,17>Z<100>AO<200>N<100>KIH<300,19>TAH<150,17>N<100>Z<50>_<300>BRAY<200>T<100>KAO<300,24>PRR<300>K EH<300,19>TEL<200,17>Z<100>AE<150>N<100>D<50>War<200,12>M<100>WUH<300,17>LL EH<200>N<100>MIH<300,19>TAH<150,17>N<100>Z<50>_<300>BRAW<200>N<100>PEY<300,24>PRR<300,22>PAE<300,17>KIH<300,19>JHIX<200,15>Z<100>TAY<200>D<100>AH<200,22>P<100>WIH<200,20>TH<100>STRIH<300,13>NX<200>Z<100>_<300>DHIY<200,12>Z<100>AR<300,13>AX<300,15>FYU<300,17>AH<200,18>V<100>MAY<300,20>FEY<300,22>VRR<300,24>EH<200,22>T<100>THIH<500,16>NX<300>Z<100>][:n0]"); | |
_emicSerial.print("P1\n"); | |
} | |
private: | |
byte _rxPin, _txPin; | |
SoftwareSerial _emicSerial; | |
}; | |
#endif |
To use this class, I have created a robot class which looks like:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @file robot.h | |
* @brief Robot Class for the Arduino Mega 2650 | |
* @author David Such | |
*/ | |
#ifndef robot_h | |
#define robot_h | |
#include <Arduino.h> | |
class Robot | |
{ | |
public: | |
/* | |
* @brief Class constructor. | |
*/ | |
Robot(): | |
leftMotor(pin_LW_Servo), rightMotor(pin_RW_Servo), | |
frontSonar(pin_FrontPing_Sensor, pin_FrontPing_Sensor, MAX_DISTANCE), | |
distanceAverage(MAX_DISTANCE), frontServo(pin_FrontPing_Servo), | |
synthesizer(pin_EMIC_SerialRx, pin_EMIC_SerialTx), realTimeClock() | |
{ | |
} | |
/* | |
* @brief Initialize the robot state. | |
*/ | |
void begin() | |
{ | |
leftMotor.begin(); | |
rightMotor.begin(); | |
frontServo.begin(); | |
synthesizer.begin(); | |
compass.begin(); | |
Wire.begin(); // Join I2C bus (address optional for master) | |
synthesizer.speakMessage("AEVA one, start up sequence in progress."); | |
delay(HOLD_OFF_TIME); | |
startTime = millis(); | |
endTime = millis() + RUN_TIME * 1000; | |
frontServo.scan(); | |
move(AHEAD_SLOW); | |
delay(2000); | |
sendMessage(address_I2C_Logging, "Master I2C bus is up."); | |
synthesizer.speakMessage("Start up sequence complete. Awaiting command."); | |
if (realTimeClock.available()) { | |
sendMessage(address_I2C_Logging, "RTC connected."); | |
realTimeClock.begin(); | |
realTimeClock.updateMsgArrayWithTime(_msgArray); | |
sendMessage(address_I2C_Logging, _msgArray); | |
realTimeClock.updateMsgArrayWithDate(_msgArray); | |
sendMessage(address_I2C_Logging, _msgArray); | |
} else { | |
sendMessage(address_I2C_Logging, "RTC unavailable."); | |
} | |
if (compass.available()) { | |
compass.updateMsgArrayWithHeading(_msgArray); | |
sendMessage(address_I2C_Logging, _msgArray); | |
} else { | |
sendMessage(address_I2C_Logging, "Compass unavailable."); | |
} | |
} | |
/* | |
* @brief Move left motor at speed (-500 to 500). | |
*/ | |
void moveLeft(int speed) { | |
leftMotor.moveAtSpeed(speed); | |
} | |
/* | |
* @brief Move right motor at speed (-500 to 500). | |
*/ | |
void moveRight(int speed) { | |
rightMotor.moveAtSpeed(speed); | |
} | |
/* | |
* @brief Move both motors at speed (-500 to 500). | |
*/ | |
void move(int speed) { | |
leftMotor.moveAtSpeed(speed); | |
rightMotor.moveAtSpeed(speed); | |
state = stateMoving; | |
} | |
/* | |
* @brief Stop both motors. | |
*/ | |
void stop() { | |
leftMotor.stop(); | |
rightMotor.stop(); | |
state = stateStopped; | |
} | |
bool moving() | |
{ | |
return (state == stateMoving); | |
} | |
bool turning() | |
{ | |
return (state == stateTurning); | |
} | |
bool stopped() | |
{ | |
return (state == stateStopped); | |
} | |
bool doneRunning(unsigned long currentTime) | |
{ | |
return (currentTime >= endTime); | |
} | |
bool obstacleAhead(unsigned int distance) | |
{ | |
return (distance <= MIN_DISTANCE); | |
} | |
bool turn(unsigned long currentTime) | |
{ | |
if (random(2) == 0) { | |
moveLeft(BACK_SLOW); | |
moveRight(AHEAD_SLOW); | |
} | |
else { | |
moveLeft(AHEAD_SLOW); | |
moveRight(BACK_SLOW); | |
} | |
state = stateTurning; | |
endStateTime = currentTime + random(500, 1000); | |
} | |
bool doneTurning(unsigned long currentTime, unsigned int distance) | |
{ | |
if (currentTime >= endStateTime) | |
return (distance > MIN_DISTANCE); | |
return false; | |
} | |
/* | |
* @brief Display text message on the logging OLED. | |
* Maximum message length is 32 chars including terminating char. | |
* Flash LED on pin 13 when sending message. | |
*/ | |
void sendMessage(byte address, char *msg) | |
{ | |
digitalWrite(pin_LED, HIGH); | |
Wire.beginTransmission(address); | |
Wire.write(msg); | |
Wire.endTransmission(); | |
digitalWrite(pin_LED, LOW); | |
} | |
/* | |
* @brief Update the state of the robot based on input from sensor and remote control. | |
* Called repeatedly while the robot is in operation. | |
*/ | |
void run() | |
{ | |
unsigned long currentTime = millis(); | |
unsigned long elapsedTime = currentTime - startTime; | |
if (stopped()) | |
return; | |
int distance = distanceAverage.add(frontSonar.getDistance()); | |
log("state: %d, currentTime: %ul, ", state, currentTime); | |
log("distance: %d\n", distance); | |
if (doneRunning(currentTime)) { | |
stop(); | |
log("End of run time"); | |
} | |
else if (moving()) { | |
log("Moving, "); | |
if (obstacleAhead(distance)) { | |
turn(currentTime); | |
log("Commencing turn, "); | |
} | |
} | |
else if (turning()) { | |
if (doneTurning(currentTime, distance)) { | |
move(AHEAD_SLOW); | |
log("Turn complete, "); | |
} | |
} | |
} | |
private: | |
HB25MotorControl leftMotor, rightMotor; | |
Synthesizer synthesizer; | |
DistanceSensor frontSonar; | |
TurretServo frontServo; | |
MovingAverage <unsigned int, 3> distanceAverage; | |
RealTimeClock realTimeClock; | |
Compass compass; | |
enum state_t { stateStopped, stateMoving, stateTurning }; | |
state_t state; | |
unsigned long startTime, endTime, endStateTime; | |
char _msgArray[32]; | |
}; | |
#endif |
Then in the main.ino file all you need to do is:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @file MegaMain.ino | |
* @brief Arduino Mega 2650 - This microcontroller is the master which co-ordinates the others | |
* via the I2C and serial bus. | |
* | |
* Log commands are sent from the main microcontroller on the I2C bus | |
* to the OLED and SD logging arduino at address 10. Piezo tones are | |
* also controlled from this microcontroller. | |
* | |
* The microcontroller at address 11 on the I2C bus controls the | |
* 2 x 16 LCD and provides access to the temperature sensor. | |
* | |
* Uses the MPL3115A2 library to display the current altitude and chip temperature. | |
* The MPL3115A2 is a low-cost, low power, highly accurate barometric pressure sensor. | |
* The sensor is very sensitive and capable of detecting a change of only 0.05kPa | |
* which equates to a 0.3m change in altitude. The MPL3115A2: | |
* | |
* - Typical pressure accuracy of ±0.05kPa | |
* - Typical altitude accuracy of ±0.3m | |
* - Typical temperature accuracy of ±3C | |
* - 3.3V sensor - use inline logic level converters or 330 ohm resistors | |
* to limit 5V signals. | |
* | |
* Test to Speech is provided by the EMIC 2 module. | |
* | |
* Front top PIR is a SimplyTronics ST-00031/81 PIR Sensor/ Wide angle PIR Sensor | |
* Note: wait 40 seconds for the PIR Sensor to warm up. | |
* | |
* Doxygen is used to self generate the supporting documentation. | |
* | |
* @author David Such | |
*/ | |
#include <Wire.h> | |
#include <Servo.h> | |
#include <NewPing.h> | |
#include <HMC5883L.h> | |
#include <DS3232RTC.h> | |
#include <SoftwareSerial.h> | |
#include <HB25MotorControl.h> | |
#include <Time.h> | |
#include "logging.h" | |
#include "constants.h" | |
#include "moving_average.h" | |
#include "pin_definitions.h" | |
#include "ping_distance_sensor.h" | |
#include "turret_servo.h" | |
#include "synthesizer.h" | |
#include "time_methods.h" | |
#include "real_time_clock.h" | |
#include "compass.h" | |
#include "robot.h" | |
Robot ava; | |
void setup() | |
{ | |
Serial.begin(9600); | |
Serial.println("AVA Initialisation."); | |
ava.begin(); | |
} | |
void loop() | |
{ | |
ava.run(); | |
} |
You don't have to use C++ classes like I did but it makes it easier to maintain your code. You can also ignore the bits which refer to the motors and other sensors, I just leave it in here for completeness.