emul/sms: add PS/2 keyboard emulation
This commit is contained in:
parent
cc8068f8ab
commit
8cecd54410
@ -193,3 +193,6 @@ void emul_printdebug()
|
||||
fprintf(stderr, "Min SP: %04x\n", m.minsp);
|
||||
fprintf(stderr, "Max IX: %04x\n", m.maxix);
|
||||
}
|
||||
|
||||
byte iord_noop() { return 0; }
|
||||
void iowr_noop(byte val) {}
|
||||
|
@ -37,3 +37,6 @@ void emul_trace(ushort addr);
|
||||
void emul_memdump();
|
||||
void emul_debugstr(char *s);
|
||||
void emul_printdebug();
|
||||
// use when a port is a NOOP, but it's not an error to access it.
|
||||
byte iord_noop();
|
||||
void iowr_noop(byte val);
|
||||
|
@ -1,5 +1,5 @@
|
||||
EXTOBJS = ../../emul.o ../../libz80/libz80.o
|
||||
OBJS = sms.o vdp.o port.o pad.o
|
||||
OBJS = sms.o vdp.o port.o pad.o kbd.o
|
||||
TARGET = sms
|
||||
CFLAGS += `pkg-config --cflags xcb`
|
||||
LDFLAGS += `pkg-config --libs xcb`
|
||||
|
@ -25,4 +25,9 @@ pad are:
|
||||
* K --> C
|
||||
* L --> Start
|
||||
|
||||
If your ROM is configured with PS/2 keyboard input, run this emulator with the
|
||||
`-k` flag to replace SMS pad emulation with keyboard emulation.
|
||||
|
||||
In both cases (pad or keyboard), only port A emulation is supported.
|
||||
|
||||
Press ESC to quit.
|
||||
|
101
emul/hw/sms/kbd.c
Normal file
101
emul/hw/sms/kbd.c
Normal file
@ -0,0 +1,101 @@
|
||||
#include "kbd.h"
|
||||
|
||||
void kbd_init(Kbd *kbd, Tristate *TH)
|
||||
{
|
||||
kbd->kc = 0;
|
||||
kbd->breaking = false;
|
||||
kbd->TH = TH;
|
||||
}
|
||||
|
||||
void kbd_pressshift(Kbd *kbd, bool ispressed)
|
||||
{
|
||||
kbd->kc = 0x12; // shift keycode
|
||||
kbd->breaking = !ispressed;
|
||||
}
|
||||
|
||||
void kbd_presskey(Kbd *kbd, uint8_t key)
|
||||
{
|
||||
kbd->kc = 0;
|
||||
switch (key) {
|
||||
case '\t': kbd->kc = 0xd; break;
|
||||
case '`': kbd->kc = 0xe; break;
|
||||
case 'q': kbd->kc = 0x15; break;
|
||||
case '1': kbd->kc = 0x16; break;
|
||||
case 'z': kbd->kc = 0x1a; break;
|
||||
case 's': kbd->kc = 0x1b; break;
|
||||
case 'a': kbd->kc = 0x1c; break;
|
||||
case 'w': kbd->kc = 0x1d; break;
|
||||
case '2': kbd->kc = 0x1e; break;
|
||||
case 'c': kbd->kc = 0x21; break;
|
||||
case 'x': kbd->kc = 0x22; break;
|
||||
case 'd': kbd->kc = 0x23; break;
|
||||
case 'e': kbd->kc = 0x24; break;
|
||||
case '4': kbd->kc = 0x25; break;
|
||||
case '3': kbd->kc = 0x26; break;
|
||||
case ' ': kbd->kc = 0x29; break;
|
||||
case 'v': kbd->kc = 0x2a; break;
|
||||
case 'f': kbd->kc = 0x2b; break;
|
||||
case 't': kbd->kc = 0x2c; break;
|
||||
case 'r': kbd->kc = 0x2d; break;
|
||||
case '5': kbd->kc = 0x2e; break;
|
||||
case 'n': kbd->kc = 0x31; break;
|
||||
case 'b': kbd->kc = 0x32; break;
|
||||
case 'h': kbd->kc = 0x33; break;
|
||||
case 'g': kbd->kc = 0x34; break;
|
||||
case 'y': kbd->kc = 0x35; break;
|
||||
case '6': kbd->kc = 0x36; break;
|
||||
case 'm': kbd->kc = 0x3a; break;
|
||||
case 'j': kbd->kc = 0x3b; break;
|
||||
case 'u': kbd->kc = 0x3c; break;
|
||||
case '7': kbd->kc = 0x3d; break;
|
||||
case '8': kbd->kc = 0x3e; break;
|
||||
case ',': kbd->kc = 0x41; break;
|
||||
case 'k': kbd->kc = 0x42; break;
|
||||
case 'i': kbd->kc = 0x43; break;
|
||||
case 'o': kbd->kc = 0x44; break;
|
||||
case '0': kbd->kc = 0x45; break;
|
||||
case '9': kbd->kc = 0x46; break;
|
||||
case '.': kbd->kc = 0x49; break;
|
||||
case '/': kbd->kc = 0x4a; break;
|
||||
case 'l': kbd->kc = 0x4b; break;
|
||||
case ';': kbd->kc = 0x4c; break;
|
||||
case 'p': kbd->kc = 0x4d; break;
|
||||
case '-': kbd->kc = 0x4e; break;
|
||||
case '\'': kbd->kc = 0x52; break;
|
||||
case '[': kbd->kc = 0x54; break;
|
||||
case '=': kbd->kc = 0x55; break;
|
||||
case '\r': kbd->kc = 0x5a; break;
|
||||
case ']': kbd->kc = 0x5b; break;
|
||||
case '\\': kbd->kc = 0x5d; break;
|
||||
case '\b': kbd->kc = 0x66; break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t kbd_rd(Kbd *kbd)
|
||||
{
|
||||
// There are 3 modes for reading the kbd:
|
||||
// 1. TH = highz: we're polling the the existence of a KC
|
||||
// 2. TH = low: return low nibble
|
||||
// 3. TH = high: return high nibble and reset KC
|
||||
uint8_t res = 0;
|
||||
uint8_t tosend = kbd->kc;
|
||||
if (kbd->breaking) {
|
||||
tosend = 0xf0;
|
||||
}
|
||||
if (*kbd->TH == TRI_HIGHZ) { // polling
|
||||
if (!kbd->kc) {
|
||||
res = 0x10; // indicate KC absence;
|
||||
}
|
||||
} else if (*kbd->TH == TRI_LOW) { // TH selected
|
||||
res = tosend & 0xf;
|
||||
} else {
|
||||
res = tosend >> 4;
|
||||
if (kbd->breaking) {
|
||||
kbd->breaking = false;
|
||||
} else {
|
||||
kbd->kc = 0;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
16
emul/hw/sms/kbd.h
Normal file
16
emul/hw/sms/kbd.h
Normal file
@ -0,0 +1,16 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "port.h"
|
||||
|
||||
#define KBD_BUFSZ 0x10
|
||||
|
||||
typedef struct {
|
||||
uint8_t kc; // last keycode to be pressed. 0 means none.
|
||||
bool breaking; // whether we should send 0xf0 before kc
|
||||
Tristate *TH;
|
||||
} Kbd;
|
||||
|
||||
void kbd_init(Kbd *kbd, Tristate *TH);
|
||||
void kbd_pressshift(Kbd *kbd, bool ispressed);
|
||||
void kbd_presskey(Kbd *kbd, uint8_t keycode);
|
||||
uint8_t kbd_rd(Kbd *kbd);
|
@ -11,6 +11,7 @@
|
||||
#include "vdp.h"
|
||||
#include "port.h"
|
||||
#include "pad.h"
|
||||
#include "kbd.h"
|
||||
|
||||
#define RAMSTART 0xc000
|
||||
#define VDP_CMD_PORT 0xbf
|
||||
@ -36,6 +37,8 @@ static VDP vdp;
|
||||
static bool vdp_changed;
|
||||
static Ports ports;
|
||||
static Pad pad;
|
||||
static Kbd kbd;
|
||||
static bool use_kbd = false;
|
||||
|
||||
static uint8_t iord_vdp_cmd()
|
||||
{
|
||||
@ -62,6 +65,11 @@ static uint8_t iord_pad()
|
||||
return pad_rd(&pad);
|
||||
}
|
||||
|
||||
static uint8_t iord_kbd()
|
||||
{
|
||||
return kbd_rd(&kbd);
|
||||
}
|
||||
|
||||
static void iowr_vdp_cmd(uint8_t val)
|
||||
{
|
||||
vdp_cmd_wr(&vdp, val);
|
||||
@ -159,14 +167,24 @@ void draw_pixels()
|
||||
static bool _handle_keypress(xcb_generic_event_t *e)
|
||||
{
|
||||
xcb_key_press_event_t *ev = (xcb_key_press_event_t *)e;
|
||||
if (ev->detail == 0x09) { // ESC
|
||||
return true;
|
||||
}
|
||||
bool ispressed = e->response_type == XCB_KEY_PRESS;
|
||||
// change keycode into symbol
|
||||
xcb_get_keyboard_mapping_reply_t* km = xcb_get_keyboard_mapping_reply(
|
||||
conn, xcb_get_keyboard_mapping(conn, ev->detail, 1), NULL);
|
||||
if (km->length) {
|
||||
xcb_keysym_t* keysyms = (xcb_keysym_t*)(km + 1);
|
||||
if (use_kbd) {
|
||||
if ((keysyms[0] == XK_Shift_L) || (keysyms[0] == XK_Shift_R)) {
|
||||
kbd_pressshift(&kbd, ispressed);
|
||||
} else if (ispressed) {
|
||||
fprintf(stderr, "pressing %x\n", keysyms[0]);
|
||||
kbd_presskey(&kbd, keysyms[0]);
|
||||
}
|
||||
} else { // pad
|
||||
switch (keysyms[0]) {
|
||||
case XK_Escape: free(km); return true;
|
||||
case 'w':
|
||||
pad_setbtn(&pad, PAD_BTN_UP, ispressed);
|
||||
break;
|
||||
@ -193,6 +211,7 @@ static bool _handle_keypress(xcb_generic_event_t *e)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(km);
|
||||
return false;
|
||||
}
|
||||
@ -242,13 +261,30 @@ void event_loop()
|
||||
}
|
||||
}
|
||||
|
||||
static void usage()
|
||||
{
|
||||
fprintf(stderr, "Usage: ./sms [-k] /path/to/rom\n");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "Usage: ./sms /path/to/rom\n");
|
||||
if (argc < 2) {
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
FILE *fp = fopen(argv[1], "r");
|
||||
int ch;
|
||||
while ((ch = getopt(argc, argv, "k")) != -1) {
|
||||
switch (ch) {
|
||||
case 'k':
|
||||
use_kbd = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (optind != argc-1) {
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
FILE *fp = fopen(argv[optind], "r");
|
||||
if (fp == NULL) {
|
||||
fprintf(stderr, "Can't open %s\n", argv[1]);
|
||||
return 1;
|
||||
@ -268,12 +304,18 @@ int main(int argc, char *argv[])
|
||||
vdp_init(&vdp);
|
||||
vdp_changed = false;
|
||||
ports_init(&ports);
|
||||
ports.portA_rd = iord_pad;
|
||||
pad_init(&pad, &ports.THA);
|
||||
kbd_init(&kbd, &ports.THA);
|
||||
if (use_kbd) {
|
||||
ports.portA_rd = iord_kbd;
|
||||
} else {
|
||||
ports.portA_rd = iord_pad;
|
||||
}
|
||||
m->iord[VDP_CMD_PORT] = iord_vdp_cmd;
|
||||
m->iord[VDP_DATA_PORT] = iord_vdp_data;
|
||||
m->iord[PORTS_IO1_PORT] = iord_ports_io1;
|
||||
m->iord[PORTS_IO2_PORT] = iord_ports_io2;
|
||||
m->iord[PORTS_CTL_PORT] = iord_noop;
|
||||
m->iowr[VDP_CMD_PORT] = iowr_vdp_cmd;
|
||||
m->iowr[VDP_DATA_PORT] = iowr_vdp_data;
|
||||
m->iowr[PORTS_CTL_PORT] = iowr_ports_ctl;
|
||||
|
@ -105,7 +105,7 @@ to TH (and also the A/B on the '157). Q is hooked to PB0 and TL.
|
||||
We start with the base recipe and add a few things:
|
||||
|
||||
1. at the top: `SYSVARS 0x72 + CONSTANT PS2_MEM`
|
||||
2. After VDP load: `641 LOAD : (ps2kc) (ps2kcB) ;` (that binds us to port B)
|
||||
2. After VDP load: `621 LOAD : (ps2kc) (ps2kcB) ;` (that binds us to port B)
|
||||
3. Right after: `411 414 LOADR` (that gives us `(key)`)
|
||||
4. After `VDP$`: `PS2$`.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user