emul/sms: add PS/2 keyboard emulation

This commit is contained in:
Virgil Dupras 2020-10-24 11:18:48 -04:00
parent cc8068f8ab
commit 8cecd54410
8 changed files with 202 additions and 32 deletions

View File

@ -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) {}

View File

@ -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);

View File

@ -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`

View File

@ -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
View 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
View 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);

View File

@ -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,38 +167,49 @@ 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);
switch (keysyms[0]) {
case XK_Escape: free(km); return true;
case 'w':
pad_setbtn(&pad, PAD_BTN_UP, ispressed);
break;
case 'a':
pad_setbtn(&pad, PAD_BTN_LEFT, ispressed);
break;
case 's':
pad_setbtn(&pad, PAD_BTN_DOWN, ispressed);
break;
case 'd':
pad_setbtn(&pad, PAD_BTN_RIGHT, ispressed);
break;
case 'h':
pad_setbtn(&pad, PAD_BTN_A, ispressed);
break;
case 'j':
pad_setbtn(&pad, PAD_BTN_B, ispressed);
break;
case 'k':
pad_setbtn(&pad, PAD_BTN_C, ispressed);
break;
case 'l':
pad_setbtn(&pad, PAD_BTN_START, ispressed);
break;
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 'w':
pad_setbtn(&pad, PAD_BTN_UP, ispressed);
break;
case 'a':
pad_setbtn(&pad, PAD_BTN_LEFT, ispressed);
break;
case 's':
pad_setbtn(&pad, PAD_BTN_DOWN, ispressed);
break;
case 'd':
pad_setbtn(&pad, PAD_BTN_RIGHT, ispressed);
break;
case 'h':
pad_setbtn(&pad, PAD_BTN_A, ispressed);
break;
case 'j':
pad_setbtn(&pad, PAD_BTN_B, ispressed);
break;
case 'k':
pad_setbtn(&pad, PAD_BTN_C, ispressed);
break;
case 'l':
pad_setbtn(&pad, PAD_BTN_START, ispressed);
break;
}
}
}
free(km);
@ -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;

View File

@ -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$`.