/* * Copyright (c) 2019, Erich Styger * * SPDX-License-Identifier: BSD-3-Clause * ***************************************** * Idea and image files from Adafruit: * https://learn.adafruit.com/animated-flying-toaster-oled-jewelry * Animated pendant for Adafruit Pro Trinket and SSD1306 OLED display, * inspired by the After Dark "Flying Toasters" screensaver. */ #include "platform.h" #if PL_CONFIG_USE_TOASTER #include "toaster.h" #include #include #include "McuUtility.h" #include "McuArmTools.h" #include "lv.h" #include "McuSystemView.h" #include "McuGDisplaySSD1306.h" static const uint8_t toastermask0[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x43, 0xF8, 0x00, 0x00, 0x1F, 0x80, 0x00, 0x00, 0x7C, 0x3F, 0x80, 0x01, 0xF1, 0xF8, 0xF0, 0x07, 0xC7, 0xC7, 0x00, 0x0F, 0x1F, 0x08, 0x2B, 0x1E, 0x7C, 0x11, 0x00, 0x04, 0xF0, 0x20, 0x56, 0x61, 0xE4, 0x22, 0x00, 0x78, 0x48, 0x40, 0xA8, 0x4E, 0x10, 0x44, 0x00, 0x53, 0x90, 0x81, 0xF8, 0x5C, 0xA0, 0x90, 0x58, 0x7C, 0xA1, 0x07, 0x90, 0x74, 0xA1, 0x21, 0x38, 0x7F, 0xB5, 0x0E, 0x30, 0x77, 0xB0, 0x90, 0x78, 0x7F, 0xB4, 0x60, 0xF0, 0x77, 0xBB, 0x03, 0xE0, 0x7F, 0xBC, 0x0F, 0xC0, 0x77, 0xBF, 0xFF, 0x00, 0x7F, 0xBF, 0xFC, 0x00, 0x3F, 0xBF, 0xF0, 0x00, 0x1F, 0xBF, 0xC0, 0x00, 0x07, 0xBE, 0x00, 0x00, 0x01, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, toastermask1[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0xF8, 0x00, 0x00, 0x1F, 0x80, 0x00, 0x00, 0x7C, 0x3F, 0x80, 0x01, 0xF1, 0xF8, 0x00, 0x07, 0xC7, 0xC1, 0xE0, 0x0F, 0x1F, 0x06, 0x10, 0x1E, 0x7C, 0x00, 0x00, 0x04, 0xF0, 0x07, 0xF0, 0x61, 0xE4, 0x18, 0x08, 0x78, 0x48, 0x20, 0x80, 0x4E, 0x10, 0x40, 0x2B, 0x53, 0x90, 0x81, 0x00, 0x5C, 0xA0, 0x80, 0x58, 0x7C, 0xA1, 0x02, 0x00, 0x74, 0xA1, 0x28, 0xB8, 0x7F, 0xB5, 0x0F, 0xF0, 0x77, 0xB0, 0x90, 0x78, 0x7F, 0xB4, 0x60, 0xF0, 0x77, 0xBB, 0x03, 0xE0, 0x7F, 0xBC, 0x0F, 0xC0, 0x77, 0xBF, 0xFF, 0x00, 0x7F, 0xBF, 0xFC, 0x00, 0x3F, 0xBF, 0xF0, 0x00, 0x1F, 0xBF, 0xC0, 0x00, 0x07, 0xBE, 0x00, 0x00, 0x01, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, toastermask2[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF8, 0x00, 0x00, 0x1F, 0x80, 0x00, 0x00, 0x7C, 0x3F, 0x80, 0x01, 0xF1, 0xF8, 0x00, 0x07, 0xC7, 0xC1, 0xE0, 0x0F, 0x1F, 0x06, 0x10, 0x1E, 0x7C, 0x00, 0x00, 0x04, 0xF0, 0x00, 0x00, 0x61, 0xE4, 0x00, 0x08, 0x78, 0x48, 0x00, 0x10, 0x4E, 0x10, 0x00, 0x18, 0x53, 0x90, 0x60, 0x70, 0x5C, 0xA0, 0x9F, 0xC8, 0x7C, 0xA1, 0x04, 0x92, 0x74, 0xA1, 0x09, 0x24, 0x7F, 0xB5, 0x02, 0x48, 0x77, 0xB0, 0x80, 0x10, 0x7F, 0xB4, 0x41, 0x00, 0x77, 0xBB, 0x20, 0x40, 0x7F, 0xBC, 0x10, 0x00, 0x77, 0xBF, 0xF8, 0x00, 0x7F, 0xBF, 0xFC, 0x00, 0x3F, 0xBF, 0xF0, 0x00, 0x1F, 0xBF, 0xC0, 0x00, 0x07, 0xBE, 0x00, 0x00, 0x01, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, toastmask[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x0C, 0x30, 0x00, 0x00, 0x30, 0x0C, 0x00, 0x00, 0xC2, 0x23, 0x00, 0x07, 0x00, 0x80, 0xC0, 0x08, 0x25, 0x50, 0x30, 0x10, 0x0B, 0xA8, 0x08, 0x21, 0x37, 0xF5, 0x04, 0x30, 0x4A, 0xE8, 0x0C, 0x3C, 0x15, 0x52, 0x34, 0x2B, 0x00, 0x80, 0xEC, 0x35, 0xC2, 0x23, 0x54, 0x1A, 0xB0, 0x0E, 0xAC, 0x0D, 0x5C, 0x15, 0x58, 0x03, 0xAB, 0xEA, 0xE0, 0x00, 0xD5, 0x55, 0x80, 0x00, 0x3A, 0xAE, 0x00, 0x00, 0x0D, 0x58, 0x00, 0x00, 0x03, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, toaster0[] = { 0x00, 0x30, 0x00, 0x00, 0x00, 0xE8, 0x00, 0x00, 0x01, 0xD8, 0x00, 0x00, 0x03, 0x74, 0xF0, 0x00, 0x07, 0xEF, 0xFE, 0x00, 0x07, 0xBC, 0x07, 0x80, 0x0F, 0xE0, 0x7F, 0xE0, 0x0F, 0x83, 0xC0, 0x70, 0x1E, 0x0E, 0x07, 0x08, 0x18, 0x38, 0x38, 0xFF, 0x30, 0xE0, 0xF7, 0xD4, 0x61, 0x83, 0xEE, 0xFF, 0x7B, 0x0F, 0xDF, 0xA8, 0x9E, 0x1B, 0xDD, 0xFE, 0x87, 0xB7, 0xBF, 0x50, 0xB1, 0xEF, 0xBB, 0xF8, 0xAC, 0x6F, 0x7E, 0x00, 0xA3, 0x5F, 0x6F, 0xA0, 0x83, 0x5E, 0xF8, 0x68, 0x8B, 0x5E, 0xDE, 0xC0, 0x80, 0x4A, 0xF1, 0xC8, 0x88, 0x4F, 0x6F, 0x80, 0x80, 0x4B, 0x9F, 0x08, 0x88, 0x44, 0xFC, 0x10, 0x80, 0x43, 0xF0, 0x20, 0x88, 0x40, 0x00, 0xC0, 0x80, 0x40, 0x03, 0x00, 0x40, 0x40, 0x0C, 0x00, 0x20, 0x40, 0x30, 0x00, 0x18, 0x41, 0xC0, 0x00, 0x06, 0x4E, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x00 }, toaster1[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xF0, 0x00, 0x00, 0xF7, 0xFE, 0x00, 0x01, 0xBC, 0x07, 0x80, 0x03, 0xE0, 0x7F, 0xE0, 0x07, 0x83, 0xC0, 0x70, 0x0E, 0x0E, 0x07, 0xF8, 0x18, 0x38, 0x3E, 0x18, 0x30, 0xE0, 0xF9, 0xE8, 0x61, 0x83, 0xFF, 0xF8, 0x7B, 0x0F, 0xF8, 0x08, 0x9E, 0x1B, 0xE7, 0xF0, 0x87, 0xB7, 0xDF, 0x7F, 0xB1, 0xEF, 0xBF, 0xD4, 0xAC, 0x6F, 0x7E, 0xFF, 0xA3, 0x5F, 0x7F, 0xA6, 0x83, 0x5E, 0xFD, 0xF8, 0x8B, 0x5E, 0xD7, 0x40, 0x80, 0x4A, 0xF0, 0x08, 0x88, 0x4F, 0x6F, 0x80, 0x80, 0x4B, 0x9F, 0x08, 0x88, 0x44, 0xFC, 0x10, 0x80, 0x43, 0xF0, 0x20, 0x88, 0x40, 0x00, 0xC0, 0x80, 0x40, 0x03, 0x00, 0x40, 0x40, 0x0C, 0x00, 0x20, 0x40, 0x30, 0x00, 0x18, 0x41, 0xC0, 0x00, 0x06, 0x4E, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x00 }, toaster2[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x07, 0xFE, 0x00, 0x00, 0x3C, 0x07, 0x80, 0x00, 0xE0, 0x7F, 0xE0, 0x03, 0x83, 0xC0, 0x70, 0x0E, 0x0E, 0x07, 0xF8, 0x18, 0x38, 0x3E, 0x18, 0x30, 0xE0, 0xF9, 0xE8, 0x61, 0x83, 0xFF, 0xF8, 0x7B, 0x0F, 0xFF, 0xF8, 0x9E, 0x1B, 0xFF, 0xF0, 0x87, 0xB7, 0xFF, 0xE8, 0xB1, 0xEF, 0xFF, 0xE0, 0xAC, 0x6F, 0x9F, 0x88, 0xA3, 0x5F, 0x60, 0x36, 0x83, 0x5E, 0xFB, 0x6D, 0x8B, 0x5E, 0xF6, 0xDB, 0x80, 0x4A, 0xFD, 0xB6, 0x88, 0x4F, 0x7F, 0xEE, 0x80, 0x4B, 0xBE, 0xFC, 0x88, 0x44, 0xDF, 0xBC, 0x80, 0x43, 0xEF, 0xF8, 0x88, 0x40, 0x07, 0xF0, 0x80, 0x40, 0x03, 0xE0, 0x40, 0x40, 0x0C, 0xC0, 0x20, 0x40, 0x30, 0x00, 0x18, 0x41, 0xC0, 0x00, 0x06, 0x4E, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x00 }, toast[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x3D, 0xDC, 0x00, 0x00, 0xFF, 0x7F, 0x00, 0x07, 0xDA, 0xAF, 0xC0, 0x0F, 0xF4, 0x57, 0xF0, 0x1E, 0xC8, 0x0A, 0xF8, 0x0F, 0xB5, 0x17, 0xF0, 0x03, 0xEA, 0xAD, 0xC8, 0x14, 0xFF, 0x7F, 0x10, 0x0A, 0x3D, 0xDC, 0xA8, 0x05, 0x4F, 0xF1, 0x50, 0x02, 0xA3, 0xEA, 0xA0, 0x00, 0x54, 0x15, 0x00, 0x00, 0x2A, 0xAA, 0x00, 0x00, 0x05, 0x50, 0x00, 0x00, 0x02, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static const uint8_t* const mask[] = { toastermask0, toastermask1, toastermask2, toastermask1, toastmask }; static const uint8_t* const img[] = { toaster0 , toaster1 , toaster2 , toaster1 , toast }; #define N_FLYERS 3 // Number of flying things struct Flyer { // Array of flying things int16_t x, y; // Top-left position * 16 (for subpixel pos updates) int8_t depth; // Stacking order is also speed, 12-24 subpixels/frame uint8_t frame; // Animation frame; Toasters cycle 0-3, Toast=255 } flyer[N_FLYERS]; // Flyer depth comparison function for qsort() static int compare(const void *a, const void *b) { return ((struct Flyer *)a)->depth - ((struct Flyer *)b)->depth; } static void drawBitmap(McuGDisplaySSD1306_PixelDim x, McuGDisplaySSD1306_PixelDim y, const uint8_t *bitmap, McuGDisplaySSD1306_PixelDim w, McuGDisplaySSD1306_PixelDim h, McuGDisplaySSD1306_PixelColor color) { TIMAGE image; image.name = ""; image.width = w; image.height = h; image.size = w*h; image.pixmap = bitmap; McuGDisplaySSD1306_DrawMonoBitmapMask(x, y, &image, color); } void TOASTER_DisplayToasters(void) { uint8_t i, f; int16_t x, y; bool resort = false; // By default, don't re-sort depths McuGDisplaySSD1306_UpdateFull(); McuGDisplaySSD1306_Clear(); /* screen black */ for(i=0; i= (64*16)) || (flyer[i].x <= (-32*16))) { // Off screen? if(McuUtility_random(0, 7-1) < 5) { // Pick random edge; 0-4 = top flyer[i].x = McuUtility_random(0, 160-1) * 16; flyer[i].y = -32 * 16; } else { // 5-6 = right flyer[i].x = 128 * 16; flyer[i].y = McuUtility_random(0, 64-1) * 16; } flyer[i].frame = McuUtility_random(0, 3-1) ? McuUtility_random(0, 4-1) : 255; // 66% toaster, else toast flyer[i].depth = 10 + McuUtility_random(0, 16-1); resort = true; } } // If any items were 'rebooted' to new position, re-sort all depths if(resort) { qsort(flyer, N_FLYERS, sizeof(struct Flyer), compare); } } //#define REFR_TIME_MS (150) /* with I2C bit banging, it requires about 130 ms for a OLED refresh */ #define REFR_TIME_MS (500) /* with I2C bit banging, it requires about 130 ms for a OLED refresh */ static lv_task_t *refr_task; static lv_obj_t *win; static bool screenSaverIsRunning = false; static bool stopScreenSaver = false; bool TOASTER_IsRunning(void) { return screenSaverIsRunning; } void TOASTER_StopScreenSaver(void) { stopScreenSaver = true; } static void refresh_task(struct _lv_task_t *param) { (void)param; /* not used */ if (stopScreenSaver) { lv_obj_del(win); win = NULL; lv_task_del(refr_task); refr_task = NULL; screenSaverIsRunning = false; } else { screenSaverIsRunning = true; #if 1 TOASTER_DisplayToasters(); #elif 1 static uint32_t cntr = 0; McuGDisplaySSD1306_PixelColor color; cntr++; if (cntr<20) { color = McuGDisplaySSD1306_COLOR_BLACK; } else if (cntr<40) { color = McuGDisplaySSD1306_COLOR_WHITE; } else { color = McuGDisplaySSD1306_COLOR_BLACK; cntr = 0; } McuGDisplaySSD1306_DrawLine( McuUtility_random(0, LV_HOR_RES-1), McuUtility_random(0, LV_VER_RES-1), McuUtility_random(0, LV_HOR_RES-1), McuUtility_random(0, LV_VER_RES-1), color); McuGDisplaySSD1306_UpdateFull(); #else line_points[0].x = McuUtility_random(0, 64); line_points[0].y = McuUtility_random(0, 64); line_points[1].x = McuUtility_random(0, 64); line_points[1].y = McuUtility_random(0, 64); lv_line_set_points(line1, line_points, 2); /*Set the points*/ lv_obj_align(line1, NULL, LV_ALIGN_IN_TOP_MID, 0, 20); #endif } } void TOASTER_Show(void) { stopScreenSaver = false; screenSaverIsRunning = false; refr_task = lv_task_create(refresh_task, REFR_TIME_MS, LV_TASK_PRIO_LOW, NULL); win = lv_obj_create(lv_scr_act(), NULL); lv_obj_set_style(win, &lv_style_transp); lv_obj_set_size(win, LV_HOR_RES, LV_VER_RES); } void TOASTER_Init(void) { McuUtility_randomSetSeed(McuArmTools_GetCycleCounter()); for(uint8_t i=0; i