| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 |
- // 'wavface' example sketch for Adafruit I2C 8x8 LED backpacks
- // and Wave Shield:
- //
- // www.adafruit.com/products/870 www.adafruit.com/products/1049
- // www.adafruit.com/products/871 www.adafruit.com/products/1050
- // www.adafruit.com/products/872 www.adafruit.com/products/1051
- // www.adafruit.com/products/959 www.adafruit.com/products/1052
- // www.adafruit.com/products/94
- //
- // Requires Adafruit_LEDBackpack, Adafruit_GFX libraries and WaveHC
- // libraries.
- //
- // This sketch shows animation roughly synchronized to prerecorded
- // speech. It's fairly complex and may be overwhelming to novice
- // programmers, who may want to start with the 'matrix8x8' example
- // and then 'roboface' before working through this code. Also, much
- // of the comments relating to the face animation have been stripped
- // here for brevity...refer to the 'roboface' sketch if you have any
- // questions how that part works.
- //
- // Additional hardware required: sounds are triggered using three
- // normally-open momentary buttons connected to Digital pins 6, 7, 8
- // and GND. (e.g. www.adafruit.com/products/1009 )
- //
- // Adafruit invests time and resources providing this open source code,
- // please support Adafruit and open-source hardware by purchasing
- // products from Adafruit!
- //
- // Written by P. Burgess for Adafruit Industries, parts adapted from
- // 'PiSpeakHC' sketch included with WaveHC library.
- // BSD license, all text above must be included in any redistribution.
- #include <Arduino.h>
- #include <Wire.h>
- #include <Adafruit_GFX.h>
- #include <WaveHC.h>
- #include <WaveUtil.h>
- #include "Adafruit_LEDBackpack.h"
- // These WAV files should be in the root level of the SD card:
- static const char PROGMEM
- wav0[] = "beware_i.wav",
- wav1[] = "ihunger.wav",
- wav2[] = "run_cowd.wav";
- static const char * const wavname[] PROGMEM = { wav0, wav1, wav2 };
- // PROGMEM makes frequent appearances throughout this code, reason being that
- // the SD card library requires gobs of precious RAM (leaving very little to
- // our own sketch). PROGMEM lets us put fixed data into program flash memory,
- // which is considerably more spacious. String tables are paritcularly nasty.
- // See www.arduino.cc/en/Reference/PROGMEM for more info.
- SdReader card; // This object holds the information for the card
- FatVolume vol; // This holds the information for the partition on the card
- FatReader root; // This holds the information for the volumes root directory
- FatReader file; // This object represent the WAV file for a phrase
- WaveHC wave; // A single wave object -- only one sound is played at a time
- // Because the two eye matrices share the same address, only four
- // matrix objects are needed for the five displays:
- #define MATRIX_EYES 0
- #define MATRIX_MOUTH_LEFT 1
- #define MATRIX_MOUTH_MIDDLE 2
- #define MATRIX_MOUTH_RIGHT 3
- Adafruit_8x8matrix matrix[4] = { // Array of Adafruit_8x8matrix objects
- Adafruit_8x8matrix(), Adafruit_8x8matrix(),
- Adafruit_8x8matrix(), Adafruit_8x8matrix() };
- // Rather than assigning matrix addresses sequentially in a loop, each
- // has a spot in this array. This makes it easier if you inadvertently
- // install one or more matrices in the wrong physical position --
- // re-order the addresses in this table and you can still refer to
- // matrices by index above, no other code or wiring needs to change.
- static const uint8_t PROGMEM matrixAddr[] = { 0x70, 0x71, 0x72, 0x73 };
- static const uint8_t PROGMEM // Bitmaps are stored in program memory
- blinkImg[][8] = { // Eye animation frames
- { B00111100, // Fully open eye
- B01111110,
- B11111111,
- B11111111,
- B11111111,
- B11111111,
- B01111110,
- B00111100 },
- { B00000000,
- B01111110,
- B11111111,
- B11111111,
- B11111111,
- B11111111,
- B01111110,
- B00111100 },
- { B00000000,
- B00000000,
- B00111100,
- B11111111,
- B11111111,
- B11111111,
- B00111100,
- B00000000 },
- { B00000000,
- B00000000,
- B00000000,
- B00111100,
- B11111111,
- B01111110,
- B00011000,
- B00000000 },
- { B00000000, // Fully closed eye
- B00000000,
- B00000000,
- B00000000,
- B10000001,
- B01111110,
- B00000000,
- B00000000 } },
- mouthImg[][24] = { // Mouth animation frames
- { B00000000, B00000000, B00000000, // Mouth position A
- B00000000, B00000000, B00000000,
- B01111111, B11111111, B11111110,
- B00000000, B00000000, B00000000,
- B00000000, B00000000, B00000000,
- B00000000, B00000000, B00000000,
- B00000000, B00000000, B00000000,
- B00000000, B00000000, B00000000 },
- { B00000000, B00000000, B00000000, // Mouth position B
- B00000000, B00000000, B00000000,
- B00111111, B11111111, B11111100,
- B00000111, B00000000, B11100000,
- B00000000, B11111111, B00000000,
- B00000000, B00000000, B00000000,
- B00000000, B00000000, B00000000,
- B00000000, B00000000, B00000000 },
- { B00000000, B00000000, B00000000, // Mouth position C
- B00000000, B00000000, B00000000,
- B00111111, B11111111, B11111100,
- B00001000, B00000000, B00010000,
- B00000110, B00000000, B01100000,
- B00000001, B11000011, B10000000,
- B00000000, B00111100, B00000000,
- B00000000, B00000000, B00000000 },
- { B00000000, B00000000, B00000000, // Mouth position D
- B00000000, B00000000, B00000000,
- B00111111, B11111111, B11111100,
- B00100000, B00000000, B00000100,
- B00010000, B00000000, B00001000,
- B00001100, B00000000, B00110000,
- B00000011, B10000001, B11000000,
- B00000000, B01111110, B00000000 },
- { B00000000, B00000000, B00000000, // Mouth position E
- B00000000, B00111100, B00000000,
- B00011111, B11000011, B11111000,
- B00000011, B10000001, B11000000,
- B00000000, B01111110, B00000000,
- B00000000, B00000000, B00000000,
- B00000000, B00000000, B00000000,
- B00000000, B00000000, B00000000 },
- { B00000000, B00111100, B00000000, // Mouth position F
- B00000000, B11000011, B00000000,
- B00001111, B00000000, B11110000,
- B00000001, B00000000, B10000000,
- B00000000, B11000011, B00000000,
- B00000000, B00111100, B00000000,
- B00000000, B00000000, B00000000,
- B00000000, B00000000, B00000000 } };
- // Animation sequences corresponding to each WAV. First number in
- // each pair is a mouth bitmap index. Second number is the hold
- // time (in frames). 255 marks end of list.
- // There is no 'magic' here, the software is NOT deriving mouth
- // position from the sound...the tables were determined by hand,
- // just as animators do it. Further explanation here:
- // http://www.idleworm.com/how/anm/03t/talk1.shtml
- static const uint8_t PROGMEM
- seq1[] = { 0, 2, 2, 5, 5, 3, 3, 7, // "Beware, I live!"
- 4, 5, 3, 4, 2, 5, 4, 3,
- 3, 4, 1, 5, 3, 5, 255 },
- seq2[] = { 0, 1, 3, 5, 1, 5, 4, 2, // "I hunger!"
- 3, 2, 1, 2, 4, 4, 1, 3,
- 4, 2, 255 },
- seq3[] = { 0, 1, 1, 2, 3, 6, 2, 5, // "Run, coward!"
- 0, 1, 4, 4, 5, 2, 1, 5,
- 3, 6, 1, 4, 255 };
- static const uint8_t * const anim[] = { seq1, seq2, seq3 };
- const uint8_t
- blinkIndex[] PROGMEM = { 1, 2, 3, 4, 3, 2, 1 }; // Blink bitmap sequence
- uint8_t
- blinkCountdown = 100, // Countdown to next blink (in frames)
- gazeCountdown = 75, // Countdown to next eye movement
- gazeFrames = 50, // Duration of eye movement (smaller = faster)
- mouthPos = 0, // Current image number for mouth
- mouthCountdown = 10, // Countdown to next mouth change
- newPos = 255, // New mouth position for current frame
- *seq, // Animation sequence currently being played back
- idx, // Current array index within animation sequence
- prevBtn = 99, // Button # pressed on last loop() iteration
- btnCount = 0; // Number of iterations same button has been held
- int8_t
- eyeX = 3, eyeY = 3, // Current eye position
- newX = 3, newY = 3, // Next eye position
- dX = 0, dY = 0; // Distance from prior to new position
- void setup() {
- Serial.begin(9600);
-
- Serial.println(F("WAV face"));
-
- if(!card.init()) Serial.println(F("Card init. failed!"));
- if(!vol.init(card)) Serial.println(F("No partition!"));
- if(!root.openRoot(vol)) Serial.println(F("Couldn't open dir"));
- Serial.println(F("Files found:"));
- root.ls();
- // Seed random number generator from an unused analog input:
- randomSeed(analogRead(A0));
- // Initialize each matrix object:
- for(uint8_t i=0; i<4; i++) {
- matrix[i].begin(pgm_read_byte(&matrixAddr[i]));
- // If using 'small' (1.2") displays vs. 'mini' (0.8"), enable this:
- // matrix[i].setRotation(3);
- }
- // Enable pull-up resistors on three button inputs.
- // Other end of each button then connects to GND.
- for(uint8_t i=6; i<=8; i++) {
- pinMode(i, INPUT);
- digitalWrite(i, HIGH); // Enable pullup
- }
- }
- void loop() {
- uint8_t i;
- // Draw eyeball in current state of blinkyness (no pupil).
- matrix[MATRIX_EYES].clear();
- matrix[MATRIX_EYES].drawBitmap(0, 0,
- blinkImg[
- (blinkCountdown < sizeof(blinkIndex)) ? // Currently blinking?
- pgm_read_byte(&blinkIndex[blinkCountdown]) : // Yes, look up bitmap #
- 0 // No, show bitmap 0
- ], 8, 8, LED_ON);
- // Decrement blink counter. At end, set random time for next blink.
- if(--blinkCountdown == 0) blinkCountdown = random(5, 180);
- if(--gazeCountdown <= gazeFrames) {
- // Eyes are in motion - draw pupil at interim position
- matrix[MATRIX_EYES].fillRect(
- newX - (dX * gazeCountdown / gazeFrames),
- newY - (dY * gazeCountdown / gazeFrames),
- 2, 2, LED_OFF);
- if(gazeCountdown == 0) { // Last frame?
- eyeX = newX; eyeY = newY; // Yes. What's new is old, then...
- do { // Pick random positions until one is within the eye circle
- newX = random(7); newY = random(7);
- dX = newX - 3; dY = newY - 3;
- } while((dX * dX + dY * dY) >= 10); // Thank you Pythagoras
- dX = newX - eyeX; // Horizontal distance to move
- dY = newY - eyeY; // Vertical distance to move
- gazeFrames = random(3, 15); // Duration of eye movement
- gazeCountdown = random(gazeFrames, 120); // Count to end of next movement
- }
- } else {
- // Not in motion yet -- draw pupil at current static position
- matrix[MATRIX_EYES].fillRect(eyeX, eyeY, 2, 2, LED_OFF);
- }
- // Scan buttons 6, 7, 8 looking for first button pressed...
- for(i=0; (i<3) && (digitalRead(i+6) == HIGH); i++);
- if(i < 3) { // Anything pressed? Yes!
- if(i == prevBtn) { // Same as last time we checked? Good!
- if(++btnCount == 3) { // 3 passes to 'debounce' button input
- playfile((char *)pgm_read_word(&wavname[i])); // Start WAV
- // Look up animation sequence # corresponding to this WAV...
- seq = (uint8_t *)pgm_read_word(&anim[i]);
- idx = 0; // Begin at first byte of data
- newPos = pgm_read_byte(&seq[idx++]); // Initial mouth pos
- mouthCountdown = pgm_read_byte(&seq[idx++]); // Hold time for pos
- }
- } else btnCount = 0; // Different button than before - start count over
- prevBtn = i;
- } else prevBtn = 99; // No buttons pressed
- if(newPos != 255) { // Is the mouth in motion?
- if(--mouthCountdown == 0) { // Count down frames to next position
- newPos = pgm_read_byte(&seq[idx++]); // New mouth position
- if(newPos == 255) { // End of list?
- mouthPos = 0; // Yes, set mouth to neutral position
- } else {
- mouthPos = newPos; // Set mouth to new position
- mouthCountdown = pgm_read_byte(&seq[idx++]); // Read hold time
- }
- }
- } else mouthPos = 0; // Mouth not in motion -- set to neutral position
- drawMouth(mouthImg[mouthPos]);
- // Refresh all matrices in one quick pass
- for(uint8_t i=0; i<4; i++) matrix[i].writeDisplay();
- delay(20);
- }
- // Draw mouth image across three adjacent displays
- void drawMouth(const uint8_t *img) {
- for(uint8_t i=0; i<3; i++) {
- matrix[MATRIX_MOUTH_LEFT + i].clear();
- matrix[MATRIX_MOUTH_LEFT + i].drawBitmap(i * -8, 0, img, 24, 8, LED_ON);
- }
- }
- // Open and start playing a WAV file
- void playfile(const char *name) {
- char filename[13]; // 8.3+NUL
- if(wave.isplaying) wave.stop(); // Stop any currently-playing WAV
- strcpy_P(filename, name); // Copy name out of PROGMEM into RAM
- if(!file.open(root, filename)) {
- Serial.print(F("Couldn't open file "));
- Serial.println(filename);
- return;
- }
- if(!wave.create(file)) {
- Serial.println(F("Not a valid WAV"));
- return;
- }
- wave.play();
- }
|