diff --git a/README.md b/README.md new file mode 100644 index 0000000..9c3f7cb --- /dev/null +++ b/README.md @@ -0,0 +1,112 @@ +# TerminalCE +Serial console for the TI 84 Plus CE +When running, sets USB port be seen as a USB-Serial adapter by a host device. +on Linux systems running systemd, run + `# systemctl start serial-getty@ttyUSB0.service` + to start a terminal through the emulated serial port on the calc. + +## Planned features +40 column terminal (hopefully) +30 rows, top 2 and bottom 2 rows being used for status bars with black on white text + +example UI: +``` ++----------------------------------------+ +| Window Title | +| | +| ... actual console area ... | +| | +| 01, 17 | ++----------------------------------------+ +``` + +for colours, or together colour bits from 4 bit rgbi input: (setting color not properly implemented yet) +``` +RRRBBGGG Hex Name +10100000 A0 Red +00000101 05 Green +00010000 10 Blue +01001010 4A Intensity +``` +Keyboard: (not implemented yet) +(top: key label, left: shift state) + +| | f1 | f2 | f3 | f4 | f5 | +|----|-------|-------|-------|-------|-------| +|0 | | | | | | +|^ | | | | | | +|A | | | | | | +|a | | | | | | +| | 14 | 13 | 12 | 11 | 10 | + +| | 2nd | mode | del | left | up | +|----|-------|-------|-------|-------|-------| +|0 | | | del | | | +|^ | | ^C | ins | | | +|A | | | | | | +|a | | | | | | +| | 15 | 16 | 17 | 71 | 73 | + +| | alpha | XT0n | stat | down | right | +|----|-------|-------|-------|-------|-------| +|0 | | | | | | +|^ | | | | | | +|A | | / | | | | +|a | | | | | | +| | 27 | 37 | 47 | 70 | 72 | + +| | math | apps | prgm | vars | clear | +|----|-------|-------|-------|-------|-------| +|0 | | | | | | +|^ | | | | | | +|A | A | B | C | | | +|a | a | b | c | | | +| | 26 | 36 | 46 | 56 | 66 | + +| | x^-1 | sin | cos | tan | ^ | +|----|-------|-------|-------|-------|-------| +|0 | | | | | ^ | +|^ | | | | | pi | +|A | D | E | F | G | H | +|a | d | e | f | g | h | +| | 25 | 35 | 45 | 55 | 65 | + +| | x^2 | , | ( | ) | / | +|----|-------|-------|-------|-------|-------| +|0 | | , | ( | ) | / | +|^ | | | { | } | | +|A | I | J | K | L | M | +|a | i | j | k | l | m | +| | 24 | 34 | 44 | 54 | 64 | + +| | log | 7 | 8 | 9 | * | +|----|-------|-------|-------|-------|-------| +|0 | | 7 | 8 | 9 | * | +|^ | | & | * | | [ | +|A | N | O | P | Q | R | +|a | n | o | p | q | r | +| | 23 | 33 | 43 | 53 | 63 | + +| | ln | 4 | 5 | 6 | - | +|----|-------|-------|-------|-------|-------| +|0 | | 4 | 5 | 6 | - | +|^ | | $ | % | ^ | ] | +|A | S | T | U | V | W | +|a | s | t | u | v | w | +| | 22 | 32 | 42 | 52 | 62 | + +| | sto-> | 1 | 2 | 3 | + | +|----|-------|-------|-------|-------|-------| +|0 | | 1 | 2 | 3 | + | +|^ | | ! | @ | # | | +|A | X | Y | Z | theta | " | +|a | x | y | z | | | +| | 21 | 31 | 41 | 51 | 61 | + +| | on | 0 | . | (-) | enter | +|----|-------|-------|-------|-------|-------| +|0 | | 0 | . | | | +|^ | | ~ | < | > | | +|a | | space | ; | ? | | +|A | | space | : | ! | | +| | | 30 | 40 | 50 | 60 | \ No newline at end of file diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..08e5182 Binary files /dev/null and b/icon.png differ diff --git a/makefile b/makefile new file mode 100644 index 0000000..b58bc81 --- /dev/null +++ b/makefile @@ -0,0 +1,22 @@ +# ---------------------------- +# Makefile Options +# ---------------------------- + +NAME = DEMO +ICON = icon.png +DESCRIPTION = "CE C Toolchain Demo" +COMPRESSED = NO + +CFLAGS = -Wall -Wextra -Oz +CXXFLAGS = -Wall -Wextra -Oz + +# ---------------------------- + +include $(shell cedev-config --makefile) + +FONTPACKNAME = termFont +FONTDIR = $(SRCDIR)/gfx +DEPS = $(BINDIR)/$(FONTPACKNAME).8xv + +$(BINDIR)/$(FONTPACKNAME).8xv: $(SRCDIR)/gfx/$(FONTPACKNAME).bin + $(Q)convbin --iformat bin --input $(SRCDIR)/gfx/$(FONTPACKNAME).bin --oformat 8xv --output $(BINDIR)/$(FONTPACKNAME).8xv --archive --name $(FONTPACKNAME) diff --git a/src/gfx/20231008_172900.jpg b/src/gfx/20231008_172900.jpg new file mode 100644 index 0000000..31bca4a Binary files /dev/null and b/src/gfx/20231008_172900.jpg differ diff --git a/src/gfx/TermFont.bin b/src/gfx/TermFont.bin new file mode 100644 index 0000000..66473dd Binary files /dev/null and b/src/gfx/TermFont.bin differ diff --git a/src/gfx/TermFont.png b/src/gfx/TermFont.png new file mode 100644 index 0000000..77cf377 Binary files /dev/null and b/src/gfx/TermFont.png differ diff --git a/src/gfx/convert-fonttiles.py b/src/gfx/convert-fonttiles.py new file mode 100644 index 0000000..469c0bf --- /dev/null +++ b/src/gfx/convert-fonttiles.py @@ -0,0 +1,29 @@ +#!/usr/bin/python +from PIL import Image +import struct + +tilesheetPNG = Image.open("TermFont.png") +tilesheetBIN = open("TermFont.bin", "wb") +tilesheetPixels = tilesheetPNG.load() + +tilesheetX, tilesheetY = tilesheetPNG.size +horizTiles = int(tilesheetX/8) +vertTiles = int(tilesheetY/8) +totalTiles = horizTiles*vertTiles + +def generateTile(x,y,image): + tile = [] + for py in range(0,8): + row = [] + for px in range(0,8): + r,g,b,a = image[(8*x)+px,(8*y)+py] + row.append(((r+g+b)>384)) + tile.append(sum([int(row[b])*(2**(7-b)) for b in range(0,8)])) + return tile + +generatedTileData = [] +for y in range(0,vertTiles): + for x in range(0,horizTiles): + generatedTileData += generateTile(x,y,tilesheetPixels) + +tilesheetBIN.write(bytearray(generatedTileData)) diff --git a/src/gfx/normalTermFont.png b/src/gfx/normalTermFont.png new file mode 100644 index 0000000..d8102a2 Binary files /dev/null and b/src/gfx/normalTermFont.png differ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..3aa176c --- /dev/null +++ b/src/main.c @@ -0,0 +1,358 @@ +/* + *-------------------------------------- + * Program Name: Terminal CE + * Author: SuperbeeeLavaman + * License: Apache License 2.0 + * Description: Dumb Terminal application for TI-84 Plus CE + *-------------------------------------- +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +srl_device_t srl; + +bool has_srl_device = false; + +uint8_t srl_buf[512]; + +static usb_error_t handle_usb_event(usb_event_t event, void *event_data, + usb_callback_data_t *callback_data __attribute__((unused))) { + usb_error_t err; + /* Delegate to srl USB callback */ + if ((err = srl_UsbEventCallback(event, event_data, callback_data)) != USB_SUCCESS) + return err; + /* Enable newly connected devices */ + if(event == USB_DEVICE_CONNECTED_EVENT && !(usb_GetRole() & USB_ROLE_DEVICE)) { + usb_device_t device = event_data; + dbg_printf("device connected\n"); + usb_ResetDevice(device); + } + + /* Call srl_Open on newly enabled device, if there is not currently a serial device in use */ + if(event == USB_HOST_CONFIGURE_EVENT || (event == USB_DEVICE_ENABLED_EVENT && !(usb_GetRole() & USB_ROLE_DEVICE))) { + + /* If we already have a serial device, ignore the new one */ + if(has_srl_device) return USB_SUCCESS; + + usb_device_t device; + if(event == USB_HOST_CONFIGURE_EVENT) { + /* Use the device representing the USB host. */ + device = usb_FindDevice(NULL, NULL, USB_SKIP_HUBS); + if(device == NULL) return USB_SUCCESS; + } else { + /* Use the newly enabled device */ + device = event_data; + } + + /* Initialize the serial library with the newly attached device */ + srl_error_t error = srl_Open(&srl, device, srl_buf, sizeof srl_buf, SRL_INTERFACE_ANY, 9600); + if(error) { + /* Print the error code to the homescreen */ + dbg_printf("Error %d initting serial\n", error); + return USB_SUCCESS; + } + + dbg_printf("serial initialized\n"); + + has_srl_device = true; + } + + if(event == USB_DEVICE_DISCONNECTED_EVENT) { + usb_device_t device = event_data; + if(device == srl.dev) { + dbg_printf("device disconnected\n"); + srl_Close(&srl); + has_srl_device = false; + } + } + return USB_SUCCESS; +} + +uint8_t fontData; +uint8_t fontBytes[2048]; +uint16_t renderGoal[1200]; +uint16_t renderCurrent[1200]; +int exit_Code = 0; +uint24_t idx = 0; +uint8_t cursorX = 0; +uint8_t cursorY = 0; +bool canUseFont = false; +uint8_t idx8 = 0; + +uint8_t colour_red = 0xA0; +uint8_t colour_green = 0x05; +uint8_t colour_blue = 0x10; +uint8_t colour_gray = 0x4A; + +uint8_t keyboardState = 0x00; + +bool newTiles = true; + +char keyactions[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0xFD, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0xFE, + " ", 0, 0, 0, 0, 0, 0, 0, + ";", 0, 0, 0, 0, 0, 0, 0, + "?", 0, 0, 0, 0, 0, 0, 0, + 0x0D, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0xFC, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0xFE, + " ", 0, 0, 0, 0, 0, 0, 0, + ":", 0, 0, 0, 0, 0, 0, 0, + "!", 0, 0, 0, 0, 0, 0, 0, + 0x0D, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0xFF, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0xFC, + "0", "1", "4", "7", ",", 0, 0, 0, + ".", "2", "5", "8", "(", 0, 0, 0, + 0, "3", "6", "9", ")", 0, 0, 0, + 0x0D, "+", "-", "*", "/", "^", 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0xFE, 0x03, 0x16, + 0, 0, 0, 0, 0, 0, 0, 0xFC, + "~", 0, 0, 0, 0, 0, 0, 0, + "<", 0, 0, 0, 0, 0, 0, 0, + ">", 0, 0, 0, 0, 0, 0, 0, + 0x0D, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +int begin() { + kb_EnableOnLatch(); + kb_ClearOnLatch(); + fontData = ti_Open("TermFont", "r"); + if (fontData == 0) { + dbg_printf("dang, no font :(\n"); + canUseFont = false; + } else { + canUseFont = true; + ti_Read(fontBytes, 1, 2048, fontData); + } + const usb_standard_descriptors_t *desc = srl_GetCDCStandardDescriptors(); + /* Initialize the USB driver with our event handler and the serial device descriptors */ + usb_error_t usb_error = usb_Init(handle_usb_event, NULL, desc, USB_DEFAULT_INIT_FLAGS); + if(usb_error) { + usb_Cleanup(); + printf("usb init error %u\n", usb_error); + } + + idx = 0; + while (idx < 80) { + renderGoal[idx] = 0x200F; + renderGoal[idx+1120] = 0x200F; + idx += 1; + } + renderGoal[39] = 0xFC0F; + return 0; +} + +void scroll() { + memcpy(renderGoal+80, renderGoal+120, 2000); + memset(renderGoal+1080, 0, 80); + newTiles = true; +} + +int end() { + usb_Cleanup(); + if (canUseFont) { + if (ti_Close(fontData) == 0) { + exit_Code = 1; + } + } + return exit_Code; +} + +bool step() { + usb_HandleEvents(); + if(has_srl_device) { + char in_buf[64]; + /* Read up to 64 bytes from the serial buffer */ + size_t bytes_read = srl_Read(&srl, in_buf, sizeof in_buf); + + /* Check for an error (e.g. device disconneced) */ + if(bytes_read < 0) { + dbg_printf("error %d on srl_Read\n", bytes_read); + has_srl_device = false; + } else if(bytes_read > 0) { + idx = 0; + char symbol; + char escape_code[16] + while (idx < bytes_read) { + symbol = in_buf[idx]; + int increment_X = 1; + if (symbol == 0x27) + if (symbol == 0x7F) { + symbol = 0x20; + cursorX -= (cursorX>0); + increment_X = 0; + } + if (symbol == 0x0D) { + cursorX = 0; + cursorY += 1; + symbol = 0x20; + increment_X = 0; + } + if (symbol == 0x05) { + srl_Write(&srl, "TerminalCE", 10); + increment_X = 0; + symbol = 0x20 + } + if (cursorX >= 40) { + cursorX = 0; + cursorY += 1; + } + if (cursorY > 25) { + scroll(); + cursorY = 25; + } + renderGoal[((40 * (cursorY+2)) + cursorX)] = (256*symbol+0xF0); + cursorX += increment_X; + idx += 1; + } + newTiles = true; + } + kb_Scan(); + idx8 = 0; + idx = 0; + char send[1]; + uint8_t keys = 0; + while (idx < 8) { + keys = kb_Data[idx]; + if (keys) { + while (idx8 < 8) { + if (keys & 0x01) { + if (keyboardState > 3) { keyboardState = 0 ; } + send[1] = keyactions[(64*keyboardState)+(8*idx)+idx8]; + } + keys = (keys >> 1) + idx8 += 1; + } + } + idx += 1; + } + + srl_Write(&srl, send, 1); + } + return !kb_On; +} + +int renderTile(int indx) { + int tileY = indx / 40; + int tileX = indx - ( 40 * tileY ); + uint16_t tileData = renderGoal[indx]; + uint8_t symbol = (uint8_t)((tileData & 0xFF00) >> 8); + uint8_t colour = (uint8_t)(tileData & 0x00FF); + uint8_t fgColour = 0x00; + uint8_t bgColour = 0x00; + if (colour & 0b10000000) { + fgColour |= colour_gray; + } + if (colour & 0b00001000) { + bgColour |= colour_gray; + } + if (colour & 0b01000000) { + fgColour |= colour_red; + } + if (colour & 0b00000100) { + bgColour |= colour_red; + } + if (colour & 0b00100000) { + fgColour |= colour_green; + } + if (colour & 0b00000010) { + bgColour |= colour_green; + } + if (colour & 0b00010000) { + fgColour |= colour_blue; + } + if (colour & 0b00000001) { + bgColour |= colour_blue; + } + uint8_t pixelX = 0; + uint8_t pixelY = 0; + uint8_t rowData; + while (pixelY < 8) { + if (canUseFont) { + rowData = fontBytes[(8*symbol)+pixelY]; + } else { + rowData = symbol; + } + pixelX = 0; + while (pixelX < 8) { + bool pixelState = rowData & (1 << (7-pixelX)); + uint8_t pixelColour; + if (pixelState) { + pixelColour = fgColour; + } else { + pixelColour = bgColour; + } + gfx_SetColor(pixelColour); + gfx_SetPixel(((8 * tileX)+pixelX),((8 * tileY)+pixelY)); + pixelX += 1; + } + pixelY += 1; + } + return 0; +} + +int draw() { + int drawIdx = 0; + int renderedNow = 0; + if (newTiles == false) { + return 0; + } + while (renderedNow < 10) { + if (renderCurrent[drawIdx] != renderGoal[drawIdx]) { + exit_Code = renderTile(drawIdx); + renderCurrent[drawIdx] = renderGoal[drawIdx]; + if (exit_Code != 0 ) { + return exit_Code; + } + renderedNow += 1; + } + if (drawIdx > 1200) { + newTiles = false; + return 0; + } + drawIdx += 1; + } + return 0; +} + +int main() { + exit_Code = begin(); // No rendering allowed! + if (exit_Code != 0 ) { + return exit_Code; + } + gfx_Begin(); + gfx_ZeroScreen(); + gfx_SetDrawScreen(); + + while (step()) { // No rendering allowed in step! + exit_Code = draw(); // As little non-rendering logic as possible + if (exit_Code != 0 ) { + gfx_End(); + return exit_Code; + } + } + + gfx_End(); + return end(); +}