/* TinyCard v2 - see http://www.technoblogy.com/show?51KR David Johnson-Davies - www.technoblogy.com - 8th April 2025 ATtiny3224 @ 10 MHz (internal); B.O.D. Disabled CC BY 4.0 Licensed under a Creative Commons Attribution 4.0 International license: http://creativecommons.org/licenses/by/4.0/ */ #define SDCARD // Comment out if no SD card socket #include #include #ifdef SDCARD #include #endif const unsigned long Timeout = (unsigned long)5*60*1000; // Timeout in millis = 5 minutes // Arduino pin numbers const int Enable = PIN_PB3; // PB3 - enable low const int Backlight = PIN_PB2; // PB2 Backlight - can do analogue // TFT display const int TFT_MOSI = PIN_PA1; // PA1 const int TFT_SCLK = PIN_PA3; // PA3 const int TFT_CS = PIN_PA4; // PA4 TFT display SPI chip select pin const int TFT_DC = PIN_PA7; // PA7 TFT display data/command select pin // SD card const int SD_CS = PIN_PB0; // SD Card Select = D3 const int SD_MISO = PIN_PA2; // PA2 for SD card // Display parameters - Adafruit 1.8" 160x128 display or AliExpress 1.8" 160x128 display (blue PCB) const int xsize = 160, ysize = 128, xoff = 0, yoff = 0, invert = 0, rotate = 6, bgr = 0; // Character set with hints ********************************************** const uint8_t CharMap[96+9][7] PROGMEM = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00 }, { 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00, 0x00 }, { 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00, 0x00 }, { 0x23, 0x13, 0x08, 0x64, 0x62, 0x00, 0x00 }, { 0x36, 0x49, 0x56, 0x20, 0x50, 0x00, 0x00 }, { 0x00, 0x08, 0x07, 0x03, 0x00, 0x00, 0x00 }, { 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00, 0x00 }, { 0x00, 0x41, 0x22, 0x1C, 0x00, 0x00, 0x00 }, { 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 0x00, 0x00 }, { 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, 0x00 }, { 0x00, 0x80, 0x70, 0x30, 0x00, 0x00, 0x00 }, { 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00 }, { 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x00 }, { 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, 0x00 }, { 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, 0x00 }, { 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00, 0x00 }, { 0x72, 0x49, 0x49, 0x49, 0x46, 0x00, 0x00 }, { 0x21, 0x41, 0x49, 0x4D, 0x33, 0x5a, 0x00 }, // 3 { 0x18, 0x14, 0x12, 0x7F, 0x10, 0x5E, 0x00 }, // 4 { 0x27, 0x45, 0x45, 0x45, 0x39, 0x00, 0x00 }, { 0x3C, 0x4A, 0x49, 0x49, 0x31, 0x00, 0x00 }, { 0x41, 0x21, 0x11, 0x09, 0x07, 0x00, 0x00 }, { 0x36, 0x49, 0x49, 0x49, 0x36, 0x00, 0x00 }, { 0x46, 0x49, 0x49, 0x29, 0x1E, 0x00, 0x00 }, { 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x40, 0x34, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x08, 0x14, 0x22, 0x41, 0x00, 0x00 }, { 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, 0x00 }, { 0x00, 0x41, 0x22, 0x14, 0x08, 0x00, 0x00 }, { 0x02, 0x01, 0x59, 0x09, 0x06, 0x00, 0x00 }, { 0x3E, 0x41, 0x5D, 0x59, 0x4E, 0x87, 0x00 }, // @ { 0x7C, 0x12, 0x11, 0x12, 0x7C, 0x00, 0x00 }, { 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00, 0x00 }, { 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00, 0x00 }, { 0x7F, 0x41, 0x41, 0x41, 0x3E, 0x00, 0x00 }, { 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00, 0x00 }, { 0x7F, 0x09, 0x09, 0x09, 0x01, 0x00, 0x00 }, { 0x3E, 0x41, 0x41, 0x51, 0x73, 0x00, 0x00 }, { 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, 0x00 }, { 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00, 0x00 }, { 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00, 0x00 }, { 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00, 0x00 }, { 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00 }, { 0x7F, 0x02, 0x1C, 0x02, 0x7F, 0x2E, 0x7E }, // M { 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x2C, 0x75 }, // N { 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00, 0x00 }, { 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00, 0x00 }, { 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00, 0x00 }, { 0x7F, 0x09, 0x19, 0x29, 0x46, 0x37, 0x00 }, // R { 0x26, 0x49, 0x49, 0x49, 0x32, 0x00, 0x00 }, { 0x03, 0x01, 0x7F, 0x01, 0x03, 0x00, 0x00 }, { 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00, 0x00 }, { 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00, 0x00 }, { 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00, 0x00 }, { 0x63, 0x14, 0x08, 0x14, 0x63, 0x00, 0x00 }, { 0x03, 0x04, 0x78, 0x04, 0x03, 0x00, 0x00 }, { 0x61, 0x51, 0x49, 0x45, 0x43, 0x00, 0x00 }, { 0x00, 0x7F, 0x41, 0x41, 0x41, 0x00, 0x00 }, { 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00 }, { 0x00, 0x41, 0x41, 0x41, 0x7F, 0x00, 0x00 }, { 0x04, 0x02, 0x01, 0x02, 0x04, 0x00, 0x00 }, { 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00 }, { 0x00, 0x03, 0x07, 0x08, 0x00, 0x00, 0x00 }, { 0x20, 0x54, 0x54, 0x78, 0x40, 0x00, 0x00 }, { 0x7F, 0x28, 0x44, 0x44, 0x38, 0x00, 0x00 }, { 0x38, 0x44, 0x44, 0x44, 0x28, 0x00, 0x00 }, { 0x38, 0x44, 0x44, 0x28, 0x7F, 0x00, 0x00 }, { 0x38, 0x54, 0x54, 0x54, 0x18, 0x00, 0x00 }, { 0x00, 0x08, 0x7E, 0x09, 0x02, 0x00, 0x00 }, { 0x18, 0xA4, 0xA4, 0x9C, 0x78, 0x8a, 0x00 }, // g { 0x7F, 0x08, 0x04, 0x04, 0x78, 0x27, 0x00 }, // h { 0x00, 0x44, 0x7D, 0x40, 0x00, 0x00, 0x00 }, { 0x20, 0x40, 0x40, 0x3D, 0x00, 0x00, 0x00 }, { 0x7F, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00 }, { 0x00, 0x41, 0x7F, 0x40, 0x00, 0x00, 0x00 }, { 0x7C, 0x04, 0x78, 0x04, 0x78, 0x00, 0x00 }, { 0x7C, 0x08, 0x04, 0x04, 0x78, 0x00, 0x00 }, { 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00 }, { 0xFC, 0x18, 0x24, 0x24, 0x18, 0x00, 0x00 }, { 0x18, 0x24, 0x24, 0x18, 0xFC, 0x00, 0x00 }, { 0x7C, 0x08, 0x04, 0x04, 0x08, 0x27, 0x00 }, // r { 0x48, 0x54, 0x54, 0x54, 0x24, 0x00, 0x00 }, { 0x04, 0x04, 0x3F, 0x44, 0x24, 0x00, 0x00 }, { 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00, 0x00 }, { 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00, 0x00 }, { 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00, 0x00 }, { 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, 0x00 }, { 0x4C, 0x90, 0x90, 0x90, 0x7C, 0x00, 0x00 }, { 0x44, 0x64, 0x54, 0x4C, 0x44, 0x14, 0x89 }, // z { 0x00, 0x08, 0x36, 0x41, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x41, 0x36, 0x08, 0x00, 0x00, 0x00 }, { 0x02, 0x01, 0x02, 0x04, 0x02, 0x00, 0x00 }, { 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x00, 0x00 }, // 127 = Cursor { 0x7F, 0x5B, 0x41, 0x5F, 0x7F, 0x00, 0x00 }, // 128 = [1] { 0x7F, 0x45, 0x55, 0x51, 0x7F, 0x00, 0x00 }, // [2] { 0x7F, 0x55, 0x55, 0x41, 0x7F, 0x00, 0x00 }, // [3] { 0x7F, 0x71, 0x77, 0x43, 0x7F, 0x00, 0x00 }, // [4] { 0x7F, 0x51, 0x55, 0x45, 0x7F, 0x00, 0x00 }, // [5] { 0x7F, 0x41, 0x55, 0x45, 0x7F, 0x00, 0x00 }, // [6] { 0x7F, 0x7D, 0x7D, 0x41, 0x7F, 0x00, 0x00 }, // [7] { 0x7F, 0x41, 0x55, 0x41, 0x7F, 0x00, 0x00 }, // [8] { 0x7F, 0x71, 0x75, 0x41, 0x7F, 0x00, 0x00 }, // 136 = [9] }; const uint16_t BigMap[3][12] PROGMEM = { { 0x3FFF, 0x3003, 0x3003, 0x3FFF, 0x3003, 0x3003, 0x33F3, 0x3003, 0x3003, 0x3FFF, 0, 0 }, // [10] { 0x3FFF, 0x3FFF, 0x3003, 0x3003, 0x3FFF, 0x3FFF, 0x3003, 0x3003, 0x3FFF, 0x3FFF, 0, 0 }, // [11] { 0x3FFF, 0x3003, 0x3003, 0x3FFF, 0x3033, 0x3033, 0x3333, 0x3303, 0x3303, 0x3FFF, 0, 0 }, // [12] }; const uint8_t F1char = 128; const uint8_t F12char = 139; const uint8_t UserA = 140; const uint8_t UserZ = 140+25; const uint8_t EOL = 13*12; // X position at end of line // TFT colour display ********************************************** uint16_t const White = 0xffff, Black = 0x0000, Grey = 0x39e7, Cyan = 0x07ff; uint16_t const Red = 0xf800, Yellow = 0xffe0, Green = 0x07e0, Blue = 0x001f; int const Leading = 8; int const CASET = 0x2A; // Define column address int const RASET = 0x2B; // Define row address int const RAMWR = 0x2C; // Write to display RAM int const RAMRD = 0x2E; // Read from display RAM // Globals - current plot position and colours int Xpos, Ypos; int Foreground = White; int Background = Black; int Scale = 1; // Text scale boolean Smooth = true; boolean Hints = true; // Send a byte to the display void SendByte (uint8_t d) { SPI.transfer(d); } // Send a command to the display void SendCommand (uint8_t c) { digitalWrite(TFT_DC, LOW); SendByte(c); digitalWrite(TFT_DC, HIGH); } // Send a command followed by two data words void SendCommand2 (uint8_t c, uint16_t d1, uint16_t d2) { digitalWrite(TFT_DC, LOW); SendByte(c); digitalWrite(TFT_DC, HIGH); SendByte(d1>>8); SendByte(d1); SendByte(d2>>8); SendByte(d2); } void InitDisplay () { pinMode(TFT_DC, OUTPUT); pinMode(TFT_CS, OUTPUT); digitalWrite(TFT_DC, HIGH); // Data digitalWrite(TFT_CS, LOW); SPI.begin(); SendCommand(0x01); // Software reset delay(250); // delay 250 ms SendCommand(0x36); SendByte(rotate<<5 | bgr<<3); // Set orientation, rgb/bgr SendCommand(0x3A); SendByte(0x55); // Set color mode - 16-bit SendCommand(0x20+invert); // Invert SendCommand(0x11); // Out of sleep mode delay(150); digitalWrite(TFT_CS, HIGH); } void DisplayOn () { digitalWrite(TFT_CS, LOW); SendCommand(0x29); // Display on delay(150); digitalWrite(TFT_CS, HIGH); } void ClearDisplay () { digitalWrite(TFT_CS, LOW); SendCommand2(CASET, yoff, yoff + ysize - 1); SendCommand2(RASET, xoff, xoff + xsize - 1); SendCommand(0x3A); SendByte(0x03); // 12-bit colour - it's faster SendCommand(RAMWR); for (int i=0; i<3840; i++) { SPI.transfer(0); SPI.transfer(0); SPI.transfer(0); SPI.transfer(0); SPI.transfer(0); SPI.transfer(0); SPI.transfer(0); SPI.transfer(0); } SendCommand(0x3A); SendByte(0x05); // Back to 16-bit colour digitalWrite(TFT_CS, HIGH); } unsigned int Colour (int r, int g, int b) { return (r & 0xf8)<<8 | (g & 0xfc)<<3 | b>>3; } // Move current plot position to x,y void MoveTo (int x, int y) { Xpos = x; Ypos = y; } // Plot point at x,y void PlotPoint (int x, int y) { digitalWrite(TFT_CS, LOW); SendCommand2(CASET, yoff+y, yoff+y); SendCommand2(RASET, xoff+x, xoff+x); SendCommand(RAMWR); SendByte(Foreground>>8); SendByte(Foreground & 0xff); digitalWrite(TFT_CS, HIGH); } // Draw a line to x,y void DrawTo (int x, int y) { digitalWrite(TFT_CS, LOW); int sx, sy, e2, err; int dx = abs(x - Xpos); int dy = abs(y - Ypos); if (Xpos < x) sx = 1; else sx = -1; if (Ypos < y) sy = 1; else sy = -1; err = dx - dy; for (;;) { PlotPoint(Xpos, Ypos); if (Xpos==x && Ypos==y) return; e2 = err<<1; if (e2 > -dy) { err = err - dy; Xpos = Xpos + sx; } if (e2 < dx) { err = err + dx; Ypos = Ypos + sy; } } digitalWrite(TFT_CS, HIGH); } void FillRect (int w, int h) { digitalWrite(TFT_CS, LOW); SendCommand2(CASET, Ypos+yoff, Ypos+yoff+h-1); SendCommand2(RASET, Xpos+xoff, Xpos+xoff+w-1); SendCommand(RAMWR); for (int i=0; i>8); SendByte(Foreground & 0xff); } } digitalWrite(TFT_CS, HIGH); } void DrawRect (int w, int h) { int x1 = Xpos, y1 = Ypos; DrawTo(x1+w-1, y1); DrawTo(x1+w-1, y1+h-1); DrawTo(x1, y1+h-1); DrawTo(x1, y1); } // Converts bit pattern abcdefgh into a0b0c0d0e0f0g0h uint16_t Stretch (uint16_t x) { x = (x & 0xF0)<<4 | (x & 0x0F); x = (x<<2 | x) & 0x3333; x = (x<<1 | x) & 0x5555; return x<<1 | x; } void WriteColumn (uint8_t bits) { uint16_t colour; for (uint8_t i=0; i<8; i++) { if (bits>>(7-i) & 1) colour = Foreground; else colour = Background; SPI.transfer(colour>>8); SPI.transfer(colour & 0xFF); } } void WriteColumns (uint16_t left, uint16_t right) { WriteColumn(left>>8); WriteColumn(left); WriteColumn(right>>8); WriteColumn(right); } void PlotChar (uint8_t c) { digitalWrite(TFT_CS, LOW); SendCommand2(CASET, yoff+Ypos, yoff+Ypos+8*Scale-1); SendCommand2(RASET, xoff+Xpos, xoff+Xpos+6*Scale-1); SendCommand(RAMWR); if (c < 137) { uint8_t col0 = pgm_read_byte(&CharMap[c-32][0]); uint16_t col0L, col0R, col1L, col1R; col0L = Stretch(col0); col0R = col0L; for (uint8_t col = 1 ; col < 5; col++) { uint8_t col1 = pgm_read_byte(&CharMap[c-32][col]); col1L = Stretch(col1); col1R = col1L; if (Scale == 1) WriteColumn(col0); // Smoothing else { if (Smooth) { for (int i=6; i>=0; i--) { for (int j=1; j<3; j++) { if (((col0>>i & 0b11) == (3-j)) && ((col1>>i & 0b11) == j)) { col0R = col0R | 1<<((i*2)+j); col1L = col1L | 1<<((i*2)+3-j); } } } } WriteColumns(col0L, col0R); col0L = col1L; col0R = col1R; } col0 = col1; } if (Scale == 1) WriteColumn(col0); else { WriteColumns(col0L, col0R); if (Hints) { uint8_t hint = pgm_read_byte(&CharMap[c-32][5]); if (hint) PlotPoint((hint>>4)+Xpos, (hint & 0x0f)+Ypos); hint = pgm_read_byte(&CharMap[c-32][6]); if (hint) PlotPoint((hint>>4)+Xpos, (hint & 0x0f)+Ypos); } } } else { uint16_t colour; for (int xx=0; xx<12; xx++) { int bits = pgm_read_word(&BigMap[c-137][xx]); for (int yy=0; yy<16; yy++) { if (bits>>(15-yy) & 1) colour = Foreground; else colour = Background; SPI.transfer(colour>>8); SPI.transfer(colour & 0xFF); } } } Xpos = Xpos + 6*Scale; digitalWrite(TFT_CS, HIGH); } // Plot text from a char array starting at the current plot position void PlotChars (char *s) { while (*s) PlotChar(*s++); } // Plot text starting at the current plot position void PlotText(PGM_P p) { while (1) { char c = pgm_read_byte(p++); if (c == 0) return; PlotChar(c); } } void PlotInt(int n) { bool lead = false; for (int d=10000; d>0; d = d/10) { char j = (n/d) % 10; if (j!=0 || lead || d==1) { PlotChar(j + '0'); lead = true; } } } void PlotHex4 (uint16_t i) { uint16_t p = 0x1000; for (uint16_t d=p; d>0; d=d/16) { uint16_t j = i/d; PlotChar((j<10) ? j+'0' : j+'W'); i = i - j*d; } } void TestChart () { MoveTo(0,0); DrawTo(xsize-1, 0); DrawTo(xsize-1, ysize-1); DrawTo(0, ysize-1); DrawTo(0, 0); Scale = 8; MoveTo((xsize-40)/2, (ysize-64)/2); PlotChar('F'); Scale = 1; } uint8_t Centre (uint8_t x) { return (xsize - x*12)/2; } void MessageDialog (PGM_P p) { int len = strlen_P(p), width = (len+2)*12, margin = (xsize - width)/2; MoveTo(margin, ysize-76); Foreground = Black; FillRect(width, 42); Foreground = White; DrawRect(width, 42); Foreground = Cyan; MoveTo(margin+12, ysize-64); PlotText(p); Foreground = White; delay(1000); } // Read screen (only AliExpress display) ********************************************** uint16_t GetPixel (uint16_t x, uint16_t y) { uint32_t pixel = 0; digitalWrite(TFT_CS, LOW); SendCommand2(CASET, yoff+y, yoff+y); SendCommand2(RASET, xoff+x, xoff+x); SendCommand(RAMRD); SPI0.CTRLA &= ~(SPI_ENABLE_bm); // Disable SPI peripheral pinMode(TFT_MOSI, INPUT); for (uint8_t i=0; i<9; i++) { digitalWrite(TFT_SCLK, HIGH); digitalWrite(TFT_SCLK, LOW); } for (uint8_t i=0; i<24; i++) { digitalWrite(TFT_SCLK, HIGH); pixel = pixel<<1 | digitalRead(TFT_MOSI); digitalWrite(TFT_SCLK, LOW); } pinMode(TFT_MOSI, OUTPUT); digitalWrite(TFT_CS, HIGH); SPI0.CTRLA |= SPI_ENABLE_bm; // Enable SPI peripheral return ((pixel & 0xf80000)>>8 | (pixel & 0xfc00)>>5 | (pixel & 0xf8)>>3); } // Keyboard ********************************************** const int ClockMask = PIN6_bm; // PA6 = D+ const int DataMask = PIN5_bm; // PA5 = D- const int KeymapSize = 0x84; volatile uint16_t KeyBuf = 0; // Control codes const int F1key = 14; const int F12key = F1key+11; // 25 const int Escape = 27; const int Tab = 9; const int NumLock = 28; const int ScrollLock = 29; const int CapsLock = 30; void SetupKeyboard() { PORTA.DIRCLR = DataMask | ClockMask; // Inputs PORTA.PIN5CTRL = PORT_PULLUPEN_bm; // Pullup on PA5 PORTA.PIN6CTRL = PORT_PULLUPEN_bm; // Pullup on PA6 } const uint8_t Keymap[] PROGMEM = // Without shift " \026 \022\020\016\017\031 \027\025\023\021\011` q1 zsaw2 " // 0x00 to 0x1F " cxde43 vftr5 nbhgy6 mju78 " // 0x20 to 0x3F " ,kio09 ./l;p- \' [= \036 \015] \\ " // 0x40 to 0x5F " \010 1 47 0.2568\033\034\030+3-*9\035 \024" // 0x60 to 0x83 // With shift " \026 \022\020\016\017\031 \027\025\023\021\011~ Q! ZSAW@ " // 0x00 to 0x1F " CXDE$# VFTR% NBHGY^ MJU&* " // 0x20 to 0x3F " ?L:P_ \" {+ \036 \015} | " // 0x40 to 0x5F " \010 1 47 0.2568\033\034\030+3-*9\035 \024"; // 0x60 to 0x83 volatile uint16_t ScanCode = 0, ScanBit = 1; ISR(PORTA_PORT_vect) { static bool Break = 0, Modifier = 0, Shift = 0, Alt = 0, Ctrl = 0; PORTA.INTFLAGS = ClockMask; // Clear CLOCK interrupt flag if (PORTA.IN & DataMask) ScanCode = ScanCode | ScanBit; ScanBit = ScanBit << 1; if (ScanBit != 0x800) return; // ScanCode incomplete // Process scan code if ((ScanCode & 0x401) != 0x400) return; // Invalid start/stop bit uint16_t s = (ScanCode & 0x1FE) >> 1; ScanCode = 0, ScanBit = 1; if (s == 0xAA) return; // BAT completion code if (s == 0xF0) { Break = 1; return; } if (s == 0xE0) { Modifier = 1; return; } if (Modifier && Break) { if (s == 0x11) Alt = 0; // Right Alt released if (s == 0x14) Ctrl = 0; // Right Ctrl released Break = 0; Modifier = 0; return; } else if (Modifier) { if (s == 0x11) Alt = 1; // Right Alt pressed if (s == 0x14) Ctrl = 1; // Right Ctrl pressed Modifier = 0; return; } else if (Break) { if ((s == 0x12) || (s == 0x59)) Shift = 0; // Left or Right Shift released if (s == 0x11) Alt = 0; // Left Alt released if (s == 0x14) Ctrl = 0; // Left Ctrl released Break = 0; return; } if ((s == 0x12) || (s == 0x59)) { Shift = 1; return; } // Left or Right Shift pressed if (s == 0x11) { Alt = 1; return; } // Left Alt pressed if (s == 0x14) { Ctrl = 1; return; } // Left Ctrl pressed uint8_t c = pgm_read_byte(&Keymap[s + KeymapSize*Shift]); if (c == 32 && s != 0x29) return; KeyBuf = (Ctrl&1)<<15 | (Alt&1)<<14 | c; return; } // Memory structure ************************************* const int MaxMessage = 1248; // Maximum total message size const int Cards = 12; // Selected with F1 to F12 const int BufferOffset = 2*(Cards+1) + 4; const int PackSize = BufferOffset + MaxMessage; uint8_t Clipboard[104]; uint8_t ClipEndPtr = 0; struct { union { struct { uint8_t PackName; // Currently selected pack uint8_t Card; // Currently selected card uint16_t EndPtr; // Points after last character uint16_t StartPtr[Cards+1]; uint8_t Buffer[MaxMessage]; }; uint8_t Copybuf[PackSize]; }; } Pack; const int Packs = 5; uint8_t BuiltinPack[Packs][PackSize] = { // Number Game { 0x01, 0x00, 0x27, 0x01, 0x00, 0x00, 0x38, 0x00, 0x4c, 0x00, 0x61, 0x00, 0x80, 0x00, 0x92, 0x00, 0xab, 0x00, 0xc0, 0x00, 0xce, 0x00, 0xde, 0x00, 0x0e, 0x01, 0x27, 0x01, 0x27, 0x01, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x20, 0x47, 0x61, 0x6d, 0x65, 0x20, 0x54, 0x68, 0x69, 0x6e, 0x6b, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x20, 0x77, 0x68, 0x6f, 0x6c, 0x65, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x31, 0x20, 0x74, 0x6f, 0x20, 0x32, 0x30, 0x2e, 0x20, 0x81, 0x4e, 0x65, 0x78, 0x74, 0x2e, 0x49, 0x73, 0x20, 0x69, 0x74, 0x20, 0x82, 0x65, 0x76, 0x65, 0x6e, 0x20, 0x6f, 0x72, 0x20, 0x86, 0x6f, 0x64, 0x64, 0x3f, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x79, 0x20, 0x62, 0x79, 0x20, 0x39, 0x2e, 0x20, 0x83, 0x4e, 0x65, 0x78, 0x74, 0x2e, 0x41, 0x64, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x69, 0x67, 0x69, 0x74, 0x73, 0x20, 0x74, 0x6f, 0x67, 0x65, 0x74, 0x68, 0x65, 0x72, 0x2e, 0x20, 0x84, 0x4e, 0x65, 0x78, 0x74, 0x2e, 0x53, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x20, 0x32, 0x2e, 0x20, 0x85, 0x4e, 0x65, 0x78, 0x74, 0x2e, 0x59, 0x6f, 0x75, 0x27, 0x76, 0x65, 0x20, 0x67, 0x6f, 0x74, 0x20, 0x37, 0x21, 0x20, 0x80, 0x54, 0x72, 0x79, 0x5f, 0x61, 0x67, 0x61, 0x69, 0x6e, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x79, 0x20, 0x62, 0x79, 0x20, 0x32, 0x2e, 0x20, 0x87, 0x4e, 0x65, 0x78, 0x74, 0x2e, 0x41, 0x64, 0x64, 0x20, 0x31, 0x30, 0x2e, 0x20, 0x88, 0x4e, 0x65, 0x78, 0x74, 0x2e, 0x48, 0x61, 0x6c, 0x76, 0x65, 0x20, 0x69, 0x74, 0x2e, 0x20, 0x89, 0x4e, 0x65, 0x78, 0x74, 0x2e, 0x53, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, 0x74, 0x68, 0x6f, 0x75, 0x67, 0x68, 0x74, 0x20, 0x6f, 0x66, 0x2e, 0x20, 0x8a, 0x4e, 0x65, 0x78, 0x74, 0x2e, 0x59, 0x6f, 0x75, 0x27, 0x76, 0x65, 0x20, 0x67, 0x6f, 0x74, 0x20, 0x35, 0x21, 0x20, 0x80, 0x54, 0x72, 0x79, 0x5f, 0x61, 0x67, 0x61, 0x69, 0x6e, 0x2e }, // The Island { 0x02, 0x02, 0xe8, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x9b, 0x00, 0xe8, 0x00, 0x24, 0x01, 0x7b, 0x01, 0xc3, 0x01, 0x19, 0x02, 0x19, 0x02, 0x19, 0x02, 0x19, 0x02, 0x19, 0x02, 0x19, 0x02, 0x54, 0x68, 0x65, 0x20, 0x49, 0x73, 0x6c, 0x61, 0x6e, 0x64, 0x0d, 0x57, 0x65, 0x6c, 0x63, 0x6f, 0x6d, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x69, 0x73, 0x6c, 0x61, 0x6e, 0x64, 0x2e, 0x20, 0x47, 0x6f, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x81, 0x66, 0x6f, 0x72, 0x65, 0x73, 0x74, 0x20, 0x6f, 0x72, 0x20, 0x61, 0x6c, 0x6f, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x82, 0x62, 0x65, 0x61, 0x63, 0x68, 0x3f, 0x41, 0x73, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x73, 0x74, 0x72, 0x75, 0x67, 0x67, 0x6c, 0x65, 0x20, 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x65, 0x73, 0x74, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x67, 0x65, 0x74, 0x20, 0x62, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x20, 0x62, 0x79, 0x20, 0x61, 0x20, 0x73, 0x6e, 0x61, 0x6b, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x64, 0x69, 0x65, 0x2e, 0x20, 0x80, 0x54, 0x72, 0x79, 0x5f, 0x61, 0x67, 0x61, 0x69, 0x6e, 0x3f, 0x59, 0x6f, 0x75, 0x20, 0x77, 0x61, 0x6c, 0x6b, 0x20, 0x61, 0x6c, 0x6f, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x65, 0x61, 0x63, 0x68, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x72, 0x65, 0x61, 0x63, 0x68, 0x20, 0x61, 0x20, 0x72, 0x69, 0x76, 0x65, 0x72, 0x2e, 0x20, 0x83, 0x53, 0x77, 0x69, 0x6d, 0x20, 0x61, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x20, 0x6f, 0x72, 0x20, 0x84, 0x77, 0x61, 0x6c, 0x6b, 0x20, 0x61, 0x6c, 0x6f, 0x6e, 0x67, 0x20, 0x62, 0x61, 0x6e, 0x6b, 0x3f, 0x41, 0x73, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x73, 0x77, 0x69, 0x6d, 0x20, 0x61, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x67, 0x65, 0x74, 0x20, 0x65, 0x61, 0x74, 0x65, 0x6e, 0x20, 0x62, 0x79, 0x20, 0x61, 0x20, 0x63, 0x72, 0x6f, 0x63, 0x6f, 0x64, 0x69, 0x6c, 0x65, 0x2e, 0x20, 0x80, 0x54, 0x72, 0x79, 0x5f, 0x61, 0x67, 0x61, 0x69, 0x6e, 0x3f, 0x59, 0x6f, 0x75, 0x20, 0x77, 0x61, 0x6c, 0x6b, 0x20, 0x61, 0x6c, 0x6f, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x69, 0x76, 0x65, 0x72, 0x20, 0x62, 0x61, 0x6e, 0x6b, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x72, 0x65, 0x61, 0x63, 0x68, 0x20, 0x61, 0x20, 0x70, 0x61, 0x6c, 0x6d, 0x20, 0x74, 0x72, 0x65, 0x65, 0x2e, 0x20, 0x85, 0x44, 0x69, 0x67, 0x20, 0x62, 0x65, 0x73, 0x69, 0x64, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x72, 0x65, 0x65, 0x20, 0x6f, 0x72, 0x20, 0x86, 0x63, 0x6c, 0x69, 0x6d, 0x62, 0x20, 0x69, 0x74, 0x3f, 0x59, 0x6f, 0x75, 0x20, 0x64, 0x69, 0x67, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x72, 0x65, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x66, 0x69, 0x6e, 0x64, 0x20, 0x61, 0x20, 0x74, 0x72, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x20, 0x63, 0x68, 0x65, 0x73, 0x74, 0x2e, 0x20, 0x59, 0x6f, 0x75, 0x27, 0x72, 0x65, 0x20, 0x72, 0x69, 0x63, 0x68, 0x21, 0x20, 0x80, 0x50, 0x6c, 0x61, 0x79, 0x5f, 0x61, 0x67, 0x61, 0x69, 0x6e, 0x3f, 0x41, 0x73, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x63, 0x6c, 0x69, 0x6d, 0x62, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x63, 0x6f, 0x6e, 0x75, 0x74, 0x20, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x73, 0x20, 0x6c, 0x6f, 0x6f, 0x73, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x68, 0x69, 0x74, 0x73, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x68, 0x65, 0x61, 0x64, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x64, 0x69, 0x65, 0x2e, 0x20, 0x80, 0x54, 0x72, 0x79, 0x5f, 0x61, 0x67, 0x61, 0x69, 0x6e, 0x3f }, // Animal Quiz { 0x00, 0x0a, 0xcc, 0x01, 0x00, 0x00, 0x35, 0x00, 0x62, 0x00, 0x8a, 0x00, 0xb8, 0x00, 0xe5, 0x00, 0x14, 0x01, 0x3a, 0x01, 0x67, 0x01, 0x85, 0x01, 0xab, 0x01, 0xcc, 0x01, 0xfb, 0x01, 0x41, 0x6e, 0x69, 0x6d, 0x61, 0x6c, 0x20, 0x51, 0x75, 0x69, 0x7a, 0x0d, 0x49, 0x73, 0x20, 0x61, 0x20, 0x70, 0x6f, 0x72, 0x70, 0x6f, 0x69, 0x73, 0x65, 0x20, 0x61, 0x20, 0x87, 0x62, 0x69, 0x72, 0x64, 0x2c, 0x20, 0x87, 0x66, 0x69, 0x73, 0x68, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x81, 0x6d, 0x61, 0x6d, 0x6d, 0x61, 0x6c, 0x3f, 0x44, 0x6f, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x61, 0x6e, 0x74, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x88, 0x66, 0x6f, 0x75, 0x72, 0x2c, 0x20, 0x82, 0x73, 0x69, 0x78, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x88, 0x65, 0x69, 0x67, 0x68, 0x74, 0x20, 0x6c, 0x65, 0x67, 0x73, 0x3f, 0x49, 0x73, 0x20, 0x61, 0x20, 0x70, 0x65, 0x6e, 0x67, 0x75, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x83, 0x62, 0x69, 0x72, 0x64, 0x2c, 0x20, 0x89, 0x66, 0x69, 0x73, 0x68, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x89, 0x6d, 0x61, 0x6d, 0x6d, 0x61, 0x6c, 0x3f, 0x44, 0x6f, 0x20, 0x6d, 0x6f, 0x73, 0x74, 0x20, 0x63, 0x72, 0x61, 0x62, 0x73, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x84, 0x73, 0x69, 0x78, 0x2c, 0x20, 0x84, 0x65, 0x69, 0x67, 0x68, 0x74, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x85, 0x74, 0x65, 0x6e, 0x20, 0x6c, 0x65, 0x67, 0x73, 0x3f, 0x4e, 0x6f, 0x74, 0x20, 0x62, 0x61, 0x64, 0x20, 0x2d, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x67, 0x6f, 0x74, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x77, 0x72, 0x6f, 0x6e, 0x67, 0x2e, 0x20, 0x80, 0x54, 0x72, 0x79, 0x5f, 0x61, 0x67, 0x61, 0x69, 0x6e, 0x3f, 0x57, 0x65, 0x6c, 0x6c, 0x20, 0x64, 0x6f, 0x6e, 0x65, 0x2c, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x67, 0x6f, 0x74, 0x20, 0x74, 0x68, 0x65, 0x6d, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x21, 0x20, 0x80, 0x50, 0x6c, 0x61, 0x79, 0x5f, 0x61, 0x67, 0x61, 0x69, 0x6e, 0x3f, 0x53, 0x6f, 0x72, 0x72, 0x79, 0x2c, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x67, 0x6f, 0x74, 0x20, 0x73, 0x6f, 0x6d, 0x65, 0x20, 0x77, 0x72, 0x6f, 0x6e, 0x67, 0x2e, 0x20, 0x80, 0x54, 0x72, 0x79, 0x5f, 0x61, 0x67, 0x61, 0x69, 0x6e, 0x3f, 0x49, 0x73, 0x20, 0x61, 0x20, 0x63, 0x72, 0x6f, 0x63, 0x6f, 0x64, 0x69, 0x6c, 0x65, 0x20, 0x61, 0x20, 0x88, 0x72, 0x65, 0x70, 0x74, 0x69, 0x6c, 0x65, 0x2c, 0x20, 0x8a, 0x6d, 0x61, 0x6d, 0x6d, 0x61, 0x6c, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x8a, 0x66, 0x69, 0x73, 0x68, 0x3f, 0x49, 0x73, 0x20, 0x61, 0x20, 0x73, 0x68, 0x61, 0x72, 0x6b, 0x20, 0x61, 0x20, 0x8b, 0x6d, 0x61, 0x6d, 0x6d, 0x61, 0x6c, 0x20, 0x6f, 0x72, 0x20, 0x89, 0x66, 0x69, 0x73, 0x68, 0x3f, 0x49, 0x73, 0x20, 0x61, 0x20, 0x62, 0x61, 0x74, 0x20, 0x61, 0x20, 0x86, 0x62, 0x69, 0x72, 0x64, 0x2c, 0x20, 0x86, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x74, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x84, 0x6d, 0x61, 0x6d, 0x6d, 0x61, 0x6c, 0x3f, 0x49, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x6f, 0x73, 0x74, 0x72, 0x69, 0x63, 0x68, 0x20, 0x61, 0x20, 0x8b, 0x6d, 0x61, 0x6d, 0x6d, 0x61, 0x6c, 0x20, 0x6f, 0x72, 0x20, 0x8b, 0x62, 0x69, 0x72, 0x64, 0x3f, 0x44, 0x6f, 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x70, 0x69, 0x64, 0x65, 0x72, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x86, 0x66, 0x6f, 0x75, 0x72, 0x2c, 0x20, 0x86, 0x73, 0x69, 0x78, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x86, 0x65, 0x69, 0x67, 0x68, 0x74, 0x20, 0x6c, 0x65, 0x67, 0x73, 0x3f }, // Colour Game { 0x00, 0x07, 0x43, 0x01, 0x00, 0x00, 0x4d, 0x00, 0x71, 0x00, 0x96, 0x00, 0xb9, 0x00, 0xdb, 0x00, 0xfd, 0x00, 0x20, 0x01, 0x43, 0x01, 0x63, 0x01, 0x88, 0x01, 0xa9, 0x01, 0xca, 0x01, 0x43, 0x6f, 0x6c, 0x6f, 0x75, 0x72, 0x20, 0x47, 0x61, 0x6d, 0x65, 0x0d, 0x54, 0x68, 0x69, 0x6e, 0x6b, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x75, 0x72, 0x2e, 0x20, 0x48, 0x6f, 0x77, 0x20, 0x6d, 0x61, 0x6e, 0x79, 0x20, 0x6c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x73, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x20, 0x69, 0x74, 0x20, 0x68, 0x61, 0x76, 0x65, 0x3f, 0x20, 0x81, 0x3c, 0x5f, 0x35, 0x2c, 0x20, 0x82, 0x35, 0x2c, 0x20, 0x83, 0x3e, 0x5f, 0x35, 0x3f, 0x44, 0x6f, 0x65, 0x73, 0x20, 0x69, 0x74, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x33, 0x20, 0x6c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x73, 0x3f, 0x20, 0x88, 0x59, 0x65, 0x73, 0x20, 0x6f, 0x72, 0x20, 0x89, 0x6e, 0x6f, 0x3f, 0x44, 0x6f, 0x65, 0x73, 0x20, 0x69, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x22, 0x62, 0x22, 0x20, 0x2d, 0x20, 0x84, 0x79, 0x65, 0x73, 0x20, 0x6f, 0x72, 0x20, 0x85, 0x6e, 0x6f, 0x3f, 0x44, 0x6f, 0x65, 0x73, 0x20, 0x69, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x22, 0x65, 0x22, 0x20, 0x2d, 0x20, 0x86, 0x79, 0x65, 0x73, 0x20, 0x6f, 0x72, 0x20, 0x87, 0x6e, 0x6f, 0x3f, 0x49, 0x20, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x74, 0x27, 0x73, 0x20, 0x22, 0x62, 0x72, 0x6f, 0x77, 0x6e, 0x22, 0x2e, 0x20, 0x80, 0x50, 0x6c, 0x61, 0x79, 0x5f, 0x61, 0x67, 0x61, 0x69, 0x6e, 0x2e, 0x49, 0x20, 0x67, 0x75, 0x65, 0x73, 0x73, 0x20, 0x69, 0x74, 0x27, 0x73, 0x20, 0x22, 0x67, 0x72, 0x65, 0x65, 0x6e, 0x22, 0x2e, 0x20, 0x80, 0x50, 0x6c, 0x61, 0x79, 0x5f, 0x61, 0x67, 0x61, 0x69, 0x6e, 0x2e, 0x49, 0x20, 0x74, 0x68, 0x69, 0x6e, 0x6b, 0x20, 0x69, 0x74, 0x27, 0x73, 0x20, 0x22, 0x6f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x2e, 0x20, 0x80, 0x50, 0x6c, 0x61, 0x79, 0x5f, 0x61, 0x67, 0x61, 0x69, 0x6e, 0x2e, 0x49, 0x20, 0x74, 0x68, 0x69, 0x6e, 0x6b, 0x20, 0x69, 0x74, 0x27, 0x73, 0x20, 0x22, 0x79, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x22, 0x2e, 0x20, 0x80, 0x50, 0x6c, 0x61, 0x79, 0x5f, 0x61, 0x67, 0x61, 0x69, 0x6e, 0x2e, 0x49, 0x20, 0x74, 0x68, 0x69, 0x6e, 0x6b, 0x20, 0x69, 0x74, 0x27, 0x73, 0x20, 0x22, 0x72, 0x65, 0x64, 0x22, 0x2e, 0x20, 0x80, 0x50, 0x6c, 0x61, 0x79, 0x5f, 0x61, 0x67, 0x61, 0x69, 0x6e, 0x2e, 0x44, 0x6f, 0x65, 0x73, 0x20, 0x69, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x22, 0x70, 0x22, 0x20, 0x2d, 0x20, 0x8a, 0x79, 0x65, 0x73, 0x20, 0x6f, 0x72, 0x20, 0x8b, 0x6e, 0x6f, 0x2e, 0x49, 0x20, 0x67, 0x75, 0x65, 0x73, 0x73, 0x20, 0x69, 0x74, 0x27, 0x73, 0x20, 0x22, 0x70, 0x69, 0x6e, 0x6b, 0x22, 0x2e, 0x20, 0x80, 0x50, 0x6c, 0x61, 0x79, 0x5f, 0x61, 0x67, 0x61, 0x69, 0x6e, 0x2e, 0x49, 0x20, 0x67, 0x75, 0x65, 0x73, 0x73, 0x20, 0x69, 0x74, 0x27, 0x73, 0x20, 0x22, 0x62, 0x6c, 0x75, 0x65, 0x22, 0x2e, 0x20, 0x80, 0x50, 0x6c, 0x61, 0x79, 0x5f, 0x61, 0x67, 0x61, 0x69, 0x6e, 0x2e }, // Twisty Maze { 0x00, 0x05, 0xcc, 0x01, 0x00, 0x00, 0x32, 0x00, 0x82, 0x00, 0xd2, 0x00, 0x22, 0x01, 0x72, 0x01, 0xcc, 0x01, 0xcc, 0x01, 0xcc, 0x01, 0xcc, 0x01, 0xcc, 0x01, 0xcc, 0x01, 0xcc, 0x01, 0x54, 0x77, 0x69, 0x73, 0x74, 0x79, 0x20, 0x4d, 0x61, 0x7a, 0x65, 0x0d, 0x46, 0x69, 0x6e, 0x64, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x77, 0x61, 0x79, 0x20, 0x6f, 0x75, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x61, 0x7a, 0x65, 0x21, 0x20, 0x81, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x2e, 0x59, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x6d, 0x61, 0x7a, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x77, 0x69, 0x73, 0x74, 0x79, 0x20, 0x6c, 0x69, 0x74, 0x74, 0x6c, 0x65, 0x20, 0x70, 0x61, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x20, 0x47, 0x6f, 0x20, 0x82, 0x6e, 0x6f, 0x72, 0x74, 0x68, 0x2c, 0x20, 0x83, 0x65, 0x61, 0x73, 0x74, 0x2c, 0x20, 0x83, 0x73, 0x6f, 0x75, 0x74, 0x68, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x82, 0x77, 0x65, 0x73, 0x74, 0x2e, 0x59, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x6d, 0x61, 0x7a, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x6c, 0x69, 0x74, 0x74, 0x6c, 0x65, 0x20, 0x74, 0x77, 0x69, 0x73, 0x74, 0x79, 0x20, 0x70, 0x61, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x20, 0x47, 0x6f, 0x20, 0x84, 0x6e, 0x6f, 0x72, 0x74, 0x68, 0x2c, 0x20, 0x81, 0x65, 0x61, 0x73, 0x74, 0x2c, 0x20, 0x81, 0x73, 0x6f, 0x75, 0x74, 0x68, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x83, 0x77, 0x65, 0x73, 0x74, 0x2e, 0x59, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x74, 0x77, 0x69, 0x73, 0x74, 0x79, 0x20, 0x6c, 0x69, 0x74, 0x74, 0x6c, 0x65, 0x20, 0x6d, 0x61, 0x7a, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x70, 0x61, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x20, 0x47, 0x6f, 0x20, 0x81, 0x6e, 0x6f, 0x72, 0x74, 0x68, 0x2c, 0x20, 0x84, 0x65, 0x61, 0x73, 0x74, 0x2c, 0x20, 0x82, 0x73, 0x6f, 0x75, 0x74, 0x68, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x81, 0x77, 0x65, 0x73, 0x74, 0x2e, 0x59, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x74, 0x77, 0x69, 0x73, 0x74, 0x79, 0x20, 0x6d, 0x61, 0x7a, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x6c, 0x69, 0x74, 0x74, 0x6c, 0x65, 0x20, 0x70, 0x61, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x20, 0x47, 0x6f, 0x20, 0x83, 0x6e, 0x6f, 0x72, 0x74, 0x68, 0x2c, 0x20, 0x82, 0x65, 0x61, 0x73, 0x74, 0x2c, 0x20, 0x85, 0x73, 0x6f, 0x75, 0x74, 0x68, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x84, 0x77, 0x65, 0x73, 0x74, 0x2e, 0x59, 0x6f, 0x75, 0x20, 0x62, 0x6c, 0x69, 0x6e, 0x6b, 0x20, 0x61, 0x73, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x73, 0x74, 0x65, 0x70, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x75, 0x6e, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x2e, 0x20, 0x59, 0x6f, 0x75, 0x27, 0x76, 0x65, 0x20, 0x65, 0x73, 0x63, 0x61, 0x70, 0x65, 0x64, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x54, 0x77, 0x69, 0x73, 0x74, 0x79, 0x20, 0x4d, 0x61, 0x7a, 0x65, 0x21, 0x20, 0x80, 0x50, 0x6c, 0x61, 0x79, 0x5f, 0x61, 0x67, 0x61, 0x69, 0x6e, 0x3f }, }; char ChooseBuiltin () { MoveTo(0, ysize-108); Foreground = Black; FillRect(160, 90); Foreground = White; DrawRect(160, 90); Foreground = Cyan; MoveTo(Centre(10), ysize-48); PlotText(PSTR("Load pack:")); Foreground = White; MoveTo(Centre(Packs), ysize-72); for (int i=1; i<=Packs; i++) PlotInt(i); Foreground = Cyan; MoveTo(Centre(12), ysize-96); PlotText(PSTR("Press 1 to ")); PlotInt(Packs); for (;;) { uint16_t c = WaitForInput(); if (c >= '1' && c <= '0'+Packs) { Foreground = Red; MoveTo(Centre(Packs)+12*(c-'1'), ysize-72); PlotChar(c); Foreground = Cyan; MoveTo(8, ysize-96); PlotText(PSTR(" Loading... ")); delay(500); return c; } else if (c == ScrollLock) { // Save screen DimBmpSave(); } else if (c == 0 || c == Escape || c == ' ') return 0; } } // SD Card interface ********************************************** #ifdef SDCARD File SDfile; void SDSave (char c) { char filename[] = "*.PAK"; filename[0] = c; SD.begin(SD_CS); SDfile = SD.open(filename, O_RDWR | O_CREAT | O_TRUNC); // Overwrite if (SDfile) { JoinBuf(Pack.Card); if (SDfile.write(Pack.Copybuf, BufferOffset+Pack.StartPtr[Cards])) { SplitBuf(Pack.Card); SDfile.close(); } else MessageDialog(PSTR("Save error")); } else { MessageDialog(PSTR("File error")); } SD.end(); ShowCard(false); } void SDLoad (char c) { char filename[] = "*.PAK"; filename[0] = c; SD.begin(SD_CS); SDfile = SD.open(filename); if (SDfile) { SDfile.read(Pack.Copybuf, BufferOffset); SDfile.read(Pack.Copybuf+BufferOffset, BufferOffset+Pack.StartPtr[Cards]); SDfile.close(); Pack.Card = 0; SplitBuf(Pack.Card); } else { MessageDialog(PSTR("File error")); } SD.end(); ShowCard(false); } char ChooseFile (bool save) { SD.begin(SD_CS); MoveTo(0, ysize-116); Foreground = Black; FillRect(160, 106); Foreground = White; DrawRect(160, 106); Foreground = Cyan; if (save) { MoveTo(Centre(10), ysize-40); PlotText(PSTR("Save pack:")); Foreground = Grey; MoveTo(3, ysize-64); PlotText(PSTR("ABCDEFGHIJKLM")); MoveTo(3, ysize-80); PlotText(PSTR("NOPQRSTUVWXYZ")); } else { MoveTo(Centre(10), ysize-40); PlotText(PSTR("Load pack:")); } Foreground = White; char test[] = "*.PAK"; for (char c='A'; c<='Z'; c++) { test[0] = c; if (SD.exists(test)) { MoveTo(3+12*((c-'A')%13), ysize-(64+16*((c-'A')/13))); PlotChar(c); } } SD.end(); Foreground = Cyan; MoveTo(8, ysize-104); PlotText(PSTR("Press A to Z")); for (;;) { uint8_t c = toupper(WaitForInput()); if (c >= 'A' && c <= 'Z') { Foreground = Red; MoveTo(3+12*((c-'A')%13), ysize-(64+16*((c-'A')/13))); PlotChar(c); Foreground = Cyan; MoveTo(8, ysize-104); if (save) PlotText(PSTR(" Saving... ")); else PlotText(PSTR(" Loading... ")); delay(500); return c; } else if (c == ScrollLock) { // Save screen DimBmpSave(); } else if (c == 0 || c == Escape || c == ' ') { ShowCard(false); return 0; } } } #endif // TinyCard program ********************************************** enum modes { Edit, Play }; volatile uint8_t Mode = Edit; volatile int Scroll = 0; const int MaxButtons = 4; const uint16_t ButtonColour[MaxButtons] = { Red, Yellow, Green, Blue }; uint8_t Button[MaxButtons]; uint8_t LastButton; const int MaxBrightness = 128; // Backlight void SplitBuf (int card) { Pack.EndPtr = Pack.StartPtr[card+1]; int tomove = Pack.StartPtr[Cards] - Pack.StartPtr[card+1]; int distance = MaxMessage - Pack.StartPtr[Cards]; // Slide characters up by 'tomove' and fill with spaces for (int p=0; p= 9) { MoveTo(xsize-5, ysize-8); PlotChar('0'+(1+card)/10); MoveTo(xsize-5, ysize-16); PlotChar('0'+(1+card)%10); } else { MoveTo(xsize-5, ysize-8); PlotChar('0'+(1+card)%10); } Foreground = White; Scale = 2; } void HideCursor() { if (Xpos != EOL) { PlotChar(' '); Xpos = Xpos - 6*Scale; } } void ShowCursor () { if (Xpos == EOL) { // End of current line if (Ypos == 0) return; // End of card int xtemp = Xpos, ytemp = Ypos; MoveTo(0, Ypos-16); PlotChar(0x7F); // Start of next line MoveTo(xtemp, ytemp); } else { PlotChar(0x7F); Xpos = Xpos - 6*Scale; } } void ShowCard (bool play) { ClearDisplay(); if (!play) PlotCardNumber(Pack.Card); MoveTo(0, ysize-16); LastButton = 0; uint8_t spaces = 13; uint16_t i = Pack.StartPtr[Pack.Card], last = i; while (i <= Pack.EndPtr) { uint8_t c = Pack.Buffer[i]; if (play && c >= F1char && c <= F12char && LastButton < MaxButtons) { Foreground = ButtonColour[LastButton]; Button[LastButton++] = c - F1char; // F1 = 0 last = i+1; } else if (c == ' ' || c == '\r' || i - last >= 13 || i == Pack.EndPtr) { // Word break if (spaces < i-last) { MoveTo(0, Ypos-16); spaces = 13; } for (uint16_t j=last; j= 13) last = i; else last = i+1; } if (c == '\r') { MoveTo(0, Ypos-16); spaces = 13; // Foreground = White; } i++; } if (!play) ShowCursor(); } void ProcessKey (uint8_t c, bool ctrl, bool alt) { // Play mode *************************************************************** if (Mode == Play) { if (c == Escape) { // Escape key goes to edit mode Mode = Edit; ShowCard(false); return; } if (c == ScrollLock) { // Save screen DimBmpSave(); return; } // Edit mode *************************************************************** } else if (Mode == Edit) { // These characters don't affect the cursor if (c == Escape) { // Escape key goes to edit mode Mode = Play; ShowCard(true); return; } if (!alt && c >= F1key && c <= F12key) { // 17 to 26 if (Pack.Card == c - F1key) return; // Card = 0 to 9 JoinBuf(Pack.Card); Pack.Card = c - F1key; SplitBuf(Pack.Card); ShowCard(false); return; } if (ctrl) { // Ctrl commands if (c == 'b') { // Built-in game char c = ChooseBuiltin(); if (c) { for (int i=0; i Pack.StartPtr[Pack.Card]) { Pack.EndPtr--; if (Xpos == 0) { // Current limitation - doesn't pop back word if it would fit ShowCard(false); } else if (Xpos == EOL && Ypos != 0) { MoveTo(0, Ypos-16); PlotChar(' '); MoveTo(EOL, Ypos+16); Xpos = Xpos - 6*Scale; PlotChar(' '); Xpos = Xpos - 6*Scale; } else { Xpos = Xpos - 6*Scale; PlotChar(' '); Xpos = Xpos - 6*Scale; } } } else if (c == '\r') { // Return key = hard return if (Ypos != 0) { Pack.Buffer[Pack.EndPtr++] = '\r'; MoveTo(0, Ypos-16); } } else if (Pack.EndPtr < Pack.StartPtr[Pack.Card+1] && !(Xpos == 13*12 && Ypos == 0)) { // Room? if (alt && c >= F1key && c <= F12key) c = c - F1key + F1char; // Convert Ctrl-Fkeys to chars // At end of line if (Xpos == EOL && Ypos != 0) { if (c == ' ') { Pack.Buffer[Pack.EndPtr++] = ' '; MoveTo(0, Ypos-16); return; } uint16_t i = Pack.EndPtr; // Search back for space on this line uint8_t p; for (p = 13; p > 0; p--) { if (Pack.Buffer[--i] == ' ') break; } if (p != 0) { Pack.Buffer[i] = ' '; Foreground = Black; Xpos = p*12; FillRect((13-p)*12-2, Scale*Leading); Foreground = White; MoveTo(0, Ypos-16); while (++i < Pack.EndPtr) PlotChar(Pack.Buffer[i]); } else MoveTo(0, Ypos-16); // Word wrap failed } Pack.Buffer[Pack.EndPtr++] = c; PlotChar(c); } ShowCursor(); } } // Buttons ********************************************** const int ButtonPin = 6; // PB1 = AIN10 int ButtonRead () { return 5 - (analogRead(ButtonPin) * 5 + 512)/1024; // 1 to 5 or 0 = no press } // Save screen in BMP format ********************************************** void DimBmpSave () { #ifdef SDCARD analogWrite(Backlight, 64); bmpSave(); analogWrite(Backlight, MaxBrightness); #endif } #ifdef SDCARD // Write two bytes, least significant byte first void writeTwo (uint16_t word) { SDfile.write(word & 0xFF); SDfile.write((word >> 8) & 0xFF); } // Write four bytes, least significant byte first void writeFour (uint32_t word) { SDfile.write(word & 0xFF); SDfile.write((word >> 8) & 0xFF); SDfile.write((word >> 16) & 0xFF); SDfile.write((word >> 24) & 0xFF); } void bmpSave () { uint16_t width = xsize, height = ysize; char filename[11] = "IMAGEA.BMP"; SD.begin(SD_CS); while (SD.exists(filename)) { filename[5]++; } SDfile = SD.open(filename, FILE_WRITE); if (!SDfile) { MessageDialog(PSTR("BMP save error")); SD.end(); return; } // // File header: 14 bytes SDfile.write('B'); SDfile.write('M'); writeFour(14+40+width*height*2); // File size in bytes writeFour(0); writeFour(14+40); // Offset to image data from start // // Image header: 40 bytes writeFour(40); // Header size writeFour(width); // Image width writeFour(height); // Image height writeTwo(1); // Planes writeTwo(16); // Bits per pixel writeFour(0); // Compression (none) writeFour(0); // Image size (0 for uncompressed) writeFour(0); // Preferred X resolution (ignore) writeFour(0); // Preferred Y resolution (ignore) writeFour(0); // Colour map entries (ignore) writeFour(0); // Important colours (ignore) // // Image data: width * height * 2 bytes for (uint16_t y=0; y>1 | (rgb & 0x1F)); // Convert to 555 format } } SDfile.close(); SD.end(); } #endif // Setup ********************************************** void ResetPack () { for (int p=0; p<=Cards; p++) Pack.StartPtr[p] = 0; Pack.Card = 0; } void DimBacklight () { for (uint8_t b=MaxBrightness; b>0; b--) { analogWrite(Backlight, b); delay(4); } } void SetupSleep () { set_sleep_mode(SLEEP_MODE_PWR_DOWN); } void GoToSleep () { MessageDialog(PSTR("Sleeping...")); DimBacklight(); SPI.end(); digitalWrite(Enable, HIGH); // Disable display, keyboard, and SD card // TFT display signals pinMode(TFT_MOSI, INPUT); pinMode(TFT_SCLK, INPUT); pinMode(TFT_CS, INPUT); pinMode(TFT_DC, INPUT); // SD card signals pinMode(SD_MISO, INPUT); pinMode(SD_CS, INPUT); // Keyboard signals PORTA.PIN5CTRL = 0; // Turn off DATA pullup PORTA.PIN6CTRL = 0; // Turn off CLOCK interrupt and pullup PORTA.DIRSET = DataMask | ClockMask; // Clock and DATA low outputs // Buttons ADC0.CTRLA = 0; // Turn off ADC PORTB.PIN1CTRL = PORT_ISC_LEVEL_gc; // Add pin change interrupt to Buttons pin PB1 sleep_enable(); sleep_cpu(); while (digitalRead(ButtonPin) == LOW); // Wait for ON/OFF button release PORTB.PIN1CTRL = 0; // Turn off pin change interrupt on Buttons pin PB1 ADC0.CTRLA = ADC0.CTRLA | 1; // Turn on ADC SetupKeyboard(); digitalWrite(Enable, LOW); // Enable display and keyboard delay(500); // Let 5V supply settle down ScanCode = 0, ScanBit = 1; // Reset keyboard scan PORTA.PIN6CTRL = PORT_ISC_FALLING_gc | PORT_PULLUPEN_bm; // Add pin change interrupt to CLOCK InitDisplay(); ClearDisplay(); DisplayOn(); pinMode(SD_CS, OUTPUT); digitalWrite(SD_CS, HIGH); // SD Card analogWrite(Backlight, MaxBrightness); // Turn on backlight } // Just used to bring us out of sleep ISR (PORTB_PORT_vect) { PORTB.INTFLAGS = PIN1_bm; // Clear interrupt flag } // Setup ********************************************** void setup() { pinMode(Backlight, OUTPUT); digitalWrite(Backlight, LOW); SetupKeyboard(); pinMode(Enable, OUTPUT); digitalWrite(Enable, LOW); // Enable display and keyboard delay(500); // Let 5V supply settle down PORTA.PIN6CTRL = PORT_ISC_FALLING_gc | PORT_PULLUPEN_bm; // Add pin change interrupt to CLOCK pinMode(SD_CS, OUTPUT); digitalWrite(SD_CS, HIGH); // SD Card SetupSleep(); InitDisplay(); //unsigned long start = millis(); ClearDisplay(); //unsigned long time = millis() - start; DisplayOn(); Scale = 2; // Big text ResetPack(); SplitBuf(Pack.Card); Mode = Edit; analogWrite(Backlight, MaxBrightness); // Turn on backlight // PlotInt(time); for(;;); MessageDialog(PSTR("TinyCard v2")); ShowCard(false); } uint16_t WaitForInput () { unsigned long timer = millis(); do { // Is there a key waiting to be processed? uint16_t c = KeyBuf; if (c) { timer = millis(); // Stay awake KeyBuf = 0; return c; // character > 5 } // Is there a button press? uint16_t button = ButtonRead(); if (button && (button <= LastButton)) { while (ButtonRead()); // Wait for button release timer = millis(); // Stay awake return button; // Button = 1 to 5 } } while (millis()-timer < Timeout && ButtonRead() != 5); GoToSleep(); return 0; } void loop () { uint16_t c = WaitForInput(); if (c == 0) { ShowCard(Mode == Play); // Return from sleep } else if (c > 5) { ProcessKey(c, c>>15 & 1, c>>14 & 1); } else { uint8_t go = Button[c-1]; JoinBuf(Pack.Card); Pack.Card = go; SplitBuf(Pack.Card); ShowCard(true); } }