| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- // 'roboface' example sketch for Adafruit I2C 8x8 LED backpacks:
- //
- // 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
- //
- // Requires Adafruit_LEDBackpack and Adafruit_GFX libraries.
- // For a simpler introduction, see the 'matrix8x8' example.
- //
- // This sketch demonstrates a couple of useful techniques:
- // 1) Addressing multiple matrices (using the 'A0' and 'A1' solder
- // pads on the back to select unique I2C addresses for each).
- // 2) Displaying the same data on multiple matrices by sharing the
- // same I2C address.
- //
- // This example uses 5 matrices at 4 addresses (two share an address)
- // to animate a face:
- //
- // 0 0
- //
- // 1 2 3
- //
- // The 'eyes' both display the same image (always looking the same
- // direction -- can't go cross-eyed) and thus share the same address
- // (0x70). The three matrices forming the mouth have unique addresses
- // (0x71, 0x72 and 0x73).
- //
- // The face animation as written is here semi-random; this neither
- // generates nor responds to actual sound, it's simply a visual effect
- // Consider this a stepping off point for your own project. Maybe you
- // could 'puppet' the face using joysticks, or synchronize the lips to
- // audio from a Wave Shield (see wavface example). Currently there are
- // only six images for the mouth. This is often sufficient for simple
- // animation, as explained here:
- // http://www.idleworm.com/how/anm/03t/talk1.shtml
- //
- // 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.
- // BSD license, all text above must be included in any redistribution.
- #include <Arduino.h>
- #include <Wire.h>
- #include <Adafruit_GFX.h>
- #include "Adafruit_LEDBackpack.h"
- // 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 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 } };
- uint8_t
- blinkIndex[] = { 1, 2, 3, 4, 3, 2, 1 }, // Blink bitmap sequence
- 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
- 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() {
- // 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(matrixAddr[i]);
- // If using 'small' (1.2") displays vs. 'mini' (0.8"), enable this:
- // matrix[i].setRotation(3);
- }
- }
- void loop() {
- // Draw eyeball in current state of blinkyness (no pupil). Note that
- // only one eye needs to be drawn. Because the two eye matrices share
- // the same address, the same data will be received by both.
- matrix[MATRIX_EYES].clear();
- // When counting down to the next blink, show the eye in the fully-
- // open state. On the last few counts (during the blink), look up
- // the corresponding bitmap index.
- matrix[MATRIX_EYES].drawBitmap(0, 0,
- blinkImg[
- (blinkCountdown < sizeof(blinkIndex)) ? // Currently blinking?
- 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);
- // Add a pupil (2x2 black square) atop the blinky eyeball bitmap.
- // Periodically, the pupil moves to a new position...
- 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);
- }
- // Draw mouth, switch to new random image periodically
- drawMouth(mouthImg[mouthPos]);
- if(--mouthCountdown == 0) {
- mouthPos = random(6); // Random image
- // If the 'neutral' position was chosen, there's a 1-in-5 chance we'll
- // select a longer hold time. This gives the appearance of periodic
- // pauses in speech (e.g. between sentences, etc.).
- mouthCountdown = ((mouthPos == 0) && (random(5) == 0)) ?
- random(10, 40) : // Longer random duration
- random(2, 8); // Shorter random duration
- }
- // Refresh all of the matrices in one quick pass
- for(uint8_t i=0; i<4; i++) matrix[i].writeDisplay();
- delay(20); // ~50 FPS
- }
- // 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);
- }
- }
|