mirror of https://github.com/arendst/Tasmota.git
252 lines
9.2 KiB
Plaintext
252 lines
9.2 KiB
Plaintext
// '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);
|
|
}
|
|
}
|
|
|