/ SHARP_BadApple / SHARP_BadApple.ino
SHARP_BadApple.ino
1 // SPDX-FileCopyrightText: 2020 Melissa LeBlanc-Williams for Adafruit Industries 2 // 3 // SPDX-License-Identifier: MIT 4 5 // Bad Apple for ESP32 with OLED SSD1306 | 2018 by Hackerspace-FFM.de | MIT-License. 6 // Adapted for Sharp Memory display + Itsy Bitsy M4 - put video.hs on QSPI storage using CircuitPython 7 #include "heatshrink_decoder.h" 8 9 #include "SdFat.h" 10 #include "Adafruit_SPIFlash.h" 11 #include <Adafruit_SharpMem.h> 12 #define BLACK 0 13 #define WHITE 1 14 15 #define SCALE 3 16 17 18 #if HEATSHRINK_DYNAMIC_ALLOC 19 #error HEATSHRINK_DYNAMIC_ALLOC must be false for static allocation test suite. 20 #endif 21 22 static heatshrink_decoder hsd; 23 24 // global storage for putPixels 25 int16_t curr_x = 0; 26 int16_t curr_y = 0; 27 28 // global storage for decodeRLE 29 int32_t runlength = -1; 30 int32_t c_to_dup = -1; 31 32 uint32_t lastRefresh = 0; 33 34 #define SHARP_SS A5 35 Adafruit_SharpMem display(&SPI, SHARP_SS, 400, 240, 3000000); 36 #define X_OFFSET (400 - SCALE*128) / 2 37 #define Y_OFFSET (240 - SCALE*64) / 2 38 39 Adafruit_FlashTransport_QSPI flashTransport; 40 Adafruit_SPIFlash flash(&flashTransport); 41 FatFileSystem fatfs; 42 43 44 void putPixels(uint8_t c, int32_t len) { 45 static uint8_t color; 46 uint8_t b = 0; 47 while(len--) { 48 b = 128; 49 for (int i=0; i<8; i++) { 50 if (c & b) { 51 color = WHITE; 52 } else { 53 color = BLACK; 54 } 55 b >>= 1; 56 if (color == BLACK) { 57 // we clear the buffer each frame so only black pixels need to be drawn 58 display.fillRect(X_OFFSET+curr_x*SCALE, Y_OFFSET+curr_y*SCALE, SCALE, SCALE, color); 59 } 60 curr_x++; 61 if(curr_x >= 128) { 62 curr_x = 0; 63 curr_y++; 64 if(curr_y >= 64) { 65 curr_y = 0; 66 display.refresh(); 67 display.clearDisplayBuffer(); 68 // 30 fps target rate 69 //if(digitalRead(0)) while((millis() - lastRefresh) < 33) ; 70 //lastRefresh = millis(); 71 } 72 } 73 } 74 } 75 } 76 77 void decodeRLE(uint8_t c) { 78 if(c_to_dup == -1) { 79 if((c == 0x55) || (c == 0xaa)) { 80 c_to_dup = c; 81 } else { 82 putPixels(c, 1); 83 } 84 } else { 85 if(runlength == -1) { 86 if(c == 0) { 87 putPixels(c_to_dup & 0xff, 1); 88 c_to_dup = -1; 89 } else if((c & 0x80) == 0) { 90 if(c_to_dup == 0x55) { 91 putPixels(0, c); 92 } else { 93 putPixels(255, c); 94 } 95 c_to_dup = -1; 96 } else { 97 runlength = c & 0x7f; 98 } 99 } else { 100 runlength = runlength | (c << 7); 101 if(c_to_dup == 0x55) { 102 putPixels(0, runlength); 103 } else { 104 putPixels(255, runlength); 105 } 106 c_to_dup = -1; 107 runlength = -1; 108 } 109 } 110 } 111 112 #define RLEBUFSIZE 4096 113 #define READBUFSIZE 2048 114 void readFile(const char * path){ 115 static uint8_t rle_buf[RLEBUFSIZE]; 116 size_t rle_bufhead = 0; 117 size_t rle_size = 0; 118 119 size_t filelen = 0; 120 size_t filesize; 121 static uint8_t compbuf[READBUFSIZE]; 122 123 Serial.printf("Reading file: %s\n", path); 124 File file = fatfs.open(path); 125 if(!file || file.isDirectory()){ 126 Serial.println("Failed to open file for reading"); 127 display.println("File open error. Upload video.hs using CircuitPython"); 128 display.refresh(); 129 return; 130 } 131 filelen = file.size(); 132 filesize = filelen; 133 Serial.printf("File size: %d\n", filelen); 134 135 // init display, putPixels and decodeRLE 136 display.clearDisplay(); 137 display.refresh(); 138 curr_x = 0; 139 curr_y = 0; 140 runlength = -1; 141 c_to_dup = -1; 142 lastRefresh = millis(); 143 144 // init decoder 145 heatshrink_decoder_reset(&hsd); 146 size_t count = 0; 147 uint32_t sunk = 0; 148 size_t toRead; 149 size_t toSink = 0; 150 uint32_t sinkHead = 0; 151 152 153 // Go through file... 154 while(filelen) { 155 if(toSink == 0) { 156 toRead = filelen; 157 if(toRead > READBUFSIZE) toRead = READBUFSIZE; 158 file.read(compbuf, toRead); 159 filelen -= toRead; 160 toSink = toRead; 161 sinkHead = 0; 162 } 163 164 // uncompress buffer 165 HSD_sink_res sres; 166 sres = heatshrink_decoder_sink(&hsd, &compbuf[sinkHead], toSink, &count); 167 //Serial.print("^^ sinked "); 168 //Serial.println(count); 169 toSink -= count; 170 sinkHead = count; 171 sunk += count; 172 if (sunk == filesize) { 173 heatshrink_decoder_finish(&hsd); 174 } 175 176 HSD_poll_res pres; 177 do { 178 rle_size = 0; 179 pres = heatshrink_decoder_poll(&hsd, rle_buf, RLEBUFSIZE, &rle_size); 180 //Serial.print("^^ polled "); 181 //Serial.println(rle_size); 182 if(pres < 0) { 183 Serial.print("POLL ERR! "); 184 Serial.println(pres); 185 return; 186 } 187 188 rle_bufhead = 0; 189 while(rle_size) { 190 rle_size--; 191 if(rle_bufhead >= RLEBUFSIZE) { 192 Serial.println("RLE_SIZE ERR!"); 193 return; 194 } 195 decodeRLE(rle_buf[rle_bufhead++]); 196 } 197 } while (pres == HSDR_POLL_MORE); 198 } 199 file.close(); 200 Serial.println("Done."); 201 } 202 203 204 205 void setup(){ 206 Serial.begin(115200); 207 //while (!Serial) delay(10); 208 Serial.println("Bad apple"); 209 210 flash.begin(); 211 // Init file system on the flash 212 fatfs.begin(&flash); 213 214 display.begin(); 215 display.clearDisplay(); 216 display.setTextColor(BLACK, WHITE); 217 display.setTextSize(2); 218 display.println("Scaled Bad Apple For SHARP Memory"); 219 display.refresh(); 220 221 readFile("/video.hs"); 222 } 223 224 void loop(){ 225 226 }