The Bluefruit stuff is working great. Same with the motorshield wing-- the motors spin etc. So overall I'm making progress. But I'm having trouble with the VS1053 board. On startup it successfully plays test sine waves and a couple of WAV files from the SD card. But after playing one or two files, it craps out and won't play any more files.
I am kinda concerned that the interrupt pin I'm using on the VS1053 is not compatible with the Bluefruit board... I'm using the default, copied from examples like simpleplayer
I hate to do this, but I'm gonna paste my code in hopes that someone will spot something obvious.
MAny thanks for any ideas...
here is the insanely long file:
- Code: Select all | TOGGLE FULL SIZE
/*
R2D2 rides again!
the idea is to resurrect an old HASBRO R2D2 droid that has been inoperative for many parsecs.
We tore out the guts but kept the following circuitry intact:
a left motor
a right motor,
a motor that spins his head
a speaker for bleeps and bloops.
R2D2's original manual is at https://www.hasbro.com/common/instruct/87245.pdf
The rebooted R2 will respond to commands from an iPhone App!
We'll use Adafruit Bluefruit app.
this code is adapted from Ada_BLE_RC, Adafruit VS1053 simple player, and bunch other stuff.
Hardware:
Adafruit Feather M0 Bluefruit LE microcontroller
Feather Wing Motorshield
Feather Wing VS1053 sound board
we'll reuse R2's motors and speakers and battery case.
Revision history:
v1: feather plays nice with motorshield.
v2: added bluetooth control thru the BLE object.
you can control R2 with iphone app. also added primitive
sound thru an old Adafruit Sound FX board.
v3: installed Adafruit VS1053 board instead. This gives us sound files
that we read from a disk. It also introduces a ton of weird defines
that gum up the code.
Known problems/bugs:
soundboard craps out after playing a couple of sounds. maybe the delay() stuff is screwing it up?
should get rid of the duration argument to the movement functions
Future ideas:
Chris Lydgate: Dec 27 2017
*/
#include <string.h>
#include <Arduino.h>
#include <SPI.h>
#include "Adafruit_BLE.h"
#include "Adafruit_BluefruitLE_SPI.h"
#include "Adafruit_BluefruitLE_UART.h"
#include "BluefruitConfig.h"
#if SOFTWARE_SERIAL_AVAILABLE
#include "SoftwareSerial.h"
#endif
#include <Wire.h>
#include <Adafruit_MotorShield.h>
#include "utility/Adafruit_MS_PWMServoDriver.h"
#include <SD.h>
#include <Adafruit_VS1053.h>
// Next comes a bunch of defines for the VS1053 featherwing. a lot of it seems preoccupied
// with switching around pins for different micros. we could probably take a bunch of it out
// since this code relies on a Feather M0.
#define VS1053_RESET -1 // VS1053 reset pin (not used!)
// Feather M0 or 32u4
#if defined(__AVR__) || defined(ARDUINO_SAMD_FEATHER_M0)
#define VS1053_CS 6 // VS1053 chip select pin (output)
#define VS1053_DCS 10 // VS1053 Data/command select pin (output)
#define CARDCS 5 // Card chip select pin
// DREQ should be an Int pin *if possible* (not possible on 32u4)
#define VS1053_DREQ 9 // VS1053 Data request, ideally an Interrupt pin
// Feather ESP8266
#elif defined(ESP8266)
#define VS1053_CS 16 // VS1053 chip select pin (output)
#define VS1053_DCS 15 // VS1053 Data/command select pin (output)
#define CARDCS 2 // Card chip select pin
#define VS1053_DREQ 0 // VS1053 Data request, ideally an Interrupt pin
// Feather ESP32
#elif defined(ESP32)
#define VS1053_CS 32 // VS1053 chip select pin (output)
#define VS1053_DCS 33 // VS1053 Data/command select pin (output)
#define CARDCS 14 // Card chip select pin
#define VS1053_DREQ 15 // VS1053 Data request, ideally an Interrupt pin
// Feather Teensy3
#elif defined(TEENSYDUINO)
#define VS1053_CS 3 // VS1053 chip select pin (output)
#define VS1053_DCS 10 // VS1053 Data/command select pin (output)
#define CARDCS 8 // Card chip select pin
#define VS1053_DREQ 4 // VS1053 Data request, ideally an Interrupt pin
// WICED feather
#elif defined(ARDUINO_STM32_FEATHER)
#define VS1053_CS PC7 // VS1053 chip select pin (output)
#define VS1053_DCS PB4 // VS1053 Data/command select pin (output)
#define CARDCS PC5 // Card chip select pin
#define VS1053_DREQ PA15 // VS1053 Data request, ideally an Interrupt pin
#elif defined(ARDUINO_FEATHER52)
#define VS1053_CS 30 // VS1053 chip select pin (output)
#define VS1053_DCS 11 // VS1053 Data/command select pin (output)
#define CARDCS 27 // Card chip select pin
#define VS1053_DREQ 31 // VS1053 Data request, ideally an Interrupt pin
#endif
Adafruit_VS1053_FilePlayer musicPlayer =
Adafruit_VS1053_FilePlayer(VS1053_RESET, VS1053_CS, VS1053_DCS, VS1053_DREQ, CARDCS);
/* Create the motor shield object with the default I2C address
then set up 3 motors. one for left, one for right, one for ht ehead.
*/
Adafruit_MotorShield AFMS = Adafruit_MotorShield();
// set the left motor to M1
Adafruit_DCMotor *leftMotor = AFMS.getMotor(1);
// set the right motor to M2
Adafruit_DCMotor *rightMotor = AFMS.getMotor(2);
// set the head motor to M3
Adafruit_DCMotor *headMotor = AFMS.getMotor(3);
/* next, set up the bluetooth stuff. First we set our device's name. it will be set via the
AT command thingy. Then we create a brand new shiny Bluefruit object, named 'ble'.
*/
String BROADCAST_NAME = "R2D2";
String BROADCAST_CMD = String("AT+GAPDEVNAME=" + BROADCAST_NAME);
Adafruit_BluefruitLE_SPI ble(BLUEFRUIT_SPI_CS, BLUEFRUIT_SPI_IRQ, BLUEFRUIT_SPI_RST);
// A small helper
void error(const __FlashStringHelper*err) {
Serial.println(err);
while (1);
}
// function prototypes over in packetparser.cpp
uint8_t readPacket(Adafruit_BLE *ble, uint16_t timeout);
float parsefloat(uint8_t *buffer);
void printHex(const uint8_t * data, const uint32_t numBytes);
// the packet buffer. each time we loop, we fill this with the packet received from iphone app.
extern uint8_t packetbuffer[];
char buf[60];
long lastPress = 0; // the timestamp in ms of the last time a button was pressed.
int MAX_SPEED = 50; // don't want R2 to move too fast!
int CRUISING_SPEED = 100;
int BUTTON_PIN = 6; // temp usage for user pressing a button
//
// the setup function runs once when you press reset or power the board
//
void setup() {
// initialize digital pin LED_BUILTIN as an output.
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(19200); // set up Serial library at 19200 bps
while (!Serial) {
; // wait for it to begin
}
lastPress = millis();
//
// set up the card on the VS1053.
//
if (!SD.begin(CARDCS)) {
Serial.println(F("SD failed, or not present"));
}
Serial.println("SD OK!");
if (! musicPlayer.begin()) { // initialise the music player
Serial.println(F("Couldn't find VS1053, do you have the right pins defined?"));
} else {
Serial.println(F("VS1053 found"));
int i;
for (i = 1; i < 5; i++) {
musicPlayer.sineTest(i, 10); // Make a tone to indicate VS1053 is working
}
musicPlayer.setVolume(20, 20); // Set volume for left, right channels. lower numbers == louder volume!
musicPlayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT); // DREQ int
musicPlayer.startPlayingFile("R2D2-01.wav");
}
AFMS.begin(); // create motorshield object with the default frequency 1.6KHz
// warm up the motors. speed to start, from 0 (off) to 255 (max speed)
leftMotor->setSpeed(0);
leftMotor->run(RELEASE);
rightMotor->setSpeed(0);
rightMotor->run(RELEASE);
headMotor->setSpeed(0);
headMotor->run(RELEASE);
Serial.println(F("setup: warming up the engines."));
Serial.println(F("-----------------------------------------"));
BLEsetup();
}
/* next function copied direct from adafruit robot example.
*/
void BLEsetup() {
Serial.print(F("Initialising the Bluefruit LE module: "));
if ( !ble.begin(VERBOSE_MODE) )
{
error(F("Couldn't find Bluefruit, make sure it's in CoMmanD mode & check wiring?"));
}
Serial.println( F("OK!") );
/* Perform a factory reset to make sure everything is in a known state */
Serial.println(F("Performing a factory reset: "));
if (! ble.factoryReset() ) {
error(F("Couldn't factory reset"));
}
//Convert the name change command to a char array
BROADCAST_CMD.toCharArray(buf, 60);
//Change the broadcast device name here!
if (ble.sendCommandCheckOK(buf)) {
Serial.println("name changed");
}
delay(250);
//reset to take effect
if (ble.sendCommandCheckOK("ATZ")) {
Serial.println("resetting");
}
delay(250);
//Confirm name change
ble.sendCommandCheckOK("AT+GAPDEVNAME");
/* Disable command echo from Bluefruit */
ble.echo(false);
/*
* Wait for connection. In other words, loop forever until user launches Bluefruit App on iphone and connects to us.
*/
while (! ble.isConnected()) {
Serial.print("launch your iphone app... ");
delay(500);
}
Serial.println("Requesting Bluefruit info:");
/* Print Bluefruit information */
ble.info();
Serial.println(F("Please use Adafruit Bluefruit LE app to connect in Controller mode"));
Serial.println(F("Then activate/use the sensors, color picker, game controller, etc!"));
Serial.println();
ble.verbose(false); // debug info is a little annoying after this point!
Serial.println(F("*****************"));
// Set Bluefruit to DATA mode
Serial.println( F("Switching to DATA mode!") );
ble.setMode(BLUEFRUIT_MODE_DATA);
Serial.println(F("*****************"));
ble.println("R2D2 at your service.");
}
/*
here is the loop function that is the heart of R2. We loop forever, checking for packets
sent from the phone. right now we will just implement the "controller" mode, which means
user might press one of the 8 buttons on the "controller" in the Adafruit app. The 8
buttons are 1 2 3 4 up down left right. Let's have up = forward, down = reverse, left = spinleft
and right = spinright. The number buttons can make R2 spin his head or make bleeps.
Each packet should contain 5 chars. Example:
Button 4 pressed: [‘!’] [‘B’] [‘4’] [‘1’] [CRC]
There's a ! followed by B for button. the button number here is 4 and the 1 means it was pressed
(0 = released). Finally there's a CRC.
Up = 5, Down = 6, Left = 7, Right = 8;
right now the command to move lasts for a set time, ie advance is set for 500 ms. probably instead we
should advance forever but just halt whenver we get a "liftup" signal.
*/
bool isMoving;
int lastButton = 0; // this is a way to check if the user has hit a new button.
void loop() {
// is there a physical override?
if (digitalRead(BUTTON_PIN) == LOW) {
Serial.println("override!");
advance();
return;
}
long elapsed = 0;
elapsed = millis() - lastPress;
Serial.println(elapsed);
lastPress += elapsed;
//next read new packet data into packetbuffer
uint8_t len = readPacket(&ble, BLE_READPACKET_TIMEOUT);
if (len == 0) return;
// OK, we should now have packet data in packetbuffer.
// Next make sure it's a button press and not something else.
//
if (packetbuffer[1] = ! 'B') {
Serial.println("not a button!");
return;
}
uint8_t buttnum = packetbuffer[2] - '0';
boolean pressed = packetbuffer[3] - '0';
if (pressed == 0) { //the button was released. stop moving!
lastButton = 0;
halt();
return;
}
if (buttnum == lastButton) { // same old button. do nothing!
return;
}
// if we're here, it means a new button was pressed.
Serial.println("button press!");
musicPlayer.stopPlaying();
switch (buttnum) {
case 1:
if (!musicPlayer.startPlayingFile("HELP.wav")) {
Serial.print("no help");
} else {
ble.println("Help me, Obi Wan Kenobi.");
}
break;
case 2:
musicPlayer.startPlayingFile("CHANCES.wav");
ble.println("Outlook is grim.");
break;
case 3:
ble.println("Cue the music.");
musicPlayer.startPlayingFile("starwars.wav");
break;
case 4:
ble.println("resetting vs1053");
if(musicPlayer.playingMusic) {
ble.println("it was playing.");
}
musicPlayer.begin();
break;
case 5:
musicPlayer.startPlayingFile("R2D2-01.wav");
advance();
ble.println("R2D2 advances!");
break;
case 6:
musicPlayer.startPlayingFile("R2D2-02.wav");
retreat();
ble.println("R2D2 retreats!");
break;
case 7:
musicPlayer.startPlayingFile("R2D2-03.wav");
spinLeft();
ble.println("R2D2 left.");
break;
case 8:
musicPlayer.startPlayingFile("R2D2-04.wav");
spinRight();
ble.println("R2D2 right.");
break;
default:
ble.println("This droid is tricky.");
break;
}
}
void advance() {
int i;
Serial.println("forward!");
leftMotor->run(FORWARD);
rightMotor->run(FORWARD);
// first, slowly get up to speed;
for (i = 0; i < CRUISING_SPEED; i++) {
leftMotor->setSpeed(i);
rightMotor->setSpeed(i);
delay(10);
}
}
void retreat() {
int i;
Serial.println("retreat!");
leftMotor->run(BACKWARD);
rightMotor->run(BACKWARD);
// first, slowly get up to speed;
for (i = 0; i < CRUISING_SPEED; i++) {
leftMotor->setSpeed(i);
rightMotor->setSpeed(i);
delay(10);
}
}
void spinLeft() {
int i;
Serial.println("left!");
leftMotor->run(BACKWARD);
// first, slowly get up to speed;
for (i = 0; i < CRUISING_SPEED; i++) {
leftMotor->setSpeed(i);
delay(10);
}
}
void spinRight() {
int i;
Serial.println("right!");
leftMotor->run(FORWARD);
// first, slowly get up to speed;
for (i = 0; i < CRUISING_SPEED; i++) {
leftMotor->setSpeed(i);
delay(10);
}
}
void spinHead() {
int i;
Serial.println("spin head!");
headMotor->run(FORWARD);
// first, slowly get up to speed;
for (i = 0; i < CRUISING_SPEED; i++) {
headMotor->setSpeed(i);
delay(10);
}
}
void halt() {
Serial.println("halt!");
isMoving = 0;
leftMotor->run(RELEASE);
rightMotor->run(RELEASE);
headMotor->run(RELEASE);
}