#include <avr/wdt.h>
#include "LedControl.h"
#include "TimerOne.h"
#define BUTTON_A 2
#define BUTTON_B 3
#define BUTTON_C 4
#define BUTTON_D 5
#define LED_DI A0
#define LED_LOAD A1
#define LED_CLK 0
#define MAX_ANIMS 8
// Notify the client over serial when a digital pin state changes
uint8_t pinMap[] = {BUTTON_A, BUTTON_B, BUTTON_C, BUTTON_D};
uint8_t pinValues[] = {0, 0, 0, 0};
uint8_t buttons[] = {0, 0, 0, 0};
/*
Communication protocol:
type (1 byte), data
0x01: button press
1 byte: each bit = 1 button (0 = A, ...)
0x02: enable / disable voting
1 byte: 0 = disable, 1 = enable
*/
uint8_t msg_status = 0x01;
uint8_t msg_button = 0x02;
uint8_t msg_voting = 0x03;
/*
pin 3 is connected to the DataIn
pin 4 is connected to the CLK
pin 5 is connected to LOAD
We have only a single MAX72XX.
*/
LedControl lc = LedControl(LED_DI, LED_CLK, LED_LOAD, 1);
typedef struct KFRAME
{
float time;
int8_t x, y;
bool visible;
float (*easing)(float);
uint8_t *img;
};
typedef struct ANIMATION
{
float time;
float x, y;
bool visible;
struct KFRAME *cur_frame, *next_frame;
uint8_t *img;
struct KFRAME *frames;
};
uint8_t buffer[8];
uint8_t img_A[]={5, B01111110, B00010001, B00010001, B00010001, B01111110};
uint8_t img_smiley[]={8, B00100001, B01000000, B10000000, B10000000, B10000000, B10000000, B01000000, B00100001};
uint8_t img_smiley_wink[]={8, B00100000, B01000000, B10000000, B10000000, B10000000, B10000000, B01000000, B00100000};
uint8_t img_smiley_small[]={8, B00000000, B01000010, B10000000, B10000000, B10000000, B10000000, B01000010, B00000000};
uint8_t img_cross[]={8, B10000001, B01000010, B00100100, B00011000, B00011000, B00100100, B01000010, B10000001};
uint8_t img_num[10][4]={
{3, B00011111, B00010001, B00011111},
{3, B00000000, B00000000, B00011111},
{3, B00011101, B00010101, B00010111},
{3, B00010101, B00010101, B00011111},
{3, B00000111, B00000100, B00011111},
{3, B00010111, B00010101, B00011101},
{3, B00011111, B00010101, B00011101},
{3, B00000001, B00000001, B00011111},
{3, B00011111, B00010101, B00011111},
{3, B00010111, B00010101, B00011111}
};
uint8_t img_letter[4][9]={
{8, B11000000, B00110000, B00011100, B00010011, B00010011, B00011100, B00110000, B11000000},
{8, B11111111, B10001001, B10001001, B10001001, B10001001, B10001001, B10001001, B01110110},
{8, B00111100, B01000010, B10000001, B10000001, B10000001, B10000001, B10000001, B01000010},
{8, B11111111, B10000001, B10000001, B10000001, B10000001, B10000001, B01000010, B00111100}
};
uint8_t img_start[15][9]={
{8, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00},
{8, 0x00, 0x00, 0x00, 0x10, 0x20, 0x00, 0x00, 0x00},
{8, 0x00, 0x00, 0x00, 0x10, 0x30, 0x18, 0x00, 0x00},
{8, 0x00, 0x00, 0x04, 0x14, 0x3C, 0x18, 0x00, 0x00},
{8, 0x00, 0x38, 0x04, 0x1C, 0x3C, 0x18, 0x00, 0x00},
{8, 0x00, 0x38, 0x7C, 0xBC, 0xBC, 0x98, 0x00, 0x00},
{8, 0x00, 0x38, 0x7C, 0xFC, 0xFC, 0xF8, 0x40, 0x3C},
{8, 0x02, 0x39, 0x7D, 0xFD, 0xFD, 0xFF, 0x7E, 0x3C},
{8, 0x0E, 0x3F, 0x7F, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C},
{8, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C},
{8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
{8, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF},
{8, 0xFF, 0xFF, 0xFF, 0xE7, 0xE7, 0xFF, 0xFF, 0xFF},
{8, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF},
{8, 0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xFF}
};
struct KFRAME ani_RtoL[] = { {0.0, 8, 0, true, &easeInLinear}, {2.0, -8, 0, true}, {-1} };
struct KFRAME ani_Bounce[] = { {0.0, 0, -12, true, &easeOutBounce}, {2.0, 0, 0, true}, {-1} };
struct KFRAME ani_Blink[] = { {0.0, 0, 0, true}, {0.25, 0, 0, false}, {0.5, 0, 0, true}, {0.75, 0, 0, false}, {1.0, 0, 0, false}, {-1} };
struct KFRAME ani_Move[] = { {0.0, 0, 0, true, &easeInLinear}, {0.2, 0, 0, true}, {0.3, -1, 0, true}, {0.6, 8, 0, true}, {-1} };
struct KFRAME ani_start[] = { {0.0, 0, 0, true, 0, (uint8_t*) &img_start[0]}, {0.1, 0, 0, true, 0, (uint8_t*) &img_start[1]},
{0.2, 0, 0, true, 0, (uint8_t*) &img_start[2]}, {0.3, 0, 0, true, 0, (uint8_t*) &img_start[3]},
{0.4, 0, 0, true, 0, (uint8_t*) &img_start[4]}, {0.5, 0, 0, true, 0, (uint8_t*) &img_start[5]},
{0.6, 0, 0, true, 0, (uint8_t*) &img_start[6]}, {0.7, 0, 0, true, 0, (uint8_t*) &img_start[7]},
{0.8, 0, 0, true, 0, (uint8_t*) &img_start[8]}, {0.9, 0, 0, true, 0, (uint8_t*) &img_start[9]},
{1.0, 0, 0, true, 0, (uint8_t*) &img_start[10]}, {1.1, 0, 0, true, 0, (uint8_t*) &img_start[11]},
{1.2, 0, 0, true, 0, (uint8_t*) &img_start[12]}, {1.3, 0, 0, true, 0, (uint8_t*) &img_start[13]},
{1.4, 0, 0, true, 0, (uint8_t*) &img_start[14]}, {1.5, 0, 0, false, 0, (uint8_t*) &img_start[14]}, {-1} };
struct KFRAME ani_smile[] = { {0.0, 0, 0, false}, {1.0, 0, 0, true, 0, img_smiley}, {1.6, 0, 0, true, 0, img_smiley}, {-1} };
struct KFRAME ani_wink[] = { {0.0, 0, 0, true, 0, (uint8_t*) &img_smiley}, {0.1, 0, 0, true, 0, (uint8_t*) &img_smiley_wink}, {0.2, 0, 0, true, 0, (uint8_t*) &img_smiley}, {-1} };
struct ANIMATION anims[MAX_ANIMS];
bool has_anims = false, timer_running = false;
void render(int8_t x, uint8_t* data);
void render_buffer(int8_t x, uint8_t y, uint8_t* data);
// 0: start, 1: after button
uint8_t state = 0;
uint16_t stateTicks = 0;
void setState(uint8_t s) {
state = s;
stateTicks = 0;
}
/*
Animations
*/
void anim_init() {
has_anims = false;
for (uint8_t i = 0; i < MAX_ANIMS; i++)
anims[i].time = -1;
}
float easeInLinear(float time) {
return time;
}
float easeOutBounce(float time) {
if(time < (1/2.75))
return (7.5625 * time * time);
if(time < (2/2.75))
{
time -= 1.5/2.75;
return (7.5625 * time * time + 0.75);
}
if(time < (2.5/2.75))
{
time -= 2.25/2.75;
return (7.5625 * time * time + 0.9375);
}
time -= 2.625/2.75;
return (7.5625 * time * time + 0.984375);
}
void anim_tick(float duration) {
// little speed-up for when there are no animations
if (!has_anims || duration < 0)
return;
clear_buffer();
Bean.setLed(random(256),random(256),random(256));
has_anims = false;
for (uint8_t i = 0; i < MAX_ANIMS; i++) {
struct ANIMATION* anim = &anims[i];
if (anim->time < 0)
continue;
//render(1, 0, img_num[(uint8_t) (anim->time/10)]);
//render(5, 0, img_num[((uint8_t) anim->time)%10]);
// render to buffer with current settings
if (anim->visible)
render(round(anim->x), round(anim->y), anim->img);
// the animation ends when the next frame has time < 0
if (anim->cur_frame->time < 0) {
anim->time = -1;
continue;
}
has_anims = true;
// advance the time and interpolate values
anim->time += duration;
while (anim->time > anim->next_frame->time && anim->next_frame->time >= 0) {
anim->cur_frame++;
anim->next_frame++;
}
anim->x = anim->cur_frame->x;
anim->y = anim->cur_frame->y;
anim->visible = anim->cur_frame->visible;
if (anim->cur_frame->img != NULL)
anim->img = anim->cur_frame->img;
// next render will render the last frame
if (anim->next_frame->time < 0) {
anim->cur_frame = anim->next_frame;
}
else {
float t = anim->time - anim->cur_frame->time;
float dt = anim->next_frame->time - anim->cur_frame->time;
if(dt != 0) {
if (anim->cur_frame->easing == NULL)
anim->cur_frame->easing = &easeInLinear;
float diff = anim->next_frame->x - anim->cur_frame->x;
if (diff != 0)
anim->x += diff * anim->cur_frame->easing(t / dt);
diff = anim->next_frame->y - anim->cur_frame->y;
if (diff != 0)
anim->y += diff * anim->cur_frame->easing(t / dt);
}
}
}
render_buffer();
// when all animations are done we can stop the timer
if (!has_anims)
Timer1.stop();
}
void anim_add(uint8_t* img, struct KFRAME* frames) {
if (img == NULL || frames == NULL || frames[0].time < 0)
return;
// find the first free animation slot
for (uint8_t i = 0; i < MAX_ANIMS; i++) {
struct ANIMATION* anim = &anims[i];
if (anim->time > -1)
continue;
anim->time = 0;
anim->img = img;
anim->frames = frames;
anim->cur_frame = &frames[0];
anim->next_frame = &frames[1];
anim->x = frames[0].x;
anim->y = frames[0].y;
anim->visible = frames[0].visible;
if (frames[0].img != NULL)
anim->img = frames[0].img;
has_anims = true;
break;
}
if (has_anims && !timer_running) {
Timer1.initialize(100000); // set a timer of length 100000 microseconds (or 0.1 sec - or 10Hz)
Timer1.attachInterrupt( animIsr ); // attach the service routine here
}
}
/*
Render one image
*/
void render(int8_t x, uint8_t* data) {
// outside of left or right side of screen
if (data == NULL || x + data[0] < 0 || x > 7)
return;
uint8_t len = (x + data[0] < 8 ? data[0] : 8 - x);
for (int8_t i = (x < 0 ? -x : 0); i < len; i++) {
lc.setRow(0, x + i, data[i + 1]);
}
}
void clear_buffer() {
memset(buffer, 0, sizeof(buffer));
}
void render(int8_t x, int8_t y, uint8_t* data) {
// outside of left or right side of screen
if (data == NULL || x + data[0] < 0 || x > 7 || y + 8 < 0 || y > 7)
return;
uint8_t len = (x + data[0] < 8 ? data[0] : 8 - x);
// special case when needing to shift
if (y > 0) {
for (int8_t i = (x < 0 ? -x : 0); i < len; i++)
buffer[x + i] |= data[i + 1] << y;
} else if (y < 0) {
uint8_t shift = (-y);
for (int8_t i = (x < 0 ? -x : 0); i < len; i++)
buffer[x + i] |= data[i + 1] >> shift;
} else {
for (int8_t i = (x < 0 ? -x : 0); i < len; i++)
buffer[x + i] |= data[i + 1];
}
}
void render_buffer() {
for (int8_t i = 0; i < 8; i++) {
lc.setRow(0, i, buffer[i]);
}
}
/*
the setup routine runs once when you press reset:
*/
void setup() {
wdt_enable(WDTO_2S);
// initialize serial communication at 57600 bits per second:
Serial.begin(57600);
// this makes it so that the arduino read function returns
// immediatly if there are no less bytes than asked for.
Serial.setTimeout(25);
Serial.print("Listening...");
// Digital pins, use analog pins as digital inputs
for (int i = 0; i < sizeof(pinMap); i++) {
pinMode(pinMap[i], INPUT_PULLUP);
//Bean.attachChangeInterrupt(pinMap[i], digitalChanged);
}
/*
The MAX72XX is in power-saving mode on startup,
we have to do a wakeup call
*/
lc.shutdown(0, false);
/* Set the brightness to a medium values */
lc.setIntensity(0, 8);
/* and clear the display */
lc.clearDisplay(0);
// render(0, img_smiley);
anim_init();
//anim_add(img_smiley, ani_Bounce);
anim_add(img_smiley, ani_smile);
anim_add(img_smiley, ani_start);
}
void animIsr() {
anim_tick(0.1);
}
void pause(uint32_t duration) {
// use Bean.sleep if there are no animations to save power but delay if we have to animate so the timings work
if (!has_anims)
Bean.sleep(duration);
else
delay(duration);
}
void serialEvent() {
char buffer[64];
size_t length = 64;
while (Serial.available()) {
length = Serial.readBytes(buffer, length);
// read an input pin
if (length > 0)
{
if (buffer[0] == msg_button) {
sendButtons();
}
else if (buffer[0] == msg_voting) {
}
else {
// blink green to acknowledge read
Bean.setLed(0,255,0);
Serial.write((uint8_t*)buffer, length);
pause(250);
Bean.setLed(0, 0, 0);
}
}
}
}
/*
the loop routine runs over and over again forever:
*/
void loop() {
Bean.setLed(255,255,255);
pause(250);
Bean.setLed(0, 0, 0);
pause(250);
wdt_reset();
serialEvent();
pause(1000);
stateTicks++;
// if after button press get back to start animation
if (stateTicks > 10) {
if (state != 2 && state != 0)
anim_add(img_smiley, ani_Bounce);
else
anim_add(img_smiley, ani_wink);
setState(2);
}
}
void sendButtons() {
char buffer[sizeof(buttons) + 1];
buffer[0] = msg_button;
memcpy(buffer + 1, buttons, sizeof(buttons));
int written = Serial.write((uint8_t*) buffer, sizeof(buffer));
if (written > 0)
memset(buttons, 0, sizeof(buttons));
}
void digitalChanged() {
bool notify = false;
for (int i = 0; i < sizeof(pinMap); i++)
{
uint8_t pinState = digitalRead(pinMap[i]);
if (pinState == pinValues[i])
continue;
// if switching from 1 to 0 then the button was just pressed
if (pinValues[i] == 1 && pinState == 0) {
buttons[i]++;
anim_add(img_letter[i], ani_Blink);
notify = true;
}
pinValues[i] = pinState;
}
if (notify)
{
sendButtons();
setState(1);
}
}
Comments