/* The MIT License (MIT) Copyright © 2023 Dean Lee Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. for the arduino uno (atmega328p) and adafruit resistive touch shield v2. this program is compiled and uploaded with the following commands: avr-gcc -O0 -DF_CPU=16000000UL -mmcu=atmega328p -c -o phone.o phone.c avr-gcc -mmcu=atmega328p phone.o -o phone avr-objcopy -O ihex -R .eeprom phone phone.hex avrdude -F -V -c arduino -p ATMEGA328P -P /dev/ttyACM0 -b 115200 -U flash:w:phone.hex compiler optimization *must* be disabled (-O0) for the screen to work. it took a whole month of painful debugging to learn this! during this debugging, I went ahead and soldiered to the ICSP as per the touch shield instructions. it is not clear to me whether this actually makes a difference. the touch input is very sensitive to power management. avoid power-board usb outlets and excessive connecting/disconnecting. */ #define BIT(bit) (1 << (bit)) #define WHITE 0xffff #define BLACK 0x0000 #define GREY 0xc618 #define LIGHTGREY 0xf79e #define GREEN 0x67e8 typedef unsigned char byte; typedef unsigned short uint16; typedef unsigned long uint32; typedef struct { int x; int y; } vector2i; typedef struct { uint16 x; uint16 y; } vector2ui; typedef struct { uint16 positionX; uint16 positionY; uint16 width; uint16 height; uint16 color; } rect; typedef enum { showDialScreen, showWriteScreen, typeChar, deleteChar, space, keyboardToggleShift, keyboardToggleAlphabet } buttonFunction; typedef enum { alphabetLC, alphabetUC, nonLetters1, nonLetters2 } keyboardMode; typedef struct { rect container; byte character; buttonFunction function; } buttonChar; typedef struct { rect container; char label[10]; buttonFunction function; } buttonString; typedef struct { rect container; byte characterSet[4]; } buttonMulti; typedef struct { byte characters[10]; int length; } dialedNums; typedef struct { byte characters[56]; int length; } writtenChars; typedef enum { refreshAddChar, refreshClearChar, redrawKeyboard, redrawScreen, nop } command; struct { rect background; buttonString buttons[2]; } mainScreenData = { {0, 0, 240, 320, LIGHTGREY}, { {{50, 50, 140, 46, GREEN}, "Dial", showDialScreen}, {{50, 110, 140, 46, GREEN}, "Write", showWriteScreen} } }; struct { rect layout[2]; buttonChar buttons[13]; dialedNums dialedNums; } dialScreenData = { { {0, 0, 240, 70, WHITE}, {0, 70, 240, 250, LIGHTGREY} }, { {{165, 72, 70, 46, GREY}, 'x', deleteChar}, {{5, 122, 70, 46, GREEN}, '1', typeChar}, {{85, 122, 70, 46, GREEN}, '2', typeChar}, {{165, 122, 70, 46, GREEN}, '3', typeChar}, {{5, 172, 70, 46, GREEN}, '4', typeChar}, {{85, 172, 70, 46, GREEN}, '5', typeChar}, {{165, 172, 70, 46, GREEN}, '6', typeChar}, {{5, 222, 70, 46, GREEN}, '7', typeChar}, {{85, 222, 70, 46, GREEN}, '8', typeChar}, {{165, 222, 70, 46, GREEN}, '9', typeChar}, {{5, 272, 70, 46, GREEN}, '*', typeChar}, {{85, 272, 70, 46, GREEN}, '0', typeChar}, {{165, 272, 70, 46, GREEN}, '#', typeChar} }, {{}, 0} }; struct { rect layout[2]; buttonMulti keyboard[26]; buttonChar buttonsGeneral[4]; keyboardMode keyboardState; writtenChars writtenChars; } writeScreenData = { { {0, 0, 320, 130, WHITE}, {0, 130, 320, 110, LIGHTGREY} }, { {{1, 143, 30, 30, GREEN}, {'q', 'Q', '1', '1'}}, {{33, 143, 30, 30, GREEN}, {'w', 'W', '2', '2'}}, {{65, 143, 30, 30, GREEN}, {'e', 'E', '3', '3'}}, {{97, 143, 30, 30, GREEN}, {'r', 'R', '4', '4'}}, {{129, 143, 30, 30, GREEN}, {'t', 'T', '5', '5'}}, {{161, 143, 30, 30, GREEN}, {'y', 'Y', '6', '6'}}, {{193, 143, 30, 30, GREEN}, {'u', 'U', '7', '7'}}, {{225, 143, 30, 30, GREEN}, {'i', 'I', '8', '8'}}, {{257, 143, 30, 30, GREEN}, {'o', 'O', '9', '9'}}, {{289, 143, 30, 30, GREEN}, {'p', 'P', '0', '0'}}, {{1, 175, 30, 30, GREEN}, {'a', 'A', '@', '`'}}, {{33, 175, 30, 30, GREEN}, {'s', 'S', '#', '~'}}, {{65, 175, 30, 30, GREEN}, {'d', 'D', '$', '-'}}, {{97, 175, 30, 30, GREEN}, {'f', 'F', '%', '_'}}, {{129, 175, 30, 30, GREEN}, {'g', 'G', '^', '='}}, {{161, 175, 30, 30, GREEN}, {'h', 'H', '&', '+'}}, {{193, 175, 30, 30, GREEN}, {'j', 'J', '*', '['}}, {{225, 175, 30, 30, GREEN}, {'k', 'K', '(', ']'}}, {{257, 175, 30, 30, GREEN}, {'l', 'L', ')', '\\'}}, {{1, 207, 30, 30, GREEN}, {'z', 'Z', '.', '{'}}, {{33, 207, 30, 30, GREEN}, {'x', 'X', ',', '}'}}, {{65, 207, 30, 30, GREEN}, {'c', 'C', '\'', '|'}}, {{97, 207, 30, 30, GREEN}, {'v', 'V', ';', '"'}}, {{129, 207, 30, 30, GREEN}, {'b', 'B', ':', '<'}}, {{161, 207, 30, 30, GREEN}, {'n', 'N', '!', '>'}}, {{193, 207, 30, 30, GREEN}, {'m', 'M', '?', '/'}} }, { {{289, 175, 30, 30, GREY}, '^', keyboardToggleShift}, {{225, 207, 30, 30, GREY}, '_', space}, {{257, 207, 30, 30, GREY}, 'x', deleteChar}, {{289, 207, 30, 30, GREY}, '/', keyboardToggleAlphabet} }, alphabetLC, {{}, 0} }; enum { mainScreen, dialScreen, writeScreen } screen = mainScreen; byte *ddrb; byte *portb; byte *spcr; byte *spsr; byte *spdr; vector2ui indexToWritePosition(int index) { vector2ui v = { 10 + (index % 14) * 22, 15 + (index / 14) * 30 }; return v; } int isPointInBounds(int x, int y, rect rect) { if (x > rect.positionX && x < (rect.positionX + rect.width) && y > rect.positionY && y < (rect.positionY + rect.height)) { return 1; } else { return 0; } } keyboardMode getToggleShift(keyboardMode mode) { switch (mode) { case alphabetLC: return alphabetUC; case alphabetUC: return alphabetLC; case nonLetters1: return nonLetters2; case nonLetters2: return nonLetters1; } } keyboardMode getToggleAlphabet(keyboardMode mode) { switch (mode) { case alphabetLC: case alphabetUC: return nonLetters1; case nonLetters1: case nonLetters2: return alphabetLC; } } void delay(uint32 milliSeconds) { volatile uint32 count = milliSeconds * 593; while (count--); } void startScreenSPI(void) { // set speed 8MHz *spcr = *spcr & ~BIT(1) & ~BIT(0); *spsr = *spsr | BIT(0); *portb = *portb & ~BIT(2); } void endScreenSPI(void) { *portb = *portb | BIT(2); } void startTouchSPI(void) { // set speed 1MHz *spcr = *spcr & ~BIT(1); *spcr = *spcr | BIT(0); *spsr = *spsr & ~BIT(0); *portb = *portb & ~BIT(0); } void endTouchSPI(void) { *portb = *portb | BIT(0); } void sendScreenCommand(byte commandByte, byte *dataBytes, byte numDataBytes) { startScreenSPI(); *portb = *portb & ~BIT(1); *spdr = commandByte; while(!(*spsr & BIT(7))); *portb = *portb | BIT(1); int i; for (i = 0; i < numDataBytes; i++) { *spdr = *dataBytes; while(!(*spsr & BIT(7))); dataBytes++; } endScreenSPI(); } byte readTouchRegister(byte reg) { byte x; startTouchSPI(); *spdr = reg | BIT(7); while(!(*spsr & BIT(7))); *spdr = 0x00; while(!(*spsr & BIT(7))); *spdr = 0x00; while(!(*spsr & BIT(7))); x = *spdr; endTouchSPI(); return x; } void writeTouchRegister(byte reg, byte data) { startTouchSPI(); *spdr = reg; while(!(*spsr & BIT(7))); *spdr = data; while(!(*spsr & BIT(7))); endTouchSPI(); } void initScreen(void) { // sequence taken directly from https://github.com/adafruit/Adafruit_ILI9341/blob/master/Adafruit_ILI9341.cpp at 14/7/21 byte initcmd[] = { // these commands are not in the ILI9341 datasheet and so are cryptic. does documentation exist or is this a closed-source binary blob? 0xef, 3, 0x03, 0x80, 0x02, 0xcf, 3, 0x00, 0xc1, 0x30, 0xed, 4, 0x64, 0x03, 0x12, 0x81, 0xe8, 3, 0x85, 0x00, 0x78, 0xcb, 5, 0x39, 0x2c, 0x00, 0x34, 0x02, 0xf7, 1, 0x20, 0xea, 2, 0x00, 0x00, // voltage stuff 0xc0, 1, 0x23, 0xc1, 1, 0x10, 0xc5, 2, 0x3e, 0x28, 0xc7, 1, 0x86, 0x36, 1, 0x48, // memory access control (set rotation) 0x37, 1, 0x00, // vertical scroll zero 0x3a, 1, 0x55, // set pixel format 16 bit 0xb1, 2, 0x00, 0x18, // set frame rate 0xb6, 3, 0x08, 0x82, 0x27, // display function control // gamma stuff 0xf2, 1, 0x00, 0x26, 1, 0x01, 0xe0, 15, 0x0f, 0x31, 0x2b, 0x0c, 0x0e, 0x08, 0x4e, 0xf1, 0x37, 0x07, 0x10, 0x03, 0x0e, 0x09, 0x00, 0xe1, 15, 0x00, 0x0e, 0x14, 0x03, 0x11, 0x07, 0x31, 0xc1, 0x48, 0x08, 0x0f, 0x0c, 0x31, 0x36, 0x0f, // end of list 0x00 }; sendScreenCommand(0x01, 0x00, 0); // software reset delay(150); byte *addr = initcmd; byte cmd, numArgs; while ((cmd = *(addr++)) > 0) { numArgs = *(addr++); sendScreenCommand(cmd, addr, numArgs); addr += numArgs; } sendScreenCommand(0x11, 0x00, 0); // exit sleep delay(150); sendScreenCommand(0x29, 0x00, 0); // display on delay(150); } void initTouch(void) { writeTouchRegister(0x03, 0x02); // reset delay(50); writeTouchRegister(0x04, 0x00); // turn on clocks writeTouchRegister(0x40, 0x01); // enable touch with x,y and z // setup adc writeTouchRegister(0x20, 0x60); writeTouchRegister(0x21, 0x02); // general touch config writeTouchRegister(0x41, 0xa4); writeTouchRegister(0x56, 0x06); // clear buffer writeTouchRegister(0x4b, 0x01); writeTouchRegister(0x4b, 0x00); writeTouchRegister(0x58, 0x01); // current limit } // this function is ugly repetitive for maximum speed void drawPrimitive(uint16 x1, uint16 y1, uint16 w, uint16 h, uint16 color) { uint16 x2 = (x1 + w - 1), y2 = (y1 + h - 1); uint32 length = (uint32)w * h; // column address set *portb = *portb & ~BIT(1); *spdr = 0x2a; while(!(*spsr & BIT(7))); *portb = *portb | BIT(1); *spdr = (byte)(x1 >> 8); while(!(*spsr & BIT(7))); *spdr = (byte)x1; while(!(*spsr & BIT(7))); *spdr = (byte)(x2 >> 8); while(!(*spsr & BIT(7))); *spdr = (byte)x2; while(!(*spsr & BIT(7))); // page address set *portb = *portb & ~BIT(1); *spdr = 0x2b; while(!(*spsr & BIT(7))); *portb = *portb | BIT(1); *spdr = (byte)(y1 >> 8); while(!(*spsr & BIT(7))); *spdr = (byte)y1; while(!(*spsr & BIT(7))); *spdr = (byte)(y2 >> 8); while(!(*spsr & BIT(7))); *spdr = (byte)y2; while(!(*spsr & BIT(7))); // memory write *portb = *portb & ~BIT(1); *spdr = 0x2c; while(!(*spsr & BIT(7))); *portb = *portb | BIT(1); while (length--) { *spdr = (byte)(color >> 8); while(!(*spsr & BIT(7))); *spdr = (byte)color; while(!(*spsr & BIT(7))); } } void drawRect(rect rect) { startScreenSPI(); drawPrimitive(rect.positionX, rect.positionY, rect.width, rect.height, rect.color); endScreenSPI(); } void drawChar(byte c, uint16 x, uint16 y, byte size, uint16 color) { // character bitmaps gratefully borrowed from https://github.com/adafruit/Adafruit-GFX-Library/blob/master/glcdfont.c byte columns[5]; // you could have allowed the static initializer syntax, but no... switch (c) { case ' ': columns[0] = 0x00; columns[1] = 0x00; columns[2] = 0x00; columns[3] = 0x00; columns[4] = 0x00; break; case '!': columns[0] = 0x00; columns[1] = 0x00; columns[2] = 0x5f; columns[3] = 0x00; columns[4] = 0x00; break; case '"': columns[0] = 0x00; columns[1] = 0x07; columns[2] = 0x00; columns[3] = 0x07; columns[4] = 0x00; break; case '#': columns[0] = 0x14; columns[1] = 0x7f; columns[2] = 0x14; columns[3] = 0x7f; columns[4] = 0x14; break; case '$': columns[0] = 0x24; columns[1] = 0x2a; columns[2] = 0x7f; columns[3] = 0x2a; columns[4] = 0x12; break; case '%': columns[0] = 0x23; columns[1] = 0x13; columns[2] = 0x08; columns[3] = 0x64; columns[4] = 0x62; break; case '&': columns[0] = 0x36; columns[1] = 0x49; columns[2] = 0x56; columns[3] = 0x20; columns[4] = 0x50; break; case '\'': columns[0] = 0x00; columns[1] = 0x08; columns[2] = 0x07; columns[3] = 0x03; columns[4] = 0x00; break; case '(': columns[0] = 0x00; columns[1] = 0x1c; columns[2] = 0x22; columns[3] = 0x41; columns[4] = 0x00; break; case ')': columns[0] = 0x00; columns[1] = 0x41; columns[2] = 0x22; columns[3] = 0x1c; columns[4] = 0x00; break; case '*': columns[0] = 0x2a; columns[1] = 0x1c; columns[2] = 0x7f; columns[3] = 0x1c; columns[4] = 0x2a; break; case '+': columns[0] = 0x08; columns[1] = 0x08; columns[2] = 0x3e; columns[3] = 0x08; columns[4] = 0x08; break; case ',': columns[0] = 0x00; columns[1] = 0x80; columns[2] = 0x70; columns[3] = 0x30; columns[4] = 0x00; break; case '-': columns[0] = 0x08; columns[1] = 0x08; columns[2] = 0x08; columns[3] = 0x08; columns[4] = 0x08; break; case '.': columns[0] = 0x00; columns[1] = 0x00; columns[2] = 0x60; columns[3] = 0x60; columns[4] = 0x00; break; case '/': columns[0] = 0x20; columns[1] = 0x10; columns[2] = 0x08; columns[3] = 0x04; columns[4] = 0x02; break; case '0': columns[0] = 0x3e; columns[1] = 0x51; columns[2] = 0x49; columns[3] = 0x45; columns[4] = 0x3e; break; case '1': columns[0] = 0x00; columns[1] = 0x42; columns[2] = 0x7f; columns[3] = 0x40; columns[4] = 0x00; break; case '2': columns[0] = 0x72; columns[1] = 0x49; columns[2] = 0x49; columns[3] = 0x49; columns[4] = 0x46; break; case '3': columns[0] = 0x21; columns[1] = 0x41; columns[2] = 0x49; columns[3] = 0x4d; columns[4] = 0x33; break; case '4': columns[0] = 0x18; columns[1] = 0x14; columns[2] = 0x12; columns[3] = 0x7f; columns[4] = 0x10; break; case '5': columns[0] = 0x27; columns[1] = 0x45; columns[2] = 0x45; columns[3] = 0x45; columns[4] = 0x39; break; case '6': columns[0] = 0x3c; columns[1] = 0x4a; columns[2] = 0x49; columns[3] = 0x49; columns[4] = 0x31; break; case '7': columns[0] = 0x41; columns[1] = 0x21; columns[2] = 0x11; columns[3] = 0x09; columns[4] = 0x07; break; case '8': columns[0] = 0x36; columns[1] = 0x49; columns[2] = 0x49; columns[3] = 0x49; columns[4] = 0x36; break; case '9': columns[0] = 0x46; columns[1] = 0x49; columns[2] = 0x49; columns[3] = 0x29; columns[4] = 0x1e; break; case ':': columns[0] = 0x00; columns[1] = 0x00; columns[2] = 0x14; columns[3] = 0x00; columns[4] = 0x00; break; case ';': columns[0] = 0x00; columns[1] = 0x40; columns[2] = 0x34; columns[3] = 0x00; columns[4] = 0x00; break; case '<': columns[0] = 0x00; columns[1] = 0x08; columns[2] = 0x14; columns[3] = 0x22; columns[4] = 0x41; break; case '=': columns[0] = 0x14; columns[1] = 0x14; columns[2] = 0x14; columns[3] = 0x14; columns[4] = 0x14; break; case '>': columns[0] = 0x00; columns[1] = 0x41; columns[2] = 0x22; columns[3] = 0x14; columns[4] = 0x08; break; case '?': columns[0] = 0x02; columns[1] = 0x01; columns[2] = 0x59; columns[3] = 0x09; columns[4] = 0x06; break; case '@': columns[0] = 0x3e; columns[1] = 0x41; columns[2] = 0x5d; columns[3] = 0x59; columns[4] = 0x4e; break; case 'A': columns[0] = 0x7c; columns[1] = 0x12; columns[2] = 0x11; columns[3] = 0x12; columns[4] = 0x7c; break; case 'B': columns[0] = 0x7f; columns[1] = 0x49; columns[2] = 0x49; columns[3] = 0x49; columns[4] = 0x36; break; case 'C': columns[0] = 0x3e; columns[1] = 0x41; columns[2] = 0x41; columns[3] = 0x41; columns[4] = 0x22; break; case 'D': columns[0] = 0x7f; columns[1] = 0x41; columns[2] = 0x41; columns[3] = 0x41; columns[4] = 0x3e; break; case 'E': columns[0] = 0x7f; columns[1] = 0x49; columns[2] = 0x49; columns[3] = 0x49; columns[4] = 0x41; break; case 'F': columns[0] = 0x7f; columns[1] = 0x09; columns[2] = 0x09; columns[3] = 0x09; columns[4] = 0x01; break; case 'G': columns[0] = 0x3e; columns[1] = 0x41; columns[2] = 0x41; columns[3] = 0x51; columns[4] = 0x73; break; case 'H': columns[0] = 0x7f; columns[1] = 0x08; columns[2] = 0x08; columns[3] = 0x08; columns[4] = 0x7f; break; case 'I': columns[0] = 0x00; columns[1] = 0x41; columns[2] = 0x7f; columns[3] = 0x41; columns[4] = 0x00; break; case 'J': columns[0] = 0x20; columns[1] = 0x40; columns[2] = 0x41; columns[3] = 0x3f; columns[4] = 0x01; break; case 'K': columns[0] = 0x7f; columns[1] = 0x08; columns[2] = 0x14; columns[3] = 0x22; columns[4] = 0x41; break; case 'L': columns[0] = 0x7f; columns[1] = 0x40; columns[2] = 0x40; columns[3] = 0x40; columns[4] = 0x40; break; case 'M': columns[0] = 0x7f; columns[1] = 0x02; columns[2] = 0x1c; columns[3] = 0x02; columns[4] = 0x7f; break; case 'N': columns[0] = 0x7f; columns[1] = 0x04; columns[2] = 0x08; columns[3] = 0x10; columns[4] = 0x7f; break; case 'O': columns[0] = 0x3e; columns[1] = 0x41; columns[2] = 0x41; columns[3] = 0x41; columns[4] = 0x3e; break; case 'P': columns[0] = 0x7f; columns[1] = 0x09; columns[2] = 0x09; columns[3] = 0x09; columns[4] = 0x06; break; case 'Q': columns[0] = 0x3e; columns[1] = 0x41; columns[2] = 0x51; columns[3] = 0x21; columns[4] = 0x5e; break; case 'R': columns[0] = 0x7f; columns[1] = 0x09; columns[2] = 0x19; columns[3] = 0x29; columns[4] = 0x46; break; case 'S': columns[0] = 0x26; columns[1] = 0x49; columns[2] = 0x49; columns[3] = 0x49; columns[4] = 0x32; break; case 'T': columns[0] = 0x03; columns[1] = 0x01; columns[2] = 0x7f; columns[3] = 0x01; columns[4] = 0x03; break; case 'U': columns[0] = 0x3f; columns[1] = 0x40; columns[2] = 0x40; columns[3] = 0x40; columns[4] = 0x3f; break; case 'V': columns[0] = 0x1f; columns[1] = 0x20; columns[2] = 0x40; columns[3] = 0x20; columns[4] = 0x1f; break; case 'W': columns[0] = 0x3f; columns[1] = 0x40; columns[2] = 0x38; columns[3] = 0x40; columns[4] = 0x3f; break; case 'X': columns[0] = 0x63; columns[1] = 0x14; columns[2] = 0x08; columns[3] = 0x14; columns[4] = 0x63; break; case 'Y': columns[0] = 0x03; columns[1] = 0x04; columns[2] = 0x78; columns[3] = 0x04; columns[4] = 0x03; break; case 'Z': columns[0] = 0x61; columns[1] = 0x59; columns[2] = 0x49; columns[3] = 0x4d; columns[4] = 0x43; break; case '[': columns[0] = 0x00; columns[1] = 0x7f; columns[2] = 0x41; columns[3] = 0x41; columns[4] = 0x41; break; case '\\': columns[0] = 0x02; columns[1] = 0x04; columns[2] = 0x08; columns[3] = 0x10; columns[4] = 0x20; break; case ']': columns[0] = 0x00; columns[1] = 0x41; columns[2] = 0x41; columns[3] = 0x41; columns[4] = 0x7f; break; case '^': columns[0] = 0x04; columns[1] = 0x02; columns[2] = 0x01; columns[3] = 0x02; columns[4] = 0x04; break; case '_': columns[0] = 0x40; columns[1] = 0x40; columns[2] = 0x40; columns[3] = 0x40; columns[4] = 0x40; break; case '`': columns[0] = 0x00; columns[1] = 0x03; columns[2] = 0x07; columns[3] = 0x08; columns[4] = 0x00; break; case 'a': columns[0] = 0x20; columns[1] = 0x54; columns[2] = 0x54; columns[3] = 0x78; columns[4] = 0x40; break; case 'b': columns[0] = 0x7f; columns[1] = 0x28; columns[2] = 0x44; columns[3] = 0x44; columns[4] = 0x38; break; case 'c': columns[0] = 0x38; columns[1] = 0x44; columns[2] = 0x44; columns[3] = 0x44; columns[4] = 0x28; break; case 'd': columns[0] = 0x38; columns[1] = 0x44; columns[2] = 0x44; columns[3] = 0x28; columns[4] = 0x7f; break; case 'e': columns[0] = 0x38; columns[1] = 0x54; columns[2] = 0x54; columns[3] = 0x54; columns[4] = 0x18; break; case 'f': columns[0] = 0x00; columns[1] = 0x08; columns[2] = 0x7e; columns[3] = 0x09; columns[4] = 0x02; break; case 'g': columns[0] = 0x18; columns[1] = 0xa4; columns[2] = 0xa4; columns[3] = 0x9c; columns[4] = 0x78; break; case 'h': columns[0] = 0x7f; columns[1] = 0x08; columns[2] = 0x04; columns[3] = 0x04; columns[4] = 0x78; break; case 'i': columns[0] = 0x00; columns[1] = 0x44; columns[2] = 0x7d; columns[3] = 0x40; columns[4] = 0x00; break; case 'j': columns[0] = 0x20; columns[1] = 0x40; columns[2] = 0x40; columns[3] = 0x3d; columns[4] = 0x00; break; case 'k': columns[0] = 0x7f; columns[1] = 0x10; columns[2] = 0x28; columns[3] = 0x44; columns[4] = 0x00; break; case 'l': columns[0] = 0x00; columns[1] = 0x41; columns[2] = 0x7f; columns[3] = 0x40; columns[4] = 0x00; break; case 'm': columns[0] = 0x7c; columns[1] = 0x04; columns[2] = 0x78; columns[3] = 0x04; columns[4] = 0x78; break; case 'n': columns[0] = 0x7c; columns[1] = 0x08; columns[2] = 0x04; columns[3] = 0x04; columns[4] = 0x78; break; case 'o': columns[0] = 0x38; columns[1] = 0x44; columns[2] = 0x44; columns[3] = 0x44; columns[4] = 0x38; break; case 'p': columns[0] = 0xfc; columns[1] = 0x18; columns[2] = 0x24; columns[3] = 0x24; columns[4] = 0x18; break; case 'q': columns[0] = 0x18; columns[1] = 0x24; columns[2] = 0x24; columns[3] = 0x18; columns[4] = 0xfc; break; case 'r': columns[0] = 0x7c; columns[1] = 0x08; columns[2] = 0x04; columns[3] = 0x04; columns[4] = 0x08; break; case 's': columns[0] = 0x48; columns[1] = 0x54; columns[2] = 0x54; columns[3] = 0x54; columns[4] = 0x24; break; case 't': columns[0] = 0x04; columns[1] = 0x04; columns[2] = 0x3f; columns[3] = 0x44; columns[4] = 0x24; break; case 'u': columns[0] = 0x3c; columns[1] = 0x40; columns[2] = 0x40; columns[3] = 0x20; columns[4] = 0x7c; break; case 'v': columns[0] = 0x1c; columns[1] = 0x20; columns[2] = 0x40; columns[3] = 0x20; columns[4] = 0x1c; break; case 'w': columns[0] = 0x3c; columns[1] = 0x40; columns[2] = 0x30; columns[3] = 0x40; columns[4] = 0x3c; break; case 'x': columns[0] = 0x44; columns[1] = 0x28; columns[2] = 0x10; columns[3] = 0x28; columns[4] = 0x44; break; case 'y': columns[0] = 0x4c; columns[1] = 0x90; columns[2] = 0x90; columns[3] = 0x90; columns[4] = 0x7c; break; case 'z': columns[0] = 0x44; columns[1] = 0x64; columns[2] = 0x54; columns[3] = 0x4c; columns[4] = 0x44; break; case '{': columns[0] = 0x00; columns[1] = 0x08; columns[2] = 0x36; columns[3] = 0x41; columns[4] = 0x00; break; case '|': columns[0] = 0x00; columns[1] = 0x00; columns[2] = 0x77; columns[3] = 0x00; columns[4] = 0x00; break; case '}': columns[0] = 0x00; columns[1] = 0x41; columns[2] = 0x36; columns[3] = 0x08; columns[4] = 0x00; break; case '~': columns[0] = 0x02; columns[1] = 0x01; columns[2] = 0x02; columns[3] = 0x04; columns[4] = 0x02; break; default: columns[0] = 0xff; columns[1] = 0xff; columns[2] = 0xff; columns[3] = 0xff; columns[4] = 0xff; break; } startScreenSPI(); byte i, j; for (i = 0; i < 5; i++) { byte column = columns[i]; for (j = 0; j < 8; j++, column >>= 1) { if (column & 1) { drawPrimitive(x + (i * size), y + (j * size), size, size, color); } } } endScreenSPI(); } void drawButton(rect rect, char character) { // the constants 15 and 24 are based on a button size of 3 drawRect(rect); uint16 x = rect.positionX + (rect.width - 15) / 2; uint16 y = rect.positionY + (rect.height - 24) / 2; drawChar(character, x, y, 3, WHITE); } void drawKeyboard(void) { int i; for (i = 0; i < 26; i++) { drawButton(writeScreenData.keyboard[i].container, writeScreenData.keyboard[i].characterSet[writeScreenData.keyboardState]); } } void drawScreen(void) { byte orientation = 0x48; // default portrait orientation int length, i, j; uint16 x, y; switch (screen) { case mainScreen: sendScreenCommand(0x36, &orientation, 1); drawRect(mainScreenData.background); length = sizeof(mainScreenData.buttons) / sizeof(buttonString); for (i = 0; i < length; i++) { buttonString button = mainScreenData.buttons[i]; drawRect(button.container); x = button.container.positionX + 14; y = button.container.positionY + (button.container.height - 24) / 2; for (j = 0; button.label[j] != 0; j++) { drawChar(button.label[j], x + j * 20, y, 3, WHITE); } } break; case dialScreen: sendScreenCommand(0x36, &orientation, 1); length = sizeof(dialScreenData.layout) / sizeof(rect); for (i = 0; i < length; i++) { drawRect(dialScreenData.layout[i]); } length = sizeof(dialScreenData.buttons) / sizeof(buttonChar); for (i = 0; i < length; i++) { drawButton(dialScreenData.buttons[i].container, dialScreenData.buttons[i].character); } for (i = 0; i < dialScreenData.dialedNums.length; i++) { drawChar(dialScreenData.dialedNums.characters[i], 10 + i * 22, 15, 3, BLACK); } break; case writeScreen: orientation = 0x28; // landscape for keyboard sendScreenCommand(0x36, &orientation, 1); length = sizeof(writeScreenData.layout) / sizeof(rect); for (i = 0; i < length; i++) { drawRect(writeScreenData.layout[i]); } drawKeyboard(); length = sizeof(writeScreenData.buttonsGeneral) / sizeof(buttonChar); for (i = 0; i < length; i++) { drawButton(writeScreenData.buttonsGeneral[i].container, writeScreenData.buttonsGeneral[i].character); } for (i = 0; i < writeScreenData.writtenChars.length; i++) { vector2ui position = indexToWritePosition(i); drawChar(writeScreenData.writtenChars.characters[i], position.x, position.y, 3, BLACK); } } } void pushChar(byte c) { int length; switch (screen) { case dialScreen: length = dialScreenData.dialedNums.length; if (length < 10) { dialScreenData.dialedNums.length++; dialScreenData.dialedNums.characters[length] = c; } break; case writeScreen: length = writeScreenData.writtenChars.length; if (length < 56) { writeScreenData.writtenChars.length++; writeScreenData.writtenChars.characters[length] = c; } break; } } void popChar(void) { switch (screen) { case dialScreen: if (dialScreenData.dialedNums.length > 0) { dialScreenData.dialedNums.length--; } break; case writeScreen: if (writeScreenData.writtenChars.length > 0) { writeScreenData.writtenChars.length--; } break; } } void addChar(void) { int index; switch (screen) { case dialScreen: index = dialScreenData.dialedNums.length - 1; drawChar(dialScreenData.dialedNums.characters[index], 10 + index * 22, 15, 3, BLACK); break; case writeScreen: index = writeScreenData.writtenChars.length - 1; vector2ui position = indexToWritePosition(index); drawChar(writeScreenData.writtenChars.characters[index], position.x, position.y, 3, BLACK); break; } } void clearChar(void) { startScreenSPI(); vector2ui position; switch (screen) { case dialScreen: drawPrimitive(10 + dialScreenData.dialedNums.length * 22, 15, 15, 24, WHITE); break; case writeScreen: position = indexToWritePosition(writeScreenData.writtenChars.length); drawPrimitive(position.x, position.y, 15, 24, WHITE); break; } endScreenSPI(); } vector2i getTouchCoords(void) { byte data[4]; int rawX, rawY, i; vector2i touch; while (!(readTouchRegister(0x4b) & 0x20)) { // buffer is not empty for (i = 0; i < 4; i++) { data[i] = readTouchRegister(0xd7); } } rawX = data[0]; rawX <<= 4; rawX |= (data[1] >> 4); rawY = data[1] & 0x0F; rawY <<= 8; rawY |= data[2]; if (rawX >= 350 && rawX <= 3700) { touch.x = (int)((rawX - 225) * 0.0655); } else touch.x = -1; // rawY value 3700 marks bottom of screen, here ignored to make non-screen touch area available, but used to calculate multiplier if (rawY >= 350) { touch.y = (int)((rawY - 150) * 0.0899); } else touch.y = -1; return touch; } command update(vector2i touch) { rect rect; int length, i, touchXLandscape, touchYLandscape; uint16 x, y; command cmd = nop; switch (screen) { case mainScreen: length = sizeof(mainScreenData.buttons) / sizeof(buttonString); for (i = 0; i < length; i++) { if (isPointInBounds(touch.x, touch.y, mainScreenData.buttons[i].container)) { switch (mainScreenData.buttons[i].function) { case showDialScreen: screen = dialScreen; cmd = redrawScreen; break; case showWriteScreen: screen = writeScreen; cmd = redrawScreen; break; } } } break; case dialScreen: length = sizeof(dialScreenData.buttons) / sizeof(buttonChar); for (i = 0; i < length; i++) { if (isPointInBounds(touch.x, touch.y, dialScreenData.buttons[i].container)) { switch (dialScreenData.buttons[i].function) { case typeChar: pushChar(dialScreenData.buttons[i].character); cmd = refreshAddChar; break; case deleteChar: popChar(); cmd = refreshClearChar; break; } } } break; case writeScreen: touchXLandscape = touch.y; touchYLandscape = 239 - touch.x; for (i = 0; i < 26; i++) { if (isPointInBounds(touchXLandscape, touchYLandscape, writeScreenData.keyboard[i].container)) { pushChar(writeScreenData.keyboard[i].characterSet[writeScreenData.keyboardState]); cmd = refreshAddChar; } } length = sizeof(writeScreenData.buttonsGeneral) / sizeof(buttonChar); for (i = 0; i < length; i++) { if (isPointInBounds(touchXLandscape, touchYLandscape, writeScreenData.buttonsGeneral[i].container)) { switch (writeScreenData.buttonsGeneral[i].function) { case deleteChar: popChar(); cmd = refreshClearChar; break; case space: pushChar(' '); break; case keyboardToggleShift: writeScreenData.keyboardState = getToggleShift(writeScreenData.keyboardState); cmd = redrawKeyboard; break; case keyboardToggleAlphabet: writeScreenData.keyboardState = getToggleAlphabet(writeScreenData.keyboardState); cmd = redrawKeyboard; break; } } } break; } if (touch.y >= 320) { screen = mainScreen; cmd = redrawScreen; } return cmd; } void render(command cmd) { switch (cmd) { case refreshAddChar: addChar(); break; case refreshClearChar: clearChar(); break; case redrawKeyboard: drawKeyboard(); break; case redrawScreen: drawScreen(); break; } } void main(void) { // define registers ddrb = (byte *)0x24; // data direction b register portb = (byte *)0x25; // port b register spcr = (byte *)0x4c; // spi control register spsr = (byte *)0x4d; // spi status register spdr = (byte *)0x4e; // spi data register // init SPI *ddrb = BIT(5)|BIT(3)|BIT(2)|BIT(1)|BIT(0); // SCK, MOSI, screen select, command/data, touch select *portb = BIT(2)|BIT(1)|BIT(0); // screen select, command/data, touch select *spcr = BIT(6)|BIT(4); // SPI enable, master select initScreen(); initTouch(); drawScreen(); while(1) { if (!(readTouchRegister(0x4b) & 0x20)) { // buffer is not empty vector2i touch = getTouchCoords(); if (touch.x >= 0 && touch.y >= 0) { command cmd = update(touch); render(cmd); delay(150); if (!(readTouchRegister(0x4b) & 0x20)) { getTouchCoords(); // clear lingering buffer content preventing double presses } } } delay(50); } }