diff --git a/README.md b/README.md index 3725710..83e720d 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ The other pin mappings are as follows: | Cassette Sense | RTS | ESP->PET | PIA1 PA4 | PIA1 PA5 | A suitable serial signal (RS-232-like) must be generated/decoded in software. -Make sure that you don't access the cassette port #1 while the adapter is +Make sure that you don't access the cassette port 1 while the adapter is connected. For programming info, refer to this excellent document: @@ -107,3 +107,15 @@ This program assumes that the driver is already loaded at start address $7000. 120 SYS (28675) 130 END ``` + +## VICE Emulation + +To help test the RS-232 driver, there is a patch for the Commodore computer +emulator VICE in the file vice-tape-rs232.patch. + +It is intended for VICE 3.6.1 and prints out decoded RS-232 transmissions +from the driver to the console. Reading data into the emulator is not +supported yet. + +To enable the emulated device, you need to open the "Tape port" settings +and select "RS-232 interface" for tape port 1. diff --git a/vice-tape-rs232.patch b/vice-tape-rs232.patch new file mode 100644 index 0000000..e026f91 --- /dev/null +++ b/vice-tape-rs232.patch @@ -0,0 +1,330 @@ +diff --git a/src/tapeport/Makefile.am b/src/tapeport/Makefile.am +index cd38b6d..cf10121 100644 +--- a/src/tapeport/Makefile.am ++++ b/src/tapeport/Makefile.am +@@ -30,4 +30,6 @@ libtapeport_a_SOURCES = \ + tapecart.h \ + tapecart-loader.h \ + tapeport.c \ +- tapeport.h ++ tapeport.h \ ++ tape-rs232.c \ ++ tape-rs232.h +diff --git a/src/tapeport/tape-rs232.c b/src/tapeport/tape-rs232.c +new file mode 100644 +index 0000000..2b66748 +--- /dev/null ++++ b/src/tapeport/tape-rs232.c +@@ -0,0 +1,268 @@ ++/* ++ * tape-rs232.h: RS-232 interface for the PET, connected to the tape port ++ * ++ * Written by ++ * Gregor Riepl ++ * ++ * This file is part of VICE, the Versatile Commodore Emulator. ++ * See README for copyright notice. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA ++ * 02111-1307 USA. ++ * ++ */ ++ ++#include "vice.h" ++ ++#include ++#include ++#include ++ ++#include "cmdline.h" ++#include "machine.h" ++#include "resources.h" ++#include "snapshot.h" ++#include "tapeport.h" ++#include "log.h" ++#include "charset.h" ++#include "alarm.h" ++#include "maincpu.h" ++ ++#include "tape-rs232.h" ++ ++ ++typedef struct { ++ /* true if the initialization routine has been called */ ++ bool initialized; ++ /* the line transmission rate in bit/s (includes frame bits) */ ++ unsigned int baudrate; ++ /* alarm callbacks */ ++ alarm_t *write_alarm; ++ alarm_t *read_alarm; ++ /* current state of the inputs/outputs */ ++ uint8_t write_level; ++ uint8_t read_level; ++ /* these are 10-bit shift registers (1 start bit + 8 data bits + 1 stop bit) */ ++ uint16_t write_register; ++ uint16_t read_register; ++ uint8_t write_shift; ++ uint8_t read_shift; ++} tape_rs232_state_t; ++static tape_rs232_state_t tape_rs232_state[TAPEPORT_MAX_PORTS] = { { false, }, }; ++ ++/* ------------------------------------------------------------------------- */ ++ ++/* device interface prototypes */ ++static void tape_rs232_powerup(int port); ++static int tape_rs232_enable(int port, int val); ++static void tape_rs232_motor(int port, int flag); ++static void tape_rs232_write(int port, int write_bit); ++static void tape_rs232_sense(int port, int sense); ++static void tape_rs232_read(int port, int val); ++/* callbacks */ ++static void tape_rs232_write_sample(CLOCK offset, void *data); ++ ++static tapeport_device_t tape_rs232_device = { ++ "RS-232 interface", /* device name */ ++ TAPEPORT_DEVICE_TYPE_SERIAL, /* device is a 'serial port' type device */ ++ VICE_MACHINE_PET, /* device works only on the PET */ ++ TAPEPORT_PORT_1_MASK | TAPEPORT_PORT_2_MASK, /* device works on tape port 1 and 2 */ ++ tape_rs232_enable, /* device enable function */ ++ tape_rs232_powerup, /* device specific hard reset function */ ++ NULL, /* NO device shutdown function */ ++ tape_rs232_motor, /* device specific set motor line function (DCE CTS) */ ++ tape_rs232_write, /* device specific set write line function (DCE RXD) */ ++ tape_rs232_sense, /* device specific set sense line function (DCE RTS) */ ++ tape_rs232_read, /* device specific set read line function (DCE TXD) */ ++ NULL, /* NO device snapshot write function */ ++ NULL /* NO device snapshot read function */ ++}; ++ ++/* ------------------------------------------------------------------------- */ ++ ++static int tape_rs232_enable(int port, int value) ++{ ++ bool enable = value != 0; ++ ++ if (port >= TAPEPORT_MAX_PORTS || port < 0) { ++ /* port no. out of range */ ++ return -1; ++ } ++ tape_rs232_state_t *dev = &tape_rs232_state[port]; ++ if (dev->initialized == enable) { ++ /* already (de)initialized */ ++ return 0; ++ } ++ ++ if (enable) { ++ /* TODO initialize */ ++ log_debug("tape_rs232: enable port %d", port); ++ dev->read_shift = 0; ++ dev->write_shift = 0; ++ /* TODO make the baud rate customizable */ ++ dev->baudrate = 300; ++ if (dev->write_alarm != NULL) { ++ alarm_destroy(dev->write_alarm); ++ } ++ dev->write_alarm = alarm_new(maincpu_alarm_context, "tape_rs232_write_sample", tape_rs232_write_sample, dev); ++ /* TODO */ ++ dev->read_alarm = NULL; ++ } else { ++ log_debug("tape_rs232: disable port %d", port); ++ /* ensure no alarms are pending, and clear them out */ ++ if (dev->write_alarm != NULL) { ++ alarm_unset(dev->write_alarm); ++ dev->write_alarm = NULL; ++ } ++ if (dev->read_alarm != NULL) { ++ alarm_unset(dev->read_alarm); ++ dev->read_alarm = NULL; ++ } ++ } ++ ++ tape_rs232_state[port].initialized = enable; ++ return 0; ++} ++ ++int tape_rs232_init(int amount) ++{ ++ log_debug("tape_rs232: initializing %d ports", amount); ++ /* clear all state data on startup */ ++ memset(tape_rs232_state, 0, sizeof(tape_rs232_state)); ++ return tapeport_device_register(TAPEPORT_DEVICE_RS232, &tape_rs232_device); ++} ++ ++/* ---------------------------------------------------------------------*/ ++ ++static void tape_rs232_powerup(int port) ++{ ++ if (port < 0 || port >= TAPEPORT_MAX_PORTS) { ++ log_error(LOG_ERR, "tape_rs232: invalid port %d", port); ++ return; ++ } ++ tape_rs232_state_t *dev = &tape_rs232_state[port]; ++ if (!dev->initialized) { ++ log_error(LOG_ERR, "tape_rs232: port %d not initialized", port); ++ return; ++ } ++ log_debug("tape_rs232: powerup port %d", port); ++ dev->read_shift = 0; ++ dev->write_shift = 0; ++ /* ensure no alarms are pending */ ++ if (dev->write_alarm != NULL) { ++ alarm_unset(dev->write_alarm); ++ } ++ if (dev->read_alarm != NULL) { ++ alarm_unset(dev->read_alarm); ++ } ++} ++ ++static void tape_rs232_write(int port, int write_bit) ++{ ++ if (port < 0 || port >= TAPEPORT_MAX_PORTS) { ++ log_error(LOG_ERR, "tape_rs232: invalid port %d", port); ++ return; ++ } ++ tape_rs232_state_t *dev = &tape_rs232_state[port]; ++ if (!dev->initialized) { ++ log_error(LOG_ERR, "tape_rs232: port %d not initialized", port); ++ return; ++ } ++ /* FIXME RS-232 bit-clocking must be done on a time base, not on level change interrupts */ ++ /* write_bit is actually the contents of VIA_PB, and the write bit is PB3, i.e. 0x08 */ ++ dev->write_level = (write_bit & 0x08) != 0 ? 1 : 0; ++ log_debug("tape_rs232: write port %d level %d", port, dev->write_level); ++ /* write in progress? */ ++ if (dev->write_shift == 0) { ++ /* no, start a new transmission - but only if this was a high->low transition (start bit) */ ++ if (dev->write_level == 0) { ++ /* initialize the shift register */ ++ dev->write_shift = 10; ++ dev->write_register = 0; ++ /* arm the first baud rate timer: sample in the middle of each bit, ++ * so the delay must be half a baud clock cycle */ ++ clock_t delay = machine_get_cycles_per_second() / (dev->baudrate * 2); ++ log_debug("tape_rs232: schedule start bit alarm after %d clock cycles", delay); ++ alarm_set(dev->write_alarm, (CLOCK) (maincpu_clk + delay)); ++ } ++ } ++} ++ ++static void tape_rs232_write_sample(CLOCK offset, void *data) ++{ ++ if (data == NULL) { ++ return; ++ } ++ tape_rs232_state_t *dev = (tape_rs232_state_t *) data; ++ /* is this the first bit of a transmission? */ ++ if (dev->write_shift == 10) { ++ /* yes, reconfigure the timer to run at baudrate intervals */ ++ alarm_unset(dev->write_alarm); ++ clock_t delay = machine_get_cycles_per_second() / dev->baudrate; ++ log_debug("tape_rs232: schedule regular bit alarm after %d clock cycles", delay); ++ alarm_set(dev->write_alarm, (CLOCK) (maincpu_clk + delay)); ++ } ++ /* shift in the next bit */ ++ dev->write_register = (dev->write_register << 1) | dev->write_level; ++ /* and decrement the counter */ ++ --dev->write_shift; ++ /* transmission complete? */ ++ if (dev->write_shift == 0) { ++ /* yes, unarm the timer */ ++ alarm_unset(dev->write_alarm); ++ /* check if start and stop bits are valid */ ++ if ((dev->write_register & 0x0200) == 0x0000 && (dev->write_register & 0x0001) == 0x0001) { ++ /* yes, output data byte */ ++ uint8_t wbyte = (uint8_t) (dev->write_register >> 1); ++ /* utf-8 codes generated by the petcii conversion routine can be max. 4 bytes (plus terminator) */ ++ char udata[5]; ++ size_t udatalen = charset_ucs_to_utf8((uint8_t *) udata, wbyte, sizeof(udata)); ++ if (udatalen <= 0) { ++ log_debug("tape_rs232: write hex 0x%02x", wbyte); ++ } else { ++ /* ensure there is a terminator */ ++ udata[udatalen] = 0; ++ log_debug("tape_rs232: write hex 0x%02x char %s", wbyte, udata); ++ } ++ } else { ++ log_warning(LOG_ERR, "tape_rs232: invalid RS-232 transmission 0x%04x", dev->write_register); ++ } ++ } ++} ++ ++static void tape_rs232_read(int port, int val) ++{ ++ if (port < 0 || port >= TAPEPORT_MAX_PORTS) { ++ log_error(LOG_ERR, "tape_rs232: invalid port %d", port); ++ return; ++ } ++ tape_rs232_state_t *dev = &tape_rs232_state[port]; ++ if (!dev->initialized) { ++ log_error(LOG_ERR, "tape_rs232: port %d not initialized", port); ++ return; ++ } ++ log_debug("tape_rs232: read port %d level %d", port, val); ++ /* TODO */ ++} ++ ++/* FIXME RTS/CTS not supported yet */ ++static void tape_rs232_motor(int port, int flag) ++{ ++ /*log_debug("tape_rs232: motor port %d level %d", port, flag);*/ ++} ++static void tape_rs232_sense(int port, int sense) ++{ ++ /*log_debug("tape_rs232: sense port %d level %d", port, sense);*/ ++} +diff --git a/src/tapeport/tape-rs232.h b/src/tapeport/tape-rs232.h +new file mode 100644 +index 0000000..eac1924 +--- /dev/null ++++ b/src/tapeport/tape-rs232.h +@@ -0,0 +1,38 @@ ++/* ++ * tape-rs232.h: RS-232 interface for the PET, connected to the tape port ++ * ++ * Written by ++ * Gregor Riepl ++ * ++ * This file is part of VICE, the Versatile Commodore Emulator. ++ * See README for copyright notice. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA ++ * 02111-1307 USA. ++ * ++ */ ++ ++#ifndef VICE_TAPE_RS232_H ++#define VICE_TAPE_RS232_H ++ ++#include "types.h" ++ ++/* Initialize data structures for the emulated serial ports. ++ * amount=number of tape devices in the system ++ * (max=2, only two tape interfaces are available in the PET) ++ */ ++extern int tape_rs232_init(int amount); ++ ++#endif