Initial commit
This commit is contained in:
parent
02deaf49ba
commit
5b40a9b083
@ -10,6 +10,11 @@ Physical recreation of a video terminal device for connecting to a serial consol
|
||||
- [TinTTY](https://github.com/unframework/tintty) for VT100 emulation
|
||||
- [ESP32Lib](https://github.com/bitluni/ESP32Lib/tree/development) for composite video output
|
||||
|
||||
## Building
|
||||
1. Connect up the hardware (TODO).
|
||||
2. Get the Arduino IDE, Install ESP32 board package, Install ESP32Lib library.
|
||||
3. Compile and upload the sketch in `video-terminal/video-terminal.ino`
|
||||
|
||||
## Resources
|
||||
- [LK201 Interface](http://www.netbsd.org/docs/Hardware/Machines/DEC/lk201.html) for keyboard specification
|
||||
|
||||
|
119
video-terminal/Display.h
Normal file
119
video-terminal/Display.h
Normal file
@ -0,0 +1,119 @@
|
||||
/*
|
||||
CONNECTION from ESP32 to a composite video TV
|
||||
|
||||
A) voltageDivider = false; B) voltageDivider = true
|
||||
|
||||
55 shades 179 shades
|
||||
|
||||
ESP32 TV ESP32 TV
|
||||
-----+ -----+ ____ 100 ohm
|
||||
G|- G|---|____|+
|
||||
pin25|--------- Comp pin25|---|____|+--------- Comp
|
||||
pin26|- pin26|- 220 ohm
|
||||
| |
|
||||
| |
|
||||
-----+ -----+
|
||||
|
||||
Connect pin 25 or 26
|
||||
|
||||
C) R–2R resistor ladder; D) unequal rungs ladder
|
||||
|
||||
55 shades up to 254 shades?
|
||||
|
||||
ESP32 TV ESP32 TV
|
||||
-----+ -----+ ____
|
||||
G|-+_____ G|---|____|
|
||||
pinA0|-| R2R |- Comp pinA0|---|____|+--------- Comp
|
||||
pinA1|-| | pinA1|---|____|
|
||||
pinA2|-| | ...|
|
||||
...|-|_____| |
|
||||
-----+ -----+
|
||||
|
||||
Connect pins of your choice (A0...A8=any pins).
|
||||
Custom ladders can be used by tweaking colorMinValue and colorMaxValue
|
||||
*/
|
||||
#include <ESP32Lib.h>
|
||||
#include <Ressources/Font6x8.h>
|
||||
//#include <Ressources/CodePage437_8x8.h>
|
||||
//#include <Ressources/CodePage437_8x14.h>
|
||||
//#include <Ressources/CodePage437_8x16.h>
|
||||
//#include <Ressources/CodePage437_8x19.h>
|
||||
#include <Ressources/CodePage437_9x16.h>
|
||||
|
||||
//pin configuration for DAC
|
||||
const int outputPin = 25;
|
||||
// A) B)
|
||||
CompositeGrayDAC videodisplay;
|
||||
// C) D)
|
||||
//CompositeGrayLadder videodisplay;
|
||||
|
||||
class Display {
|
||||
|
||||
public:
|
||||
// view(able) size
|
||||
int vw = 350;
|
||||
int vh = 235;
|
||||
|
||||
// offsets (to fix overscan, depends on your display)
|
||||
int ow = 10;
|
||||
int oh = 26;
|
||||
|
||||
// display size
|
||||
int w = 365; // vw + 2*ow
|
||||
int h = 275; // vh + 2*oh
|
||||
|
||||
#define FONT CodePage437_9x16 // for options, see the Resources includes ^^^
|
||||
int font_w = 9;
|
||||
int font_h = 16;
|
||||
|
||||
int cols = vw / font_w;
|
||||
int rows = vh / font_h;
|
||||
|
||||
void setup()
|
||||
{
|
||||
// Composite video init, see options in the header ^^^
|
||||
// A)
|
||||
//videodisplay.init(CompMode::MODEPAL288P, 25, false);
|
||||
// B)
|
||||
videodisplay.init(CompMode::MODEPAL288P, 25, true);
|
||||
videodisplay.clear();
|
||||
|
||||
videodisplay.xres = w;
|
||||
videodisplay.yres = h;
|
||||
|
||||
videodisplay.setFont(FONT);
|
||||
|
||||
/*/ view area test
|
||||
videodisplay.rect(ow, oh, vw, vh, 50);
|
||||
/**/
|
||||
}
|
||||
|
||||
void scroll(int d) {
|
||||
videodisplay.scroll(d, 0);
|
||||
}
|
||||
|
||||
void fill_rect(int x, int y, int w, int h, int color) {
|
||||
videodisplay.fillRect(x, y, w, h, color);
|
||||
}
|
||||
|
||||
void pixel(int x, int y, int color) {
|
||||
videodisplay.dotFast(x, y, color);
|
||||
}
|
||||
|
||||
void print_character(int col, int row, int fg_color, int bg_color, char character) {
|
||||
int x = ow + col*font_w;
|
||||
int y = oh + row*font_h;
|
||||
videodisplay.setCursor(x, y);
|
||||
videodisplay.frontColor = fg_color;
|
||||
videodisplay.backColor = bg_color;
|
||||
videodisplay.print(character);
|
||||
}
|
||||
|
||||
int get_display_width() { return w; }
|
||||
int get_display_height() { return h; }
|
||||
int get_view_width() { return vw; }
|
||||
int get_view_height() { return vh; }
|
||||
int get_view_width_offset() { return ow; }
|
||||
int get_view_height_offset() { return oh; }
|
||||
|
||||
};
|
216
video-terminal/Keyboard.h
Normal file
216
video-terminal/Keyboard.h
Normal file
@ -0,0 +1,216 @@
|
||||
/* SOURCE: http://www.netbsd.org/docs/Hardware/Machines/DEC/lk201.html */
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
/* requires LED number data */
|
||||
#define LK_LED_ENABLE 0x13 /* light LED */
|
||||
#define LK_LED_DISABLE 0x11 /* turn off LED */
|
||||
|
||||
#define LED_WAIT 0x81 /* Wait LED */
|
||||
#define LED_COMP 0x82 /* Compose LED */
|
||||
#define LED_LOCK 0x84 /* Lock LED */
|
||||
#define LED_HOLD 0x88 /* Hold Screen LED */
|
||||
#define LED_ALL 0x8F /* All LED's */
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
/* Requires volume data byte */
|
||||
#define LK_CL_ENABLE 0x1B /* keyclick enable. Requires volume */
|
||||
/* byte. This does not affect the */
|
||||
/* SHIFT key. The CTRL key requires */
|
||||
/* LK_CL_ENABLE and LK_CCL_ENABLE to */
|
||||
/* have been sent before it clicks. */
|
||||
/* All other keys are only controlled */
|
||||
/* by LK_CL_ENABLE. */
|
||||
|
||||
#define LK_CCL_ENABLE 0xBB /* Enable keyclicks for the CTRL key. */
|
||||
/* The CTRL keyclick volume is set to */
|
||||
/* be the same as the rest of the keys */
|
||||
/* LK_CCL_ENABLE sets a flag in the */
|
||||
/* keyboard with is logically AND'ed */
|
||||
/* with the LK_CL_ENABLE flag to enable*/
|
||||
/* CTRL key keyclicks. */
|
||||
|
||||
#define LK_CL_DISABLE 0x99 /* keyclick disable */
|
||||
#define LK_CCL_DISABLE 0xB9 /* CTRL key keyclick disable */
|
||||
|
||||
#define LK_SOUND_CLICK 0x9F /* causes the LK201 to sound a keyclick*/
|
||||
|
||||
/* max volume is 0, lowest is 0x7 */
|
||||
#define LK_PARAM_VOLUME(v) (0x80|((v)&0x7))
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
/* requires bell volume data */
|
||||
#define LK_BELL_ENABLE 0x23 /* enable the keyboard bell. Requires */
|
||||
/* volume data byte. */
|
||||
|
||||
#define LK_BELL_DISABLE 0xA1 /* disable the keyboard bell. */
|
||||
|
||||
#define LK_RING_BELL 0xA7 /* ring the keyboard bell */
|
||||
|
||||
/* max volume is 0, lowest is 0x7 */
|
||||
#define LK_PARAM_VOLUME(v) (0x80|((v)&0x7))
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
#define LK_UPDOWN 0x86
|
||||
#define LK_AUTODOWN 0x82
|
||||
#define LK_DOWN 0x80
|
||||
|
||||
#define LK_CMD_MODE(m,div) ((m)|((div)<<3))
|
||||
|
||||
#define LK_MODECHG_ACK 0xBA /* sent by the keyboard to acknowledge a */
|
||||
/* successful mode change. */
|
||||
|
||||
|
||||
#define LK_PFX_KEYDOWN 0xB9 /* indicates that the next byte is a key- */
|
||||
/* code for a key already down in a */
|
||||
/* division that has been changed to */
|
||||
/* LK_UPDOWN. I think this means that if */
|
||||
/* for example, the 'a' key is in LK_DOWN */
|
||||
/* mode and the key is being held down and*/
|
||||
/* division 1 is switched to LK_UPDOWN */
|
||||
/* mode, the keyboard will produce the */
|
||||
/* byte LK_PFX_KEYDOWN followed by 0xC2 */
|
||||
/* (KEY_A). */
|
||||
|
||||
#define LK_CMD_RPT_TO_DOWN 0xD9 /* This command causes all divisions which */
|
||||
/* are programmed for LK_AUTODOWN mode to */
|
||||
/* be switched to LK_DOWN mode. */
|
||||
|
||||
|
||||
#define LK_CMD_ENB_RPT 0xE3 /* enables auto repeat on the keys */
|
||||
/* which are in LK_AUTODOWN mode */
|
||||
|
||||
#define LK_CMD_DIS_RPT 0xE1 /* disables auto repeat on all keys, but */
|
||||
/* does not change the mode that the */
|
||||
/* divisions are programmed to. */
|
||||
|
||||
#define LK_CMD_TMP_NORPT 0xD1 /* temporary auto repeat disable. This */
|
||||
/* command disables auto repeat for the key*/
|
||||
/* which is currently pressed down. Auto */
|
||||
/* repeat is re-enabled when another key is*/
|
||||
/* pressed. */
|
||||
|
||||
|
||||
#define LK_INPUT_ERROR 0xB6 /* sent by the keyboard if it receives an */
|
||||
/* invalid command. */
|
||||
|
||||
#define LK_NO_ERROR 0x00 /* No Error */
|
||||
#define LK_KDOWN_ERROR 0x3D /* Key down on powerup error */
|
||||
#define LK_POWER_ERROR 0x3E /* Keyboard failure on pwrup tst */
|
||||
|
||||
#define LK_ALLUP 0xB3
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
bool mod_shift = false;
|
||||
bool mod_ctrl = false;
|
||||
#define KB_CHAR(key_base, key_shift) c = ((mod_shift) ? (key_shift) : (key_base));
|
||||
|
||||
void keyboard_handle_key(int key)
|
||||
{
|
||||
int c = -1;
|
||||
switch (key) {
|
||||
/* Key Division 191-255 */
|
||||
case 191: KB_CHAR('`', '~'); break; // (xbf): KEY_TILDE
|
||||
case 192: KB_CHAR('1', '!'); break; // (xc0): KEY_TR_1
|
||||
case 193: KB_CHAR('q', 'Q'); break; // (xc1): KEY_Q
|
||||
case 194: KB_CHAR('a', 'A'); break; // (xc2): KEY_A
|
||||
case 195: KB_CHAR('z', 'Z'); break; // (xc3): KEY_Z
|
||||
case 197: KB_CHAR('2', '@'); break; // (xc5): KEY_TR_2
|
||||
case 198: KB_CHAR('w', 'W'); break; // (xc6): KEY_W
|
||||
case 199: KB_CHAR('s', 'S'); break; // (xc7): KEY_S
|
||||
case 200: KB_CHAR('x', 'X'); break; // (xc8): KEY_X
|
||||
case 201: KB_CHAR('<', '>'); break; // (xc9): KEY_LANGLE_RANGLE
|
||||
case 203: KB_CHAR('3', '#'); break; // (xcb): KEY_TR_3
|
||||
case 204: KB_CHAR('e', 'E'); break; // (xcc): KEY_E
|
||||
case 205: KB_CHAR('d', 'D'); break; // (xcd): KEY_D
|
||||
case 206: KB_CHAR('c', 'C'); break; // (xce): KEY_C
|
||||
case 208: KB_CHAR('4', '$'); break; // (xd0): KEY_TR_4
|
||||
case 209: KB_CHAR('r', 'R'); break; // (xd1): KEY_R
|
||||
case 210: KB_CHAR('f', 'F'); break; // (xd2): KEY_F
|
||||
case 211: KB_CHAR('v', 'V'); break; // (xd3): KEY_V
|
||||
case 212: KB_CHAR(' ', ' '); break; // (xd4): KEY_SPACE
|
||||
case 214: KB_CHAR('5', '%'); break; // (xd6): KEY_TR_5
|
||||
case 215: KB_CHAR('t', 'T'); break; // (xd7): KEY_T
|
||||
case 216: KB_CHAR('g', 'G'); break; // (xd8): KEY_G
|
||||
case 217: KB_CHAR('b', 'B'); break; // (xd9): KEY_B
|
||||
case 219: KB_CHAR('6', '^'); break; // (xdb): KEY_TR_6
|
||||
case 220: KB_CHAR('y', 'Y'); break; // (xdc): KEY_Y
|
||||
case 221: KB_CHAR('h', 'H'); break; // (xdd): KEY_H
|
||||
case 222: KB_CHAR('n', 'N'); break; // (xde): KEY_N
|
||||
case 224: KB_CHAR('7', '&'); break; // (xe0): KEY_TR_7
|
||||
case 225: KB_CHAR('u', 'U'); break; // (xe1): KEY_U
|
||||
case 226: KB_CHAR('j', 'J'); break; // (xe2): KEY_J
|
||||
case 227: KB_CHAR('m', 'M'); break; // (xe3): KEY_M
|
||||
case 229: KB_CHAR('8', '*'); break; // (xe5): KEY_TR_8
|
||||
case 230: KB_CHAR('i', 'I'); break; // (xe6): KEY_I
|
||||
case 231: KB_CHAR('k', 'K'); break; // (xe7): KEY_K
|
||||
case 232: KB_CHAR(',', '<'); break; // (xe8): KEY_COMMA
|
||||
case 234: KB_CHAR('9', '('); break; // (xea): KEY_TR_9
|
||||
case 235: KB_CHAR('o', 'O'); break; // (xeb): KEY_O
|
||||
case 236: KB_CHAR('l', 'L'); break; // (xec): KEY_L
|
||||
case 237: KB_CHAR('.', '>'); break; // (xed): KEY_PERIOD
|
||||
case 239: KB_CHAR('0', ')'); break; // (xef): KEY_TR_0
|
||||
case 240: KB_CHAR('p', 'P'); break; // (xf0): KEY_P
|
||||
case 242: KB_CHAR(';', ':'); break; // (xf2): KEY_SEMICOLON
|
||||
case 243: KB_CHAR('/', '?'); break; // (xf3): KEY_QMARK
|
||||
case 245: KB_CHAR('=', '+'); break; // (xf5): KEY_PLUS
|
||||
case 246: KB_CHAR(']', '}'); break; // (xf6): KEY_RBRACE
|
||||
case 247: KB_CHAR('\\', '|'); break; // (xf7): KEY_VBAR
|
||||
case 249: KB_CHAR('-', '_'); break; // (xf9): KEY_UBAR
|
||||
case 250: KB_CHAR('[', '{'); break; // (xfa): KEY_LBRACE
|
||||
case 251: KB_CHAR('\'', '"'); break; // (xfb): KEY_QUOTE
|
||||
/* Key Division 2: 145 - 165 */
|
||||
/*146 (x92): KEY_KP_0
|
||||
148 (x94): KEY_KP_PERIOD
|
||||
149 (x95): KEY_KP_ENTER
|
||||
150 (x96): KEY_KP_1
|
||||
151 (x97): KEY_KP_2
|
||||
152 (x98): KEY_KP_3
|
||||
153 (x99): KEY_KP_4
|
||||
154 (x9a): KEY_KP_5
|
||||
155 (x9b): KEY_KP_6
|
||||
156 (x9c): KEY_KP_COMMA
|
||||
157 (x9d): KEY_KP_7
|
||||
158 (x9e): KEY_KP_8
|
||||
159 (x9f): KEY_KP_9
|
||||
160 (xa0): KEY_KP_HYPHEN
|
||||
161 (xa1): KEY_KP_PF1
|
||||
162 (xa2): KEY_KP_PF2
|
||||
163 (xa3): KEY_KP_PF3
|
||||
164 (xa4): KEY_KP_PF4*/
|
||||
/* Key Division 3: 188 - 188 */
|
||||
case 188: c = 0177; Serial.print("<DELETE>"); break; // (xbc): KEY_DELETE
|
||||
/* Key Division 4: 189 - 190 */
|
||||
case 189: c = '\n'; break; // (xbd): KEY_RETURN
|
||||
case 190: c = '\t'; break; // (xbe): KEY_TAB
|
||||
/* Key Division 5: (176 - 178) */
|
||||
/*176 (xb0): KEY_LOCK
|
||||
177 (xb1): KEY_META*/
|
||||
/* Key Division 6: (173 - 175) */
|
||||
case 174: mod_shift = !mod_shift; Serial.print("<SHIFT "); Serial.print(mod_shift); Serial.print(">"); break; // (xae): KEY_SHIFT
|
||||
case 175: mod_ctrl = !mod_ctrl; Serial.print("<CTRL "); Serial.print(mod_ctrl); Serial.print(">"); break; // (xaf): KEY_CTRL*/
|
||||
/* Key Division 7: (166 - 168) */
|
||||
case 167: SerialTty.print("\033[D"); Serial.print("LEFT "); break; // (xa7): KEY_LEFT
|
||||
case 168: SerialTty.print("\033[C"); Serial.print("RIGHT "); break; // (xa8): KEY_RIGHT
|
||||
/* Key Division 8: (169 - 172) */
|
||||
case 169: SerialTty.print("\033[B"); Serial.print("DOWN "); break; // (xa9): KEY_DOWN
|
||||
case 170: SerialTty.print("\033[A"); Serial.print("UP "); break; // (xaa): KEY_UP
|
||||
case 171: break; // (xab): KEY_R_SHIFT
|
||||
|
||||
case LK_ALLUP:
|
||||
mod_shift = false;
|
||||
mod_ctrl = false;
|
||||
Serial.print("<ALLUP>");
|
||||
break;
|
||||
default:
|
||||
Serial.print(key); Serial.print(' ');
|
||||
}
|
||||
if (c != -1) {
|
||||
Serial.print((char) c);
|
||||
SerialTty.print((char) c);
|
||||
}
|
||||
}
|
274
video-terminal/font454.h
Normal file
274
video-terminal/font454.h
Normal file
@ -0,0 +1,274 @@
|
||||
|
||||
|
||||
#ifndef FONT454_H
|
||||
#define FONT454_H
|
||||
|
||||
//#include <avr/io.h>
|
||||
//#include <avr/pgmspace.h>
|
||||
|
||||
// generated from https://bitbucket.org/thesheep/font454
|
||||
|
||||
// 4-pixel-wide matrix of 0-3 intensity, packed AABBCCDD
|
||||
static const unsigned char font454[] PROGMEM = {
|
||||
// default ASCII character set
|
||||
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, 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,
|
||||
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, 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,
|
||||
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, 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,
|
||||
0x30, 0x30, 0x20, 0x00, 0x30, 0x00,
|
||||
0xcc, 0x88, 0x00, 0x00, 0x00, 0x00,
|
||||
0x88, 0xfc, 0x88, 0xfc, 0x88, 0x00,
|
||||
0x20, 0xb8, 0xf0, 0x3c, 0xb8, 0x00,
|
||||
0xcc, 0x08, 0x30, 0x80, 0xcc, 0x00,
|
||||
0xb0, 0xcc, 0x34, 0xcc, 0xbc, 0x00,
|
||||
0x30, 0x20, 0x00, 0x00, 0x00, 0x00,
|
||||
0x70, 0x80, 0xc0, 0x80, 0x70, 0x00,
|
||||
0x34, 0x08, 0x0c, 0x08, 0x34, 0x00,
|
||||
0x00, 0x98, 0x74, 0x98, 0x00, 0x00,
|
||||
0x00, 0x30, 0xfc, 0x30, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x30, 0x80, 0x00,
|
||||
0x00, 0x00, 0xfc, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x30, 0x00,
|
||||
0x0c, 0x18, 0x30, 0x90, 0xc0, 0x00,
|
||||
0x78, 0xcc, 0xcc, 0xcc, 0xb4, 0x00,
|
||||
0x30, 0xb0, 0x30, 0x30, 0x30, 0x00,
|
||||
0xe4, 0x0c, 0x74, 0x80, 0xfc, 0x00,
|
||||
0xb8, 0x0c, 0x34, 0x0c, 0xb8, 0x00,
|
||||
0x30, 0x90, 0xcc, 0xfc, 0x0c, 0x00,
|
||||
0xfc, 0xc0, 0xf8, 0x0c, 0xf8, 0x00,
|
||||
0x78, 0xc0, 0xb8, 0xcc, 0xb8, 0x00,
|
||||
0xfc, 0x08, 0x30, 0x80, 0xc0, 0x00,
|
||||
0xb8, 0xcc, 0x74, 0xcc, 0xb8, 0x00,
|
||||
0xb8, 0xcc, 0xbc, 0x0c, 0xb4, 0x00,
|
||||
0x00, 0x30, 0x00, 0x30, 0x00, 0x00,
|
||||
0x00, 0x30, 0x00, 0x30, 0x80, 0x00,
|
||||
0x0c, 0x30, 0xc0, 0x30, 0x0c, 0x00,
|
||||
0x00, 0xfc, 0x00, 0xfc, 0x00, 0x00,
|
||||
0xc0, 0x30, 0x0c, 0x30, 0xc0, 0x00,
|
||||
0xb4, 0x0c, 0x34, 0x00, 0x30, 0x00,
|
||||
0x78, 0x8c, 0xcc, 0x80, 0x6c, 0x00,
|
||||
0x74, 0x88, 0xcc, 0xec, 0xcc, 0x00,
|
||||
0xf4, 0xcc, 0xf4, 0xcc, 0xf4, 0x00,
|
||||
0x7c, 0x80, 0xc0, 0x80, 0x7c, 0x00,
|
||||
0xe4, 0xc8, 0xcc, 0xc8, 0xe4, 0x00,
|
||||
0xfc, 0xc0, 0xf0, 0xc0, 0xfc, 0x00,
|
||||
0xfc, 0xc0, 0xf0, 0xc0, 0xc0, 0x00,
|
||||
0x7c, 0x80, 0xc0, 0x8c, 0x7c, 0x00,
|
||||
0xcc, 0xcc, 0xfc, 0xcc, 0xcc, 0x00,
|
||||
0x30, 0x30, 0x30, 0x30, 0x30, 0x00,
|
||||
0x0c, 0x0c, 0x0c, 0xcc, 0xb8, 0x00,
|
||||
0xcc, 0xc8, 0xe0, 0xc8, 0xcc, 0x00,
|
||||
0xc0, 0xc0, 0xc0, 0xc0, 0xfc, 0x00,
|
||||
0x88, 0xec, 0xfc, 0xdc, 0xcc, 0x00,
|
||||
0x8c, 0xdc, 0xec, 0xdc, 0xc8, 0x00,
|
||||
0x74, 0x88, 0xcc, 0x88, 0x74, 0x00,
|
||||
0xe4, 0xcc, 0xcc, 0xe4, 0xc0, 0x00,
|
||||
0x74, 0x88, 0xcc, 0x8c, 0x7c, 0x00,
|
||||
0xe4, 0xcc, 0xcc, 0xe4, 0xcc, 0x00,
|
||||
0xb8, 0xc0, 0xb8, 0x0c, 0xb8, 0x00,
|
||||
0xfc, 0x30, 0x30, 0x30, 0x30, 0x00,
|
||||
0xcc, 0xcc, 0xcc, 0x8c, 0x68, 0x00,
|
||||
0xcc, 0xcc, 0xcc, 0x98, 0x30, 0x00,
|
||||
0xcc, 0xcc, 0xdc, 0xfc, 0xec, 0x00,
|
||||
0xcc, 0x88, 0x20, 0x88, 0xcc, 0x00,
|
||||
0xcc, 0x88, 0x64, 0x30, 0x30, 0x00,
|
||||
0xfc, 0x08, 0x30, 0x80, 0xfc, 0x00,
|
||||
0xf0, 0xc0, 0xc0, 0xc0, 0xf0, 0x00,
|
||||
0xc0, 0x90, 0x30, 0x18, 0x0c, 0x00,
|
||||
0x3c, 0x0c, 0x0c, 0x0c, 0x3c, 0x00,
|
||||
0x30, 0xdc, 0x88, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xfc,
|
||||
0xc0, 0x20, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x6c, 0x8c, 0x8c, 0x6c, 0x00,
|
||||
0xc0, 0xe4, 0xc8, 0xc8, 0xe4, 0x00,
|
||||
0x00, 0x6c, 0x80, 0x80, 0x6c, 0x00,
|
||||
0x0c, 0x6c, 0x8c, 0x8c, 0x6c, 0x00,
|
||||
0x00, 0x38, 0x8c, 0xe0, 0x7c, 0x00,
|
||||
0x1c, 0x30, 0xb8, 0x30, 0x30, 0x30,
|
||||
0x00, 0xe4, 0x88, 0x6c, 0x08, 0xe4,
|
||||
0xc0, 0xe4, 0xc8, 0xcc, 0xcc, 0x00,
|
||||
0x30, 0x00, 0x70, 0x30, 0x2c, 0x00,
|
||||
0x0c, 0x00, 0x1c, 0x0c, 0xcc, 0xb8,
|
||||
0xc0, 0xcc, 0xe0, 0xc8, 0xcc, 0x00,
|
||||
0x70, 0x30, 0x30, 0x20, 0x18, 0x00,
|
||||
0x00, 0xf4, 0xfc, 0xec, 0xcc, 0x00,
|
||||
0x00, 0xe4, 0xc8, 0xcc, 0xcc, 0x00,
|
||||
0x00, 0x74, 0x88, 0x88, 0x74, 0x00,
|
||||
0x00, 0xe4, 0xc8, 0xc8, 0xe4, 0xc0,
|
||||
0x00, 0x64, 0x88, 0x8c, 0x6c, 0x0c,
|
||||
0x00, 0x8c, 0xe0, 0xc0, 0xc0, 0x00,
|
||||
0x00, 0x6c, 0x90, 0x18, 0xf4, 0x00,
|
||||
0x30, 0xb8, 0x30, 0x20, 0x18, 0x00,
|
||||
0x00, 0xcc, 0xcc, 0x8c, 0x78, 0x00,
|
||||
0x00, 0xcc, 0xcc, 0x98, 0x20, 0x00,
|
||||
0x00, 0xcc, 0xdc, 0xfc, 0xec, 0x00,
|
||||
0x00, 0xcc, 0x64, 0x64, 0xcc, 0x00,
|
||||
0x00, 0xcc, 0xcc, 0x78, 0x08, 0xa4,
|
||||
0x00, 0xfc, 0x18, 0x90, 0xfc, 0x00,
|
||||
0x2c, 0x30, 0xd0, 0x30, 0x2c, 0x00,
|
||||
0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||
0xe0, 0x30, 0x1c, 0x30, 0xe0, 0x00,
|
||||
0xb0, 0xc0, 0x74, 0x0c, 0x38, 0x00,
|
||||
0xec, 0x88, 0x88, 0x88, 0xec, 0x00,
|
||||
|
||||
// line-drawing character set
|
||||
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, 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,
|
||||
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, 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,
|
||||
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, 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,
|
||||
0x30, 0x30, 0x20, 0x00, 0x30, 0x00,
|
||||
0xcc, 0x88, 0x00, 0x00, 0x00, 0x00,
|
||||
0x88, 0xfc, 0x88, 0xfc, 0x88, 0x00,
|
||||
0x20, 0xb8, 0xf0, 0x3c, 0xb8, 0x00,
|
||||
0xcc, 0x08, 0x30, 0x80, 0xcc, 0x00,
|
||||
0xb0, 0xcc, 0x34, 0xcc, 0xbc, 0x00,
|
||||
0x30, 0x20, 0x00, 0x00, 0x00, 0x00,
|
||||
0x70, 0x80, 0xc0, 0x80, 0x70, 0x00,
|
||||
0x34, 0x08, 0x0c, 0x08, 0x34, 0x00,
|
||||
0x00, 0x98, 0x74, 0x98, 0x00, 0x00,
|
||||
0x00, 0x30, 0xfc, 0x30, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x30, 0x80, 0x00,
|
||||
0x00, 0x00, 0xfc, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x30, 0x00,
|
||||
0x0c, 0x18, 0x30, 0x90, 0xc0, 0x00,
|
||||
0x78, 0xcc, 0xcc, 0xcc, 0xb4, 0x00,
|
||||
0x30, 0xb0, 0x30, 0x30, 0x30, 0x00,
|
||||
0xe4, 0x0c, 0x74, 0x80, 0xfc, 0x00,
|
||||
0xb8, 0x0c, 0x34, 0x0c, 0xb8, 0x00,
|
||||
0x30, 0x90, 0xcc, 0xfc, 0x0c, 0x00,
|
||||
0xfc, 0xc0, 0xf8, 0x0c, 0xf8, 0x00,
|
||||
0x78, 0xc0, 0xb8, 0xcc, 0xb8, 0x00,
|
||||
0xfc, 0x08, 0x30, 0x80, 0xc0, 0x00,
|
||||
0xb8, 0xcc, 0x74, 0xcc, 0xb8, 0x00,
|
||||
0xb8, 0xcc, 0xbc, 0x0c, 0xb4, 0x00,
|
||||
0x00, 0x30, 0x00, 0x30, 0x00, 0x00,
|
||||
0x00, 0x30, 0x00, 0x30, 0x80, 0x00,
|
||||
0x0c, 0x30, 0xc0, 0x30, 0x0c, 0x00,
|
||||
0x00, 0xfc, 0x00, 0xfc, 0x00, 0x00,
|
||||
0xc0, 0x30, 0x0c, 0x30, 0xc0, 0x00,
|
||||
0xb4, 0x0c, 0x34, 0x00, 0x30, 0x00,
|
||||
0x78, 0x8c, 0xcc, 0x80, 0x6c, 0x00,
|
||||
0x74, 0x88, 0xcc, 0xec, 0xcc, 0x00,
|
||||
0xf4, 0xcc, 0xf4, 0xcc, 0xf4, 0x00,
|
||||
0x7c, 0x80, 0xc0, 0x80, 0x7c, 0x00,
|
||||
0xe4, 0xc8, 0xcc, 0xc8, 0xe4, 0x00,
|
||||
0xfc, 0xc0, 0xf0, 0xc0, 0xfc, 0x00,
|
||||
0xfc, 0xc0, 0xf0, 0xc0, 0xc0, 0x00,
|
||||
0x7c, 0x80, 0xc0, 0x8c, 0x7c, 0x00,
|
||||
0xcc, 0xcc, 0xfc, 0xcc, 0xcc, 0x00,
|
||||
0x30, 0x30, 0x30, 0x30, 0x30, 0x00,
|
||||
0x0c, 0x0c, 0x0c, 0xcc, 0xb8, 0x00,
|
||||
0xcc, 0xc8, 0xe0, 0xc8, 0xcc, 0x00,
|
||||
0xc0, 0xc0, 0xc0, 0xc0, 0xfc, 0x00,
|
||||
0x88, 0xec, 0xfc, 0xdc, 0xcc, 0x00,
|
||||
0x8c, 0xdc, 0xec, 0xdc, 0xc8, 0x00,
|
||||
0x74, 0x88, 0xcc, 0x88, 0x74, 0x00,
|
||||
0xe4, 0xcc, 0xcc, 0xe4, 0xc0, 0x00,
|
||||
0x74, 0x88, 0xcc, 0x8c, 0x7c, 0x00,
|
||||
0xe4, 0xcc, 0xcc, 0xe4, 0xcc, 0x00,
|
||||
0xb8, 0xc0, 0xb8, 0x0c, 0xb8, 0x00,
|
||||
0xfc, 0x30, 0x30, 0x30, 0x30, 0x00,
|
||||
0xcc, 0xcc, 0xcc, 0x8c, 0x68, 0x00,
|
||||
0xcc, 0xcc, 0xcc, 0x98, 0x30, 0x00,
|
||||
0xcc, 0xcc, 0xdc, 0xfc, 0xec, 0x00,
|
||||
0xcc, 0x88, 0x20, 0x88, 0xcc, 0x00,
|
||||
0xcc, 0x88, 0x64, 0x30, 0x30, 0x00,
|
||||
0xfc, 0x08, 0x30, 0x80, 0xfc, 0x00,
|
||||
0xf0, 0xc0, 0xc0, 0xc0, 0xf0, 0x00,
|
||||
0xc0, 0x90, 0x30, 0x18, 0x0c, 0x00,
|
||||
0x3c, 0x0c, 0x0c, 0x0c, 0x3c, 0x00,
|
||||
0x30, 0xdc, 0x88, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xfc,
|
||||
0xc0, 0x20, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x6c, 0x8c, 0x8c, 0x6c, 0x00,
|
||||
0xc0, 0xe4, 0xc8, 0xc8, 0xe4, 0x00,
|
||||
0x00, 0x6c, 0x80, 0x80, 0x6c, 0x00,
|
||||
0x0c, 0x6c, 0x8c, 0x8c, 0x6c, 0x00,
|
||||
0x00, 0x38, 0x8c, 0xe0, 0x7c, 0x00,
|
||||
0x1c, 0x30, 0xb8, 0x30, 0x30, 0x30,
|
||||
0x00, 0xe4, 0x88, 0x6c, 0x08, 0xe4,
|
||||
0xc0, 0xe4, 0xc8, 0xcc, 0xcc, 0x00,
|
||||
0x30, 0x00, 0x70, 0x30, 0x2c, 0x00,
|
||||
0x30, 0x30, 0xf0, 0x00, 0x00, 0x00, // 0x6A j
|
||||
0x00, 0x00, 0xf0, 0x30, 0x30, 0x30, // 0x6B k
|
||||
0x00, 0x00, 0x3f, 0x30, 0x30, 0x30, // 0x6C l
|
||||
0x30, 0x30, 0x3f, 0x00, 0x00, 0x00, // 0x6D m
|
||||
0x30, 0x30, 0xff, 0x30, 0x30, 0x30, // 0x6E n
|
||||
0x00, 0x74, 0x88, 0x88, 0x74, 0x00,
|
||||
0x00, 0xe4, 0xc8, 0xc8, 0xe4, 0xc0,
|
||||
0x00, 0x00, 0xff, 0x00, 0x00, 0x00, // 0x71 q
|
||||
0x00, 0x8c, 0xe0, 0xc0, 0xc0, 0x00,
|
||||
0x00, 0x6c, 0x90, 0x18, 0xf4, 0x00,
|
||||
0x30, 0x30, 0x3f, 0x30, 0x30, 0x30, // 0x74 t
|
||||
0x30, 0x30, 0xf0, 0x30, 0x30, 0x30, // 0x75 u
|
||||
0x30, 0x30, 0xff, 0x00, 0x00, 0x00, // 0x76 v
|
||||
0x00, 0x00, 0xff, 0x30, 0x30, 0x30, // 0x77 w
|
||||
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, // 0x78 x
|
||||
0x00, 0xcc, 0xcc, 0x78, 0x08, 0xa4,
|
||||
0x00, 0xfc, 0x18, 0x90, 0xfc, 0x00,
|
||||
0x2c, 0x30, 0xd0, 0x30, 0x2c, 0x00,
|
||||
0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||
0xe0, 0x30, 0x1c, 0x30, 0xe0, 0x00,
|
||||
0xb0, 0xc0, 0x74, 0x0c, 0x38, 0x00,
|
||||
0xec, 0x88, 0x88, 0x88, 0xec, 0x00
|
||||
};
|
||||
|
||||
#endif
|
758
video-terminal/tintty.cpp
Normal file
758
video-terminal/tintty.cpp
Normal file
@ -0,0 +1,758 @@
|
||||
#define TFT_BLACK 0x0000
|
||||
#define TFT_BLUE 0x0014
|
||||
#define TFT_RED 0xA000
|
||||
#define TFT_GREEN 0x0500
|
||||
#define TFT_CYAN 0x0514
|
||||
#define TFT_MAGENTA 0xA014
|
||||
#define TFT_YELLOW 0xA500
|
||||
#define TFT_WHITE 0xA514
|
||||
|
||||
#define TFT_BOLD_BLACK 0x8410
|
||||
#define TFT_BOLD_BLUE 0x001F
|
||||
#define TFT_BOLD_RED 0xF800
|
||||
#define TFT_BOLD_GREEN 0x07E0
|
||||
#define TFT_BOLD_CYAN 0x07FF
|
||||
#define TFT_BOLD_MAGENTA 0xF81F
|
||||
#define TFT_BOLD_YELLOW 0xFFE0
|
||||
#define TFT_BOLD_WHITE 0xFFFF
|
||||
|
||||
#include "tintty.h"
|
||||
#include "font454.h"
|
||||
|
||||
// exported variable for input logic
|
||||
// @todo refactor
|
||||
bool tintty_cursor_key_mode_application;
|
||||
|
||||
const uint16_t ANSI_COLORS[] = {
|
||||
TFT_BLACK,
|
||||
TFT_RED,
|
||||
TFT_GREEN,
|
||||
TFT_YELLOW,
|
||||
TFT_BLUE,
|
||||
TFT_MAGENTA,
|
||||
TFT_CYAN,
|
||||
TFT_WHITE
|
||||
};
|
||||
|
||||
const uint16_t ANSI_BOLD_COLORS[] = {
|
||||
TFT_BOLD_BLACK,
|
||||
TFT_BOLD_RED,
|
||||
TFT_BOLD_GREEN,
|
||||
TFT_BOLD_YELLOW,
|
||||
TFT_BOLD_BLUE,
|
||||
TFT_BOLD_MAGENTA,
|
||||
TFT_BOLD_CYAN,
|
||||
TFT_BOLD_WHITE
|
||||
};
|
||||
|
||||
// cursor animation
|
||||
const int16_t IDLE_CYCLE_MAX = 500;
|
||||
const int16_t IDLE_CYCLE_ON = (IDLE_CYCLE_MAX/2);
|
||||
|
||||
const int16_t TAB_SIZE = 4;
|
||||
|
||||
// cursor and character position is in global buffer coordinate space (may exceed screen height)
|
||||
struct tintty_state {
|
||||
// @todo consider storing cursor position as single int offset
|
||||
int16_t cursor_col, cursor_row;
|
||||
uint16_t bg_ansi_color, fg_ansi_color;
|
||||
bool bold;
|
||||
|
||||
// cursor mode
|
||||
bool cursor_key_mode_application;
|
||||
|
||||
// saved DEC cursor info (in screen coords)
|
||||
int16_t dec_saved_col, dec_saved_row, dec_saved_bg, dec_saved_fg;
|
||||
uint8_t dec_saved_g4bank;
|
||||
bool dec_saved_bold, dec_saved_no_wrap;
|
||||
|
||||
// @todo deal with integer overflow
|
||||
int16_t top_row; // first displayed row in a logical scrollback buffer
|
||||
bool no_wrap;
|
||||
bool cursor_hidden;
|
||||
|
||||
char out_char;
|
||||
int16_t out_char_col, out_char_row;
|
||||
uint8_t out_char_g4bank; // current set shift state, G0 to G3
|
||||
int16_t out_clear_before, out_clear_after;
|
||||
|
||||
uint8_t g4bank_char_set[4];
|
||||
|
||||
int16_t idle_cycle_count; // @todo track during blocking reads mid-command
|
||||
} state;
|
||||
|
||||
struct tintty_rendered {
|
||||
int16_t cursor_col, cursor_row;
|
||||
int16_t top_row;
|
||||
} rendered;
|
||||
|
||||
// @todo support negative cursor_row
|
||||
void _render(tintty_display *display) {
|
||||
// expose the cursor key mode state
|
||||
tintty_cursor_key_mode_application = state.cursor_key_mode_application;
|
||||
|
||||
// if scrolling, prepare the "recycled" screen area
|
||||
if (state.top_row != rendered.top_row) {
|
||||
// clear the new piece of screen to be recycled as blank space
|
||||
// @todo handle scroll-up
|
||||
if (state.top_row > rendered.top_row) {
|
||||
// pre-clear the lines at the bottom
|
||||
// @todo always use black instead of current background colour?
|
||||
// @todo deal with overflow from multiplication by CHAR_HEIGHT
|
||||
/*int16_t old_bottom_y = rendered.top_row * FONT_HEIGHT + display->screen_row_count * FONT_HEIGHT; // bottom of text may not align with screen height
|
||||
int16_t new_bottom_y = state.top_row * FONT_HEIGHT + display->screen_height; // extend to bottom edge of new displayed area
|
||||
int16_t clear_sbuf_bottom = new_bottom_y % display->screen_height;
|
||||
int16_t clear_height = min((int)display->screen_height, new_bottom_y - old_bottom_y);
|
||||
int16_t clear_sbuf_top = clear_sbuf_bottom - clear_height;*/
|
||||
|
||||
// if rectangle straddles the screen buffer top edge, render that slice at bottom edge
|
||||
/*if (clear_sbuf_top < 0) {
|
||||
display->fill_rect(
|
||||
0,
|
||||
clear_sbuf_top + display->screen_height,
|
||||
display->screen_width,
|
||||
-clear_sbuf_top,
|
||||
ANSI_COLORS[state.bg_ansi_color]
|
||||
);
|
||||
}*/
|
||||
|
||||
// if rectangle is not entirely above top edge, render the normal slice
|
||||
/*if (clear_sbuf_bottom > 0) {
|
||||
display->fill_rect(
|
||||
0,
|
||||
max(0, (int)clear_sbuf_top),
|
||||
display->screen_width,
|
||||
clear_sbuf_bottom - max(0, (int)clear_sbuf_top),
|
||||
ANSI_COLORS[state.bg_ansi_color]
|
||||
);
|
||||
}*/
|
||||
}
|
||||
|
||||
// update displayed scroll
|
||||
display->set_vscroll((state.top_row) % display->screen_row_count); // @todo deal with overflow from multiplication
|
||||
|
||||
// save rendered state
|
||||
rendered.top_row = state.top_row;
|
||||
}
|
||||
|
||||
// render character if needed
|
||||
if (state.out_char != 0) {
|
||||
const uint16_t fg_tft_color = state.bold ? ANSI_BOLD_COLORS[state.fg_ansi_color] : ANSI_COLORS[state.fg_ansi_color];
|
||||
const uint16_t bg_tft_color = ANSI_COLORS[state.bg_ansi_color];
|
||||
const uint8_t char_set = state.g4bank_char_set[state.out_char_g4bank & 0x03]; // ensure 0-3 value
|
||||
|
||||
display->print_character(state.out_char_col, state.out_char_row, fg_tft_color, bg_tft_color, state.out_char);
|
||||
|
||||
// clear for next render
|
||||
state.out_char = 0;
|
||||
state.out_clear_before = 0;
|
||||
state.out_clear_after = 0;
|
||||
|
||||
// the char draw may overpaint the cursor, in which case
|
||||
// mark it for repaint
|
||||
if (
|
||||
rendered.cursor_col == state.out_char_col &&
|
||||
rendered.cursor_row == state.out_char_row
|
||||
) {
|
||||
display->print_cursor(rendered.cursor_col, rendered.cursor_row, ANSI_COLORS[state.bg_ansi_color]);
|
||||
rendered.cursor_col = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// reflect new cursor bar render state
|
||||
const bool cursor_bar_shown = (
|
||||
!state.cursor_hidden &&
|
||||
state.idle_cycle_count < IDLE_CYCLE_ON
|
||||
);
|
||||
|
||||
// clear existing rendered cursor bar if needed
|
||||
// @todo detect if it is already cleared during scroll
|
||||
if (rendered.cursor_col >= 0) {
|
||||
if (
|
||||
!cursor_bar_shown ||
|
||||
rendered.cursor_col != state.cursor_col ||
|
||||
rendered.cursor_row != state.cursor_row
|
||||
) {
|
||||
display->print_cursor(rendered.cursor_col, rendered.cursor_row, ANSI_COLORS[state.bg_ansi_color]);
|
||||
|
||||
// record the fact that cursor bar is not on screen
|
||||
rendered.cursor_col = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// render new cursor bar if not already shown
|
||||
// (sometimes right after clearing existing bar)
|
||||
if (rendered.cursor_col < 0) {
|
||||
if (cursor_bar_shown) {
|
||||
display->print_cursor(rendered.cursor_col, rendered.cursor_row, ANSI_COLORS[state.bg_ansi_color]);
|
||||
display->print_cursor(state.cursor_col, state.cursor_row, state.bold ? ANSI_BOLD_COLORS[state.fg_ansi_color] : ANSI_COLORS[state.fg_ansi_color]);
|
||||
|
||||
// save new rendered state
|
||||
rendered.cursor_col = state.cursor_col;
|
||||
rendered.cursor_row = state.cursor_row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void bell() {
|
||||
// TODO
|
||||
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/ledc.html
|
||||
// https://github.com/lbernstone/Tone32
|
||||
}
|
||||
|
||||
void _ensure_cursor_vscroll(tintty_display *display) {
|
||||
// move displayed window down to cover cursor
|
||||
// @todo support scrolling up as well
|
||||
if (state.cursor_row - state.top_row >= display->screen_row_count) {
|
||||
state.top_row = state.cursor_row - display->screen_row_count + 1;
|
||||
}
|
||||
}
|
||||
|
||||
void _send_sequence(
|
||||
void (*send_char)(char ch),
|
||||
char* str
|
||||
) {
|
||||
// send zero-terminated sequence character by character
|
||||
while (*str) {
|
||||
send_char(*str);
|
||||
str += 1;
|
||||
}
|
||||
}
|
||||
|
||||
char _read_decimal(
|
||||
char (*peek_char)(),
|
||||
char (*read_char)()
|
||||
) {
|
||||
uint16_t accumulator = 0;
|
||||
|
||||
while (isdigit(peek_char())) {
|
||||
const char digit_character = read_char();
|
||||
const uint16_t digit = digit_character - '0';
|
||||
accumulator = accumulator * 10 + digit;
|
||||
}
|
||||
|
||||
return accumulator;
|
||||
}
|
||||
|
||||
void _apply_graphic_rendition(
|
||||
uint16_t* arg_list,
|
||||
uint16_t arg_count
|
||||
) {
|
||||
if (arg_count == 0) {
|
||||
// special case for resetting to default style
|
||||
state.bg_ansi_color = 0;
|
||||
state.fg_ansi_color = 7;
|
||||
state.bold = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// process commands
|
||||
// @todo support bold/etc for better colour support
|
||||
// @todo 39/49?
|
||||
for (uint16_t arg_index = 0; arg_index < arg_count; arg_index += 1) {
|
||||
const uint16_t arg_value = arg_list[arg_index];
|
||||
|
||||
if (arg_value == 0) {
|
||||
// reset to default style
|
||||
state.bg_ansi_color = 0;
|
||||
state.fg_ansi_color = 7;
|
||||
state.bold = false;
|
||||
} else if (arg_value == 1) {
|
||||
// bold
|
||||
state.bold = true;
|
||||
} else if (arg_value >= 30 && arg_value <= 37) {
|
||||
// foreground ANSI colour
|
||||
state.fg_ansi_color = arg_value - 30;
|
||||
} else if (arg_value >= 40 && arg_value <= 47) {
|
||||
// background ANSI colour
|
||||
state.bg_ansi_color = arg_value - 40;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _apply_mode_setting(
|
||||
bool mode_on,
|
||||
uint16_t* arg_list,
|
||||
uint16_t arg_count
|
||||
) {
|
||||
// process modes
|
||||
for (uint16_t arg_index = 0; arg_index < arg_count; arg_index += 1) {
|
||||
const uint16_t mode_id = arg_list[arg_index];
|
||||
|
||||
switch (mode_id) {
|
||||
case 4:
|
||||
// insert/replace mode
|
||||
// @todo this should be off for most practical purposes anyway?
|
||||
// ... otherwise visually shifting line text is expensive
|
||||
break;
|
||||
|
||||
case 20:
|
||||
// auto-LF
|
||||
// ignoring per http://vt100.net/docs/vt220-rm/chapter4.html section 4.6.6
|
||||
break;
|
||||
|
||||
case 34:
|
||||
// cursor visibility
|
||||
state.cursor_hidden = !mode_on;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _exec_escape_question_command(
|
||||
char (*peek_char)(),
|
||||
char (*read_char)(),
|
||||
void (*send_char)(char ch)
|
||||
) {
|
||||
// @todo support multiple mode commands
|
||||
// per http://vt100.net/docs/vt220-rm/chapter4.html section 4.6.1,
|
||||
// ANSI and DEC modes cannot mix; that is, '[?25;20;?7l' is not a valid Esc-command
|
||||
// (noting this because https://www.gnu.org/software/screen/manual/html_node/Control-Sequences.html
|
||||
// makes it look like the question mark is a prefix)
|
||||
const uint16_t mode = _read_decimal(peek_char, read_char);
|
||||
const bool mode_on = (read_char() != 'l');
|
||||
|
||||
switch (mode) {
|
||||
case 1:
|
||||
// cursor key mode (normal/application)
|
||||
state.cursor_key_mode_application = mode_on;
|
||||
break;
|
||||
|
||||
case 7:
|
||||
// auto wrap mode
|
||||
state.no_wrap = !mode_on;
|
||||
break;
|
||||
|
||||
case 25:
|
||||
// cursor visibility
|
||||
state.cursor_hidden = !mode_on;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// @todo cursor position report
|
||||
void _exec_escape_bracket_command_with_args(
|
||||
char (*peek_char)(),
|
||||
char (*read_char)(),
|
||||
void (*send_char)(char ch),
|
||||
tintty_display *display,
|
||||
uint16_t* arg_list,
|
||||
uint16_t arg_count
|
||||
) {
|
||||
// convenient arg getter
|
||||
#define ARG(index, default_value) (arg_count > index ? arg_list[index] : default_value)
|
||||
|
||||
// process next character after Escape-code, bracket and any numeric arguments
|
||||
const char command_character = read_char();
|
||||
|
||||
switch (command_character) {
|
||||
case '?':
|
||||
// question-mark commands
|
||||
_exec_escape_question_command(peek_char, read_char, send_char);
|
||||
break;
|
||||
|
||||
case 'A':
|
||||
// cursor up (no scroll)
|
||||
state.cursor_row = max((int)state.top_row, state.cursor_row - ARG(0, 1));
|
||||
break;
|
||||
|
||||
case 'B':
|
||||
// cursor down (no scroll)
|
||||
state.cursor_row = min(state.top_row + display->screen_row_count - 1, state.cursor_row + ARG(0, 1));
|
||||
break;
|
||||
|
||||
case 'C':
|
||||
// cursor right (no scroll)
|
||||
state.cursor_col = min(display->screen_col_count - 1, state.cursor_col + ARG(0, 1));
|
||||
break;
|
||||
|
||||
case 'D':
|
||||
// cursor left (no scroll)
|
||||
state.cursor_col = max(0, state.cursor_col - ARG(0, 1));
|
||||
break;
|
||||
|
||||
case 'H':
|
||||
case 'f':
|
||||
// Direct Cursor Addressing (row;col)
|
||||
state.cursor_col = max(0, min(display->screen_col_count - 1, ARG(1, 1) - 1));
|
||||
state.cursor_row = state.top_row + max(0, min(display->screen_row_count - 1, ARG(0, 1) - 1));
|
||||
break;
|
||||
|
||||
case 'J':
|
||||
// clear screen
|
||||
state.out_char = ' ';
|
||||
state.out_char_col = state.cursor_col;
|
||||
state.out_char_row = state.cursor_row;
|
||||
|
||||
{
|
||||
const int16_t rel_row = state.cursor_row - state.top_row;
|
||||
|
||||
state.out_clear_before = ARG(0, 0) != 0
|
||||
? rel_row * display->screen_col_count + state.cursor_col
|
||||
: 0;
|
||||
state.out_clear_after = ARG(0, 0) != 1
|
||||
? (display->screen_row_count - 1 - rel_row) * display->screen_col_count + (display->screen_col_count - 1 - state.cursor_col)
|
||||
: 0;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'K':
|
||||
// clear line
|
||||
state.out_char = ' ';
|
||||
state.out_char_col = state.cursor_col;
|
||||
state.out_char_row = state.cursor_row;
|
||||
|
||||
state.out_clear_before = ARG(0, 0) != 0
|
||||
? state.cursor_col
|
||||
: 0;
|
||||
state.out_clear_after = ARG(0, 0) != 1
|
||||
? display->screen_col_count - 1 - state.cursor_col
|
||||
: 0;
|
||||
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
// graphic rendition mode
|
||||
_apply_graphic_rendition(arg_list, arg_count);
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
// set mode
|
||||
_apply_mode_setting(true, arg_list, arg_count);
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
// unset mode
|
||||
_apply_mode_setting(false, arg_list, arg_count);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _exec_escape_bracket_command(
|
||||
char (*peek_char)(),
|
||||
char (*read_char)(),
|
||||
void (*send_char)(char ch),
|
||||
tintty_display *display
|
||||
) {
|
||||
const uint16_t MAX_COMMAND_ARG_COUNT = 10;
|
||||
uint16_t arg_list[MAX_COMMAND_ARG_COUNT];
|
||||
uint16_t arg_count = 0;
|
||||
|
||||
// start parsing arguments if any
|
||||
// (this means that '' is treated as no arguments, but '0;' is treated as two arguments, each being zero)
|
||||
// @todo ignore trailing semi-colon instead of treating it as marking an extra zero arg?
|
||||
if (isdigit(peek_char())) {
|
||||
// keep consuming arguments while we have space
|
||||
while (arg_count < MAX_COMMAND_ARG_COUNT) {
|
||||
// consume decimal number
|
||||
arg_list[arg_count] = _read_decimal(peek_char, read_char);
|
||||
arg_count += 1;
|
||||
|
||||
// stop processing if next char is not separator
|
||||
if (peek_char() != ';') {
|
||||
break;
|
||||
}
|
||||
|
||||
// consume separator before starting next argument
|
||||
read_char();
|
||||
}
|
||||
}
|
||||
|
||||
_exec_escape_bracket_command_with_args(
|
||||
peek_char,
|
||||
read_char,
|
||||
send_char,
|
||||
display,
|
||||
arg_list,
|
||||
arg_count
|
||||
);
|
||||
}
|
||||
|
||||
// set the characters displayed for given G0-G3 bank
|
||||
void _exec_character_set(
|
||||
uint8_t g4bank_index,
|
||||
char (*read_char)()
|
||||
) {
|
||||
switch (read_char()) {
|
||||
case 'A':
|
||||
case 'B':
|
||||
// normal character set (UK/US)
|
||||
state.g4bank_char_set[g4bank_index] = 0;
|
||||
break;
|
||||
|
||||
case '0':
|
||||
// line-drawing
|
||||
state.g4bank_char_set[g4bank_index] = 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
// alternate sets are unsupported
|
||||
state.g4bank_char_set[g4bank_index] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// @todo terminal reset
|
||||
// @todo parse modes with arguments even if they are no-op
|
||||
void _exec_escape_code(
|
||||
char (*peek_char)(),
|
||||
char (*read_char)(),
|
||||
void (*send_char)(char ch),
|
||||
tintty_display *display
|
||||
) {
|
||||
// read next character after Escape-code
|
||||
// @todo time out?
|
||||
char esc_character = read_char();
|
||||
|
||||
// @todo support for (, ), #, c, cursor save/restore
|
||||
switch (esc_character) {
|
||||
case '[':
|
||||
_exec_escape_bracket_command(peek_char, read_char, send_char, display);
|
||||
break;
|
||||
|
||||
case 'D':
|
||||
// index (move down and possibly scroll)
|
||||
state.cursor_row += 1;
|
||||
_ensure_cursor_vscroll(display);
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
// reverse index (move up and possibly scroll)
|
||||
state.cursor_row -= 1;
|
||||
_ensure_cursor_vscroll(display);
|
||||
break;
|
||||
|
||||
case 'E':
|
||||
// next line
|
||||
state.cursor_row += 1;
|
||||
state.cursor_col = 0;
|
||||
_ensure_cursor_vscroll(display);
|
||||
break;
|
||||
|
||||
case 'Z':
|
||||
// Identify Terminal (DEC Private)
|
||||
_send_sequence(send_char, "\e[?1;0c"); // DA response: no options
|
||||
break;
|
||||
|
||||
case '7':
|
||||
// save cursor
|
||||
// @todo verify that the screen-relative coordinate approach is valid
|
||||
state.dec_saved_col = state.cursor_col;
|
||||
state.dec_saved_row = state.cursor_row - state.top_row; // relative to top
|
||||
state.dec_saved_bg = state.bg_ansi_color;
|
||||
state.dec_saved_fg = state.fg_ansi_color;
|
||||
state.dec_saved_g4bank = state.out_char_g4bank;
|
||||
state.dec_saved_bold = state.bold;
|
||||
state.dec_saved_no_wrap = state.no_wrap;
|
||||
break;
|
||||
|
||||
case '8':
|
||||
// restore cursor
|
||||
state.cursor_col = state.dec_saved_col;
|
||||
state.cursor_row = state.dec_saved_row + state.top_row; // relative to top
|
||||
state.bg_ansi_color = state.dec_saved_bg;
|
||||
state.fg_ansi_color = state.dec_saved_fg;
|
||||
state.out_char_g4bank = state.dec_saved_g4bank;
|
||||
state.bold = state.dec_saved_bold;
|
||||
state.no_wrap = state.dec_saved_no_wrap;
|
||||
break;
|
||||
|
||||
case '=':
|
||||
case '>':
|
||||
// keypad mode setting - ignoring
|
||||
break;
|
||||
|
||||
case '(':
|
||||
// set G0
|
||||
_exec_character_set(0, read_char);
|
||||
break;
|
||||
|
||||
case ')':
|
||||
// set G1
|
||||
_exec_character_set(1, read_char);
|
||||
break;
|
||||
|
||||
case '*':
|
||||
// set G2
|
||||
_exec_character_set(2, read_char);
|
||||
break;
|
||||
|
||||
case '+':
|
||||
// set G3
|
||||
_exec_character_set(3, read_char);
|
||||
break;
|
||||
|
||||
default:
|
||||
// unrecognized character, silently ignore
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _main(
|
||||
char (*peek_char)(),
|
||||
char (*read_char)(),
|
||||
void (*send_char)(char str),
|
||||
tintty_display *display
|
||||
) {
|
||||
// start in default idle state
|
||||
char initial_character = read_char();
|
||||
|
||||
if (initial_character >= 0x20 && initial_character <= 0x7e) {
|
||||
// output displayable character
|
||||
state.out_char = initial_character;
|
||||
state.out_char_col = state.cursor_col;
|
||||
state.out_char_row = state.cursor_row;
|
||||
|
||||
// update caret
|
||||
state.cursor_col += 1;
|
||||
|
||||
if (state.cursor_col >= display->screen_col_count) {
|
||||
if (state.no_wrap) {
|
||||
state.cursor_col = display->screen_col_count - 1;
|
||||
} else {
|
||||
state.cursor_col = 0;
|
||||
state.cursor_row += 1;
|
||||
_ensure_cursor_vscroll(display);
|
||||
}
|
||||
}
|
||||
|
||||
// reset idle state
|
||||
state.idle_cycle_count = 0;
|
||||
} else {
|
||||
// @todo bell, answer-back (0x05), delete
|
||||
switch (initial_character) {
|
||||
case '\a':
|
||||
// bell
|
||||
bell();
|
||||
break;
|
||||
case '\n':
|
||||
// line-feed
|
||||
state.cursor_row += 1;
|
||||
_ensure_cursor_vscroll(display);
|
||||
break;
|
||||
|
||||
case '\r':
|
||||
// carriage-return
|
||||
state.cursor_col = 0;
|
||||
break;
|
||||
|
||||
case '\b':
|
||||
// backspace
|
||||
state.cursor_col -= 1;
|
||||
|
||||
if (state.cursor_col < 0) {
|
||||
if (state.no_wrap) {
|
||||
state.cursor_col = 0;
|
||||
} else {
|
||||
state.cursor_col = display->screen_col_count - 1;
|
||||
state.cursor_row -= 1;
|
||||
_ensure_cursor_vscroll(display);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case '\t':
|
||||
// tab
|
||||
{
|
||||
// @todo blank out the existing characters? not sure if that is expected
|
||||
const int16_t tab_num = state.cursor_col / TAB_SIZE;
|
||||
state.cursor_col = min(display->screen_col_count - 1, (tab_num + 1) * TAB_SIZE);
|
||||
}
|
||||
break;
|
||||
|
||||
case '\e':
|
||||
// Escape-command
|
||||
_exec_escape_code(peek_char, read_char, send_char, display);
|
||||
break;
|
||||
|
||||
case '\x0f':
|
||||
// Shift-In (use G0)
|
||||
// see also the fun reason why these are called this way:
|
||||
// https://en.wikipedia.org/wiki/Shift_Out_and_Shift_In_characters
|
||||
state.out_char_g4bank = 0;
|
||||
break;
|
||||
|
||||
case '\x0e':
|
||||
// Shift-Out (use G1)
|
||||
state.out_char_g4bank = 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
// nothing, just animate cursor
|
||||
delay(1);
|
||||
state.idle_cycle_count = (state.idle_cycle_count + 1) % IDLE_CYCLE_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
_render(display);
|
||||
}
|
||||
|
||||
void tintty_run(
|
||||
char (*peek_char)(),
|
||||
char (*read_char)(),
|
||||
void (*send_char)(char str),
|
||||
tintty_display *display
|
||||
) {
|
||||
// set up initial state
|
||||
state.cursor_col = 0;
|
||||
state.cursor_row = 0;
|
||||
state.top_row = 0;
|
||||
state.no_wrap = 0;
|
||||
state.cursor_hidden = 0;
|
||||
state.bg_ansi_color = 0;
|
||||
state.fg_ansi_color = 7;
|
||||
state.bold = false;
|
||||
|
||||
state.cursor_key_mode_application = false;
|
||||
|
||||
state.dec_saved_col = 0;
|
||||
state.dec_saved_row = 0;
|
||||
state.dec_saved_bg = state.bg_ansi_color;
|
||||
state.dec_saved_fg = state.fg_ansi_color;
|
||||
state.dec_saved_g4bank = 0;
|
||||
state.dec_saved_bold = state.bold;
|
||||
state.dec_saved_no_wrap = false;
|
||||
|
||||
state.out_char = 0;
|
||||
state.out_char_g4bank = 0;
|
||||
state.g4bank_char_set[0] = 0;
|
||||
state.g4bank_char_set[1] = 0;
|
||||
state.g4bank_char_set[2] = 0;
|
||||
state.g4bank_char_set[3] = 0;
|
||||
|
||||
rendered.cursor_col = -1;
|
||||
rendered.cursor_row = -1;
|
||||
|
||||
// clear screen
|
||||
display->fill_rect(0, 0, display->screen_width, display->screen_height, TFT_BLACK);
|
||||
|
||||
// reset TFT scroll to default
|
||||
display->set_vscroll(0);
|
||||
|
||||
// initial render
|
||||
_render(display);
|
||||
|
||||
// send CR to indicate that the screen is ready
|
||||
// (this works with the agetty --wait-cr option to help wait until Arduino boots)
|
||||
send_char('\r');
|
||||
|
||||
// main read cycle
|
||||
while (1) {
|
||||
_main(peek_char, read_char, send_char, display);
|
||||
}
|
||||
}
|
||||
|
||||
void tintty_idle(
|
||||
tintty_display *display
|
||||
) {
|
||||
delay(1);
|
||||
|
||||
// animate cursor
|
||||
state.idle_cycle_count = (state.idle_cycle_count + 1) % IDLE_CYCLE_MAX;
|
||||
|
||||
// re-render
|
||||
_render(display);
|
||||
}
|
37
video-terminal/tintty.h
Normal file
37
video-terminal/tintty.h
Normal file
@ -0,0 +1,37 @@
|
||||
#include "Arduino.h"
|
||||
|
||||
extern bool tintty_cursor_key_mode_application;
|
||||
|
||||
/**
|
||||
* Renderer callbacks.
|
||||
*/
|
||||
struct tintty_display {
|
||||
int16_t screen_width, screen_height;
|
||||
int16_t screen_col_count, screen_row_count; // width and height divided by char size
|
||||
|
||||
void (*fill_rect)(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color);
|
||||
void (*draw_pixels)(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t *pixels);
|
||||
void (*print_character)(int16_t col, int16_t row, uint16_t fg_color, uint16_t bg_color, char character);
|
||||
void (*print_cursor)(int16_t col, int16_t row, uint16_t color);
|
||||
void (*set_vscroll)(int16_t offset); // scroll offset for entire screen
|
||||
};
|
||||
|
||||
/**
|
||||
* Main entry point.
|
||||
* Peek/read callbacks are expected to block until input is available;
|
||||
* while sketch is waiting for input, it should call the tintty_idle() hook
|
||||
* to allow animating cursor, etc.
|
||||
*/
|
||||
void tintty_run(
|
||||
char (*peek_char)(),
|
||||
char (*read_char)(),
|
||||
void (*send_char)(char str),
|
||||
tintty_display *display
|
||||
);
|
||||
|
||||
/**
|
||||
* Hook to call while e.g. sketch is waiting for input
|
||||
*/
|
||||
void tintty_idle(
|
||||
tintty_display *display
|
||||
);
|
153
video-terminal/video-terminal.ino
Normal file
153
video-terminal/video-terminal.ino
Normal file
@ -0,0 +1,153 @@
|
||||
/**
|
||||
* Video Terminal
|
||||
* VT100 emulated by ESP32 + TV display + LK201 keyboard
|
||||
*
|
||||
* TinTTY main sketch
|
||||
* by Nick Matantsev 2017
|
||||
*
|
||||
* Original reference: VT100 emulation code written by Martin K. Schroeder
|
||||
* and modified by Peter Scargill.
|
||||
*/
|
||||
|
||||
#include <SPI.h>
|
||||
|
||||
#include <Adafruit_GFX.h>
|
||||
|
||||
#include "tintty.h"
|
||||
|
||||
#define SerialTty Serial1
|
||||
#define SerialKbd Serial2
|
||||
|
||||
#include "Keyboard.h"
|
||||
|
||||
#include "Display.h"
|
||||
Display display;
|
||||
|
||||
int16_t scrolled = 0;
|
||||
|
||||
#define FONT_SCALE 2
|
||||
|
||||
uint16_t make_bw_color(uint16_t color) {
|
||||
return (color >> 8) | (color & 0xFF);
|
||||
}
|
||||
|
||||
struct tintty_display ili9341_display = {
|
||||
display.vw,//ILI9341_WIDTH,
|
||||
display.vh,//(ILI9341_HEIGHT - KEYBOARD_HEIGHT),
|
||||
display.vw / display.font_w,//ILI9341_WIDTH / TINTTY_CHAR_WIDTH,
|
||||
display.vh / display.font_h,//(ILI9341_HEIGHT - KEYBOARD_HEIGHT) / TINTTY_CHAR_HEIGHT,
|
||||
|
||||
[=](int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color){
|
||||
//tft.fillRect(x, y, w, h, color);
|
||||
//display.fill_rect(x*FONT_SCALE + display.ow, (y*FONT_SCALE + display.oh, w*FONT_SCALE, h*FONT_SCALE, make_bw_color(color));
|
||||
},
|
||||
|
||||
[=](int16_t x, int16_t y, int16_t w, int16_t h, uint16_t *pixels){
|
||||
//tft.setAddrWindow(x, y, x + w - 1, y + h - 1);
|
||||
//tft.pushColors(pixels, w * h, 1);
|
||||
for (int px = 0; px < w; px++) {
|
||||
for (int py = 0; py < h; py++) {
|
||||
int i = py*w + px;
|
||||
//display.pixel(x + display.ow + px, y + display.oh + py, pixels[i]);
|
||||
//display.fill_rect(x*FONT_SCALE + display.ow + px*FONT_SCALE, y*FONT_SCALE + display.oh + py*FONT_SCALE, FONT_SCALE, FONT_SCALE, make_bw_color(pixels[i]));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
[=](int16_t col, int16_t row, uint16_t fg_color, uint16_t bg_color, char character){
|
||||
display.print_character(col, row - scrolled, make_bw_color(fg_color), make_bw_color(bg_color), character);
|
||||
},
|
||||
|
||||
[=](int16_t col, int16_t row, uint16_t color){
|
||||
display.fill_rect(col*display.font_w + display.ow, (row+1 - scrolled)*display.font_h-1 + display.oh, display.font_w, 1, make_bw_color(color));
|
||||
//display.print_character(col, row, make_bw_color(color), 0, '_');
|
||||
},
|
||||
|
||||
[=](int16_t offset){
|
||||
//tft.vertScroll(0, (ILI9341_HEIGHT - KEYBOARD_HEIGHT), offset);
|
||||
int16_t rows = display.vh / display.font_h;
|
||||
int16_t diff = (rows + offset - (scrolled % rows)) % rows;
|
||||
display.scroll(diff*display.font_h);
|
||||
scrolled += diff;
|
||||
}
|
||||
};
|
||||
|
||||
void tty_keyboard_process()
|
||||
{
|
||||
// read keyboard and send it to the host
|
||||
if (SerialKbd.available() > 0) {
|
||||
int key = SerialKbd.read();
|
||||
keyboard_handle_key(key);
|
||||
}
|
||||
}
|
||||
|
||||
// buffer to test various input sequences
|
||||
char *test_buffer = "-- \e[1mTinTTY\e[m --\r\n";
|
||||
uint8_t test_buffer_cursor = 0;
|
||||
|
||||
void input_init() {};
|
||||
void input_idle() {};
|
||||
|
||||
void setup() {
|
||||
// Debug port
|
||||
Serial.begin(115200);
|
||||
Serial.println("Running!");
|
||||
|
||||
// TTY host
|
||||
SerialTty.begin(9600, SERIAL_8N1, 18, 19, false, 100);
|
||||
|
||||
// LK201 keyboard connected to pins 16 and 17
|
||||
SerialKbd.begin(4800);
|
||||
|
||||
display.setup();
|
||||
//uint16_t tftID = tft.readID();
|
||||
//tft.begin(tftID);
|
||||
|
||||
input_init();
|
||||
|
||||
tintty_run(
|
||||
[=](){
|
||||
// peek idle loop
|
||||
while (true) {
|
||||
// first peek from the test buffer
|
||||
if (test_buffer[test_buffer_cursor]) {
|
||||
return test_buffer[test_buffer_cursor];
|
||||
}
|
||||
|
||||
tty_keyboard_process();
|
||||
|
||||
// fall back to normal blocking serial input
|
||||
if (SerialTty.available() > 0) {
|
||||
return (char)SerialTty.peek();
|
||||
}
|
||||
|
||||
// idle logic only after peeks failed
|
||||
tintty_idle(&ili9341_display);
|
||||
input_idle();
|
||||
}
|
||||
},
|
||||
[=](){
|
||||
while(true) {
|
||||
// process at least one idle loop first to allow input to happen
|
||||
tintty_idle(&ili9341_display);
|
||||
input_idle();
|
||||
tty_keyboard_process();
|
||||
|
||||
// first read from the test buffer
|
||||
if (test_buffer[test_buffer_cursor]) {
|
||||
return test_buffer[test_buffer_cursor++];
|
||||
}
|
||||
|
||||
// fall back to normal blocking serial input
|
||||
if (SerialTty.available() > 0) {
|
||||
return (char)SerialTty.read();
|
||||
}
|
||||
}
|
||||
},
|
||||
[=](char ch){ SerialTty.print(ch); },
|
||||
&ili9341_display
|
||||
);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
Loading…
Reference in New Issue
Block a user