checkpoint: keyboard copro code works -ish with dvhstx text mode
This commit is contained in:
parent
f356fba7d8
commit
a6851c595e
21 changed files with 9688 additions and 1 deletions
|
|
@ -1 +1 @@
|
||||||
depends=Adafruit ILI9341, Adafruit BusIO, SD, Adafruit NeoPixel, Adafruit VS1053 Library, Adafruit BluefruitLE nRF51, Adafruit seesaw Library, Ethernet, Adafruit IO Arduino, FastLED, Adafruit LiquidCrystal, Adafruit SoftServo, TinyWireM, Adafruit AM radio library, WaveHC, Adafruit LED Backpack Library, MAX31850 OneWire, Adafruit VC0706 Serial Camera Library, RTClib, Adafruit SleepyDog Library, Adafruit Thermal Printer Library, Adafruit Zero I2S Library, Adafruit EPD, Adafruit SSD1351 library, Adafruit FONA Library, Adafruit Motor Shield V2 Library, Adafruit NeoMatrix, Adafruit Soundboard library, Adafruit Circuit Playground, ArduinoJson, Adafruit TCS34725, Adafruit Pixie, Adafruit GPS Library, TinyGPS, WiFi101, Adafruit DotStar, Adafruit Si7021 Library, Adafruit WS2801 Library, Mouse, Keyboard, Time, IRremote, Adafruit LSM9DS0 Library, Adafruit Arcada Library, MIDIUSB, PubSubClient, Adafruit LIS2MDL, Adafruit NeoPXL8, Adafruit MCP23017 Arduino Library, Adafruit MLX90640, LiquidCrystal, Adafruit NeoTrellis M4 Library, RGB matrix Panel, Adafruit MLX90614 Library, Adafruit RGB LCD Shield Library, MAX6675 library, Adafruit MP3, Adafruit Keypad, Adafruit Arcada GifDecoder, Keypad, Neosegment, Encoder, Adafruit TiCoServo, Adafruit Trellis Library, FauxmoESP, Adafruit LSM303 Accel, Adafruit LSM303DLH Mag, Adafruit LSM303DLHC, CapacitiveSensor, Adafruit Zero PDM Library, Adafruit DMA neopixel library, elapsedMillis, DST RTC, Adafruit SHARP Memory Display, Adafruit SPIFlash, BSEC Software Library, WiiChuck, Adafruit DPS310, Adafruit AHTX0, RotaryEncoder, Adafruit MCP9808 Library, LSM303, Adafruit Protomatter, Adafruit IS31FL3741 Library, Sensirion I2C SCD4x, Adafruit TestBed, Bounce2, Adafruit AHRS, Adafruit DRV2605 Library, STM32duino VL53L4CD, PicoDVI - Adafruit Fork, Adafruit MMA8451 Library, Adafruit TSC2007, GFX Library for Arduino, Adafruit PyCamera Library, Adafruit ADG72x, Adafruit BNO055, Adafruit SHT4x Library, Adafruit VCNL4200 Library, Adafruit GC9A01A
|
depends=Adafruit ILI9341, Adafruit BusIO, SD, Adafruit NeoPixel, Adafruit VS1053 Library, Adafruit BluefruitLE nRF51, Adafruit seesaw Library, Ethernet, Adafruit IO Arduino, FastLED, Adafruit LiquidCrystal, Adafruit SoftServo, TinyWireM, Adafruit AM radio library, WaveHC, Adafruit LED Backpack Library, MAX31850 OneWire, Adafruit VC0706 Serial Camera Library, RTClib, Adafruit SleepyDog Library, Adafruit Thermal Printer Library, Adafruit Zero I2S Library, Adafruit EPD, Adafruit SSD1351 library, Adafruit FONA Library, Adafruit Motor Shield V2 Library, Adafruit NeoMatrix, Adafruit Soundboard library, Adafruit Circuit Playground, ArduinoJson, Adafruit TCS34725, Adafruit Pixie, Adafruit GPS Library, TinyGPS, WiFi101, Adafruit DotStar, Adafruit Si7021 Library, Adafruit WS2801 Library, Mouse, Keyboard, Time, IRremote, Adafruit LSM9DS0 Library, Adafruit Arcada Library, MIDIUSB, PubSubClient, Adafruit LIS2MDL, Adafruit NeoPXL8, Adafruit MCP23017 Arduino Library, Adafruit MLX90640, LiquidCrystal, Adafruit NeoTrellis M4 Library, RGB matrix Panel, Adafruit MLX90614 Library, Adafruit RGB LCD Shield Library, MAX6675 library, Adafruit MP3, Adafruit Keypad, Adafruit Arcada GifDecoder, Keypad, Neosegment, Encoder, Adafruit TiCoServo, Adafruit Trellis Library, FauxmoESP, Adafruit LSM303 Accel, Adafruit LSM303DLH Mag, Adafruit LSM303DLHC, CapacitiveSensor, Adafruit Zero PDM Library, Adafruit DMA neopixel library, elapsedMillis, DST RTC, Adafruit SHARP Memory Display, Adafruit SPIFlash, BSEC Software Library, WiiChuck, Adafruit DPS310, Adafruit AHTX0, RotaryEncoder, Adafruit MCP9808 Library, LSM303, Adafruit Protomatter, Adafruit IS31FL3741 Library, Sensirion I2C SCD4x, Adafruit TestBed, Bounce2, Adafruit AHRS, Adafruit DRV2605 Library, STM32duino VL53L4CD, PicoDVI - Adafruit Fork, Adafruit MMA8451 Library, Adafruit TSC2007, GFX Library for Arduino, Adafruit PyCamera Library, Adafruit ADG72x, Adafruit BNO055, Adafruit SHT4x Library, Adafruit VCNL4200 Library, Adafruit GC9A01A, Adafruit DVI HSTX
|
||||||
|
|
|
||||||
265
runcpm-rp2350-hstx-usb/keyboard-copro/keyboard-copro.ino
Normal file
265
runcpm-rp2350-hstx-usb/keyboard-copro/keyboard-copro.ino
Normal file
|
|
@ -0,0 +1,265 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// pio-usb is required for rp2040 host
|
||||||
|
#include "pio_usb.h"
|
||||||
|
#include "Adafruit_TinyUSB.h"
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "Adafruit_dvhstx.h"
|
||||||
|
|
||||||
|
DVHSTXText3 display(DVHSTX_PINOUT_DEFAULT);
|
||||||
|
|
||||||
|
// Pin D+ for host, D- = D+ + 1
|
||||||
|
#ifndef PIN_USB_HOST_DP
|
||||||
|
#define PIN_USB_HOST_DP 16
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Pin for enabling Host VBUS. comment out if not used
|
||||||
|
#ifndef PIN_5V_EN
|
||||||
|
#define PIN_5V_EN 18
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef PIN_5V_EN_STATE
|
||||||
|
#define PIN_5V_EN_STATE 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// USB Host object
|
||||||
|
Adafruit_USBH_Host USBHost;
|
||||||
|
|
||||||
|
// Serial output for RunCPM
|
||||||
|
SerialPIO pio_serial(1 /* RX of the sibling board */, SerialPIO::NOPIN);
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
// ensure text generation interrupt takes place on core0
|
||||||
|
display.begin();
|
||||||
|
display.println("Hello hstx\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup1() {
|
||||||
|
while(!display) ;
|
||||||
|
delay(10);
|
||||||
|
display.println("Hello hstx in setup1\n");
|
||||||
|
// override tools menu CPU frequency setting
|
||||||
|
//set_sys_clock_khz(264'000, true);
|
||||||
|
|
||||||
|
Serial.begin(115200);
|
||||||
|
#if 0
|
||||||
|
while ( !Serial ) delay(10); // wait for native usb
|
||||||
|
Serial.println("Core1 setup to run TinyUSB host with pio-usb");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef PIN_5V_EN
|
||||||
|
pinMode(PIN_5V_EN, OUTPUT);
|
||||||
|
digitalWrite(PIN_5V_EN, PIN_5V_EN_STATE);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG;
|
||||||
|
pio_cfg.pin_dp = PIN_USB_HOST_DP;
|
||||||
|
pio_cfg.tx_ch = dma_claim_unused_channel(true);
|
||||||
|
dma_channel_unclaim(pio_cfg.tx_ch);
|
||||||
|
|
||||||
|
USBHost.configure_pio_usb(1, &pio_cfg);
|
||||||
|
|
||||||
|
// run host stack on controller (rhport) 1
|
||||||
|
// Note: For rp2040 pico-pio-usb, calling USBHost.begin() on core1 will have most of the
|
||||||
|
// host bit-banging processing works done in core1 to free up core0 for other works
|
||||||
|
USBHost.begin(1);
|
||||||
|
|
||||||
|
// this `begin` is a void function, no way to check for failure!
|
||||||
|
pio_serial.begin(115200);
|
||||||
|
display.println("end of setup1\n");
|
||||||
|
display.show_cursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
int old_ascii = -1;
|
||||||
|
uint32_t repeat_timeout;
|
||||||
|
// this matches Linux default of 500ms to first repeat, 1/20s thereafter
|
||||||
|
const uint32_t default_repeat_time = 50;
|
||||||
|
const uint32_t initial_repeat_time = 500;
|
||||||
|
|
||||||
|
void send_ascii(uint8_t code, uint32_t repeat_time=default_repeat_time) {
|
||||||
|
old_ascii = code;
|
||||||
|
repeat_timeout = millis() + repeat_time;
|
||||||
|
if (code >= 32 && code < 127) {
|
||||||
|
display.printf("%c", code);
|
||||||
|
} else {
|
||||||
|
display.printf("\\x%02x", code);
|
||||||
|
}
|
||||||
|
pio_serial.write(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop1()
|
||||||
|
{
|
||||||
|
static bool last_serial;
|
||||||
|
if (!last_serial && Serial) {
|
||||||
|
last_serial = true;
|
||||||
|
Serial.println("Hello host serial");
|
||||||
|
}
|
||||||
|
uint32_t now = millis();
|
||||||
|
uint32_t deadline = repeat_timeout - now;
|
||||||
|
if (old_ascii >= 0 && deadline > INT32_MAX) {
|
||||||
|
send_ascii(old_ascii);
|
||||||
|
deadline = repeat_timeout - now;
|
||||||
|
} else if (old_ascii < 0) {
|
||||||
|
deadline = UINT32_MAX;
|
||||||
|
}
|
||||||
|
tuh_task_ext(deadline, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when device with hid interface is mounted
|
||||||
|
// Report descriptor is also available for use.
|
||||||
|
// tuh_hid_parse_report_descriptor() can be used to parse common/simple enough
|
||||||
|
// descriptor. Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE,
|
||||||
|
// it will be skipped therefore report_desc = NULL, desc_len = 0
|
||||||
|
void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_report, uint16_t desc_len) {
|
||||||
|
(void)desc_report;
|
||||||
|
(void)desc_len;
|
||||||
|
uint16_t vid, pid;
|
||||||
|
tuh_vid_pid_get(dev_addr, &vid, &pid);
|
||||||
|
|
||||||
|
Serial.printf("HID device address = %d, instance = %d is mounted\r\n", dev_addr, instance);
|
||||||
|
Serial.printf("VID = %04x, PID = %04x\r\n", vid, pid);
|
||||||
|
|
||||||
|
uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);
|
||||||
|
if (itf_protocol == HID_ITF_PROTOCOL_KEYBOARD) {
|
||||||
|
Serial.printf("HID Keyboard\r\n");
|
||||||
|
if (!tuh_hid_receive_report(dev_addr, instance)) {
|
||||||
|
Serial.printf("Error: cannot request to receive report\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when device with hid interface is un-mounted
|
||||||
|
void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) {
|
||||||
|
Serial.printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define FLAG_ALPHABETIC (1)
|
||||||
|
#define FLAG_SHIFT (2)
|
||||||
|
#define FLAG_NUMLOCK (4)
|
||||||
|
#define FLAG_CTRL (8)
|
||||||
|
#define FLAG_LUT (16)
|
||||||
|
|
||||||
|
const char * const lut[] = {
|
||||||
|
"!@#$%^&*()", /* 0 - shifted numeric keys */
|
||||||
|
"\r\x1b\10\t -=[]\\#;'`,./", /* 1 - symbol keys */
|
||||||
|
"\n\x1b\177\t _+{}|~:\"~<>?", /* 2 - shifted */
|
||||||
|
"\12\13\10\22", /* 3 - arrow keys RLDU */
|
||||||
|
"/*-+\n1234567890.", /* 4 - keypad w/numlock */
|
||||||
|
"/*-+\n\xff\2\xff\4\xff\3\xff\1\xff\xff.", /* 5 - keypad w/o numlock */
|
||||||
|
};
|
||||||
|
|
||||||
|
struct keycode_mapper {
|
||||||
|
uint8_t first, last, code, flags;
|
||||||
|
} keycode_to_ascii[] = {
|
||||||
|
{ HID_KEY_A, HID_KEY_Z, 'a', FLAG_ALPHABETIC, },
|
||||||
|
|
||||||
|
{ HID_KEY_1, HID_KEY_9, 0, FLAG_SHIFT | FLAG_LUT, },
|
||||||
|
{ HID_KEY_1, HID_KEY_9, '1', 0, },
|
||||||
|
{ HID_KEY_0, HID_KEY_0, ')', FLAG_SHIFT, },
|
||||||
|
{ HID_KEY_0, HID_KEY_0, '0', 0, },
|
||||||
|
|
||||||
|
{ HID_KEY_ENTER, HID_KEY_ENTER, '\n', FLAG_CTRL },
|
||||||
|
{ HID_KEY_ENTER, HID_KEY_SLASH, 2, FLAG_SHIFT | FLAG_LUT, },
|
||||||
|
{ HID_KEY_ENTER, HID_KEY_SLASH, 1, FLAG_LUT, },
|
||||||
|
|
||||||
|
{ HID_KEY_F1, HID_KEY_F1, 0x1e, 0, }, // help key on xerox 820 kbd
|
||||||
|
|
||||||
|
{ HID_KEY_ARROW_RIGHT, HID_KEY_ARROW_UP, 3, FLAG_LUT },
|
||||||
|
|
||||||
|
{ HID_KEY_KEYPAD_DIVIDE, HID_KEY_KEYPAD_DECIMAL, 4, FLAG_NUMLOCK | FLAG_LUT },
|
||||||
|
{ HID_KEY_KEYPAD_DIVIDE, HID_KEY_KEYPAD_DECIMAL, 5, FLAG_LUT },
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
bool report_contains(const hid_keyboard_report_t &report, uint8_t key) {
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
if (report.keycode[i] == key) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hid_keyboard_report_t old_report;
|
||||||
|
|
||||||
|
static bool caps, num;
|
||||||
|
static uint8_t old_leds;
|
||||||
|
void process_event(uint8_t dev_addr, uint8_t instance, const hid_keyboard_report_t &report) {
|
||||||
|
bool alt = report.modifier & 0x44;
|
||||||
|
bool shift = report.modifier & 0x22;
|
||||||
|
bool ctrl = report.modifier & 0x11;
|
||||||
|
uint8_t code = 0;
|
||||||
|
|
||||||
|
if (report.keycode[0] == 1 && report.keycode[1] == 1) {
|
||||||
|
// keyboard says it has exceeded max kro
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// something was pressed or release, so cancel any key repeat
|
||||||
|
old_ascii = -1;
|
||||||
|
|
||||||
|
for (auto keycode : report.keycode) {
|
||||||
|
if (keycode == 0) continue;
|
||||||
|
if (report_contains(old_report, keycode)) continue;
|
||||||
|
|
||||||
|
/* key is newly pressed */
|
||||||
|
if (keycode == HID_KEY_NUM_LOCK) {
|
||||||
|
Serial.println("toggle numlock");
|
||||||
|
num = !num;
|
||||||
|
} else if (keycode == HID_KEY_CAPS_LOCK) {
|
||||||
|
Serial.println("toggle capslock");
|
||||||
|
caps = !caps;
|
||||||
|
} else {
|
||||||
|
for (const auto &mapper : keycode_to_ascii) {
|
||||||
|
if (!(keycode >= mapper.first && keycode <= mapper.last))
|
||||||
|
continue;
|
||||||
|
if (mapper.flags & FLAG_SHIFT && !shift)
|
||||||
|
continue;
|
||||||
|
if (mapper.flags & FLAG_NUMLOCK && !num)
|
||||||
|
continue;
|
||||||
|
if (mapper.flags & FLAG_CTRL && !ctrl)
|
||||||
|
continue;
|
||||||
|
if (mapper.flags & FLAG_LUT) {
|
||||||
|
code = lut[mapper.code][keycode - mapper.first];
|
||||||
|
} else {
|
||||||
|
code = keycode - mapper.first + mapper.code;
|
||||||
|
}
|
||||||
|
if (mapper.flags & FLAG_ALPHABETIC) {
|
||||||
|
if (shift ^ caps) {
|
||||||
|
code ^= ('a' ^ 'A');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ctrl) code &= 0x1f;
|
||||||
|
if (alt) code ^= 0x80;
|
||||||
|
send_ascii(code, initial_repeat_time);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t leds = (caps << 1) | num;
|
||||||
|
if (leds != old_leds) {
|
||||||
|
old_leds = leds;
|
||||||
|
Serial.printf("Send LEDs report %d (dev:instance = %d:%d)\r\n", leds, dev_addr, instance);
|
||||||
|
// no worky
|
||||||
|
auto r = tuh_hid_set_report(dev_addr, instance/*idx*/, 0/*report_id*/, HID_REPORT_TYPE_OUTPUT/*report_type*/, &old_leds, sizeof(old_leds));
|
||||||
|
Serial.printf("set_report() -> %d\n", (int)r);
|
||||||
|
}
|
||||||
|
old_report = report;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when received report from device via interrupt endpoint
|
||||||
|
void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len) {
|
||||||
|
if ( len != sizeof(hid_keyboard_report_t) ) {
|
||||||
|
Serial.printf("report len = %u NOT 8, probably something wrong !!\r\n", len);
|
||||||
|
} else {
|
||||||
|
process_event(dev_addr, instance, *(hid_keyboard_report_t*)report);
|
||||||
|
}
|
||||||
|
// continue to request to receive report
|
||||||
|
if (!tuh_hid_receive_report(dev_addr, instance)) {
|
||||||
|
Serial.printf("Error: cannot request to receive report\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
21
runcpm-rp2350-hstx-usb/runcpm-pico/LICENSE
Normal file
21
runcpm-rp2350-hstx-usb/runcpm-pico/LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 Mockba the Borg
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
31
runcpm-rp2350-hstx-usb/runcpm-pico/README.md
Normal file
31
runcpm-rp2350-hstx-usb/runcpm-pico/README.md
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
|
||||||
|
|
||||||
|
SPDX-License-Identifier: MIT
|
||||||
|
-->
|
||||||
|
|
||||||
|
This is a port of runcpm to the raspberry pi pico.
|
||||||
|
|
||||||
|
It is based on:
|
||||||
|
* [RunCPM](https://github.com/MockbaTheBorg/RunCPM/)
|
||||||
|
* [RunCPM_RPi_Pico](https://github.com/guidol70/RunCPM_RPi_Pico)
|
||||||
|
|
||||||
|
It works on a Raspberry Pi Pico (or Pico W). It uses the internal flash
|
||||||
|
for storage, and can be mounted as USB storage.
|
||||||
|
|
||||||
|
If your Pico is placed on the Pico DV carrier board, you also get a 100x30
|
||||||
|
character screen to enjoy your CP/M output on!
|
||||||
|
|
||||||
|
First, build for your device. You must
|
||||||
|
* Use the Philhower Pico Core
|
||||||
|
* In the Tools menu, select
|
||||||
|
* A flash size option that includes at least 512kB for filesystem
|
||||||
|
* USB Stack: Adafruit TinyUSB
|
||||||
|
|
||||||
|
After it boots the first time, you need to
|
||||||
|
* Format the flash device on your host computer
|
||||||
|
* Create the folder "<DEVICE>/A/0"
|
||||||
|
* Put something useful in that folder, such as [Zork](http://www.retroarchive.org/cpm/games/zork123_80.zip)
|
||||||
|
* Files must respect the "8.3" naming convention (8 letters filename + 3 letters extension) and be all uppercase
|
||||||
|
* Safely eject the drive, then reset the emulator.
|
||||||
|
* Now at the "A0>" prompt you can run "ZORK1".
|
||||||
531
runcpm-rp2350-hstx-usb/runcpm-pico/abstraction_arduino.h
Normal file
531
runcpm-rp2350-hstx-usb/runcpm-pico/abstraction_arduino.h
Normal file
|
|
@ -0,0 +1,531 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Mockba the Borg
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#ifndef ABSTRACT_H
|
||||||
|
#define ABSTRACT_H
|
||||||
|
|
||||||
|
#ifdef PROFILE
|
||||||
|
#define printf(a, b) Serial.println(b)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// #if defined ARDUINO_SAM_DUE || defined ADAFRUIT_GRAND_CENTRAL_M4
|
||||||
|
#define HostOS 0x01
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
#if defined CORE_TEENSY
|
||||||
|
#define HostOS 0x04
|
||||||
|
#endif
|
||||||
|
#if defined ESP32
|
||||||
|
#define HostOS 0x05
|
||||||
|
#endif
|
||||||
|
#if defined _STM32_DEF_
|
||||||
|
#define HostOS 0x06
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Memory abstraction functions */
|
||||||
|
/*===============================================================================*/
|
||||||
|
bool _RamLoad(char* filename, uint16 address) {
|
||||||
|
File32 f;
|
||||||
|
bool result = false;
|
||||||
|
|
||||||
|
if (f = SD.open(filename, FILE_READ)) {
|
||||||
|
while (f.available())
|
||||||
|
_RamWrite(address++, f.read());
|
||||||
|
f.close();
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Filesystem (disk) abstraction fuctions */
|
||||||
|
/*===============================================================================*/
|
||||||
|
File32 rootdir, userdir;
|
||||||
|
#define FOLDERCHAR '/'
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8 dr;
|
||||||
|
uint8 fn[8];
|
||||||
|
uint8 tp[3];
|
||||||
|
uint8 ex, s1, s2, rc;
|
||||||
|
uint8 al[16];
|
||||||
|
uint8 cr, r0, r1, r2;
|
||||||
|
} CPM_FCB;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8 dr;
|
||||||
|
uint8 fn[8];
|
||||||
|
uint8 tp[3];
|
||||||
|
uint8 ex, s1, s2, rc;
|
||||||
|
uint8 al[16];
|
||||||
|
} CPM_DIRENTRY;
|
||||||
|
|
||||||
|
static DirFat_t fileDirEntry;
|
||||||
|
|
||||||
|
bool _sys_exists(uint8* filename) {
|
||||||
|
return(SD.exists((const char *)filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
File32 _sys_fopen_w(uint8* filename) {
|
||||||
|
return(SD.open((char*)filename, O_CREAT | O_WRITE));
|
||||||
|
}
|
||||||
|
|
||||||
|
int _sys_fputc(uint8 ch, File32& f) {
|
||||||
|
return(f.write(ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _sys_fflush(File32& f) {
|
||||||
|
f.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _sys_fclose(File32& f) {
|
||||||
|
f.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
int _sys_select(uint8* disk) {
|
||||||
|
uint8 result = FALSE;
|
||||||
|
File32 f;
|
||||||
|
|
||||||
|
digitalWrite(LED, HIGH ^ LEDinv);
|
||||||
|
if (f = SD.open((char*)disk, O_READ)) {
|
||||||
|
if (f.isDirectory())
|
||||||
|
result = TRUE;
|
||||||
|
f.close();
|
||||||
|
}
|
||||||
|
digitalWrite(LED, LOW ^ LEDinv);
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
long _sys_filesize(uint8* filename) {
|
||||||
|
long l = -1;
|
||||||
|
File32 f;
|
||||||
|
|
||||||
|
digitalWrite(LED, HIGH ^ LEDinv);
|
||||||
|
if (f = SD.open((char*)filename, O_RDONLY)) {
|
||||||
|
l = f.size();
|
||||||
|
f.close();
|
||||||
|
}
|
||||||
|
digitalWrite(LED, LOW ^ LEDinv);
|
||||||
|
return(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _sys_openfile(uint8* filename) {
|
||||||
|
File32 f;
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
digitalWrite(LED, HIGH ^ LEDinv);
|
||||||
|
f = SD.open((char*)filename, O_READ);
|
||||||
|
if (f) {
|
||||||
|
f.dirEntry(&fileDirEntry);
|
||||||
|
f.close();
|
||||||
|
result = 1;
|
||||||
|
}
|
||||||
|
digitalWrite(LED, LOW ^ LEDinv);
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _sys_makefile(uint8* filename) {
|
||||||
|
File32 f;
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
digitalWrite(LED, HIGH ^ LEDinv);
|
||||||
|
f = SD.open((char*)filename, O_CREAT | O_WRITE);
|
||||||
|
if (f) {
|
||||||
|
f.close();
|
||||||
|
result = 1;
|
||||||
|
}
|
||||||
|
digitalWrite(LED, LOW ^ LEDinv);
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _sys_deletefile(uint8* filename) {
|
||||||
|
digitalWrite(LED, HIGH ^ LEDinv);
|
||||||
|
return(SD.remove((char*)filename));
|
||||||
|
digitalWrite(LED, LOW ^ LEDinv);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _sys_renamefile(uint8* filename, uint8* newname) {
|
||||||
|
File32 f;
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
digitalWrite(LED, HIGH ^ LEDinv);
|
||||||
|
f = SD.open((char*)filename, O_WRITE | O_APPEND);
|
||||||
|
if (f) {
|
||||||
|
if (f.rename((char*)newname)) {
|
||||||
|
f.close();
|
||||||
|
result = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
digitalWrite(LED, LOW ^ LEDinv);
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUGLOG
|
||||||
|
void _sys_logbuffer(uint8* buffer) {
|
||||||
|
#ifdef CONSOLELOG
|
||||||
|
puts((char*)buffer);
|
||||||
|
#else
|
||||||
|
File32 f;
|
||||||
|
uint8 s = 0;
|
||||||
|
while (*(buffer + s)) // Computes buffer size
|
||||||
|
++s;
|
||||||
|
if (f = SD.open(LogName, O_CREAT | O_APPEND | O_WRITE)) {
|
||||||
|
f.write(buffer, s);
|
||||||
|
f.flush();
|
||||||
|
f.close();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool _sys_extendfile(char* fn, unsigned long fpos)
|
||||||
|
{
|
||||||
|
uint8 result = true;
|
||||||
|
File32 f;
|
||||||
|
unsigned long i;
|
||||||
|
|
||||||
|
digitalWrite(LED, HIGH ^ LEDinv);
|
||||||
|
if (f = SD.open(fn, O_WRITE | O_APPEND)) {
|
||||||
|
if (fpos > f.size()) {
|
||||||
|
for (i = 0; i < f.size() - fpos; ++i) {
|
||||||
|
if (f.write((uint8)0) != 1) {
|
||||||
|
result = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.close();
|
||||||
|
} else {
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
digitalWrite(LED, LOW ^ LEDinv);
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8 _sys_readseq(uint8* filename, long fpos) {
|
||||||
|
uint8 result = 0xff;
|
||||||
|
File32 f;
|
||||||
|
uint8 bytesread;
|
||||||
|
uint8 dmabuf[BlkSZ];
|
||||||
|
uint8 i;
|
||||||
|
|
||||||
|
digitalWrite(LED, HIGH ^ LEDinv);
|
||||||
|
f = SD.open((char*)filename, O_READ);
|
||||||
|
if (f) {
|
||||||
|
if (f.seek(fpos)) {
|
||||||
|
for (i = 0; i < BlkSZ; ++i)
|
||||||
|
dmabuf[i] = 0x1a;
|
||||||
|
bytesread = f.read(&dmabuf[0], BlkSZ);
|
||||||
|
if (bytesread) {
|
||||||
|
for (i = 0; i < BlkSZ; ++i)
|
||||||
|
_RamWrite(dmaAddr + i, dmabuf[i]);
|
||||||
|
}
|
||||||
|
result = bytesread ? 0x00 : 0x01;
|
||||||
|
} else {
|
||||||
|
result = 0x01;
|
||||||
|
}
|
||||||
|
f.close();
|
||||||
|
} else {
|
||||||
|
result = 0x10;
|
||||||
|
}
|
||||||
|
digitalWrite(LED, LOW ^ LEDinv);
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8 _sys_writeseq(uint8* filename, long fpos) {
|
||||||
|
uint8 result = 0xff;
|
||||||
|
File32 f;
|
||||||
|
|
||||||
|
digitalWrite(LED, HIGH ^ LEDinv);
|
||||||
|
if (_sys_extendfile((char*)filename, fpos))
|
||||||
|
f = SD.open((char*)filename, O_RDWR);
|
||||||
|
if (f) {
|
||||||
|
if (f.seek(fpos)) {
|
||||||
|
if (f.write(_RamSysAddr(dmaAddr), BlkSZ))
|
||||||
|
result = 0x00;
|
||||||
|
} else {
|
||||||
|
result = 0x01;
|
||||||
|
}
|
||||||
|
f.close();
|
||||||
|
} else {
|
||||||
|
result = 0x10;
|
||||||
|
}
|
||||||
|
digitalWrite(LED, LOW ^ LEDinv);
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8 _sys_readrand(uint8* filename, long fpos) {
|
||||||
|
uint8 result = 0xff;
|
||||||
|
File32 f;
|
||||||
|
uint8 bytesread;
|
||||||
|
uint8 dmabuf[BlkSZ];
|
||||||
|
uint8 i;
|
||||||
|
long extSize;
|
||||||
|
|
||||||
|
digitalWrite(LED, HIGH ^ LEDinv);
|
||||||
|
f = SD.open((char*)filename, O_READ);
|
||||||
|
if (f) {
|
||||||
|
if (f.seek(fpos)) {
|
||||||
|
for (i = 0; i < BlkSZ; ++i)
|
||||||
|
dmabuf[i] = 0x1a;
|
||||||
|
bytesread = f.read(&dmabuf[0], BlkSZ);
|
||||||
|
if (bytesread) {
|
||||||
|
for (i = 0; i < BlkSZ; ++i)
|
||||||
|
_RamWrite(dmaAddr + i, dmabuf[i]);
|
||||||
|
}
|
||||||
|
result = bytesread ? 0x00 : 0x01;
|
||||||
|
} else {
|
||||||
|
if (fpos >= 65536L * BlkSZ) {
|
||||||
|
result = 0x06; // seek past 8MB (largest file size in CP/M)
|
||||||
|
} else {
|
||||||
|
extSize = f.size();
|
||||||
|
// round file size up to next full logical extent
|
||||||
|
extSize = ExtSZ * ((extSize / ExtSZ) + ((extSize % ExtSZ) ? 1 : 0));
|
||||||
|
if (fpos < extSize)
|
||||||
|
result = 0x01; // reading unwritten data
|
||||||
|
else
|
||||||
|
result = 0x04; // seek to unwritten extent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.close();
|
||||||
|
} else {
|
||||||
|
result = 0x10;
|
||||||
|
}
|
||||||
|
digitalWrite(LED, LOW ^ LEDinv);
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8 _sys_writerand(uint8* filename, long fpos) {
|
||||||
|
uint8 result = 0xff;
|
||||||
|
File32 f;
|
||||||
|
|
||||||
|
digitalWrite(LED, HIGH ^ LEDinv);
|
||||||
|
if (_sys_extendfile((char*)filename, fpos)) {
|
||||||
|
f = SD.open((char*)filename, O_RDWR);
|
||||||
|
}
|
||||||
|
if (f) {
|
||||||
|
if (f.seek(fpos)) {
|
||||||
|
if (f.write(_RamSysAddr(dmaAddr), BlkSZ))
|
||||||
|
result = 0x00;
|
||||||
|
} else {
|
||||||
|
result = 0x06;
|
||||||
|
}
|
||||||
|
f.close();
|
||||||
|
} else {
|
||||||
|
result = 0x10;
|
||||||
|
}
|
||||||
|
digitalWrite(LED, LOW ^ LEDinv);
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8 findNextDirName[13];
|
||||||
|
static uint16 fileRecords = 0;
|
||||||
|
static uint16 fileExtents = 0;
|
||||||
|
static uint16 fileExtentsUsed = 0;
|
||||||
|
static uint16 firstFreeAllocBlock;
|
||||||
|
|
||||||
|
uint8 _findnext(uint8 isdir) {
|
||||||
|
File32 f;
|
||||||
|
uint8 result = 0xff;
|
||||||
|
bool isfile;
|
||||||
|
uint32 bytes;
|
||||||
|
|
||||||
|
digitalWrite(LED, HIGH ^ LEDinv);
|
||||||
|
if (allExtents && fileRecords) {
|
||||||
|
_mockupDirEntry();
|
||||||
|
result = 0;
|
||||||
|
} else {
|
||||||
|
while (f = userdir.openNextFile()) {
|
||||||
|
f.getName((char*)&findNextDirName[0], 13);
|
||||||
|
isfile = !f.isDirectory();
|
||||||
|
bytes = f.size();
|
||||||
|
f.dirEntry(&fileDirEntry);
|
||||||
|
f.close();
|
||||||
|
if (!isfile)
|
||||||
|
continue;
|
||||||
|
_HostnameToFCBname(findNextDirName, fcbname);
|
||||||
|
if (match(fcbname, pattern)) {
|
||||||
|
if (isdir) {
|
||||||
|
// account for host files that aren't multiples of the block size
|
||||||
|
// by rounding their bytes up to the next multiple of blocks
|
||||||
|
if (bytes & (BlkSZ - 1)) {
|
||||||
|
bytes = (bytes & ~(BlkSZ - 1)) + BlkSZ;
|
||||||
|
}
|
||||||
|
fileRecords = bytes / BlkSZ;
|
||||||
|
fileExtents = fileRecords / BlkEX + ((fileRecords & (BlkEX - 1)) ? 1 : 0);
|
||||||
|
fileExtentsUsed = 0;
|
||||||
|
firstFreeAllocBlock = firstBlockAfterDir;
|
||||||
|
_mockupDirEntry();
|
||||||
|
} else {
|
||||||
|
fileRecords = 0;
|
||||||
|
fileExtents = 0;
|
||||||
|
fileExtentsUsed = 0;
|
||||||
|
firstFreeAllocBlock = firstBlockAfterDir;
|
||||||
|
}
|
||||||
|
_RamWrite(tmpFCB, filename[0] - '@');
|
||||||
|
_HostnameToFCB(tmpFCB, findNextDirName);
|
||||||
|
result = 0x00;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
digitalWrite(LED, LOW ^ LEDinv);
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8 _findfirst(uint8 isdir) {
|
||||||
|
uint8 path[4] = { '?', FOLDERCHAR, '?', 0 };
|
||||||
|
path[0] = filename[0];
|
||||||
|
path[2] = filename[2];
|
||||||
|
if (userdir)
|
||||||
|
userdir.close();
|
||||||
|
userdir = SD.open((char*)path); // Set directory search to start from the first position
|
||||||
|
_HostnameToFCBname(filename, pattern);
|
||||||
|
fileRecords = 0;
|
||||||
|
fileExtents = 0;
|
||||||
|
fileExtentsUsed = 0;
|
||||||
|
return(_findnext(isdir));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8 _findnextallusers(uint8 isdir) {
|
||||||
|
uint8 result = 0xFF;
|
||||||
|
char dirname[13];
|
||||||
|
bool done = false;
|
||||||
|
|
||||||
|
while (!done) {
|
||||||
|
while (!userdir) {
|
||||||
|
userdir = rootdir.openNextFile();
|
||||||
|
if (!userdir) {
|
||||||
|
done = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
userdir.getName(dirname, sizeof dirname);
|
||||||
|
if (userdir.isDirectory() && strlen(dirname) == 1 && isxdigit(dirname[0])) {
|
||||||
|
currFindUser = dirname[0] <= '9' ? dirname[0] - '0' : toupper(dirname[0]) - 'A' + 10;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
userdir.close();
|
||||||
|
}
|
||||||
|
if (userdir) {
|
||||||
|
result = _findnext(isdir);
|
||||||
|
if (result) {
|
||||||
|
userdir.close();
|
||||||
|
} else {
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = 0xFF;
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8 _findfirstallusers(uint8 isdir) {
|
||||||
|
uint8 path[2] = { '?', 0 };
|
||||||
|
|
||||||
|
path[0] = filename[0];
|
||||||
|
if (rootdir)
|
||||||
|
rootdir.close();
|
||||||
|
if (userdir)
|
||||||
|
userdir.close();
|
||||||
|
rootdir = SD.open((char*)path); // Set directory search to start from the first position
|
||||||
|
strcpy((char*)pattern, "???????????");
|
||||||
|
if (!rootdir)
|
||||||
|
return 0xFF;
|
||||||
|
fileRecords = 0;
|
||||||
|
fileExtents = 0;
|
||||||
|
fileExtentsUsed = 0;
|
||||||
|
return(_findnextallusers(isdir));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8 _Truncate(char* filename, uint8 rc) {
|
||||||
|
File32 f;
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
digitalWrite(LED, HIGH ^ LEDinv);
|
||||||
|
f = SD.open((char*)filename, O_WRITE | O_APPEND);
|
||||||
|
if (f) {
|
||||||
|
if (f.truncate(rc * BlkSZ)) {
|
||||||
|
f.close();
|
||||||
|
result = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
digitalWrite(LED, LOW ^ LEDinv);
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _MakeUserDir() {
|
||||||
|
uint8 dFolder = cDrive + 'A';
|
||||||
|
uint8 uFolder = toupper(tohex(userCode));
|
||||||
|
|
||||||
|
uint8 path[4] = { dFolder, FOLDERCHAR, uFolder, 0 };
|
||||||
|
|
||||||
|
digitalWrite(LED, HIGH ^ LEDinv);
|
||||||
|
SD.mkdir((char*)path);
|
||||||
|
digitalWrite(LED, LOW ^ LEDinv);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8 _sys_makedisk(uint8 drive) {
|
||||||
|
uint8 result = 0;
|
||||||
|
if (drive < 1 || drive>16) {
|
||||||
|
result = 0xff;
|
||||||
|
} else {
|
||||||
|
uint8 dFolder = drive + '@';
|
||||||
|
uint8 disk[2] = { dFolder, 0 };
|
||||||
|
digitalWrite(LED, HIGH ^ LEDinv);
|
||||||
|
if (!SD.mkdir((char*)disk)) {
|
||||||
|
result = 0xfe;
|
||||||
|
} else {
|
||||||
|
uint8 path[4] = { dFolder, FOLDERCHAR, '0', 0 };
|
||||||
|
SD.mkdir((char*)path);
|
||||||
|
}
|
||||||
|
digitalWrite(LED, LOW ^ LEDinv);
|
||||||
|
}
|
||||||
|
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hardware abstraction functions */
|
||||||
|
/*===============================================================================*/
|
||||||
|
void _HardwareOut(const uint32 Port, const uint32 Value) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 _HardwareIn(const uint32 Port) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Console abstraction functions */
|
||||||
|
/*===============================================================================*/
|
||||||
|
|
||||||
|
#include "arduino_hooks.h"
|
||||||
|
|
||||||
|
int _kbhit(void) {
|
||||||
|
if (_kbhit_hook && _kbhit_hook()) { return true; }
|
||||||
|
return(Serial.available());
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8 _getch(void) {
|
||||||
|
while(true) {
|
||||||
|
if(_kbhit_hook && _kbhit_hook()) { return _getch_hook(); }
|
||||||
|
if(Serial.available()) { return Serial.read(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8 _getche(void) {
|
||||||
|
uint8 ch = _getch();
|
||||||
|
_putch(ch);
|
||||||
|
return(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _putch(uint8 ch) {
|
||||||
|
Serial.write(ch);
|
||||||
|
if(_putch_hook) _putch_hook(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _clrscr(void) {
|
||||||
|
Serial.print("\e[H\e[J");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
10
runcpm-rp2350-hstx-usb/runcpm-pico/arduino_hooks.c
Normal file
10
runcpm-rp2350-hstx-usb/runcpm-pico/arduino_hooks.c
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#include "arduino_hooks.h"
|
||||||
|
|
||||||
|
bool (*_kbhit_hook)(void);
|
||||||
|
uint8_t (*_getch_hook)(void);
|
||||||
|
void (*_putch_hook)(uint8_t ch);
|
||||||
|
|
||||||
13
runcpm-rp2350-hstx-usb/runcpm-pico/arduino_hooks.h
Normal file
13
runcpm-rp2350-hstx-usb/runcpm-pico/arduino_hooks.h
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
extern bool (*_kbhit_hook)(void);
|
||||||
|
extern uint8_t (*_getch_hook)(void);
|
||||||
|
extern void (*_putch_hook)(uint8_t ch);
|
||||||
|
|
||||||
891
runcpm-rp2350-hstx-usb/runcpm-pico/ccp.h
Normal file
891
runcpm-rp2350-hstx-usb/runcpm-pico/ccp.h
Normal file
|
|
@ -0,0 +1,891 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Mockba the Borg
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#ifndef CCP_H
|
||||||
|
#define CCP_H
|
||||||
|
|
||||||
|
// CP/M BDOS calls
|
||||||
|
#include "cpm.h"
|
||||||
|
|
||||||
|
#define CmdFCB (BatchFCB + 48) // FCB for use by internal commands
|
||||||
|
#define ParFCB 0x005C // FCB for use by line parameters
|
||||||
|
#define SecFCB 0x006C // Secondary part of FCB for renaming files
|
||||||
|
#define Trampoline (CmdFCB + 36) // Trampoline for running external commands
|
||||||
|
|
||||||
|
#define inBuf (BDOSjmppage - 256) // Input buffer location
|
||||||
|
#define cmdLen 125 // Maximum size of a command line (sz+rd+cmd+\0)
|
||||||
|
|
||||||
|
#define defDMA 0x0080 // Default DMA address
|
||||||
|
#define defLoad 0x0100 // Default load address
|
||||||
|
|
||||||
|
// CCP global variables
|
||||||
|
uint8 pgSize = 22; // for TYPE
|
||||||
|
uint8 curDrive = 0; // 0 -> 15 = A -> P .. Current drive for the CCP (same as RAM[DSKByte])
|
||||||
|
uint8 parDrive = 0; // 0 -> 15 = A -> P .. Drive for the first file parameter
|
||||||
|
uint8 curUser = 0; // 0 -> 15 .. Current user area to access
|
||||||
|
bool sFlag = FALSE; // Submit Flag
|
||||||
|
uint8 sRecs = 0; // Number of records on the Submit file
|
||||||
|
uint8 prompt[8] = "\r\n >";
|
||||||
|
uint16 pbuf, perr;
|
||||||
|
uint8 blen; // Actual size of the typed command line (size of the buffer)
|
||||||
|
|
||||||
|
static const char *Commands[] =
|
||||||
|
{
|
||||||
|
// Standard CP/M commands
|
||||||
|
"DIR",
|
||||||
|
"ERA",
|
||||||
|
"TYPE",
|
||||||
|
"SAVE",
|
||||||
|
"REN",
|
||||||
|
"USER",
|
||||||
|
|
||||||
|
// Extra CCP commands
|
||||||
|
"CLS",
|
||||||
|
"DEL",
|
||||||
|
"EXIT",
|
||||||
|
"PAGE",
|
||||||
|
"VOL",
|
||||||
|
"?",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
// Used to call BDOS from inside the CCP
|
||||||
|
uint16 _ccp_bdos(uint8 function, uint16 de) {
|
||||||
|
SET_LOW_REGISTER(BC, function);
|
||||||
|
DE = de;
|
||||||
|
_Bdos();
|
||||||
|
|
||||||
|
return (HL & 0xffff);
|
||||||
|
} // _ccp_bdos
|
||||||
|
|
||||||
|
// Compares two strings (Atmel doesn't like strcmp)
|
||||||
|
uint8 _ccp_strcmp(char *stra, char *strb) {
|
||||||
|
while (*stra && *strb && (*stra == *strb)) {
|
||||||
|
++stra;
|
||||||
|
++strb;
|
||||||
|
}
|
||||||
|
return (*stra == *strb);
|
||||||
|
} // _ccp_strcmp
|
||||||
|
|
||||||
|
// Gets the command ID number
|
||||||
|
uint8 _ccp_cnum(void) {
|
||||||
|
uint8 result = 255;
|
||||||
|
uint8 command[9];
|
||||||
|
uint8 i = 0;
|
||||||
|
|
||||||
|
if (!_RamRead(CmdFCB)) { // If a drive was set, then the command is external
|
||||||
|
while (i < 8 && _RamRead(CmdFCB + i + 1) != ' ') {
|
||||||
|
command[i] = _RamRead(CmdFCB + i + 1);
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
command[i] = 0;
|
||||||
|
i = 0;
|
||||||
|
while (Commands[i]) {
|
||||||
|
if (_ccp_strcmp((char *)command, (char *)Commands[i])) {
|
||||||
|
result = i;
|
||||||
|
perr = defDMA + 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (result);
|
||||||
|
} // _ccp_cnum
|
||||||
|
|
||||||
|
// Returns true if character is a separator
|
||||||
|
uint8 _ccp_delim(uint8 ch) {
|
||||||
|
return (ch == 0 || ch == ' ' || ch == '=' || ch == '.' || ch == ':' || ch == ';' || ch == '<' || ch == '>');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prints the FCB filename
|
||||||
|
void _ccp_printfcb(uint16 fcb, uint8 compact) {
|
||||||
|
uint8 i, ch;
|
||||||
|
|
||||||
|
ch = _RamRead(fcb);
|
||||||
|
if (ch && compact) {
|
||||||
|
_ccp_bdos( C_WRITE, ch + '@');
|
||||||
|
_ccp_bdos( C_WRITE, ':');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 1; i < 12; ++i) {
|
||||||
|
ch = _RamRead(fcb + i);
|
||||||
|
if ((ch == ' ') && compact)
|
||||||
|
continue;
|
||||||
|
if (i == 9)
|
||||||
|
_ccp_bdos(C_WRITE, compact ? '.' : ' ');
|
||||||
|
_ccp_bdos(C_WRITE, ch);
|
||||||
|
}
|
||||||
|
} // _ccp_printfcb
|
||||||
|
|
||||||
|
// Initializes the FCB
|
||||||
|
void _ccp_initFCB(uint16 address, uint8 size) {
|
||||||
|
uint8 i;
|
||||||
|
|
||||||
|
for (i = 0; i < size; ++i)
|
||||||
|
_RamWrite(address + i, 0x00);
|
||||||
|
|
||||||
|
for (i = 0; i < 11; ++i)
|
||||||
|
_RamWrite(address + 1 + i, 0x20);
|
||||||
|
} // _ccp_initFCB
|
||||||
|
|
||||||
|
// Name to FCB
|
||||||
|
uint8 _ccp_nameToFCB(uint16 fcb) {
|
||||||
|
uint8 pad, plen, ch, n = 0;
|
||||||
|
|
||||||
|
// Checks for a drive and places it on the Command FCB
|
||||||
|
if (_RamRead(pbuf + 1) == ':') {
|
||||||
|
ch = toupper(_RamRead(pbuf++));
|
||||||
|
_RamWrite(fcb, ch - '@'); // Makes the drive 0x1-0xF for A-P
|
||||||
|
++pbuf; // Points pbuf past the :
|
||||||
|
blen -= 2;
|
||||||
|
}
|
||||||
|
if (blen) {
|
||||||
|
++fcb;
|
||||||
|
|
||||||
|
plen = 8;
|
||||||
|
pad = ' ';
|
||||||
|
ch = toupper(_RamRead(pbuf));
|
||||||
|
|
||||||
|
while (blen && plen) {
|
||||||
|
if (_ccp_delim(ch))
|
||||||
|
break;
|
||||||
|
++pbuf;
|
||||||
|
--blen;
|
||||||
|
if (ch == '*')
|
||||||
|
pad = '?';
|
||||||
|
if (pad == '?') {
|
||||||
|
ch = pad;
|
||||||
|
n = n | 0x80; // Name is not unique
|
||||||
|
}
|
||||||
|
--plen;
|
||||||
|
++n;
|
||||||
|
_RamWrite(fcb++, ch);
|
||||||
|
ch = toupper(_RamRead(pbuf));
|
||||||
|
}
|
||||||
|
|
||||||
|
while (plen--)
|
||||||
|
_RamWrite(fcb++, pad);
|
||||||
|
plen = 3;
|
||||||
|
pad = ' ';
|
||||||
|
if (ch == '.') {
|
||||||
|
++pbuf;
|
||||||
|
--blen;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (blen && plen) {
|
||||||
|
ch = toupper(_RamRead(pbuf));
|
||||||
|
if (_ccp_delim(ch))
|
||||||
|
break;
|
||||||
|
++pbuf;
|
||||||
|
--blen;
|
||||||
|
if (ch == '*')
|
||||||
|
pad = '?';
|
||||||
|
if (pad == '?') {
|
||||||
|
ch = pad;
|
||||||
|
n = n | 0x80; // Name is not unique
|
||||||
|
}
|
||||||
|
--plen;
|
||||||
|
++n;
|
||||||
|
_RamWrite(fcb++, ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (plen--)
|
||||||
|
_RamWrite(fcb++, pad);
|
||||||
|
}
|
||||||
|
return (n);
|
||||||
|
} // _ccp_nameToFCB
|
||||||
|
|
||||||
|
// Converts the ParFCB name to a number
|
||||||
|
uint16 _ccp_fcbtonum() {
|
||||||
|
uint8 ch;
|
||||||
|
uint16 n = 0;
|
||||||
|
uint8 pos = ParFCB + 1;
|
||||||
|
|
||||||
|
while (TRUE) {
|
||||||
|
ch = _RamRead(pos++);
|
||||||
|
if ((ch < '0') || (ch > '9')) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
n = (n * 10) + (ch - '0');
|
||||||
|
}
|
||||||
|
return (n);
|
||||||
|
} // _ccp_fcbtonum
|
||||||
|
|
||||||
|
// DIR command
|
||||||
|
void _ccp_dir(void) {
|
||||||
|
uint8 i;
|
||||||
|
uint8 dirHead[6] = "A: ";
|
||||||
|
uint8 dirSep[6] = " | ";
|
||||||
|
uint32 fcount = 0; // Number of files printed
|
||||||
|
uint32 ccount = 0; // Number of columns printed
|
||||||
|
|
||||||
|
if (_RamRead(ParFCB + 1) == ' ')
|
||||||
|
for (i = 1; i < 12; ++i)
|
||||||
|
_RamWrite(ParFCB + i, '?');
|
||||||
|
dirHead[0] = _RamRead(ParFCB) ? _RamRead(ParFCB) + '@' : prompt[2];
|
||||||
|
|
||||||
|
_puts("\r\n");
|
||||||
|
if (!_SearchFirst(ParFCB, TRUE)) {
|
||||||
|
_puts((char *)dirHead);
|
||||||
|
_ccp_printfcb(tmpFCB, FALSE);
|
||||||
|
++fcount;
|
||||||
|
++ccount;
|
||||||
|
|
||||||
|
while (!_SearchNext(ParFCB, TRUE)) {
|
||||||
|
if (!ccount) {
|
||||||
|
_puts( "\r\n");
|
||||||
|
_puts( (char *)dirHead);
|
||||||
|
} else {
|
||||||
|
_puts((char *)dirSep);
|
||||||
|
}
|
||||||
|
_ccp_printfcb(tmpFCB, FALSE);
|
||||||
|
++fcount;
|
||||||
|
++ccount;
|
||||||
|
if (ccount > 3)
|
||||||
|
ccount = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_puts("No file");
|
||||||
|
}
|
||||||
|
} // _ccp_dir
|
||||||
|
|
||||||
|
// ERA command
|
||||||
|
void _ccp_era(void) {
|
||||||
|
if (_ccp_bdos(F_DELETE, ParFCB))
|
||||||
|
_puts("\r\nNo file");
|
||||||
|
} // _ccp_era
|
||||||
|
|
||||||
|
// TYPE command
|
||||||
|
uint8 _ccp_type(void) {
|
||||||
|
uint8 i, c, l = 0, error = TRUE;
|
||||||
|
uint16 a, p = 0;
|
||||||
|
|
||||||
|
if (!_ccp_bdos(F_OPEN, ParFCB)) {
|
||||||
|
_puts("\r\n");
|
||||||
|
|
||||||
|
while (!_ccp_bdos(F_READ, ParFCB)) {
|
||||||
|
i = 128;
|
||||||
|
a = dmaAddr;
|
||||||
|
|
||||||
|
while (i) {
|
||||||
|
c = _RamRead(a);
|
||||||
|
if (c == 0x1a)
|
||||||
|
break;
|
||||||
|
_ccp_bdos(C_WRITE, c);
|
||||||
|
if (c == 0x0a) {
|
||||||
|
++l;
|
||||||
|
if (pgSize && (l == pgSize)) {
|
||||||
|
l = 0;
|
||||||
|
p = _ccp_bdos(C_READ, 0x0000);
|
||||||
|
if (p == 3)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
--i;
|
||||||
|
++a;
|
||||||
|
}
|
||||||
|
if (p == 3)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
error = FALSE;
|
||||||
|
}
|
||||||
|
return (error);
|
||||||
|
} // _ccp_type
|
||||||
|
|
||||||
|
// SAVE command
|
||||||
|
uint8 _ccp_save(void) {
|
||||||
|
uint8 error = TRUE;
|
||||||
|
uint16 pages = _ccp_fcbtonum();
|
||||||
|
uint16 i, dma;
|
||||||
|
|
||||||
|
if (pages < 256) {
|
||||||
|
error = FALSE;
|
||||||
|
|
||||||
|
while (_RamRead(pbuf) == ' ' && blen) { // Skips any leading spaces
|
||||||
|
++pbuf;
|
||||||
|
--blen;
|
||||||
|
}
|
||||||
|
_ccp_nameToFCB(SecFCB); // Loads file name onto the ParFCB
|
||||||
|
if (_ccp_bdos(F_MAKE, SecFCB)) {
|
||||||
|
_puts("Err: create");
|
||||||
|
} else {
|
||||||
|
if (_ccp_bdos(F_OPEN, SecFCB)) {
|
||||||
|
_puts("Err: open");
|
||||||
|
} else {
|
||||||
|
pages *= 2; // Calculates the number of CP/M blocks to write
|
||||||
|
dma = defLoad;
|
||||||
|
_puts("\r\n");
|
||||||
|
|
||||||
|
for (i = 0; i < pages; i++) {
|
||||||
|
_ccp_bdos( F_DMAOFF, dma);
|
||||||
|
_ccp_bdos( F_WRITE, SecFCB);
|
||||||
|
dma += 128;
|
||||||
|
_ccp_bdos( C_WRITE, '.');
|
||||||
|
}
|
||||||
|
_ccp_bdos(F_CLOSE, SecFCB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (error);
|
||||||
|
} // _ccp_save
|
||||||
|
|
||||||
|
// REN command
|
||||||
|
void _ccp_ren(void) {
|
||||||
|
uint8 ch, i;
|
||||||
|
|
||||||
|
++pbuf;
|
||||||
|
|
||||||
|
_ccp_nameToFCB(SecFCB);
|
||||||
|
|
||||||
|
for (i = 0; i < 12; ++i) { // Swap the filenames on the fcb
|
||||||
|
ch = _RamRead(ParFCB + i);
|
||||||
|
_RamWrite( ParFCB + i, _RamRead(SecFCB + i));
|
||||||
|
_RamWrite( SecFCB + i, ch);
|
||||||
|
}
|
||||||
|
if (_ccp_bdos(F_RENAME, ParFCB))
|
||||||
|
_puts("\r\nNo file");
|
||||||
|
} // _ccp_ren
|
||||||
|
|
||||||
|
// USER command
|
||||||
|
uint8 _ccp_user(void) {
|
||||||
|
uint8 error = TRUE;
|
||||||
|
|
||||||
|
curUser = (uint8)_ccp_fcbtonum();
|
||||||
|
if (curUser < 16) {
|
||||||
|
_ccp_bdos(F_USERNUM, curUser);
|
||||||
|
error = FALSE;
|
||||||
|
}
|
||||||
|
return (error);
|
||||||
|
} // _ccp_user
|
||||||
|
|
||||||
|
// PAGE command
|
||||||
|
uint8 _ccp_page(void) {
|
||||||
|
uint8 error = TRUE;
|
||||||
|
uint16 r = _ccp_fcbtonum();
|
||||||
|
|
||||||
|
if (r < 256) {
|
||||||
|
pgSize = (uint8)r;
|
||||||
|
error = FALSE;
|
||||||
|
}
|
||||||
|
return (error);
|
||||||
|
} // _ccp_page
|
||||||
|
|
||||||
|
// VOL command
|
||||||
|
uint8 _ccp_vol(void) {
|
||||||
|
uint8 error = FALSE;
|
||||||
|
uint8 letter = _RamRead(ParFCB) ? '@' + _RamRead(ParFCB) : 'A' + curDrive;
|
||||||
|
uint8 folder[5] = {letter, FOLDERCHAR, '0', FOLDERCHAR, 0};
|
||||||
|
uint8 filename[13] = {letter, FOLDERCHAR, '0', FOLDERCHAR, 'I', 'N', 'F', 'O', '.', 'T', 'X', 'T', 0};
|
||||||
|
uint8 bytesread;
|
||||||
|
uint8 i, j;
|
||||||
|
|
||||||
|
_puts("\r\nVolumes on ");
|
||||||
|
_putcon(folder[0]);
|
||||||
|
_puts(":\r\n");
|
||||||
|
|
||||||
|
for (i = 0; i < 16; ++i) {
|
||||||
|
folder[2] = i < 10 ? i + 48 : i + 55;
|
||||||
|
if (_sys_exists(folder)) {
|
||||||
|
_putcon(i < 10 ? ' ' : '1');
|
||||||
|
_putcon(i < 10 ? folder[2] : 38 + i);
|
||||||
|
_puts(": ");
|
||||||
|
filename[2] = i < 10 ? i + 48 : i + 55;
|
||||||
|
bytesread = (uint8)_sys_readseq(filename, 0);
|
||||||
|
if (!bytesread) {
|
||||||
|
for (j = 0; j < 128; ++j) {
|
||||||
|
if ((_RamRead(dmaAddr + j) < 32) || (_RamRead(dmaAddr + j) > 126))
|
||||||
|
break;
|
||||||
|
_putcon(_RamRead(dmaAddr + j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_puts("\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (error);
|
||||||
|
} // _ccp_vol
|
||||||
|
|
||||||
|
// ?/Help command
|
||||||
|
uint8 _ccp_hlp(void) {
|
||||||
|
_puts("\r\nCCP Commands:\r\n");
|
||||||
|
_puts("\t? - Shows this list of commands\r\n");
|
||||||
|
_puts("\tCLS - Clears the screen\r\n");
|
||||||
|
_puts("\tDEL - Alias to ERA\r\n");
|
||||||
|
_puts("\tEXIT - Terminates RunCPM\r\n");
|
||||||
|
_puts("\tPAGE [<n>] - Sets the page size for TYPE\r\n");
|
||||||
|
_puts("\t or disables paging if no parameter passed\r\n");
|
||||||
|
_puts("\tVOL [drive] - Shows the volume information\r\n");
|
||||||
|
_puts("\t which comes from each volume's INFO.TXT");
|
||||||
|
return(FALSE);
|
||||||
|
}
|
||||||
|
#ifdef HASLUA
|
||||||
|
|
||||||
|
// External (.LUA) command
|
||||||
|
uint8 _ccp_lua(void) {
|
||||||
|
uint8 error = TRUE;
|
||||||
|
uint8 found, drive, user = 0;
|
||||||
|
uint16 loadAddr = defLoad;
|
||||||
|
|
||||||
|
_RamWrite( CmdFCB + 9, 'L');
|
||||||
|
_RamWrite( CmdFCB + 10, 'U');
|
||||||
|
_RamWrite( CmdFCB + 11, 'A');
|
||||||
|
|
||||||
|
drive = _RamRead(CmdFCB);
|
||||||
|
found = !_ccp_bdos(F_OPEN, CmdFCB); // Look for the program on the FCB drive, current or specified
|
||||||
|
if (!found) { // If not found
|
||||||
|
if (!drive) { // and the search was on the default drive
|
||||||
|
_RamWrite(CmdFCB, 0x01); // Then look on drive A: user 0
|
||||||
|
if (curUser) {
|
||||||
|
user = curUser; // Save the current user
|
||||||
|
_ccp_bdos(F_USERNUM, 0x0000); // then set it to 0
|
||||||
|
}
|
||||||
|
found = !_ccp_bdos(F_OPEN, CmdFCB);
|
||||||
|
if (!found) { // If still not found then
|
||||||
|
if (curUser) { // If current user not = 0
|
||||||
|
_RamWrite(CmdFCB, 0x00); // look on current drive user 0
|
||||||
|
found = !_ccp_bdos(F_OPEN, CmdFCB); // and try again
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found) {
|
||||||
|
_puts("\r\n");
|
||||||
|
|
||||||
|
_ccp_bdos(F_RUNLUA, CmdFCB);
|
||||||
|
if (user) { // If a user was selected
|
||||||
|
_ccp_bdos(F_USERNUM, curUser); // Set it back
|
||||||
|
user = 0;
|
||||||
|
}
|
||||||
|
_RamWrite(CmdFCB, drive); // Set the command FCB drive back to what it was
|
||||||
|
cDrive = oDrive; // And restore cDrive
|
||||||
|
error = FALSE;
|
||||||
|
}
|
||||||
|
if (user) // If a user was selected
|
||||||
|
_ccp_bdos(F_USERNUM, curUser); // Set it back
|
||||||
|
_RamWrite(CmdFCB, drive); // Set the command FCB drive back to what it was
|
||||||
|
|
||||||
|
return (error);
|
||||||
|
} // _ccp_lua
|
||||||
|
#endif // ifdef HASLUA
|
||||||
|
|
||||||
|
// External (.COM) command
|
||||||
|
uint8 _ccp_ext(void) {
|
||||||
|
bool error = TRUE, found = FALSE;
|
||||||
|
uint8 drive = 0, user = 0;
|
||||||
|
uint16 loadAddr = defLoad;
|
||||||
|
|
||||||
|
bool wasBlank = (_RamRead(CmdFCB + 9) == ' ');
|
||||||
|
bool wasSUB = ((_RamRead(CmdFCB + 9) == 'S') &&
|
||||||
|
(_RamRead(CmdFCB + 10) == 'U') &&
|
||||||
|
(_RamRead(CmdFCB + 11) == 'B'));
|
||||||
|
|
||||||
|
if (!wasSUB) {
|
||||||
|
if (wasBlank) {
|
||||||
|
_RamWrite(CmdFCB + 9, 'C'); //first look for a .COM file
|
||||||
|
_RamWrite(CmdFCB + 10, 'O');
|
||||||
|
_RamWrite(CmdFCB + 11, 'M');
|
||||||
|
}
|
||||||
|
|
||||||
|
drive = _RamRead(CmdFCB); // Get the drive from the command FCB
|
||||||
|
found = !_ccp_bdos(F_OPEN, CmdFCB); // Look for the program on the FCB drive, current or specified
|
||||||
|
if (!found) { // If not found
|
||||||
|
if (!drive) { // and the search was on the default drive
|
||||||
|
_RamWrite(CmdFCB, 0x01); // Then look on drive A: user 0
|
||||||
|
if (curUser) {
|
||||||
|
user = curUser; // Save the current user
|
||||||
|
_ccp_bdos(F_USERNUM, 0x0000); // then set it to 0
|
||||||
|
}
|
||||||
|
found = !_ccp_bdos(F_OPEN, CmdFCB);
|
||||||
|
if (!found) { // If still not found then
|
||||||
|
if (curUser) { // If current user not = 0
|
||||||
|
_RamWrite(CmdFCB, 0x00); // look on current drive user 0
|
||||||
|
found = !_ccp_bdos(F_OPEN, CmdFCB); // and try again
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
_RamWrite(CmdFCB, drive); // restore previous drive
|
||||||
|
_ccp_bdos(F_USERNUM, curUser); // restore to previous user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//if .COM not found then look for a .SUB file
|
||||||
|
if ((wasBlank || wasSUB) && !found && !sFlag) { //don't auto-submit while executing a submit file
|
||||||
|
_RamWrite(CmdFCB + 9, 'S');
|
||||||
|
_RamWrite(CmdFCB + 10, 'U');
|
||||||
|
_RamWrite(CmdFCB + 11, 'B');
|
||||||
|
|
||||||
|
drive = _RamRead(CmdFCB); // Get the drive from the command FCB
|
||||||
|
found = !_ccp_bdos(F_OPEN, CmdFCB); // Look for the program on the FCB drive, current or specified
|
||||||
|
if (!found) { // If not found
|
||||||
|
if (!drive) { // and the search was on the default drive
|
||||||
|
_RamWrite(CmdFCB, 0x01); // Then look on drive A: user 0
|
||||||
|
if (curUser) {
|
||||||
|
user = curUser; // Save the current user
|
||||||
|
_ccp_bdos(F_USERNUM, 0x0000); // then set it to 0
|
||||||
|
}
|
||||||
|
found = !_ccp_bdos(F_OPEN, CmdFCB);
|
||||||
|
if (!found) { // If still not found then
|
||||||
|
if (curUser) { // If current user not = 0
|
||||||
|
_RamWrite(CmdFCB, 0x00); // look on current drive user 0
|
||||||
|
found = !_ccp_bdos(F_OPEN, CmdFCB); // and try again
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
_RamWrite(CmdFCB, drive); // restore previous drive
|
||||||
|
_ccp_bdos(F_USERNUM, curUser); // restore to previous user
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
//_puts(".SUB file found!\n");
|
||||||
|
int i;
|
||||||
|
|
||||||
|
//move FCB's (CmdFCB --> ParFCB --> SecFCB)
|
||||||
|
for (i = 0; i < 16; i++) {
|
||||||
|
//ParFCB to SecFCB
|
||||||
|
_RamWrite(SecFCB + i, _RamRead(ParFCB + i));
|
||||||
|
//CmdFCB to ParFCB
|
||||||
|
_RamWrite(ParFCB + i, _RamRead(CmdFCB + i));
|
||||||
|
}
|
||||||
|
// (Re)Initialize the CmdFCB
|
||||||
|
_ccp_initFCB(CmdFCB, 36);
|
||||||
|
|
||||||
|
//put 'SUBMIT.COM' in CmdFCB
|
||||||
|
const char *str = "SUBMIT COM";
|
||||||
|
int s = (int)strlen(str);
|
||||||
|
for (i = 0; i < s; i++)
|
||||||
|
_RamWrite(CmdFCB + i + 1, str[i]);
|
||||||
|
|
||||||
|
//now try to find SUBMIT.COM file
|
||||||
|
found = !_ccp_bdos(F_OPEN, CmdFCB); // Look for the program on the FCB drive, current or specified
|
||||||
|
if (!found) { // If not found
|
||||||
|
if (!drive) { // and the search was on the default drive
|
||||||
|
_RamWrite(CmdFCB, 0x01); // Then look on drive A: user 0
|
||||||
|
if (curUser) {
|
||||||
|
user = curUser; // Save the current user
|
||||||
|
_ccp_bdos(F_USERNUM, 0x0000); // then set it to 0
|
||||||
|
}
|
||||||
|
found = !_ccp_bdos(F_OPEN, CmdFCB);
|
||||||
|
if (!found) { // If still not found then
|
||||||
|
if (curUser) { // If current user not = 0
|
||||||
|
_RamWrite(CmdFCB, 0x00); // look on current drive user 0
|
||||||
|
found = !_ccp_bdos(F_OPEN, CmdFCB); // and try again
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found) {
|
||||||
|
//insert "@" into command buffer
|
||||||
|
//note: this is so the rest will be parsed correctly
|
||||||
|
blen = _RamRead(defDMA);
|
||||||
|
if (blen < cmdLen) {
|
||||||
|
blen++;
|
||||||
|
_RamWrite(defDMA, blen);
|
||||||
|
}
|
||||||
|
uint8 lc = '@';
|
||||||
|
for (i = 0; i < blen; i++) {
|
||||||
|
uint8 nc = _RamRead(defDMA + 1 + i);
|
||||||
|
_RamWrite(defDMA + 1 + i, lc);
|
||||||
|
lc = nc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found) { // Program was found somewhere
|
||||||
|
_puts("\r\n");
|
||||||
|
_ccp_bdos(F_DMAOFF, loadAddr); // Sets the DMA address for the loading
|
||||||
|
while (!_ccp_bdos(F_READ, CmdFCB)) { // Loads the program into memory
|
||||||
|
loadAddr += 128;
|
||||||
|
if (loadAddr == BDOSjmppage) { // Breaks if it reaches the end of TPA
|
||||||
|
_puts("\r\nNo Memory");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ccp_bdos(F_DMAOFF, loadAddr); // Points the DMA offset to the next loadAddr
|
||||||
|
}
|
||||||
|
_ccp_bdos(F_DMAOFF, defDMA); // Points the DMA offset back to the default
|
||||||
|
|
||||||
|
if (user) { // If a user was selected
|
||||||
|
_ccp_bdos(F_USERNUM, curUser); // Set it back
|
||||||
|
user = 0;
|
||||||
|
}
|
||||||
|
_RamWrite(CmdFCB, drive); // Set the command FCB drive back to what it was
|
||||||
|
cDrive = oDrive; // And restore cDrive
|
||||||
|
|
||||||
|
// Place a trampoline to call the external command
|
||||||
|
// as it may return using RET instead of JP 0000h
|
||||||
|
loadAddr = Trampoline;
|
||||||
|
_RamWrite(loadAddr, CALL); // CALL 0100h
|
||||||
|
_RamWrite16(loadAddr + 1, defLoad);
|
||||||
|
_RamWrite(loadAddr + 3, JP); // JP USERF
|
||||||
|
_RamWrite16(loadAddr + 4, BIOSjmppage + B_USERF);
|
||||||
|
|
||||||
|
Z80reset(); // Resets the Z80 CPU
|
||||||
|
SET_LOW_REGISTER(BC, _RamRead(DSKByte)); // Sets C to the current drive/user
|
||||||
|
PC = loadAddr; // Sets CP/M application jump point
|
||||||
|
SP = BDOSjmppage; // Sets the stack to the top of the TPA
|
||||||
|
|
||||||
|
Z80run(); // Starts Z80 simulation
|
||||||
|
|
||||||
|
error = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user) // If a user was selected
|
||||||
|
_ccp_bdos(F_USERNUM, curUser); // Set it back
|
||||||
|
_RamWrite(CmdFCB, drive); // Set the command FCB drive back to what it was
|
||||||
|
|
||||||
|
return(error);
|
||||||
|
} // _ccp_ext
|
||||||
|
|
||||||
|
// Prints a command error
|
||||||
|
void _ccp_cmdError() {
|
||||||
|
uint8 ch;
|
||||||
|
|
||||||
|
_puts("\r\n");
|
||||||
|
while ((ch = _RamRead(perr++))) {
|
||||||
|
if (ch == ' ')
|
||||||
|
break;
|
||||||
|
_ccp_bdos(C_WRITE, toupper(ch));
|
||||||
|
}
|
||||||
|
_puts("?\r\n");
|
||||||
|
} // _ccp_cmdError
|
||||||
|
|
||||||
|
// Reads input, either from the $$$.SUB or console
|
||||||
|
void _ccp_readInput(void) {
|
||||||
|
uint8 i;
|
||||||
|
uint8 chars;
|
||||||
|
|
||||||
|
if (sFlag) { // Are we running a submit?
|
||||||
|
if (!sRecs) { // Are we already counting?
|
||||||
|
_ccp_bdos(F_OPEN, BatchFCB); // Open the batch file
|
||||||
|
sRecs = _RamRead(BatchFCB + 15); // Gets its record count
|
||||||
|
}
|
||||||
|
--sRecs; // Counts one less
|
||||||
|
_RamWrite(BatchFCB + 32, sRecs); // And sets to be the next read
|
||||||
|
_ccp_bdos( F_DMAOFF, defDMA); // Reset current DMA
|
||||||
|
_ccp_bdos( F_READ, BatchFCB); // And reads the last sector
|
||||||
|
chars = _RamRead(defDMA); // Then moves it to the input buffer
|
||||||
|
|
||||||
|
for (i = 0; i <= chars; ++i)
|
||||||
|
_RamWrite(inBuf + i + 1, _RamRead(defDMA + i));
|
||||||
|
_RamWrite(inBuf + i + 1, 0);
|
||||||
|
_puts((char *)_RamSysAddr(inBuf + 2));
|
||||||
|
if (!sRecs) {
|
||||||
|
_ccp_bdos(F_DELETE, BatchFCB); // Deletes the submit file
|
||||||
|
sFlag = FALSE; // and clears the submit flag
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_ccp_bdos(C_READSTR, inBuf); // Reads the command line from console
|
||||||
|
if (Debug)
|
||||||
|
Z80run();
|
||||||
|
}
|
||||||
|
} // _ccp_readInput
|
||||||
|
|
||||||
|
// Main CCP code
|
||||||
|
void _ccp(void) {
|
||||||
|
uint8 i;
|
||||||
|
|
||||||
|
sFlag = (bool)_ccp_bdos(DRV_ALLRESET, 0x0000);
|
||||||
|
_ccp_bdos(DRV_SET, curDrive);
|
||||||
|
|
||||||
|
for (i = 0; i < 36; ++i)
|
||||||
|
_RamWrite(BatchFCB + i, _RamRead(tmpFCB + i));
|
||||||
|
|
||||||
|
while (TRUE) {
|
||||||
|
curDrive = (uint8)_ccp_bdos(DRV_GET, 0x0000); // Get current drive
|
||||||
|
curUser = (uint8)_ccp_bdos(F_USERNUM, 0x00FF); // Get current user
|
||||||
|
_RamWrite(DSKByte, (curUser << 4) + curDrive); // Set user/drive on addr DSKByte
|
||||||
|
|
||||||
|
parDrive = curDrive; // Initially the parameter drive is the same as the current drive
|
||||||
|
|
||||||
|
sprintf((char *) prompt, "\r\n%c%u%c", 'A' + curDrive, curUser, sFlag ? '$' : '>');
|
||||||
|
_puts((char *)prompt);
|
||||||
|
|
||||||
|
_RamWrite(inBuf, cmdLen); // Sets the buffer size to read the command line
|
||||||
|
_ccp_readInput();
|
||||||
|
|
||||||
|
blen = _RamRead(inBuf + 1); // Obtains the number of bytes read
|
||||||
|
|
||||||
|
_ccp_bdos(F_DMAOFF, defDMA); // Reset current DMA
|
||||||
|
if (blen) {
|
||||||
|
_RamWrite(inBuf + 2 + blen, 0); // "Closes" the read buffer with a \0
|
||||||
|
pbuf = inBuf + 2; // Points pbuf to the first command character
|
||||||
|
|
||||||
|
while (_RamRead(pbuf) == ' ' && blen) { // Skips any leading spaces
|
||||||
|
++pbuf;
|
||||||
|
--blen;
|
||||||
|
}
|
||||||
|
if (!blen) // There were only spaces
|
||||||
|
continue;
|
||||||
|
if (_RamRead(pbuf) == ';') // Found a comment line
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// parse for DU: command line shortcut
|
||||||
|
bool errorFlag = FALSE, continueFlag = FALSE;
|
||||||
|
uint8 ch, tDrive = 0, tUser = curUser, u = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < blen; i++) {
|
||||||
|
ch = toupper(_RamRead(pbuf + i));
|
||||||
|
if ((ch >= 'A') && (ch <= 'P')) {
|
||||||
|
if (tDrive) { // if we've already specified a new drive
|
||||||
|
break; // not a DU: command
|
||||||
|
} else {
|
||||||
|
tDrive = ch - '@';
|
||||||
|
}
|
||||||
|
} else if ((ch >= '0') && (ch <= '9')) {
|
||||||
|
tUser = u = (u * 10) + (ch - '0');
|
||||||
|
} else if (ch == ':') {
|
||||||
|
if (i == blen - 1) { // if we at the end of the command line
|
||||||
|
if (tUser >= 16) { // if invalid user
|
||||||
|
errorFlag = TRUE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (tDrive != 0) {
|
||||||
|
cDrive = oDrive = tDrive - 1;
|
||||||
|
_RamWrite(DSKByte, (_RamRead(DSKByte) & 0xf0) | cDrive);
|
||||||
|
_ccp_bdos(DRV_SET, cDrive);
|
||||||
|
if (Status)
|
||||||
|
curDrive = 0;
|
||||||
|
}
|
||||||
|
if (tUser != curUser) {
|
||||||
|
curUser = tUser;
|
||||||
|
_ccp_bdos(F_USERNUM, curUser);
|
||||||
|
}
|
||||||
|
continueFlag = TRUE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else { // invalid character
|
||||||
|
break; // don't error; may be valid (non-DU:) command
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (errorFlag) {
|
||||||
|
_ccp_cmdError(); // print command error
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (continueFlag) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_ccp_initFCB(CmdFCB, 36); // Initializes the command FCB
|
||||||
|
|
||||||
|
perr = pbuf; // Saves the pointer in case there's an error
|
||||||
|
if (_ccp_nameToFCB(CmdFCB) > 8) { // Extracts the command from the buffer
|
||||||
|
_ccp_cmdError(); // Command name cannot be non-unique or have an extension
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_RamWrite(defDMA, blen); // Move the command line at this point to 0x0080
|
||||||
|
|
||||||
|
for (i = 0; i < blen; ++i)
|
||||||
|
_RamWrite(defDMA + i + 1, toupper(_RamRead(pbuf + i)));
|
||||||
|
while (i++ < 127) // "Zero" the rest of the DMA buffer
|
||||||
|
_RamWrite(defDMA + i, 0);
|
||||||
|
_ccp_initFCB( ParFCB, 18); // Initializes the parameter FCB
|
||||||
|
_ccp_initFCB( SecFCB, 18); // Initializes the secondary FCB
|
||||||
|
|
||||||
|
while (_RamRead(pbuf) == ' ' && blen) { // Skips any leading spaces
|
||||||
|
++pbuf;
|
||||||
|
--blen;
|
||||||
|
}
|
||||||
|
_ccp_nameToFCB(ParFCB); // Loads the next file parameter onto the parameter FCB
|
||||||
|
|
||||||
|
while (_RamRead(pbuf) == ' ' && blen) { // Skips any leading spaces
|
||||||
|
++pbuf;
|
||||||
|
--blen;
|
||||||
|
}
|
||||||
|
_ccp_nameToFCB(SecFCB); // Loads the next file parameter onto the secondary FCB
|
||||||
|
|
||||||
|
i = FALSE; // Checks if the command is valid and executes
|
||||||
|
|
||||||
|
switch (_ccp_cnum()) {
|
||||||
|
// Standard CP/M commands
|
||||||
|
case 0: { // DIR
|
||||||
|
_ccp_dir();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 1: { // ERA
|
||||||
|
_ccp_era();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 2: { // TYPE
|
||||||
|
i = _ccp_type();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 3: { // SAVE
|
||||||
|
i = _ccp_save();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 4: { // REN
|
||||||
|
_ccp_ren();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 5: { // USER
|
||||||
|
i = _ccp_user();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extra CCP commands
|
||||||
|
case 6: { // CLS
|
||||||
|
_clrscr();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 7: { // DEL is an alias to ERA
|
||||||
|
_ccp_era();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 8: { // EXIT
|
||||||
|
_puts( "Terminating RunCPM.\r\n");
|
||||||
|
_puts( "CPU Halted.\r\n");
|
||||||
|
Status = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 9: { // PAGE
|
||||||
|
i = _ccp_page();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 10: { // VOL
|
||||||
|
i = _ccp_vol();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 11: { // HELP
|
||||||
|
i = _ccp_hlp();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// External/Lua commands
|
||||||
|
case 255: { // It is an external command
|
||||||
|
i = _ccp_ext();
|
||||||
|
#ifdef HASLUA
|
||||||
|
if (i)
|
||||||
|
i = _ccp_lua();
|
||||||
|
#endif // ifdef HASLUA
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
i = TRUE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} // switch
|
||||||
|
cDrive = oDrive = curDrive; // Restore cDrive and oDrive
|
||||||
|
if (i)
|
||||||
|
_ccp_cmdError();
|
||||||
|
}
|
||||||
|
if ((Status == 1) || (Status == 2))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_puts("\r\n");
|
||||||
|
} // _ccp
|
||||||
|
|
||||||
|
#endif // ifndef CCP_H
|
||||||
64
runcpm-rp2350-hstx-usb/runcpm-pico/console.h
Normal file
64
runcpm-rp2350-hstx-usb/runcpm-pico/console.h
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Mockba the Borg
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#ifndef CONSOLE_H
|
||||||
|
#define CONSOLE_H
|
||||||
|
|
||||||
|
extern int _kbhit(void);
|
||||||
|
uint8_t _getch(void);
|
||||||
|
void _putch(uint8 ch);
|
||||||
|
|
||||||
|
/* see main.c for definition */
|
||||||
|
|
||||||
|
uint8 mask8bit = 0x7f; // TO be used for masking 8 bit characters (XMODEM related)
|
||||||
|
// If set to 0x7f, RunCPM masks the 8th bit of characters sent
|
||||||
|
// to the console. This is the standard CP/M behavior.
|
||||||
|
// If set to 0xff, RunCPM passes 8 bit characters. This is
|
||||||
|
// required for XMODEM to work.
|
||||||
|
// Use the CONSOLE7 and CONSOLE8 programs to change this on the fly.
|
||||||
|
|
||||||
|
uint8 _chready(void) // Checks if there's a character ready for input
|
||||||
|
{
|
||||||
|
return(_kbhit() ? 0xff : 0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8 _getchNB(void) // Gets a character, non-blocking, no echo
|
||||||
|
{
|
||||||
|
return(_kbhit() ? _getch() : 0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _putcon(uint8 ch) // Puts a character
|
||||||
|
{
|
||||||
|
_putch(ch & mask8bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _puts(const char* str) // Puts a \0 terminated string
|
||||||
|
{
|
||||||
|
while (*str)
|
||||||
|
_putcon(*(str++));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _puthex8(uint8 c) // Puts a HH hex string
|
||||||
|
{
|
||||||
|
_putcon(tohex(c >> 4));
|
||||||
|
_putcon(tohex(c & 0x0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _puthex16(uint16 w) // puts a HHHH hex string
|
||||||
|
{
|
||||||
|
_puthex8(w >> 8);
|
||||||
|
_puthex8(w & 0x00ff);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _putdec(uint16_t w) {
|
||||||
|
char buf[] = " 0";
|
||||||
|
size_t i=sizeof(buf)-1;
|
||||||
|
while(w) {
|
||||||
|
assert(i > 0);
|
||||||
|
buf[--i] = '0' + (w % 10);
|
||||||
|
w /= 10;
|
||||||
|
}
|
||||||
|
_puts(buf);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
1782
runcpm-rp2350-hstx-usb/runcpm-pico/cpm.h
Normal file
1782
runcpm-rp2350-hstx-usb/runcpm-pico/cpm.h
Normal file
File diff suppressed because it is too large
Load diff
4637
runcpm-rp2350-hstx-usb/runcpm-pico/cpu.h
Normal file
4637
runcpm-rp2350-hstx-usb/runcpm-pico/cpu.h
Normal file
File diff suppressed because it is too large
Load diff
635
runcpm-rp2350-hstx-usb/runcpm-pico/disk.h
Normal file
635
runcpm-rp2350-hstx-usb/runcpm-pico/disk.h
Normal file
|
|
@ -0,0 +1,635 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Mockba the Borg
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#ifndef DISK_H
|
||||||
|
#define DISK_H
|
||||||
|
|
||||||
|
/* see main.c for definition */
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __DJGPP__
|
||||||
|
#include <dirent.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
Disk errors
|
||||||
|
*/
|
||||||
|
#define errWRITEPROT 1
|
||||||
|
#define errSELECT 2
|
||||||
|
|
||||||
|
#define RW (roVector & (1 << cDrive))
|
||||||
|
|
||||||
|
// Prints out a BDOS error
|
||||||
|
void _error(uint8 error) {
|
||||||
|
_puts("\r\nBdos Err on ");
|
||||||
|
_putcon('A' + cDrive);
|
||||||
|
_puts(": ");
|
||||||
|
switch (error) {
|
||||||
|
case errWRITEPROT:
|
||||||
|
_puts("R/O");
|
||||||
|
break;
|
||||||
|
case errSELECT:
|
||||||
|
_puts("Select");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
_puts("\r\nCP/M ERR");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Status = _getch();
|
||||||
|
_puts("\r\n");
|
||||||
|
cDrive = oDrive = _RamRead(DSKByte) & 0x0f;
|
||||||
|
Status = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selects the disk to be used by the next disk function
|
||||||
|
int _SelectDisk(uint8 dr) {
|
||||||
|
uint8 result = 0xff;
|
||||||
|
uint8 disk[2] = { 'A', 0 };
|
||||||
|
|
||||||
|
if (!dr || dr == '?') {
|
||||||
|
dr = cDrive; // This will set dr to defDisk in case no disk is passed
|
||||||
|
} else {
|
||||||
|
--dr; // Called from BDOS, set dr back to 0=A: format
|
||||||
|
}
|
||||||
|
|
||||||
|
disk[0] += dr;
|
||||||
|
if (_sys_select(&disk[0])) {
|
||||||
|
loginVector = loginVector | (1 << (disk[0] - 'A'));
|
||||||
|
result = 0x00;
|
||||||
|
} else {
|
||||||
|
_error(errSELECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts a FCB entry onto a host OS filename string
|
||||||
|
uint8 _FCBtoHostname(uint16 fcbaddr, uint8* filename) {
|
||||||
|
uint8 addDot = TRUE;
|
||||||
|
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
|
||||||
|
uint8 i = 0;
|
||||||
|
uint8 unique = TRUE;
|
||||||
|
uint8 c;
|
||||||
|
|
||||||
|
if (F->dr && F->dr != '?') {
|
||||||
|
*(filename++) = (F->dr - 1) + 'A';
|
||||||
|
} else {
|
||||||
|
*(filename++) = cDrive + 'A';
|
||||||
|
}
|
||||||
|
*(filename++) = FOLDERCHAR;
|
||||||
|
|
||||||
|
*(filename++) = toupper(tohex(userCode));
|
||||||
|
*(filename++) = FOLDERCHAR;
|
||||||
|
|
||||||
|
if (F->dr != '?') {
|
||||||
|
while (i < 8) {
|
||||||
|
c = F->fn[i] & 0x7F;
|
||||||
|
#ifdef NOSLASH
|
||||||
|
if (c == '/')
|
||||||
|
c = '_';
|
||||||
|
#endif
|
||||||
|
if (c > 32)
|
||||||
|
*(filename++) = toupper(c);
|
||||||
|
if (c == '?')
|
||||||
|
unique = FALSE;
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
i = 0;
|
||||||
|
while (i < 3) {
|
||||||
|
c = F->tp[i] & 0x7F;
|
||||||
|
if (c > 32) {
|
||||||
|
if (addDot) {
|
||||||
|
addDot = FALSE;
|
||||||
|
*(filename++) = '.'; // Only add the dot if there's an extension
|
||||||
|
}
|
||||||
|
#ifdef NOSLASH
|
||||||
|
if (c == '/')
|
||||||
|
c = '_';
|
||||||
|
#endif
|
||||||
|
*(filename++) = toupper(c);
|
||||||
|
}
|
||||||
|
if (c == '?')
|
||||||
|
unique = FALSE;
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (i = 0; i < 8; ++i)
|
||||||
|
*(filename++) = '?';
|
||||||
|
*(filename++) = '.';
|
||||||
|
for (i = 0; i < 3; ++i)
|
||||||
|
*(filename++) = '?';
|
||||||
|
unique = FALSE;
|
||||||
|
}
|
||||||
|
*filename = 0x00;
|
||||||
|
|
||||||
|
return(unique);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convers a host OS filename string onto a FCB entry
|
||||||
|
void _HostnameToFCB(uint16 fcbaddr, uint8* filename) {
|
||||||
|
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
|
||||||
|
uint8 i = 0;
|
||||||
|
|
||||||
|
++filename;
|
||||||
|
if (*filename == FOLDERCHAR) { // Skips the drive and / if needed
|
||||||
|
filename += 3;
|
||||||
|
} else {
|
||||||
|
--filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (*filename != 0 && *filename != '.') {
|
||||||
|
F->fn[i] = toupper(*filename);
|
||||||
|
++filename;
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
while (i < 8) {
|
||||||
|
F->fn[i] = ' ';
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
if (*filename == '.')
|
||||||
|
++filename;
|
||||||
|
i = 0;
|
||||||
|
while (*filename != 0) {
|
||||||
|
F->tp[i] = toupper(*filename);
|
||||||
|
++filename;
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
while (i < 3) {
|
||||||
|
F->tp[i] = ' ';
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts a string name (AB.TXT) onto FCB name (AB TXT)
|
||||||
|
void _HostnameToFCBname(uint8* from, uint8* to) {
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
++from;
|
||||||
|
if (*from == FOLDERCHAR) { // Skips the drive and / if needed
|
||||||
|
from += 3;
|
||||||
|
} else {
|
||||||
|
--from;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (*from != 0 && *from != '.') {
|
||||||
|
*to = toupper(*from);
|
||||||
|
++to; ++from; ++i;
|
||||||
|
}
|
||||||
|
while (i < 8) {
|
||||||
|
*to = ' ';
|
||||||
|
++to; ++i;
|
||||||
|
}
|
||||||
|
if (*from == '.')
|
||||||
|
++from;
|
||||||
|
i = 0;
|
||||||
|
while (*from != 0) {
|
||||||
|
*to = toupper(*from);
|
||||||
|
++to; ++from; ++i;
|
||||||
|
}
|
||||||
|
while (i < 3) {
|
||||||
|
*to = ' ';
|
||||||
|
++to; ++i;
|
||||||
|
}
|
||||||
|
*to = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a fake directory entry for the current dmaAddr FCB
|
||||||
|
void _mockupDirEntry(void) {
|
||||||
|
CPM_DIRENTRY* DE = (CPM_DIRENTRY*)_RamSysAddr(dmaAddr);
|
||||||
|
uint8 blocks, i;
|
||||||
|
|
||||||
|
for (i = 0; i < sizeof(CPM_DIRENTRY); ++i)
|
||||||
|
_RamWrite(dmaAddr + i, 0x00); // zero out directory entry
|
||||||
|
_HostnameToFCB(dmaAddr, (uint8*)findNextDirName);
|
||||||
|
|
||||||
|
if (allUsers) {
|
||||||
|
DE->dr = currFindUser; // set user code for return
|
||||||
|
} else {
|
||||||
|
DE->dr = userCode;
|
||||||
|
}
|
||||||
|
// does file fit in a single directory entry?
|
||||||
|
if (fileExtents <= extentsPerDirEntry) {
|
||||||
|
if (fileExtents) {
|
||||||
|
DE->ex = (fileExtents - 1 + fileExtentsUsed) % (MaxEX + 1);
|
||||||
|
DE->s2 = (fileExtents - 1 + fileExtentsUsed) / (MaxEX + 1);
|
||||||
|
DE->rc = fileRecords - (BlkEX * (fileExtents - 1));
|
||||||
|
}
|
||||||
|
blocks = (fileRecords >> blockShift) + ((fileRecords & blockMask) ? 1 : 0);
|
||||||
|
fileRecords = 0;
|
||||||
|
fileExtents = 0;
|
||||||
|
fileExtentsUsed = 0;
|
||||||
|
} else { // no, max out the directory entry
|
||||||
|
DE->ex = (extentsPerDirEntry - 1 + fileExtentsUsed) % (MaxEX + 1);
|
||||||
|
DE->s2 = (extentsPerDirEntry - 1 + fileExtentsUsed) / (MaxEX + 1);
|
||||||
|
DE->rc = BlkEX;
|
||||||
|
blocks = numAllocBlocks < 256 ? 16 : 8;
|
||||||
|
// update remaining records and extents for next call
|
||||||
|
fileRecords -= BlkEX * extentsPerDirEntry;
|
||||||
|
fileExtents -= extentsPerDirEntry;
|
||||||
|
fileExtentsUsed += extentsPerDirEntry;
|
||||||
|
}
|
||||||
|
// phoney up an appropriate number of allocation blocks
|
||||||
|
if (numAllocBlocks < 256) {
|
||||||
|
for (i = 0; i < blocks; ++i)
|
||||||
|
DE->al[i] = (uint8)firstFreeAllocBlock++;
|
||||||
|
} else {
|
||||||
|
for (i = 0; i < 2 * blocks; i += 2) {
|
||||||
|
DE->al[i] = firstFreeAllocBlock & 0xFF;
|
||||||
|
DE->al[i + 1] = firstFreeAllocBlock >> 8;
|
||||||
|
++firstFreeAllocBlock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches a FCB name to a search pattern
|
||||||
|
uint8 match(uint8* fcbname, uint8* pattern) {
|
||||||
|
uint8 result = 1;
|
||||||
|
uint8 i;
|
||||||
|
|
||||||
|
for (i = 0; i < 12; ++i) {
|
||||||
|
if (*pattern == '?' || *pattern == *fcbname) {
|
||||||
|
++pattern; ++fcbname;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
result = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the size of a file
|
||||||
|
long _FileSize(uint16 fcbaddr) {
|
||||||
|
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
|
||||||
|
long r, l = -1;
|
||||||
|
|
||||||
|
if (!_SelectDisk(F->dr)) {
|
||||||
|
_FCBtoHostname(fcbaddr, &filename[0]);
|
||||||
|
l = _sys_filesize(filename);
|
||||||
|
r = l % BlkSZ;
|
||||||
|
if (r)
|
||||||
|
l = l + BlkSZ - r;
|
||||||
|
}
|
||||||
|
return(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opens a file
|
||||||
|
uint8 _OpenFile(uint16 fcbaddr) {
|
||||||
|
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
|
||||||
|
uint8 result = 0xff;
|
||||||
|
long len;
|
||||||
|
int32 i;
|
||||||
|
|
||||||
|
if (!_SelectDisk(F->dr)) {
|
||||||
|
_FCBtoHostname(fcbaddr, &filename[0]);
|
||||||
|
if (_sys_openfile(&filename[0])) {
|
||||||
|
|
||||||
|
len = _FileSize(fcbaddr) / BlkSZ; // Compute the len on the file in blocks
|
||||||
|
|
||||||
|
F->s1 = 0x00;
|
||||||
|
F->s2 = 0x80; // set unmodified flag
|
||||||
|
|
||||||
|
|
||||||
|
F->rc = len > MaxRC ? MaxRC : (uint8)len;
|
||||||
|
for (i = 0; i < 16; ++i) // Clean up AL
|
||||||
|
F->al[i] = 0x00;
|
||||||
|
|
||||||
|
result = 0x00;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closes a file
|
||||||
|
uint8 _CloseFile(uint16 fcbaddr) {
|
||||||
|
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
|
||||||
|
uint8 result = 0xff;
|
||||||
|
|
||||||
|
if (!_SelectDisk(F->dr)) {
|
||||||
|
if (!(F->s2 & 0x80)) { // if file is modified
|
||||||
|
if (!RW) {
|
||||||
|
_FCBtoHostname(fcbaddr, &filename[0]);
|
||||||
|
if (fcbaddr == BatchFCB)
|
||||||
|
_Truncate((char*)filename, F->rc); // Truncate $$$.SUB to F->rc CP/M records so SUBMIT.COM can work
|
||||||
|
result = 0x00;
|
||||||
|
} else {
|
||||||
|
_error(errWRITEPROT);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = 0x00;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a file
|
||||||
|
uint8 _MakeFile(uint16 fcbaddr) {
|
||||||
|
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
|
||||||
|
uint8 result = 0xff;
|
||||||
|
uint8 i;
|
||||||
|
|
||||||
|
if (!_SelectDisk(F->dr)) {
|
||||||
|
if (!RW) {
|
||||||
|
_FCBtoHostname(fcbaddr, &filename[0]);
|
||||||
|
if (_sys_makefile(&filename[0])) {
|
||||||
|
F->ex = 0x00; // Makefile also initializes the FCB (file becomes "open")
|
||||||
|
F->s1 = 0x00;
|
||||||
|
F->s2 = 0x00; // newly created files are already modified
|
||||||
|
F->rc = 0x00;
|
||||||
|
for (i = 0; i < 16; ++i) // Clean up AL
|
||||||
|
F->al[i] = 0x00;
|
||||||
|
F->cr = 0x00;
|
||||||
|
result = 0x00;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_error(errWRITEPROT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Searches for the first directory file
|
||||||
|
uint8 _SearchFirst(uint16 fcbaddr, uint8 isdir) {
|
||||||
|
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
|
||||||
|
uint8 result = 0xff;
|
||||||
|
|
||||||
|
if (!_SelectDisk(F->dr)) {
|
||||||
|
_FCBtoHostname(fcbaddr, &filename[0]);
|
||||||
|
allUsers = F->dr == '?';
|
||||||
|
allExtents = F->ex == '?';
|
||||||
|
if (allUsers) {
|
||||||
|
result = _findfirstallusers(isdir);
|
||||||
|
} else {
|
||||||
|
result = _findfirst(isdir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Searches for the next directory file
|
||||||
|
uint8 _SearchNext(uint16 fcbaddr, uint8 isdir) {
|
||||||
|
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(tmpFCB);
|
||||||
|
uint8 result = 0xff;
|
||||||
|
|
||||||
|
if (!_SelectDisk(F->dr)) {
|
||||||
|
if (allUsers) {
|
||||||
|
result = _findnextallusers(isdir);
|
||||||
|
} else {
|
||||||
|
result = _findnext(isdir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deletes a file
|
||||||
|
uint8 _DeleteFile(uint16 fcbaddr) {
|
||||||
|
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
|
||||||
|
#if defined(USE_PUN) || defined(USE_LST)
|
||||||
|
CPM_FCB* T = (CPM_FCB*)_RamSysAddr(tmpFCB);
|
||||||
|
#endif
|
||||||
|
uint8 result = 0xff;
|
||||||
|
uint8 deleted = 0xff;
|
||||||
|
|
||||||
|
if (!_SelectDisk(F->dr)) {
|
||||||
|
if (!RW) {
|
||||||
|
result = _SearchFirst(fcbaddr, FALSE); // FALSE = Does not create a fake dir entry when finding the file
|
||||||
|
while (result != 0xff) {
|
||||||
|
#ifdef USE_PUN
|
||||||
|
if (!strcmp((char*)T->fn, "PUN TXT") && pun_open) {
|
||||||
|
_sys_fclose(pun_dev);
|
||||||
|
pun_open = FALSE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_LST
|
||||||
|
if (!strcmp((char*)T->fn, "LST TXT") && lst_open) {
|
||||||
|
_sys_fclose(lst_dev);
|
||||||
|
lst_open = FALSE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
_FCBtoHostname(tmpFCB, &filename[0]);
|
||||||
|
if (_sys_deletefile(&filename[0]))
|
||||||
|
deleted = 0x00;
|
||||||
|
result = _SearchFirst(fcbaddr, FALSE); // FALSE = Does not create a fake dir entry when finding the file
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_error(errWRITEPROT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return(deleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renames a file
|
||||||
|
uint8 _RenameFile(uint16 fcbaddr) {
|
||||||
|
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
|
||||||
|
uint8 result = 0xff;
|
||||||
|
|
||||||
|
if (!_SelectDisk(F->dr)) {
|
||||||
|
if (!RW) {
|
||||||
|
_RamWrite(fcbaddr + 16, _RamRead(fcbaddr)); // Prevents rename from moving files among folders
|
||||||
|
_FCBtoHostname(fcbaddr + 16, &newname[0]);
|
||||||
|
_FCBtoHostname(fcbaddr, &filename[0]);
|
||||||
|
if (_sys_renamefile(&filename[0], &newname[0]))
|
||||||
|
result = 0x00;
|
||||||
|
} else {
|
||||||
|
_error(errWRITEPROT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sequential read
|
||||||
|
uint8 _ReadSeq(uint16 fcbaddr) {
|
||||||
|
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
|
||||||
|
uint8 result = 0xff;
|
||||||
|
|
||||||
|
long fpos = ((F->s2 & MaxS2) * BlkS2 * BlkSZ) +
|
||||||
|
(F->ex * BlkEX * BlkSZ) +
|
||||||
|
(F->cr * BlkSZ);
|
||||||
|
|
||||||
|
if (!_SelectDisk(F->dr)) {
|
||||||
|
_FCBtoHostname(fcbaddr, &filename[0]);
|
||||||
|
result = _sys_readseq(&filename[0], fpos);
|
||||||
|
if (!result) { // Read succeeded, adjust FCB
|
||||||
|
++F->cr;
|
||||||
|
if (F->cr > MaxCR) {
|
||||||
|
F->cr = 1;
|
||||||
|
++F->ex;
|
||||||
|
}
|
||||||
|
if (F->ex > MaxEX) {
|
||||||
|
F->ex = 0;
|
||||||
|
++F->s2;
|
||||||
|
}
|
||||||
|
if ((F->s2 & 0x7F) > MaxS2)
|
||||||
|
result = 0xfe; // (todo) not sure what to do
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sequential write
|
||||||
|
uint8 _WriteSeq(uint16 fcbaddr) {
|
||||||
|
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
|
||||||
|
uint8 result = 0xff;
|
||||||
|
|
||||||
|
long fpos = ((F->s2 & MaxS2) * BlkS2 * BlkSZ) +
|
||||||
|
(F->ex * BlkEX * BlkSZ) +
|
||||||
|
(F->cr * BlkSZ);
|
||||||
|
|
||||||
|
if (!_SelectDisk(F->dr)) {
|
||||||
|
if (!RW) {
|
||||||
|
_FCBtoHostname(fcbaddr, &filename[0]);
|
||||||
|
result = _sys_writeseq(&filename[0], fpos);
|
||||||
|
if (!result) { // Write succeeded, adjust FCB
|
||||||
|
F->s2 &= 0x7F; // reset unmodified flag
|
||||||
|
++F->cr;
|
||||||
|
if (F->cr > MaxCR) {
|
||||||
|
F->cr = 1;
|
||||||
|
++F->ex;
|
||||||
|
}
|
||||||
|
if (F->ex > MaxEX) {
|
||||||
|
F->ex = 0;
|
||||||
|
++F->s2;
|
||||||
|
}
|
||||||
|
if (F->s2 > MaxS2)
|
||||||
|
result = 0xfe; // (todo) not sure what to do
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_error(errWRITEPROT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Random read
|
||||||
|
uint8 _ReadRand(uint16 fcbaddr) {
|
||||||
|
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
|
||||||
|
uint8 result = 0xff;
|
||||||
|
|
||||||
|
int32 record = (F->r2 << 16) | (F->r1 << 8) | F->r0;
|
||||||
|
long fpos = record * BlkSZ;
|
||||||
|
|
||||||
|
if (!_SelectDisk(F->dr)) {
|
||||||
|
_FCBtoHostname(fcbaddr, &filename[0]);
|
||||||
|
result = _sys_readrand(&filename[0], fpos);
|
||||||
|
if (result == 0 || result == 1 || result == 4) {
|
||||||
|
// adjust FCB unless error #6 (seek past 8MB - max CP/M file & disk size)
|
||||||
|
F->cr = record & 0x7F;
|
||||||
|
F->ex = (record >> 7) & 0x1f;
|
||||||
|
if (F->s2 & 0x80) {
|
||||||
|
F->s2 = ((record >> 12) & MaxS2) | 0x80;
|
||||||
|
} else {
|
||||||
|
F->s2 = (record >> 12) & MaxS2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Random write
|
||||||
|
uint8 _WriteRand(uint16 fcbaddr) {
|
||||||
|
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
|
||||||
|
uint8 result = 0xff;
|
||||||
|
|
||||||
|
int32 record = (F->r2 << 16) | (F->r1 << 8) | F->r0;
|
||||||
|
long fpos = record * BlkSZ;
|
||||||
|
|
||||||
|
if (!_SelectDisk(F->dr)) {
|
||||||
|
if (!RW) {
|
||||||
|
_FCBtoHostname(fcbaddr, &filename[0]);
|
||||||
|
result = _sys_writerand(&filename[0], fpos);
|
||||||
|
if (!result) { // Write succeeded, adjust FCB
|
||||||
|
F->cr = record & 0x7F;
|
||||||
|
F->ex = (record >> 7) & 0x1f;
|
||||||
|
F->s2 = (record >> 12) & MaxS2; // resets unmodified flag
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_error(errWRITEPROT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the size of a CP/M file
|
||||||
|
uint8 _GetFileSize(uint16 fcbaddr) {
|
||||||
|
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
|
||||||
|
uint8 result = 0xff;
|
||||||
|
int32 count = _FileSize(DE) >> 7;
|
||||||
|
|
||||||
|
if (count != -1) {
|
||||||
|
F->r0 = count & 0xff;
|
||||||
|
F->r1 = (count >> 8) & 0xff;
|
||||||
|
F->r2 = (count >> 16) & 0xff;
|
||||||
|
}
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the next random record
|
||||||
|
uint8 _SetRandom(uint16 fcbaddr) {
|
||||||
|
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
|
||||||
|
uint8 result = 0x00;
|
||||||
|
|
||||||
|
int32 count = F->cr & 0x7f;
|
||||||
|
count += (F->ex & 0x1f) << 7;
|
||||||
|
count += (F->s2 & MaxS2) << 12;
|
||||||
|
|
||||||
|
F->r0 = count & 0xff;
|
||||||
|
F->r1 = (count >> 8) & 0xff;
|
||||||
|
F->r2 = (count >> 16) & 0xff;
|
||||||
|
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the current user area
|
||||||
|
void _SetUser(uint8 user) {
|
||||||
|
userCode = user & 0x1f; // BDOS unoficially allows user areas 0-31
|
||||||
|
// this may create folders from G-V if this function is called from an user program
|
||||||
|
// It is an unwanted behavior, but kept as BDOS does it
|
||||||
|
#ifdef NOHIGHUSER
|
||||||
|
if(userCode < 16)
|
||||||
|
#endif
|
||||||
|
_MakeUserDir(); // Creates the user dir (0-F[G-V]) if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a disk directory folder
|
||||||
|
uint8 _MakeDisk(uint16 fcbaddr) {
|
||||||
|
CPM_FCB* F = (CPM_FCB*)_RamSysAddr(fcbaddr);
|
||||||
|
return(_sys_makedisk(F->dr));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if there's a temp submit file present
|
||||||
|
uint8 _CheckSUB(void) {
|
||||||
|
uint8 result;
|
||||||
|
uint8 oCode = userCode; // Saves the current user code (original BDOS does not do this)
|
||||||
|
_HostnameToFCB(tmpFCB, (uint8*)"$???????.???"); // The original BDOS in fact only looks for a file which start with $
|
||||||
|
#ifdef BATCHA
|
||||||
|
_RamWrite(tmpFCB, 1); // Forces it to be checked on drive A:
|
||||||
|
#endif
|
||||||
|
#ifdef BATCH0
|
||||||
|
userCode = 0; // Forces it to be checked on user 0
|
||||||
|
#endif
|
||||||
|
result = (_SearchFirst(tmpFCB, FALSE) == 0x00) ? 0xff : 0x00;
|
||||||
|
userCode = oCode; // Restores the current user code
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HASLUA
|
||||||
|
// Executes a Lua script
|
||||||
|
uint8 _RunLua(uint16 fcbaddr) {
|
||||||
|
uint8 luascript[17];
|
||||||
|
uint8 result = 0xff;
|
||||||
|
|
||||||
|
if (_FCBtoHostname(fcbaddr, &luascript[0])) { // Script name must be unique
|
||||||
|
if (!_SearchFirst(fcbaddr, FALSE)) { // and must exist
|
||||||
|
result = _RunLuaScript((char*)&luascript[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
252
runcpm-rp2350-hstx-usb/runcpm-pico/globals.h
Normal file
252
runcpm-rp2350-hstx-usb/runcpm-pico/globals.h
Normal file
|
|
@ -0,0 +1,252 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Mockba the Borg
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#ifndef GLOBALS_H
|
||||||
|
#define GLOBALS_H
|
||||||
|
|
||||||
|
/* Some definitions needed globally */
|
||||||
|
#ifdef __MINGW32__
|
||||||
|
#include <ctype.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Definition for enabling incrementing the R register for each M1 cycle */
|
||||||
|
#define DO_INCR
|
||||||
|
|
||||||
|
/* Definitions for enabling PUN: and LST: devices */
|
||||||
|
#define USE_PUN // The pun.txt and lst.txt files will appear on drive A: user 0
|
||||||
|
#define USE_LST
|
||||||
|
|
||||||
|
/* Definitions for file/console based debugging */
|
||||||
|
//#define DEBUG // Enables the internal debugger (enabled by default on vstudio debug builds)
|
||||||
|
//#define iDEBUG // Enables instruction logging onto iDebug.log (for development debug only)
|
||||||
|
//#define DEBUGLOG // Writes extensive call trace information to RunCPM.log
|
||||||
|
//#define CONSOLELOG // Writes debug information to console instead of file
|
||||||
|
//#define LOGBIOS_NOT 01 // If defined will not log this BIOS function number
|
||||||
|
//#define LOGBIOS_ONLY 02 // If defines will log only this BIOS function number
|
||||||
|
//#define LOGBDOS_NOT 06 // If defined will not log this BDOS function number
|
||||||
|
//#define LOGBDOS_ONLY 22 // If defines will log only this BDOS function number
|
||||||
|
#define LogName "RunCPM.log"
|
||||||
|
|
||||||
|
/* RunCPM version for the greeting header */
|
||||||
|
#define VERSION "6.0"
|
||||||
|
#define VersionBCD 0x60
|
||||||
|
|
||||||
|
/* Definition of which CCP to use (must define only one) */
|
||||||
|
#define CCP_INTERNAL // If this is defined, an internal CCP will emulated
|
||||||
|
//#define CCP_DR
|
||||||
|
//#define CCP_CCPZ
|
||||||
|
//#define CCP_ZCPR2
|
||||||
|
//#define CCP_ZCPR3
|
||||||
|
//#define CCP_Z80
|
||||||
|
|
||||||
|
/* Definition of the CCP memory information */
|
||||||
|
//
|
||||||
|
#ifdef CCP_INTERNAL
|
||||||
|
#define CCPname "INTERNAL v3.0" // Will use the CCP from ccp.h
|
||||||
|
#define VersionCCP 0x30 // 0x10 and above reserved for Internal CCP
|
||||||
|
#define BatchFCB (tmpFCB + 48)
|
||||||
|
#define CCPaddr BDOSjmppage // Internal CCP has size 0
|
||||||
|
#endif
|
||||||
|
//
|
||||||
|
#ifdef CCP_DR
|
||||||
|
#define CCPname "CCP-DR." STR(TPASIZE) "K"
|
||||||
|
#define VersionCCP 0x00 // Version to be used by INFO.COM
|
||||||
|
#define BatchFCB (CCPaddr + 0x7AC) // Position of the $$$.SUB fcb on this CCP
|
||||||
|
#define CCPaddr (BDOSjmppage-0x0800) // CCP memory address
|
||||||
|
#endif
|
||||||
|
//
|
||||||
|
#ifdef CCP_CCPZ
|
||||||
|
#define CCPname "CCP-CCPZ." STR(TPASIZE) "K"
|
||||||
|
#define VersionCCP 0x01
|
||||||
|
#define BatchFCB (CCPaddr + 0x7A) // Position of the $$$.SUB fcb on this CCP
|
||||||
|
#define CCPaddr (BDOSjmppage-0x0800)
|
||||||
|
#endif
|
||||||
|
//
|
||||||
|
#ifdef CCP_ZCPR2
|
||||||
|
#define CCPname "CCP-ZCP2." STR(TPASIZE) "K"
|
||||||
|
#define VersionCCP 0x02
|
||||||
|
#define BatchFCB (CCPaddr + 0x5E) // Position of the $$$.SUB fcb on this CCP
|
||||||
|
#define CCPaddr (BDOSjmppage-0x0800)
|
||||||
|
#endif
|
||||||
|
//
|
||||||
|
#ifdef CCP_ZCPR3
|
||||||
|
#define CCPname "CCP-ZCP3." STR(TPASIZE) "K"
|
||||||
|
#define VersionCCP 0x03
|
||||||
|
#define BatchFCB (CCPaddr + 0x5E) // Position of the $$$.SUB fcb on this CCP
|
||||||
|
#define CCPaddr (BDOSjmppage-0x1000)
|
||||||
|
#endif
|
||||||
|
//
|
||||||
|
#ifdef CCP_Z80
|
||||||
|
#define CCPname "CCP-Z80." STR(TPASIZE) "K"
|
||||||
|
#define VersionCCP 0x04
|
||||||
|
#define BatchFCB (CCPaddr + 0x79E) // Position of the $$$.SUB fcb on this CCP
|
||||||
|
#define CCPaddr (BDOSjmppage-0x0800)
|
||||||
|
#endif
|
||||||
|
//
|
||||||
|
#ifndef CCPname
|
||||||
|
#error No CCP defined
|
||||||
|
#endif
|
||||||
|
//
|
||||||
|
#define STR_HELPER(x) #x
|
||||||
|
#define STR(x) STR_HELPER(x)
|
||||||
|
// #define CCPHEAD "\r\nRunCPM Version " VERSION " (CP/M " STR(TPASIZE) "K)\r\n"
|
||||||
|
#define CCPHEAD "\r\nRunCPM [" TEXT_BOLD "v" VERSION TEXT_NORMAL "] => CCP:[" TEXT_BOLD CCPname TEXT_NORMAL "] TPA:" TEXT_BOLD STR(TPASIZE) "K" TEXT_NORMAL "\r\n"
|
||||||
|
|
||||||
|
#define NOSLASH // Will translate '/' to '_' on filenames to prevent directory errors
|
||||||
|
|
||||||
|
//#define HASLUA // Will enable Lua scripting (BDOS call 254)
|
||||||
|
// Should be passed externally per-platform with -DHASLUA
|
||||||
|
|
||||||
|
//#define PROFILE // For measuring time taken to run a CP/M command
|
||||||
|
// This should be enabled only for debugging purposes when trying to improve emulation speed
|
||||||
|
|
||||||
|
#define NOHIGHUSER // Prevents the creation of user folders above 'F' (15) by programs
|
||||||
|
// Original CP/M BDOS allows it, but I prefer to keep the folders clean
|
||||||
|
|
||||||
|
/* Definition for CP/M 2.2 user number support */
|
||||||
|
|
||||||
|
#define BATCHA // If this is defined, the $$$.SUB will be looked for on drive A:
|
||||||
|
//#define BATCH0 // If this is defined, the $$$.SUB will be looked for on user area 0
|
||||||
|
// The default behavior of DRI's CP/M 2.2 was to have $$$.SUB created on the current drive/user while looking for it
|
||||||
|
// on drive A: current user, which made it complicated to run SUBMITs when not logged to drive A: user 0
|
||||||
|
|
||||||
|
/* Some environment and type definitions */
|
||||||
|
|
||||||
|
#ifndef TRUE
|
||||||
|
#define FALSE 0
|
||||||
|
#define TRUE 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef signed char int8;
|
||||||
|
typedef signed short int16;
|
||||||
|
typedef signed int int32;
|
||||||
|
typedef unsigned char uint8;
|
||||||
|
typedef unsigned short uint16;
|
||||||
|
typedef unsigned int uint32;
|
||||||
|
|
||||||
|
#define LOW_DIGIT(x) ((x) & 0xf)
|
||||||
|
#define HIGH_DIGIT(x) (((x) >> 4) & 0xf)
|
||||||
|
#define LOW_REGISTER(x) ((x) & 0xff)
|
||||||
|
#define HIGH_REGISTER(x) (((x) >> 8) & 0xff)
|
||||||
|
|
||||||
|
#define SET_LOW_REGISTER(x, v) x = (((x) & 0xff00) | ((v) & 0xff))
|
||||||
|
#define SET_HIGH_REGISTER(x, v) x = (((x) & 0xff) | (((v) & 0xff) << 8))
|
||||||
|
|
||||||
|
#define WORD16(x) ((x) & 0xffff)
|
||||||
|
|
||||||
|
/* CP/M Page 0 definitions */
|
||||||
|
#define IOByte 0x03
|
||||||
|
#define DSKByte 0x04
|
||||||
|
|
||||||
|
/* CP/M disk definitions */
|
||||||
|
#define BlkSZ 128 // CP/M block size
|
||||||
|
#define BlkEX 128 // Number of blocks on an extension
|
||||||
|
#define ExtSZ (BlkSZ * BlkEX)
|
||||||
|
#define BlkS2 4096 // Number of blocks on a S2 (module)
|
||||||
|
#define MaxEX 31 // Maximum value the EX field can take
|
||||||
|
#define MaxS2 15 // Maximum value the S2 (modules) field can take - Can be set to 63 to emulate CP/M Plus
|
||||||
|
#define MaxCR 128 // Maximum value the CR field can take
|
||||||
|
#define MaxRC 128 // Maximum value the RC field can take
|
||||||
|
|
||||||
|
/* CP/M memory definitions */
|
||||||
|
#define TPASIZE 64 // Can be 60 for CP/M 2.2 compatibility or more, up to 64 for extra memory
|
||||||
|
// Values other than 60 or 64 would require rebuilding the CCP
|
||||||
|
// For TPASIZE<60 CCP ORG = (SIZEK * 1024) - 0x0C00
|
||||||
|
|
||||||
|
#define BANKS 1 // Number of memory banks available
|
||||||
|
static uint8 curBank = 1; // Number of the current RAM bank in use (1-x, not 0-x)
|
||||||
|
static uint8 isXmove = FALSE; // Used by BIOS
|
||||||
|
static uint8 srcBank = 1; // Source bank for memory MOVE
|
||||||
|
static uint8 dstBank = 1; // Destination bank for memory MOVE
|
||||||
|
static uint8 ioBank = 1; // Destination bank for sector IO
|
||||||
|
|
||||||
|
#define PAGESIZE 64 * 1024 // RAM(plus ROM) needs to be 64K to avoid compatibility issues
|
||||||
|
#define MEMSIZE PAGESIZE * BANKS // Total RAM size
|
||||||
|
|
||||||
|
#if BANKS==1
|
||||||
|
#define RAM_FAST // If this is defined, all RAM function calls become direct access (see below)
|
||||||
|
// This saves about 2K on the Arduino code and should bring speed improvements
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef RAM_FAST // Makes all function calls to memory access into direct RAM access (less calls / less code)
|
||||||
|
static uint8 RAM[MEMSIZE];
|
||||||
|
#define _RamSysAddr(a) &RAM[a]
|
||||||
|
#define _RamRead(a) RAM[a]
|
||||||
|
#define _RamRead16(a) ((RAM[((a) & 0xffff) + 1] << 8) | RAM[(a) & 0xffff])
|
||||||
|
#define _RamWrite(a, v) RAM[a] = v
|
||||||
|
#define _RamWrite16(a, v) RAM[a] = (v) & 0xff; RAM[(a) + 1] = (v) >> 8
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Size of the allocated pages (Minimum size = 1 page = 256 bytes)
|
||||||
|
|
||||||
|
// BIOS Pages (512 bytes from the top of memory)
|
||||||
|
#define BIOSjmppage (PAGESIZE - 512)
|
||||||
|
#define BIOSpage (BIOSjmppage + 256)
|
||||||
|
|
||||||
|
// BDOS Pages (depends on TPASIZE for external CCPs)
|
||||||
|
#if defined CCP_INTERNAL
|
||||||
|
#define BDOSjmppage (BIOSjmppage - 256)
|
||||||
|
#define BDOSpage (BDOSjmppage + 16)
|
||||||
|
#else
|
||||||
|
#define BDOSjmppage (TPASIZE * 1024) - 1024
|
||||||
|
#define BDOSpage (BDOSjmppage + 256)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define DPBaddr (BIOSpage + 128) // Address of the Disk Parameter Block (Hardcoded in BIOS)
|
||||||
|
#define DPHaddr (DPBaddr + 15) // Address of the Disk Parameter Header
|
||||||
|
|
||||||
|
#define SCBaddr (BDOSpage + 3) // Address of the System Control Block
|
||||||
|
#define tmpFCB (BDOSpage + 16) // Address of the temporary FCB
|
||||||
|
|
||||||
|
/* Definition of global variables */
|
||||||
|
static uint8 filename[17]; // Current filename in host filesystem format
|
||||||
|
static uint8 newname[17]; // New filename in host filesystem format
|
||||||
|
static uint8 fcbname[13]; // Current filename in CP/M format
|
||||||
|
static uint8 pattern[13]; // File matching pattern in CP/M format
|
||||||
|
static uint16 dmaAddr = 0x0080; // Current dmaAddr
|
||||||
|
static uint8 oDrive = 0; // Old selected drive
|
||||||
|
static uint8 cDrive = 0; // Currently selected drive
|
||||||
|
static uint8 userCode = 0; // Current user code
|
||||||
|
static uint16 roVector = 0;
|
||||||
|
static uint16 loginVector = 0;
|
||||||
|
static uint8 allUsers = FALSE; // true when dr is '?' in BDOS search first
|
||||||
|
static uint8 allExtents = FALSE; // true when ex is '?' in BDOS search first
|
||||||
|
static uint8 currFindUser = 0; // user number of current directory in BDOS search first on all user numbers
|
||||||
|
static uint8 blockShift; // disk allocation block shift
|
||||||
|
static uint8 blockMask; // disk allocation block mask
|
||||||
|
static uint8 extentMask; // disk extent mask
|
||||||
|
static uint16 firstBlockAfterDir; // first allocation block after directory
|
||||||
|
static uint16 numAllocBlocks; // # of allocation blocks on disk
|
||||||
|
static uint8 extentsPerDirEntry; // # of logical (16K) extents in a directory entry
|
||||||
|
#define logicalExtentBytes (16*1024UL)
|
||||||
|
static uint16 physicalExtentBytes;// # bytes described by 1 directory entry
|
||||||
|
|
||||||
|
#define tohex(x) ((x) < 10 ? (x) + 48 : (x) + 87)
|
||||||
|
|
||||||
|
/* Definition of externs to prevent precedence compilation errors */
|
||||||
|
#ifdef __cplusplus // If building on Arduino
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef RAM_FAST
|
||||||
|
extern uint8* _RamSysAddr(uint16 address);
|
||||||
|
extern void _RamWrite(uint16 address, uint8 value);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern void _Bdos(void);
|
||||||
|
extern void _Bios(void);
|
||||||
|
|
||||||
|
extern void _HostnameToFCB(uint16 fcbaddr, uint8* filename);
|
||||||
|
extern void _HostnameToFCBname(uint8* from, uint8* to);
|
||||||
|
extern void _mockupDirEntry(void);
|
||||||
|
extern uint8 match(uint8* fcbname, uint8* pattern);
|
||||||
|
|
||||||
|
extern void _puts(const char* str);
|
||||||
|
|
||||||
|
#ifdef __cplusplus // If building on Arduino
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
130
runcpm-rp2350-hstx-usb/runcpm-pico/hardware/pico/feather_dvi.h
Normal file
130
runcpm-rp2350-hstx-usb/runcpm-pico/hardware/pico/feather_dvi.h
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Mockba the Borg
|
||||||
|
// SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#include <SdFat.h> // SDFat - Adafruit Fork
|
||||||
|
#include <PicoDVI.h>
|
||||||
|
#include "../../console.h"
|
||||||
|
#include "../../arduino_hooks.h"
|
||||||
|
|
||||||
|
#ifndef USE_DISPLAY
|
||||||
|
#define USE_DISPLAY (1)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef USE_MSC
|
||||||
|
#define USE_MSC (0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if USE_DISPLAY
|
||||||
|
DVItext1 display(DVI_RES_800x240p30, adafruit_feather_dvi_cfg);
|
||||||
|
#endif
|
||||||
|
#define SPI_CLOCK (20'000'000)
|
||||||
|
#define SD_CS_PIN (10)
|
||||||
|
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK)
|
||||||
|
DedicatedSpiCard blockdevice;
|
||||||
|
FatFileSystem SD; // Filesystem object from SdFat
|
||||||
|
|
||||||
|
// =========================================================================================
|
||||||
|
// Define Board-Data
|
||||||
|
// GP25 green onboard LED
|
||||||
|
// =========================================================================================
|
||||||
|
#define LED (13)
|
||||||
|
#define LEDinv (false)
|
||||||
|
#define board_pico
|
||||||
|
#define board_analog_io
|
||||||
|
#define board_digital_io
|
||||||
|
|
||||||
|
// FUNCTIONS REQUIRED FOR USB MASS STORAGE ---------------------------------
|
||||||
|
|
||||||
|
#if USE_MSC
|
||||||
|
Adafruit_USBD_MSC usb_msc; // USB mass storage object
|
||||||
|
static bool msc_changed = true; // Is set true on filesystem changes
|
||||||
|
|
||||||
|
// Callback on READ10 command.
|
||||||
|
int32_t msc_read_cb(uint32_t lba, void *buffer, uint32_t bufsize) {
|
||||||
|
return blockdevice.readBlocks(lba, (uint8_t *)buffer, bufsize / 512) ? bufsize : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback on WRITE10 command.
|
||||||
|
int32_t msc_write_cb(uint32_t lba, uint8_t *buffer, uint32_t bufsize) {
|
||||||
|
digitalWrite(LED_BUILTIN, HIGH);
|
||||||
|
return blockdevice.writeBlocks(lba, buffer, bufsize / 512) ? bufsize : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback on WRITE10 completion.
|
||||||
|
void msc_flush_cb(void) {
|
||||||
|
blockdevice.syncBlocks(); // Sync with blockdevice
|
||||||
|
SD.cacheClear(); // Clear filesystem cache to force refresh
|
||||||
|
digitalWrite(LED_BUILTIN, LOW);
|
||||||
|
msc_changed = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if USE_DISPLAY
|
||||||
|
uint16_t underCursor = ' ';
|
||||||
|
|
||||||
|
void putch_display(uint8_t ch) {
|
||||||
|
auto x = display.getCursorX();
|
||||||
|
auto y = display.getCursorY();
|
||||||
|
display.drawPixel(x, y, underCursor);
|
||||||
|
if(ch == 8) {
|
||||||
|
if(x > 0) {
|
||||||
|
display.setCursor(--x, y);
|
||||||
|
display.drawPixel(x, y, ' ');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
display.write(ch);
|
||||||
|
}
|
||||||
|
x = display.getCursorX();
|
||||||
|
y = display.getCursorY();
|
||||||
|
underCursor = display.getPixel(x, y);
|
||||||
|
display.drawPixel(x, y, 0xDB);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint8_t getch_serial1(void) {
|
||||||
|
while(true) {
|
||||||
|
int r = Serial1.read();
|
||||||
|
if(r != -1) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool kbhit_serial1(void) {
|
||||||
|
return Serial1.available();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool port_init_early() {
|
||||||
|
#if USE_DISPLAY
|
||||||
|
vreg_set_voltage(VREG_VOLTAGE_1_20);
|
||||||
|
delay(10);
|
||||||
|
if (!display.begin()) { return false; }
|
||||||
|
_putch_hook = putch_display;
|
||||||
|
#endif
|
||||||
|
_getch_hook = getch_serial1;
|
||||||
|
_kbhit_hook = kbhit_serial1;
|
||||||
|
// USB mass storage / filesystem setup (do BEFORE Serial init)
|
||||||
|
if (!blockdevice.begin(SD_CONFIG)) { _puts("Failed to initialize SD card"); return false; }
|
||||||
|
#if USE_MSC
|
||||||
|
// Set disk vendor id, product id and revision
|
||||||
|
usb_msc.setID("Adafruit", "Internal Flash", "1.0");
|
||||||
|
// Set disk size, block size is 512 regardless of blockdevice page size
|
||||||
|
usb_msc.setCapacity(blockdevice.sectorCount(), 512);
|
||||||
|
usb_msc.setReadWriteCallback(msc_read_cb, msc_write_cb, msc_flush_cb);
|
||||||
|
usb_msc.setUnitReady(true); // MSC is ready for read/write
|
||||||
|
if (!usb_msc.begin()) {
|
||||||
|
_puts("Failed to initialize USB MSC"); return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool port_flash_begin() {
|
||||||
|
if (!SD.begin(&blockdevice, true, 1)) { // Start filesystem on the blockdevice
|
||||||
|
_puts("!SD.begin()"); return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
12
runcpm-rp2350-hstx-usb/runcpm-pico/host.h
Normal file
12
runcpm-rp2350-hstx-usb/runcpm-pico/host.h
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Mockba the Borg
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#ifndef HOST_H
|
||||||
|
#define HOST_H
|
||||||
|
|
||||||
|
uint8 hostbdos(uint16 dmaaddr) {
|
||||||
|
return(0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
115
runcpm-rp2350-hstx-usb/runcpm-pico/main.c
Normal file
115
runcpm-rp2350-hstx-usb/runcpm-pico/main.c
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
// Copyright (c) 2016 - Marcelo Dantas
|
||||||
|
// SPDX-FileCopyrightText: 2023 Mockba the Borg
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// Only build this code if not on Arduino
|
||||||
|
#ifndef ARDUINO
|
||||||
|
|
||||||
|
/* globals.h must be the first file included - it defines the bare essentials */
|
||||||
|
#include "globals.h"
|
||||||
|
|
||||||
|
/* Any system specific includes should go here - this will define system functions used by the abstraction */
|
||||||
|
|
||||||
|
/* all the RunCPM includes must go after the system specific includes */
|
||||||
|
|
||||||
|
/*
|
||||||
|
abstraction.h - Adds all system dependent calls and definitions needed by RunCPM
|
||||||
|
This should be the only file modified for portability. Any other file
|
||||||
|
shoud be kept the same.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include "abstraction_vstudio.h"
|
||||||
|
#else
|
||||||
|
#include "abstraction_posix.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// AUX: device configuration
|
||||||
|
#ifdef USE_PUN
|
||||||
|
FILE* pun_dev;
|
||||||
|
int pun_open = FALSE;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// PRT: device configuration
|
||||||
|
#ifdef USE_LST
|
||||||
|
FILE* lst_dev;
|
||||||
|
int lst_open = FALSE;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "ram.h" // ram.h - Implements the RAM
|
||||||
|
#include "console.h" // console.h - Defines all the console abstraction functions
|
||||||
|
#include "cpu.h" // cpu.h - Implements the emulated CPU
|
||||||
|
#include "disk.h" // disk.h - Defines all the disk access abstraction functions
|
||||||
|
#include "host.h" // host.h - Custom host-specific BDOS call
|
||||||
|
#include "cpm.h" // cpm.h - Defines the CPM structures and calls
|
||||||
|
#ifdef CCP_INTERNAL
|
||||||
|
#include "ccp.h" // ccp.h - Defines a simple internal CCP
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
|
||||||
|
#ifdef DEBUGLOG
|
||||||
|
_sys_deletefile((uint8*)LogName);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
_host_init(argc, &argv[0]);
|
||||||
|
_console_init();
|
||||||
|
_clrscr();
|
||||||
|
_puts(" CP/M Emulator v" VERSION " by Marcelo Dantas\r\n");
|
||||||
|
_puts(" Built " __DATE__ " - " __TIME__ "\r\n");
|
||||||
|
#ifdef HASLUA
|
||||||
|
_puts(" with Lua scripting support\r\n");
|
||||||
|
#endif
|
||||||
|
_puts("-----------------------------------------\r\n");
|
||||||
|
_puts("BIOS at 0x");
|
||||||
|
_puthex16(BIOSjmppage);
|
||||||
|
_puts(" - ");
|
||||||
|
_puts("BDOS at 0x");
|
||||||
|
_puthex16(BDOSjmppage);
|
||||||
|
_puts("\r\n");
|
||||||
|
_puts("CCP " CCPname " at 0x");
|
||||||
|
_puthex16(CCPaddr);
|
||||||
|
_puts("\r\n");
|
||||||
|
#if BANKS > 1
|
||||||
|
_puts("Banked Memory: ");
|
||||||
|
_puthex8(BANKS);
|
||||||
|
_puts(" banks\r\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
while (TRUE) {
|
||||||
|
_puts(CCPHEAD);
|
||||||
|
_PatchCPM(); // Patches the CP/M entry points and other things in
|
||||||
|
Status = 0;
|
||||||
|
#ifdef CCP_INTERNAL
|
||||||
|
_ccp();
|
||||||
|
#else
|
||||||
|
if (!_sys_exists((uint8*)CCPname)) {
|
||||||
|
_puts("Unable to load CP/M CCP.\r\nCPU halted.\r\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_RamLoad((uint8*)CCPname, CCPaddr); // Loads the CCP binary file into memory
|
||||||
|
Z80reset(); // Resets the Z80 CPU
|
||||||
|
SET_LOW_REGISTER(BC, _RamRead(DSKByte)); // Sets C to the current drive/user
|
||||||
|
PC = CCPaddr; // Sets CP/M application jump point
|
||||||
|
Z80run(); // Starts simulation
|
||||||
|
#endif
|
||||||
|
if (Status == 1) // This is set by a call to BIOS 0 - ends CP/M
|
||||||
|
break;
|
||||||
|
#ifdef USE_PUN
|
||||||
|
if (pun_dev)
|
||||||
|
_sys_fflush(pun_dev);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_LST
|
||||||
|
if (lst_dev)
|
||||||
|
_sys_fflush(lst_dev);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
_puts("\r\n");
|
||||||
|
_console_reset();
|
||||||
|
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
52
runcpm-rp2350-hstx-usb/runcpm-pico/ram.h
Normal file
52
runcpm-rp2350-hstx-usb/runcpm-pico/ram.h
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Mockba the Borg
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#ifndef RAM_H
|
||||||
|
#define RAM_H
|
||||||
|
|
||||||
|
/* see main.c for definition */
|
||||||
|
|
||||||
|
#ifndef RAM_FAST
|
||||||
|
static uint8 RAM[MEMSIZE]; // Definition of the emulated RAM
|
||||||
|
|
||||||
|
uint8* _RamSysAddr(uint16 address) {
|
||||||
|
if (address < CCPaddr) {
|
||||||
|
return(&RAM[address * curBank]);
|
||||||
|
} else {
|
||||||
|
return(&RAM[address]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8 _RamRead(uint16 address) {
|
||||||
|
if (address < CCPaddr) {
|
||||||
|
return(RAM[address * curBank]);
|
||||||
|
} else {
|
||||||
|
return(RAM[address]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16 _RamRead16(uint16 address) {
|
||||||
|
if (address < CCPaddr) {
|
||||||
|
return(RAM[address * curBank] + (RAM[(address * curBank) + 1] << 8));
|
||||||
|
} else {
|
||||||
|
return(RAM[address] + (RAM[address + 1] << 8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _RamWrite(uint16 address, uint8 value) {
|
||||||
|
if (address < CCPaddr) {
|
||||||
|
RAM[address * curBank] = value;
|
||||||
|
} else {
|
||||||
|
RAM[address] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _RamWrite16(uint16 address, uint16 value) {
|
||||||
|
// Z80 is a "little indian" (8 bit era joke)
|
||||||
|
_RamWrite(address, value & 0xff);
|
||||||
|
_RamWrite(address + 1, (value >> 8) & 0xff);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
20
runcpm-rp2350-hstx-usb/runcpm-pico/resource.h
Normal file
20
runcpm-rp2350-hstx-usb/runcpm-pico/resource.h
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Mockba the Borg
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//{{NO_DEPENDENCIES}}
|
||||||
|
// Microsoft Visual C++ generated include file.
|
||||||
|
// Used by RunCPM.rc
|
||||||
|
//
|
||||||
|
#define IDI_ICON1 101
|
||||||
|
|
||||||
|
// Next default values for new objects
|
||||||
|
//
|
||||||
|
#ifdef APSTUDIO_INVOKED
|
||||||
|
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||||
|
#define _APS_NEXT_RESOURCE_VALUE 102
|
||||||
|
#define _APS_NEXT_COMMAND_VALUE 40001
|
||||||
|
#define _APS_NEXT_CONTROL_VALUE 1001
|
||||||
|
#define _APS_NEXT_SYMED_VALUE 101
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
226
runcpm-rp2350-hstx-usb/runcpm-pico/runcpm-pico.ino
Normal file
226
runcpm-rp2350-hstx-usb/runcpm-pico/runcpm-pico.ino
Normal file
|
|
@ -0,0 +1,226 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Mockba the Borg
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// only AVR and ARM CPU
|
||||||
|
// #include <MemoryFree.h>
|
||||||
|
|
||||||
|
// =========================================================================================
|
||||||
|
// Guido Lehwalder's Code-Revision-Number
|
||||||
|
// =========================================================================================
|
||||||
|
#define GL_REV "GL20230303.0"
|
||||||
|
|
||||||
|
#include <SPI.h>
|
||||||
|
|
||||||
|
#include <SdFat.h> // One SD library to rule them all - Greinman SdFat from Library Manager
|
||||||
|
#include <Adafruit_SPIFlash.h>
|
||||||
|
|
||||||
|
#ifndef USE_VT100
|
||||||
|
#define USE_VT100 (0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if USE_VT100
|
||||||
|
#define TEXT_BOLD "\033[1m"
|
||||||
|
#define TEXT_NORMAL "\033[0m"
|
||||||
|
#else
|
||||||
|
#define TEXT_BOLD ""
|
||||||
|
#define TEXT_NORMAL ""
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "globals.h"
|
||||||
|
|
||||||
|
|
||||||
|
// =========================================================================================
|
||||||
|
// Board definitions go into the "hardware" folder, if you use a board different than the
|
||||||
|
// Arduino DUE, choose/change a file from there and reference that file here
|
||||||
|
// =========================================================================================
|
||||||
|
|
||||||
|
// Raspberry Pi Pico - normal (LED = GPIO25)
|
||||||
|
#include "hardware/pico/feather_dvi.h"
|
||||||
|
|
||||||
|
#ifndef BOARD_TEXT
|
||||||
|
#define BOARD_TEXT USB_MANUFACTURER " " USB_PRODUCT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "abstraction_arduino.h"
|
||||||
|
// Raspberry Pi Pico W(iFi) (LED = GPIO32)
|
||||||
|
// #include "hardware/pico/pico_w_sd_spi.h"
|
||||||
|
|
||||||
|
// =========================================================================================
|
||||||
|
// Delays for LED blinking
|
||||||
|
// =========================================================================================
|
||||||
|
#define sDELAY 100
|
||||||
|
#define DELAY 1200
|
||||||
|
|
||||||
|
|
||||||
|
// =========================================================================================
|
||||||
|
// Serial port speed
|
||||||
|
// =========================================================================================
|
||||||
|
#define SERIALSPD 115200
|
||||||
|
|
||||||
|
// =========================================================================================
|
||||||
|
// PUN: device configuration
|
||||||
|
// =========================================================================================
|
||||||
|
#ifdef USE_PUN
|
||||||
|
File32 pun_dev;
|
||||||
|
int pun_open = FALSE;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// =========================================================================================
|
||||||
|
// LST: device configuration
|
||||||
|
// =========================================================================================
|
||||||
|
#ifdef USE_LST
|
||||||
|
File32 lst_dev;
|
||||||
|
int lst_open = FALSE;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "ram.h"
|
||||||
|
#include "console.h"
|
||||||
|
#include "cpu.h"
|
||||||
|
#include "disk.h"
|
||||||
|
#include "host.h"
|
||||||
|
#include "cpm.h"
|
||||||
|
#ifdef CCP_INTERNAL
|
||||||
|
#include "ccp.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void setup(void) {
|
||||||
|
pinMode(LED, OUTPUT);
|
||||||
|
digitalWrite(LED, LOW ^ LEDinv);
|
||||||
|
|
||||||
|
|
||||||
|
// =========================================================================================
|
||||||
|
// Serial Port Definition
|
||||||
|
// =========================================================================================
|
||||||
|
// Serial =USB / Serial1 =UART0/COM1 / Serial2 =UART1/COM2
|
||||||
|
|
||||||
|
Serial1.setRX(1); // Pin 2
|
||||||
|
Serial1.setTX(0); // Pin 1
|
||||||
|
Serial1.begin(SERIALSPD);
|
||||||
|
|
||||||
|
Serial2.setRX(5); // Pin 7
|
||||||
|
Serial2.setTX(4); // Pin 6
|
||||||
|
Serial2.begin(SERIALSPD);
|
||||||
|
|
||||||
|
// or
|
||||||
|
|
||||||
|
// Serial1.setRX(17); // Pin 22
|
||||||
|
// Serial1.setTX(16); // Pin 21
|
||||||
|
|
||||||
|
// Serial2.setRX(21); // Pin 27
|
||||||
|
// Serial2.setTX(20); // Pin 26
|
||||||
|
// =========================================================================================
|
||||||
|
|
||||||
|
if (!port_init_early()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(WAIT_SERIAL)
|
||||||
|
// _clrscr();
|
||||||
|
// _puts("Opening serial-port...\r\n");
|
||||||
|
Serial.begin(SERIALSPD);
|
||||||
|
while (!Serial) { // Wait until serial is connected
|
||||||
|
digitalWrite(LED, HIGH ^ LEDinv);
|
||||||
|
delay(sDELAY);
|
||||||
|
digitalWrite(LED, LOW ^ LEDinv);
|
||||||
|
delay(DELAY);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef DEBUGLOG
|
||||||
|
_sys_deletefile((uint8 *)LogName);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// =========================================================================================
|
||||||
|
// Printing the Startup-Messages
|
||||||
|
// =========================================================================================
|
||||||
|
|
||||||
|
_clrscr();
|
||||||
|
|
||||||
|
// if (bootup_press == 1)
|
||||||
|
// { _puts("Recognized " TEXT_BOLD "#" TEXT_NORMAL " key as pressed! :)\r\n\r\n");
|
||||||
|
// }
|
||||||
|
|
||||||
|
_puts("CP/M Emulator " TEXT_BOLD "v" VERSION "" TEXT_NORMAL " by " TEXT_BOLD "Marcelo Dantas" TEXT_NORMAL "\r\n");
|
||||||
|
_puts("----------------------------------------------\r\n");
|
||||||
|
_puts(" running on [" TEXT_BOLD BOARD_TEXT TEXT_NORMAL "]\r\n");
|
||||||
|
_puts("----------------------------------------------\r\n");
|
||||||
|
|
||||||
|
_puts("BIOS at [" TEXT_BOLD "0x");
|
||||||
|
_puthex16(BIOSjmppage);
|
||||||
|
// _puts(" - ");
|
||||||
|
_puts("" TEXT_NORMAL "]\r\n");
|
||||||
|
|
||||||
|
_puts("BDOS at [" TEXT_BOLD "0x");
|
||||||
|
_puthex16(BDOSjmppage);
|
||||||
|
_puts("" TEXT_NORMAL "]\r\n");
|
||||||
|
|
||||||
|
_puts("CCP " CCPname " at [" TEXT_BOLD "0x");
|
||||||
|
_puthex16(CCPaddr);
|
||||||
|
_puts("" TEXT_NORMAL "]\r\n");
|
||||||
|
|
||||||
|
#if BANKS > 1
|
||||||
|
_puts("Banked Memory [" TEXT_BOLD "");
|
||||||
|
_puthex8(BANKS);
|
||||||
|
_puts("" TEXT_NORMAL "]banks\r\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Serial.printf("Free Memory [" TEXT_BOLD "%d bytes" TEXT_NORMAL "]\r\n", freeMemory());
|
||||||
|
|
||||||
|
_puts("CPU-Clock [" TEXT_BOLD);
|
||||||
|
_putdec((clock_get_hz( clk_sys ) + 500'000) / 1'000'000);
|
||||||
|
_puts(TEXT_NORMAL "] MHz\r\n");
|
||||||
|
|
||||||
|
_puts("Init Storage [ " TEXT_BOLD "");
|
||||||
|
if (port_flash_begin()) {
|
||||||
|
_puts("OK " TEXT_NORMAL "]\r\n");
|
||||||
|
_puts("----------------------------------------------");
|
||||||
|
|
||||||
|
if (VersionCCP >= 0x10 || SD.exists(CCPname)) {
|
||||||
|
while (true) {
|
||||||
|
_puts(CCPHEAD);
|
||||||
|
_PatchCPM();
|
||||||
|
Status = 0;
|
||||||
|
#ifndef CCP_INTERNAL
|
||||||
|
if (!_RamLoad((char *)CCPname, CCPaddr)) {
|
||||||
|
_puts("Unable to load the CCP.\r\nCPU halted.\r\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Z80reset();
|
||||||
|
SET_LOW_REGISTER(BC, _RamRead(DSKByte));
|
||||||
|
PC = CCPaddr;
|
||||||
|
Z80run();
|
||||||
|
#else
|
||||||
|
_ccp();
|
||||||
|
#endif
|
||||||
|
if (Status == 1)
|
||||||
|
break;
|
||||||
|
#ifdef USE_PUN
|
||||||
|
if (pun_dev)
|
||||||
|
_sys_fflush(pun_dev);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_LST
|
||||||
|
if (lst_dev)
|
||||||
|
_sys_fflush(lst_dev);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_puts("Unable to load CP/M CCP.\r\nCPU halted.\r\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_puts("ERR " TEXT_NORMAL "]\r\nUnable to initialize SD card.\r\nCPU halted.\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if loop is reached, blink LED forever to signal error
|
||||||
|
void loop(void) {
|
||||||
|
digitalWrite(LED, HIGH^LEDinv);
|
||||||
|
delay(DELAY);
|
||||||
|
digitalWrite(LED, LOW^LEDinv);
|
||||||
|
delay(DELAY);
|
||||||
|
digitalWrite(LED, HIGH^LEDinv);
|
||||||
|
delay(DELAY);
|
||||||
|
digitalWrite(LED, LOW^LEDinv);
|
||||||
|
delay(DELAY * 4);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue