mirror of
https://github.com/hsoft/collapseos.git
synced 2024-12-24 14:38:05 +11:00
emul/hw: add TI-84+ emulator
I implement the screen using XCB which is much more friendly than z80e's SDL+CMake for development machines that want to install minimal dependencies (for example, a port-less OpenBSD rig).
This commit is contained in:
parent
25fc0a3c72
commit
9216057db8
11
emul/emul.c
11
emul/emul.c
@ -76,6 +76,17 @@ bool emul_step()
|
||||
}
|
||||
}
|
||||
|
||||
bool emul_steps(unsigned int steps)
|
||||
{
|
||||
while (steps) {
|
||||
if (!emul_step()) {
|
||||
return false;
|
||||
}
|
||||
steps--;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void emul_loop()
|
||||
{
|
||||
while (emul_step());
|
||||
|
@ -21,5 +21,6 @@ typedef struct {
|
||||
|
||||
Machine* emul_init();
|
||||
bool emul_step();
|
||||
bool emul_steps(unsigned int steps);
|
||||
void emul_loop();
|
||||
void emul_printdebug();
|
||||
|
@ -49,7 +49,7 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
FILE *fp = fopen(argv[1], "r");
|
||||
if (fp == NULL) {
|
||||
fprintf(stderr, "Can't open %s\n", optarg);
|
||||
fprintf(stderr, "Can't open %s\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
Machine *m = emul_init();
|
||||
|
1
emul/hw/ti/.gitignore
vendored
Normal file
1
emul/hw/ti/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/ti84
|
6
emul/hw/ti/Makefile
Normal file
6
emul/hw/ti/Makefile
Normal file
@ -0,0 +1,6 @@
|
||||
OBJS = ti84.o t6a04.o kbd.o ../../emul.o ../../libz80/libz80.o
|
||||
CFLAGS += `pkg-config --cflags xcb`
|
||||
|
||||
ti84: $(OBJS)
|
||||
$(CC) `pkg-config --libs xcb` $(OBJS) -o $@
|
||||
|
26
emul/hw/ti/README.md
Normal file
26
emul/hw/ti/README.md
Normal file
@ -0,0 +1,26 @@
|
||||
# TI-84+ emulator
|
||||
|
||||
This emulates a TI-84+ with its screen and keyboard. This is suitable for
|
||||
running the `ti84` recipe.
|
||||
|
||||
## Build
|
||||
|
||||
You need `xcb` and `pkg-config` to build this. If you have them, run `make`.
|
||||
You'll get a `ti84` executable.
|
||||
|
||||
## Usage
|
||||
|
||||
Launch the emulator with `./ti84 /path/to/rom` (you can use the binary from the
|
||||
`ti84` recipe. Use the small one, not the one having been filled to 1MB).
|
||||
|
||||
This will show a window with the LCD screen's content on it. Most applications,
|
||||
upon boot, halt after initialization and stay halted until the ON key is
|
||||
pressed. The ON key is mapped to the tilde (~) key.
|
||||
|
||||
Press ESC to quit.
|
||||
|
||||
As for the rest of the mappings, they map at the key level. For example, the 'Y'
|
||||
key maps to '1' (which yields 'y' when in alpha mode). Therefore, '1' and 'Y'
|
||||
map to the same calculator key. Backspace maps to DEL.
|
||||
|
||||
Left Shift maps to 2nd. Left Ctrl maps to Alpha.
|
101
emul/hw/ti/kbd.c
Normal file
101
emul/hw/ti/kbd.c
Normal file
@ -0,0 +1,101 @@
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "kbd.h"
|
||||
|
||||
void kbd_init(KBD *kbd)
|
||||
{
|
||||
memset(kbd->pressed, 0xff, 8);
|
||||
kbd->selected = 0xff;
|
||||
}
|
||||
|
||||
uint8_t kbd_rd(KBD *kbd)
|
||||
{
|
||||
uint8_t res = 0xff;
|
||||
for (int i=0; i<8; i++) {
|
||||
if ((kbd->selected & (1<<i)) == 0) {
|
||||
res &= kbd->pressed[i];
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void kbd_wr(KBD *kbd, uint8_t val)
|
||||
{
|
||||
kbd->selected = val;
|
||||
}
|
||||
|
||||
// The key is separated in two nibble. High nibble is group, low nibble is key.
|
||||
void kbd_setkey(KBD *kbd, uint8_t key, bool pressed)
|
||||
{
|
||||
uint8_t group = kbd->pressed[key>>4];
|
||||
if (pressed) {
|
||||
group &= ~(1<<(key&0x7));
|
||||
} else {
|
||||
group |= 1<<(key&0x7);
|
||||
}
|
||||
kbd->pressed[key>>4] = group;
|
||||
}
|
||||
|
||||
// Attempts to returns a key code corresponding to the specified char. 0 if
|
||||
// nothing matches.
|
||||
uint8_t kbd_trans(char c)
|
||||
{
|
||||
c = toupper(c);
|
||||
switch (c) {
|
||||
case 0x0a:
|
||||
case 0x0d: return 0x10; // ENTER
|
||||
case '+': return 0x11;
|
||||
case '-': return 0x12;
|
||||
case '*': return 0x13;
|
||||
case '/': return 0x14;
|
||||
case '^': return 0x15;
|
||||
case '3': return 0x21;
|
||||
case '6': return 0x22;
|
||||
case '9': return 0x23;
|
||||
case ')': return 0x24;
|
||||
case '.': return 0x30;
|
||||
case '2': return 0x31;
|
||||
case '5': return 0x32;
|
||||
case '8': return 0x33;
|
||||
case '(': return 0x34;
|
||||
case '0': return 0x40;
|
||||
case '1': return 0x41;
|
||||
case '4': return 0x42;
|
||||
case '7': return 0x43;
|
||||
case ',': return 0x44;
|
||||
case 0x7f: return 0x67; // DEL
|
||||
case '"': return 0x11;
|
||||
case 'W': return 0x12;
|
||||
case 'R': return 0x13;
|
||||
case 'M': return 0x14;
|
||||
case 'H': return 0x15;
|
||||
case '?': return 0x20;
|
||||
case 'V': return 0x22;
|
||||
case 'Q': return 0x23;
|
||||
case 'L': return 0x24;
|
||||
case 'G': return 0x25;
|
||||
case ':': return 0x30;
|
||||
case 'Z': return 0x31;
|
||||
case 'U': return 0x32;
|
||||
case 'P': return 0x33;
|
||||
case 'K': return 0x34;
|
||||
case 'F': return 0x35;
|
||||
case 'C': return 0x36;
|
||||
case ' ': return 0x40;
|
||||
case 'Y': return 0x41;
|
||||
case 'T': return 0x42;
|
||||
case 'O': return 0x43;
|
||||
case 'J': return 0x44;
|
||||
case 'E': return 0x45;
|
||||
case 'B': return 0x46;
|
||||
case 'X': return 0x51;
|
||||
case 'S': return 0x52;
|
||||
case 'N': return 0x53;
|
||||
case 'I': return 0x54;
|
||||
case 'D': return 0x55;
|
||||
case 'A': return 0x56;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
25
emul/hw/ti/kbd.h
Normal file
25
emul/hw/ti/kbd.h
Normal file
@ -0,0 +1,25 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// These are keycodes for special keys
|
||||
#define KBD_ALPHA 0x57
|
||||
#define KBD_2ND 0x65
|
||||
|
||||
// NOTE: We don't manage the ON key here.
|
||||
typedef struct {
|
||||
// Bitmask of pressed keys. Like on real hardware, 0xff means nothing
|
||||
// pressed. The group 7 has no key, but for code simplicity, we have a neat
|
||||
// array of 8 bytes.
|
||||
uint8_t pressed[8];
|
||||
// Selected groups. Active low.
|
||||
uint8_t selected;
|
||||
} KBD;
|
||||
|
||||
void kbd_init(KBD *kbd);
|
||||
uint8_t kbd_rd(KBD *kbd);
|
||||
void kbd_wr(KBD *kbd, uint8_t val);
|
||||
// The key is separated in two nibble. High nibble is group, low nibble is key.
|
||||
void kbd_setkey(KBD *kbd, uint8_t key, bool pressed);
|
||||
// Attempts to returns a key code corresponding to the specified char. 0 if
|
||||
// nothing matches.
|
||||
uint8_t kbd_trans(char c);
|
141
emul/hw/ti/t6a04.c
Normal file
141
emul/hw/ti/t6a04.c
Normal file
@ -0,0 +1,141 @@
|
||||
#include <string.h>
|
||||
#include "t6a04.h"
|
||||
|
||||
void t6a04_init(T6A04 *lcd)
|
||||
{
|
||||
memset(lcd->ram, 0, T6A04_RAMSIZE);
|
||||
lcd->enabled = false;
|
||||
lcd->incmode = T6A04_XINC;
|
||||
lcd->offset = 0;
|
||||
lcd->currow = 0;
|
||||
lcd->curcol = 0;
|
||||
lcd->just_moved = true;
|
||||
}
|
||||
|
||||
uint8_t t6a04_cmd_rd(T6A04 *lcd)
|
||||
{
|
||||
return 0; // we are always ready for a new cmd
|
||||
}
|
||||
|
||||
/*
|
||||
* 0x00/0x01: 6/8 bit mode
|
||||
* 0x02/0x03: enable/disable
|
||||
* 0x04-0x07: incmodes
|
||||
* 0x20-0x34: set col
|
||||
* 0x40-0x7f: set Z offset
|
||||
* 0x80-0xbf: set row
|
||||
* 0xc0-0xff: set contrast
|
||||
*/
|
||||
void t6a04_cmd_wr(T6A04 *lcd, uint8_t val)
|
||||
{
|
||||
if ((val & 0xc0) == 0xc0) {
|
||||
// contrast, ignoring
|
||||
} else if (val & 0x80) {
|
||||
lcd->currow = val & 0x3f;
|
||||
lcd->just_moved = true;
|
||||
} else if (val & 0x40) {
|
||||
lcd->offset = val & 0x3f;
|
||||
} else if (val & 0x20) {
|
||||
lcd->curcol = val & 0x1f;
|
||||
lcd->just_moved = true;
|
||||
} else if (val & 0x18) {
|
||||
// stuff we don't emulate
|
||||
} else if (val & 0x04) {
|
||||
lcd->incmode = val & 0x03;
|
||||
} else if (val & 0x02) {
|
||||
lcd->enabled = val & 0x01;
|
||||
} else {
|
||||
lcd->has8bitmode = val;
|
||||
}
|
||||
}
|
||||
|
||||
// Advance current position according to current incmode
|
||||
static void _advance(T6A04 *lcd)
|
||||
{
|
||||
uint8_t maxY = lcd->has8bitmode ? 14 : 19;
|
||||
switch (lcd->incmode) {
|
||||
case T6A04_XDEC:
|
||||
lcd->currow = (lcd->currow-1) & 0x3f;
|
||||
break;
|
||||
case T6A04_XINC:
|
||||
lcd->currow = (lcd->currow+1) & 0x3f;
|
||||
break;
|
||||
case T6A04_YDEC:
|
||||
if (lcd->curcol == 0) {
|
||||
lcd->curcol = maxY;
|
||||
} else {
|
||||
lcd->curcol--;
|
||||
}
|
||||
break;
|
||||
case T6A04_YINC:
|
||||
if (lcd->curcol < maxY) {
|
||||
lcd->curcol++;
|
||||
} else {
|
||||
lcd->curcol = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t t6a04_data_rd(T6A04 *lcd)
|
||||
{
|
||||
uint8_t res;
|
||||
if (lcd->just_moved) {
|
||||
// After a move command, the first read op is a noop.
|
||||
lcd->just_moved = false;
|
||||
return 0;
|
||||
}
|
||||
if (lcd->has8bitmode) {
|
||||
int pos = lcd->currow * T6A04_ROWSIZE + lcd->curcol;
|
||||
res = lcd->ram[pos];
|
||||
} else {
|
||||
// 6bit mode is a bit more complicated because the 6-bit number often
|
||||
// spans two bytes. We manage this by loading two bytes into a uint16_t
|
||||
// and then shift it right properly.
|
||||
// bitpos represents the leftmost bit of our 6bit number.
|
||||
int bitpos = lcd->curcol * 6;
|
||||
// offset represents the shift right we need to perform from the two
|
||||
// bytes following bitpos/8 so that we can have our number with a 6-bit
|
||||
// mask.
|
||||
// Example, col 3 has a bitpos of 18, which means that it loads bytes 2
|
||||
// and 3. Its bits would be in bit pos 14:8, which means it has an
|
||||
// offset of 8. There is always an offset and its always in the 3-10
|
||||
// range
|
||||
int offset = 10 - (bitpos % 8); // 10 is for 16bit - 6bit
|
||||
int pos = (lcd->currow * T6A04_ROWSIZE) + (bitpos / 8);
|
||||
uint16_t word = lcd->ram[pos] << 8;
|
||||
word |= lcd->ram[pos+1];
|
||||
res = (word >> offset) & 0x3f;
|
||||
}
|
||||
_advance(lcd);
|
||||
return res;
|
||||
}
|
||||
|
||||
void t6a04_data_wr(T6A04 *lcd, uint8_t val)
|
||||
{
|
||||
lcd->just_moved = false;
|
||||
if (lcd->has8bitmode) {
|
||||
int pos = lcd->currow * T6A04_ROWSIZE + lcd->curcol;
|
||||
lcd->ram[pos] = val;
|
||||
} else {
|
||||
// See comments in t6a04_data_rd().
|
||||
int bitpos = lcd->curcol * 6;
|
||||
int offset = 10 - (bitpos % 8);
|
||||
int pos = (lcd->currow * T6A04_ROWSIZE) + (bitpos / 8);
|
||||
uint16_t word = lcd->ram[pos] << 8;
|
||||
word |= lcd->ram[pos+1];
|
||||
// word contains our current ram value. Let's fit val in this.
|
||||
word &= ~(0x003f << offset);
|
||||
word |= val << offset;
|
||||
lcd->ram[pos] = word >> 8;
|
||||
lcd->ram[pos+1] = word & 0xff;
|
||||
}
|
||||
_advance(lcd);
|
||||
}
|
||||
|
||||
bool t6a04_pixel(T6A04 *lcd, uint8_t y, uint8_t x)
|
||||
{
|
||||
x = (x + lcd->offset) & 0x3f;
|
||||
uint8_t val = lcd->ram[x * T6A04_ROWSIZE + (y / 8)];
|
||||
return (val >> (7 - (y % 8))) & 1;
|
||||
}
|
41
emul/hw/ti/t6a04.h
Normal file
41
emul/hw/ti/t6a04.h
Normal file
@ -0,0 +1,41 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define T6A04_ROWSIZE (120/8)
|
||||
#define T6A04_RAMSIZE 64*T6A04_ROWSIZE
|
||||
|
||||
typedef enum {
|
||||
T6A04_XDEC = 0,
|
||||
T6A04_XINC = 1,
|
||||
T6A04_YDEC = 2,
|
||||
T6A04_YINC = 3
|
||||
} T6A04_INCMODE;
|
||||
|
||||
typedef struct {
|
||||
// RAM is organized in 64 rows of 120 pixels (there is some offscreen
|
||||
// memory). Each byte holds 8 pixels. unset means no pixel, set means pixel.
|
||||
uint8_t ram[T6A04_RAMSIZE];
|
||||
bool enabled;
|
||||
// Whether the 8bit mode is enabled.
|
||||
bool has8bitmode;
|
||||
// Current "increment mode"
|
||||
T6A04_INCMODE incmode;
|
||||
// current Z offset
|
||||
uint8_t offset;
|
||||
// Currently active row
|
||||
uint8_t currow;
|
||||
// Currently active col (actual meaning depends on whether we're in 8bit
|
||||
// mode)
|
||||
uint8_t curcol;
|
||||
// True when a movement command was just made or if the LCD was just
|
||||
// initialized. When this is true, a read operation on the data port will be
|
||||
// invalid (returns zero and doesn't autoinc).
|
||||
bool just_moved;
|
||||
} T6A04;
|
||||
|
||||
void t6a04_init(T6A04 *lcd);
|
||||
uint8_t t6a04_cmd_rd(T6A04 *lcd);
|
||||
void t6a04_cmd_wr(T6A04 *lcd, uint8_t val);
|
||||
uint8_t t6a04_data_rd(T6A04 *lcd);
|
||||
void t6a04_data_wr(T6A04 *lcd, uint8_t val);
|
||||
bool t6a04_pixel(T6A04 *lcd, uint8_t y, uint8_t x);
|
314
emul/hw/ti/ti84.c
Normal file
314
emul/hw/ti/ti84.c
Normal file
@ -0,0 +1,314 @@
|
||||
/* TI-84+
|
||||
*
|
||||
* A plain TI-84 with its built-in keyboard as an input and its LCD screen
|
||||
* as an output.
|
||||
*
|
||||
* Uses XCB to render the screen and record keystrokes.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
#include "../../emul.h"
|
||||
#include "t6a04.h"
|
||||
#include "kbd.h"
|
||||
|
||||
#define RAMSTART 0x8000
|
||||
#define KBD_PORT 0x01
|
||||
#define INTERRUPT_PORT 0x03
|
||||
#define LCD_CMD_PORT 0x10
|
||||
#define LCD_DATA_PORT 0x11
|
||||
#define MAX_ROMSIZE 0x2000
|
||||
|
||||
static xcb_connection_t *conn;
|
||||
static xcb_screen_t *screen;
|
||||
|
||||
/* graphics contexts */
|
||||
static xcb_gcontext_t fg;
|
||||
/* win */
|
||||
static xcb_drawable_t win;
|
||||
|
||||
// pixels to draw. We draw them in one shot.
|
||||
static xcb_rectangle_t rectangles[96*64];
|
||||
|
||||
static Machine *m;
|
||||
static T6A04 lcd;
|
||||
static bool lcd_changed;
|
||||
static KBD kbd;
|
||||
static bool on_was_pressed;
|
||||
|
||||
static uint8_t iord_lcd_cmd()
|
||||
{
|
||||
return t6a04_cmd_rd(&lcd);
|
||||
}
|
||||
|
||||
static uint8_t iord_lcd_data()
|
||||
{
|
||||
return t6a04_data_rd(&lcd);
|
||||
}
|
||||
|
||||
static uint8_t iord_kbd()
|
||||
{
|
||||
return kbd_rd(&kbd);
|
||||
}
|
||||
|
||||
static uint8_t iord_interrupt()
|
||||
{
|
||||
return on_was_pressed ? 1 : 0;
|
||||
}
|
||||
|
||||
static void iowr_lcd_cmd(uint8_t val)
|
||||
{
|
||||
t6a04_cmd_wr(&lcd, val);
|
||||
}
|
||||
|
||||
static void iowr_lcd_data(uint8_t val)
|
||||
{
|
||||
lcd_changed = true;
|
||||
t6a04_data_wr(&lcd, val);
|
||||
}
|
||||
|
||||
static void iowr_kbd(uint8_t val)
|
||||
{
|
||||
kbd_wr(&kbd, val);
|
||||
}
|
||||
|
||||
static void iowr_interrupt(uint8_t val)
|
||||
{
|
||||
if ((val & 1) == 0) {
|
||||
on_was_pressed = false;
|
||||
}
|
||||
}
|
||||
|
||||
// TIL: XCB doesn't have a builtin way to translate a keycode to an ASCII char.
|
||||
// Using Xlib looks complicated. This will probably not work in many cases (non
|
||||
// query keyboards and all...), but for now, let's go with this.
|
||||
static uint8_t keycode_to_tikbd(xcb_keycode_t kc)
|
||||
{
|
||||
switch (kc) {
|
||||
case 0x0a: return 0x41; // 1
|
||||
case 0x0b: return 0x31; // 2
|
||||
case 0x0c: return 0x21; // 3
|
||||
case 0x0d: return 0x42; // 4
|
||||
case 0x0e: return 0x32; // 5
|
||||
case 0x0f: return 0x22; // 6
|
||||
case 0x10: return 0x43; // 7
|
||||
case 0x11: return 0x33; // 8
|
||||
case 0x12: return 0x23; // 9
|
||||
case 0x13: return 0x40; // 0
|
||||
case 0x14: return 0x12; // -
|
||||
case 0x15: return 0x11; // +
|
||||
case 0x16: return 0x67; // DEL
|
||||
case 0x18: return 0x23; // Q
|
||||
case 0x19: return 0x12; // W
|
||||
case 0x1a: return 0x45; // E
|
||||
case 0x1b: return 0x13; // R
|
||||
case 0x1c: return 0x42; // T
|
||||
case 0x1d: return 0x41; // Y
|
||||
case 0x1e: return 0x32; // U
|
||||
case 0x1f: return 0x54; // I
|
||||
case 0x20: return 0x43; // O
|
||||
case 0x21: return 0x33; // P
|
||||
case 0x22: return 0x34; // (
|
||||
case 0x23: return 0x24; // )
|
||||
case 0x24: return 0x10; // Return
|
||||
case 0x25: return KBD_ALPHA; // LCTRL
|
||||
case 0x26: return 0x56; // A
|
||||
case 0x27: return 0x52; // S
|
||||
case 0x28: return 0x55; // D
|
||||
case 0x29: return 0x35; // F
|
||||
case 0x2a: return 0x25; // G
|
||||
case 0x2b: return 0x15; // H
|
||||
case 0x2c: return 0x44; // J
|
||||
case 0x2d: return 0x34; // K
|
||||
case 0x2e: return 0x24; // L
|
||||
case 0x2f: return 0x30; // :
|
||||
case 0x30: return 0x11; // "
|
||||
case 0x32: return KBD_2ND; // Lshift
|
||||
case 0x34: return 0x31; // Z
|
||||
case 0x35: return 0x51; // X
|
||||
case 0x36: return 0x36; // C
|
||||
case 0x37: return 0x22; // V
|
||||
case 0x38: return 0x46; // B
|
||||
case 0x39: return 0x53; // N
|
||||
case 0x3a: return 0x14; // M
|
||||
case 0x3b: return 0x44; // ,
|
||||
case 0x3c: return 0x30; // .
|
||||
case 0x3d: return 0x20; // ?
|
||||
case 0x41: return 0x40; // Space
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void create_window()
|
||||
{
|
||||
uint32_t mask;
|
||||
uint32_t values[2];
|
||||
|
||||
/* Create the window */
|
||||
win = xcb_generate_id(conn);
|
||||
mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
|
||||
values[0] = screen->white_pixel;
|
||||
values[1] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS |
|
||||
XCB_EVENT_MASK_KEY_RELEASE;
|
||||
xcb_create_window(
|
||||
conn,
|
||||
screen->root_depth,
|
||||
win,
|
||||
screen->root,
|
||||
0, 0,
|
||||
150, 150,
|
||||
10,
|
||||
XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
||||
screen->root_visual,
|
||||
mask, values);
|
||||
|
||||
fg = xcb_generate_id(conn);
|
||||
mask = XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES;
|
||||
values[0] = screen->black_pixel;
|
||||
values[1] = 0;
|
||||
xcb_create_gc(conn, fg, screen->root, mask, values);
|
||||
|
||||
/* Map the window on the screen */
|
||||
xcb_map_window(conn, win);
|
||||
}
|
||||
|
||||
bool get_pixel(int x, int y)
|
||||
{
|
||||
return t6a04_pixel(&lcd, x, y);
|
||||
}
|
||||
|
||||
void draw_pixels()
|
||||
{
|
||||
xcb_get_geometry_reply_t *geom;
|
||||
|
||||
geom = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL);
|
||||
|
||||
xcb_clear_area(
|
||||
conn, 0, win, 0, 0, geom->width, geom->height);
|
||||
// Figure out inner size to maximize a 96x64 screen (1.5 aspect ratio)
|
||||
int psize = geom->height / 64;
|
||||
if (geom->width / 96 < psize) {
|
||||
// width is the constraint
|
||||
psize = geom->width / 96;
|
||||
}
|
||||
int innerw = psize * 96;
|
||||
int innerh = psize * 64;
|
||||
int innerx = (geom->width - innerw) / 2;
|
||||
int innery = (geom->height - innerh) / 2;
|
||||
int drawcnt = 0;
|
||||
for (int i=0; i<96; i++) {
|
||||
for (int j=0; j<64; j++) {
|
||||
if (get_pixel(i, j)) {
|
||||
int x = innerx + (i*psize);
|
||||
int y = innery + (j*psize);
|
||||
rectangles[drawcnt].x = x;
|
||||
rectangles[drawcnt].y = y;
|
||||
rectangles[drawcnt].height = psize;
|
||||
rectangles[drawcnt].width = psize;
|
||||
drawcnt++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (drawcnt) {
|
||||
xcb_poly_fill_rectangle(
|
||||
conn, win, fg, drawcnt, rectangles);
|
||||
}
|
||||
lcd_changed = false;
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
void event_loop()
|
||||
{
|
||||
if (!emul_step()) {
|
||||
// We're done
|
||||
return;
|
||||
}
|
||||
while (1) {
|
||||
emul_step();
|
||||
if (lcd_changed) {
|
||||
// To avoid overdrawing, we'll let the CPU run a bit to finish its
|
||||
// drawing operation.
|
||||
emul_steps(100);
|
||||
draw_pixels();
|
||||
}
|
||||
xcb_generic_event_t *e = xcb_poll_for_event(conn);
|
||||
if (!e) {
|
||||
continue;
|
||||
}
|
||||
switch (e->response_type & ~0x80) {
|
||||
/* ESC to exit */
|
||||
case XCB_KEY_RELEASE:
|
||||
case XCB_KEY_PRESS: {
|
||||
xcb_key_press_event_t *ev = (xcb_key_press_event_t *)e;
|
||||
if (ev->detail == 0x09) return;
|
||||
if (ev->detail == 0x31 && e->response_type == XCB_KEY_PRESS) {
|
||||
// tilde, mapped to ON
|
||||
on_was_pressed = true;
|
||||
Z80INT(&m->cpu, 0);
|
||||
Z80Execute(&m->cpu); // unhalts the CPU
|
||||
}
|
||||
uint8_t key = keycode_to_tikbd(ev->detail);
|
||||
if (key) {
|
||||
kbd_setkey(&kbd, key, e->response_type == XCB_KEY_PRESS);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case XCB_EXPOSE: {
|
||||
draw_pixels();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(e);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "Usage: ./ti84 /path/to/rom\n");
|
||||
return 1;
|
||||
}
|
||||
FILE *fp = fopen(argv[1], "r");
|
||||
if (fp == NULL) {
|
||||
fprintf(stderr, "Can't open %s\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
m = emul_init();
|
||||
m->ramstart = RAMSTART;
|
||||
int i = 0;
|
||||
int c;
|
||||
while ((c = fgetc(fp)) != EOF && i < MAX_ROMSIZE) {
|
||||
m->mem[i++] = c & 0xff;
|
||||
}
|
||||
pclose(fp);
|
||||
if (i == MAX_ROMSIZE) {
|
||||
fprintf(stderr, "ROM image too large.\n");
|
||||
return 1;
|
||||
}
|
||||
t6a04_init(&lcd);
|
||||
kbd_init(&kbd);
|
||||
lcd_changed = false;
|
||||
on_was_pressed = false;
|
||||
m->iord[KBD_PORT] = iord_kbd;
|
||||
m->iord[INTERRUPT_PORT] = iord_interrupt;
|
||||
m->iord[LCD_CMD_PORT] = iord_lcd_cmd;
|
||||
m->iord[LCD_DATA_PORT] = iord_lcd_data;
|
||||
m->iowr[KBD_PORT] = iowr_kbd;
|
||||
m->iowr[INTERRUPT_PORT] = iowr_interrupt;
|
||||
m->iowr[LCD_CMD_PORT] = iowr_lcd_cmd;
|
||||
m->iowr[LCD_DATA_PORT] = iowr_lcd_data;
|
||||
conn = xcb_connect(NULL, NULL);
|
||||
screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
|
||||
create_window();
|
||||
draw_pixels();
|
||||
event_loop();
|
||||
emul_printdebug();
|
||||
return 0;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
TARGET = os.rom
|
||||
TARGET = os.bin
|
||||
BASEDIR = ../..
|
||||
ZASM = $(BASEDIR)/emul/zasm/zasm
|
||||
KERNEL = $(BASEDIR)/kernel
|
||||
@ -9,10 +9,13 @@ MKTIUPGRADE = mktiupgrade
|
||||
all: $(TARGET)
|
||||
$(TARGET): glue.asm
|
||||
$(ZASM) $(KERNEL) $(APPS) < glue.asm > $@
|
||||
|
||||
os.rom: $(TARGET)
|
||||
cp $(TARGET) $@
|
||||
truncate -s 1M $@
|
||||
|
||||
os.8xu: $(TARGET)
|
||||
$(MKTIUPGRADE) -p -k keys/0A.key -d TI-84+ $(TARGET) $@ 00
|
||||
os.8xu: os.rom
|
||||
$(MKTIUPGRADE) -p -k keys/0A.key -d TI-84+ os.rom $@ 00
|
||||
|
||||
.PHONY: send
|
||||
send: os.8xu
|
||||
|
Loading…
Reference in New Issue
Block a user