Port RunCPM to the Metro RP2350 with HSTX
This uses the built in HSTX connector for video out, and the built in SD card for file storage. It's a 1-board emulator for the 8-bit CP/M operating system! At this time, unreleased modifications to Adafruit_dvhstx and Pico-PIO-USB are needed. Otherwise, the code will not work properly.
This commit is contained in:
parent
a6851c595e
commit
4151751dd7
6 changed files with 287 additions and 322 deletions
|
|
@ -6,6 +6,9 @@
|
|||
#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
|
||||
|
|
@ -28,16 +31,22 @@ Adafruit_USBH_Host USBHost;
|
|||
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(120'000, true);
|
||||
//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");
|
||||
|
|
@ -50,6 +59,9 @@ void setup1() {
|
|||
|
||||
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
|
||||
|
|
@ -59,6 +71,8 @@ void setup1() {
|
|||
|
||||
// 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;
|
||||
|
|
@ -70,16 +84,21 @@ 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) {
|
||||
Serial.printf("'%c'\r\n", code);
|
||||
if (code >= 32 && code < 127) {
|
||||
display.printf("%c", code);
|
||||
} else {
|
||||
Serial.printf("'\\x%02x'\r\n", code);
|
||||
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) {
|
||||
|
|
@ -166,12 +185,12 @@ bool report_contains(const hid_keyboard_report_t &report, uint8_t key) {
|
|||
|
||||
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;
|
||||
bool caps = old_report.reserved & 1;
|
||||
bool num = old_report.reserved & 2;
|
||||
uint8_t code = 0;
|
||||
|
||||
if (report.keycode[0] == 1 && report.keycode[1] == 1) {
|
||||
|
|
@ -188,8 +207,10 @@ void process_event(uint8_t dev_addr, uint8_t instance, const hid_keyboard_report
|
|||
|
||||
/* 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) {
|
||||
|
|
@ -219,15 +240,15 @@ void process_event(uint8_t dev_addr, uint8_t instance, const hid_keyboard_report
|
|||
}
|
||||
}
|
||||
|
||||
uint8_t leds = (caps | (num << 1));
|
||||
if (leds != old_report.reserved) {
|
||||
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*/, &leds, sizeof(leds));
|
||||
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;
|
||||
old_report.reserved = leds;
|
||||
}
|
||||
|
||||
// Invoked when received report from device via interrupt endpoint
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@
|
|||
#define HostOS 0x01
|
||||
// #endif
|
||||
|
||||
#if !defined(FILE_TYPE)
|
||||
#define FILE_TYPE FsFile
|
||||
#endif
|
||||
|
||||
#if defined CORE_TEENSY
|
||||
#define HostOS 0x04
|
||||
#endif
|
||||
|
|
@ -26,7 +30,7 @@
|
|||
/* Memory abstraction functions */
|
||||
/*===============================================================================*/
|
||||
bool _RamLoad(char* filename, uint16 address) {
|
||||
File32 f;
|
||||
FILE_TYPE f;
|
||||
bool result = false;
|
||||
|
||||
if (f = SD.open(filename, FILE_READ)) {
|
||||
|
|
@ -40,7 +44,7 @@ bool _RamLoad(char* filename, uint16 address) {
|
|||
|
||||
/* Filesystem (disk) abstraction fuctions */
|
||||
/*===============================================================================*/
|
||||
File32 rootdir, userdir;
|
||||
FILE_TYPE rootdir, userdir;
|
||||
#define FOLDERCHAR '/'
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -60,31 +64,29 @@ typedef struct {
|
|||
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) {
|
||||
FILE_TYPE _sys_fopen_w(uint8* filename) {
|
||||
return(SD.open((char*)filename, O_CREAT | O_WRITE));
|
||||
}
|
||||
|
||||
int _sys_fputc(uint8 ch, File32& f) {
|
||||
int _sys_fputc(uint8 ch, FILE_TYPE& f) {
|
||||
return(f.write(ch));
|
||||
}
|
||||
|
||||
void _sys_fflush(File32& f) {
|
||||
void _sys_fflush(FILE_TYPE& f) {
|
||||
f.flush();
|
||||
}
|
||||
|
||||
void _sys_fclose(File32& f) {
|
||||
void _sys_fclose(FILE_TYPE& f) {
|
||||
f.close();
|
||||
}
|
||||
|
||||
int _sys_select(uint8* disk) {
|
||||
uint8 result = FALSE;
|
||||
File32 f;
|
||||
FILE_TYPE f;
|
||||
|
||||
digitalWrite(LED, HIGH ^ LEDinv);
|
||||
if (f = SD.open((char*)disk, O_READ)) {
|
||||
|
|
@ -98,7 +100,7 @@ int _sys_select(uint8* disk) {
|
|||
|
||||
long _sys_filesize(uint8* filename) {
|
||||
long l = -1;
|
||||
File32 f;
|
||||
FILE_TYPE f;
|
||||
|
||||
digitalWrite(LED, HIGH ^ LEDinv);
|
||||
if (f = SD.open((char*)filename, O_RDONLY)) {
|
||||
|
|
@ -110,13 +112,12 @@ long _sys_filesize(uint8* filename) {
|
|||
}
|
||||
|
||||
int _sys_openfile(uint8* filename) {
|
||||
File32 f;
|
||||
FILE_TYPE f;
|
||||
int result = 0;
|
||||
|
||||
digitalWrite(LED, HIGH ^ LEDinv);
|
||||
f = SD.open((char*)filename, O_READ);
|
||||
if (f) {
|
||||
f.dirEntry(&fileDirEntry);
|
||||
f.close();
|
||||
result = 1;
|
||||
}
|
||||
|
|
@ -125,7 +126,7 @@ int _sys_openfile(uint8* filename) {
|
|||
}
|
||||
|
||||
int _sys_makefile(uint8* filename) {
|
||||
File32 f;
|
||||
FILE_TYPE f;
|
||||
int result = 0;
|
||||
|
||||
digitalWrite(LED, HIGH ^ LEDinv);
|
||||
|
|
@ -145,7 +146,7 @@ int _sys_deletefile(uint8* filename) {
|
|||
}
|
||||
|
||||
int _sys_renamefile(uint8* filename, uint8* newname) {
|
||||
File32 f;
|
||||
FILE_TYPE f;
|
||||
int result = 0;
|
||||
|
||||
digitalWrite(LED, HIGH ^ LEDinv);
|
||||
|
|
@ -165,7 +166,7 @@ void _sys_logbuffer(uint8* buffer) {
|
|||
#ifdef CONSOLELOG
|
||||
puts((char*)buffer);
|
||||
#else
|
||||
File32 f;
|
||||
FILE_TYPE f;
|
||||
uint8 s = 0;
|
||||
while (*(buffer + s)) // Computes buffer size
|
||||
++s;
|
||||
|
|
@ -181,7 +182,7 @@ void _sys_logbuffer(uint8* buffer) {
|
|||
bool _sys_extendfile(char* fn, unsigned long fpos)
|
||||
{
|
||||
uint8 result = true;
|
||||
File32 f;
|
||||
FILE_TYPE f;
|
||||
unsigned long i;
|
||||
|
||||
digitalWrite(LED, HIGH ^ LEDinv);
|
||||
|
|
@ -204,7 +205,7 @@ bool _sys_extendfile(char* fn, unsigned long fpos)
|
|||
|
||||
uint8 _sys_readseq(uint8* filename, long fpos) {
|
||||
uint8 result = 0xff;
|
||||
File32 f;
|
||||
FILE_TYPE f;
|
||||
uint8 bytesread;
|
||||
uint8 dmabuf[BlkSZ];
|
||||
uint8 i;
|
||||
|
|
@ -234,7 +235,7 @@ uint8 _sys_readseq(uint8* filename, long fpos) {
|
|||
|
||||
uint8 _sys_writeseq(uint8* filename, long fpos) {
|
||||
uint8 result = 0xff;
|
||||
File32 f;
|
||||
FILE_TYPE f;
|
||||
|
||||
digitalWrite(LED, HIGH ^ LEDinv);
|
||||
if (_sys_extendfile((char*)filename, fpos))
|
||||
|
|
@ -256,7 +257,7 @@ uint8 _sys_writeseq(uint8* filename, long fpos) {
|
|||
|
||||
uint8 _sys_readrand(uint8* filename, long fpos) {
|
||||
uint8 result = 0xff;
|
||||
File32 f;
|
||||
FILE_TYPE f;
|
||||
uint8 bytesread;
|
||||
uint8 dmabuf[BlkSZ];
|
||||
uint8 i;
|
||||
|
|
@ -297,7 +298,7 @@ uint8 _sys_readrand(uint8* filename, long fpos) {
|
|||
|
||||
uint8 _sys_writerand(uint8* filename, long fpos) {
|
||||
uint8 result = 0xff;
|
||||
File32 f;
|
||||
FILE_TYPE f;
|
||||
|
||||
digitalWrite(LED, HIGH ^ LEDinv);
|
||||
if (_sys_extendfile((char*)filename, fpos)) {
|
||||
|
|
@ -325,7 +326,7 @@ static uint16 fileExtentsUsed = 0;
|
|||
static uint16 firstFreeAllocBlock;
|
||||
|
||||
uint8 _findnext(uint8 isdir) {
|
||||
File32 f;
|
||||
FILE_TYPE f;
|
||||
uint8 result = 0xff;
|
||||
bool isfile;
|
||||
uint32 bytes;
|
||||
|
|
@ -339,7 +340,6 @@ uint8 _findnext(uint8 isdir) {
|
|||
f.getName((char*)&findNextDirName[0], 13);
|
||||
isfile = !f.isDirectory();
|
||||
bytes = f.size();
|
||||
f.dirEntry(&fileDirEntry);
|
||||
f.close();
|
||||
if (!isfile)
|
||||
continue;
|
||||
|
|
@ -440,7 +440,7 @@ uint8 _findfirstallusers(uint8 isdir) {
|
|||
}
|
||||
|
||||
uint8 _Truncate(char* filename, uint8 rc) {
|
||||
File32 f;
|
||||
FILE_TYPE f;
|
||||
int result = 0;
|
||||
|
||||
digitalWrite(LED, HIGH ^ LEDinv);
|
||||
|
|
|
|||
|
|
@ -1,130 +0,0 @@
|
|||
// 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;
|
||||
}
|
||||
|
||||
|
|
@ -1,56 +1,91 @@
|
|||
// SPDX-FileCopyrightText: 2023 Mockba the Borg
|
||||
// SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// pio-usb is required for rp2040 host
|
||||
#include "pio_usb.h"
|
||||
#include "../../arduino_hooks.h"
|
||||
#include "../../console.h"
|
||||
#include "Adafruit_TinyUSB.h"
|
||||
#include "pico/stdlib.h"
|
||||
#include "Adafruit_dvhstx.h"
|
||||
#include "pio_usb.h"
|
||||
#include <Adafruit_dvhstx.h>
|
||||
#include <SdCard/SdSpiCard.h>
|
||||
#include <SdFat.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);
|
||||
#define SD_CS_PIN 39
|
||||
|
||||
void setup() {
|
||||
// ensure text generation interrupt takes place on core0
|
||||
display.begin();
|
||||
display.println("Hello hstx\n");
|
||||
SdFat SD;
|
||||
SdSpiConfig config(SD_CS_PIN, DEDICATED_SPI, SD_SCK_MHZ(16), &SPI1);
|
||||
|
||||
static bool start1;
|
||||
|
||||
// =========================================================================================
|
||||
// Define Board-Data
|
||||
// GP25 green onboard LED
|
||||
// =========================================================================================
|
||||
#define LED (13)
|
||||
#define LEDinv (false)
|
||||
#define board_pico
|
||||
#define board_analog_io
|
||||
#define board_digital_io
|
||||
|
||||
queue_t kb_queue;
|
||||
|
||||
void putch_display(uint8_t ch) {
|
||||
if (ch == 8) {
|
||||
auto x = display.getCursorX();
|
||||
if (x > 0) {
|
||||
auto y = display.getCursorY();
|
||||
display.setCursor(--x, y);
|
||||
display.drawPixel(x, y, ' ');
|
||||
}
|
||||
} else {
|
||||
display.write(ch);
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
uint8_t getch_usbhost(void) {
|
||||
uint8_t result;
|
||||
queue_remove_blocking(&kb_queue, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool kbhit_usbhost(void) { return queue_get_level(&kb_queue) > 0; }
|
||||
|
||||
bool port_init_early() {
|
||||
if (!display.begin()) {
|
||||
return false;
|
||||
}
|
||||
display.showCursor();
|
||||
_putch_hook = putch_display;
|
||||
delay(10);
|
||||
|
||||
queue_init_with_spinlock(&kb_queue, 1, 64, spin_lock_claim_unused(true));
|
||||
start1 = true;
|
||||
_getch_hook = getch_usbhost;
|
||||
_kbhit_hook = kbhit_usbhost;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool port_flash_begin() {
|
||||
if (!SD.begin(config)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
* USB Host keyboard
|
||||
*************************************************************************/
|
||||
|
||||
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
|
||||
while (!start1)
|
||||
;
|
||||
|
||||
#ifdef PIN_5V_EN
|
||||
pinMode(PIN_5V_EN, OUTPUT);
|
||||
|
|
@ -59,20 +94,17 @@ Serial.begin(115200);
|
|||
|
||||
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);
|
||||
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
|
||||
// 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();
|
||||
Serial.println("end of setup1");
|
||||
}
|
||||
|
||||
int old_ascii = -1;
|
||||
|
|
@ -81,24 +113,13 @@ uint32_t repeat_timeout;
|
|||
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) {
|
||||
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);
|
||||
queue_try_add(&kb_queue, &code); // failure is ignored
|
||||
}
|
||||
|
||||
void loop1()
|
||||
{
|
||||
static bool last_serial;
|
||||
if (!last_serial && Serial) {
|
||||
last_serial = true;
|
||||
Serial.println("Hello host serial");
|
||||
}
|
||||
void loop1() {
|
||||
uint32_t now = millis();
|
||||
uint32_t deadline = repeat_timeout - now;
|
||||
if (old_ascii >= 0 && deadline > INT32_MAX) {
|
||||
|
|
@ -115,13 +136,15 @@ Serial.println("Hello host serial");
|
|||
// 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 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("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);
|
||||
|
|
@ -135,7 +158,8 @@ void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_re
|
|||
|
||||
// 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);
|
||||
Serial.printf("HID device address = %d, instance = %d is unmounted\r\n",
|
||||
dev_addr, instance);
|
||||
}
|
||||
|
||||
#define FLAG_ALPHABETIC (1)
|
||||
|
|
@ -144,41 +168,81 @@ void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) {
|
|||
#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 */
|
||||
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_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_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_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_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_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 },
|
||||
{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;
|
||||
if (report.keycode[i] == key)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -187,7 +251,8 @@ 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) {
|
||||
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;
|
||||
|
|
@ -202,15 +267,17 @@ void process_event(uint8_t dev_addr, uint8_t instance, const hid_keyboard_report
|
|||
old_ascii = -1;
|
||||
|
||||
for (auto keycode : report.keycode) {
|
||||
if (keycode == 0) continue;
|
||||
if (report_contains(old_report, keycode)) continue;
|
||||
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");
|
||||
Serial.println("toggle numlock");
|
||||
num = !num;
|
||||
} else if (keycode == HID_KEY_CAPS_LOCK) {
|
||||
Serial.println("toggle capslock");
|
||||
Serial.println("toggle capslock");
|
||||
caps = !caps;
|
||||
} else {
|
||||
for (const auto &mapper : keycode_to_ascii) {
|
||||
|
|
@ -232,8 +299,10 @@ void process_event(uint8_t dev_addr, uint8_t instance, const hid_keyboard_report
|
|||
code ^= ('a' ^ 'A');
|
||||
}
|
||||
}
|
||||
if (ctrl) code &= 0x1f;
|
||||
if (alt) code ^= 0x80;
|
||||
if (ctrl)
|
||||
code &= 0x1f;
|
||||
if (alt)
|
||||
code ^= 0x80;
|
||||
send_ascii(code, initial_repeat_time);
|
||||
break;
|
||||
}
|
||||
|
|
@ -243,20 +312,25 @@ void process_event(uint8_t dev_addr, uint8_t instance, const hid_keyboard_report
|
|||
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);
|
||||
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));
|
||||
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);
|
||||
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);
|
||||
process_event(dev_addr, instance, *(hid_keyboard_report_t *)report);
|
||||
}
|
||||
// continue to request to receive report
|
||||
if (!tuh_hid_receive_report(dev_addr, instance)) {
|
||||
|
|
@ -12,8 +12,7 @@
|
|||
|
||||
#include <SPI.h>
|
||||
|
||||
#include <SdFat.h> // One SD library to rule them all - Greinman SdFat from Library Manager
|
||||
#include <Adafruit_SPIFlash.h>
|
||||
#include <SdFat.h> // One SD library to rule them all - Greinman SdFat from Library Manager
|
||||
|
||||
#ifndef USE_VT100
|
||||
#define USE_VT100 (0)
|
||||
|
|
@ -29,14 +28,14 @@
|
|||
|
||||
#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
|
||||
// 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"
|
||||
// Metro RP2350
|
||||
#include "hardware/pico/metro_rp2350.h"
|
||||
|
||||
#ifndef BOARD_TEXT
|
||||
#define BOARD_TEXT USB_MANUFACTURER " " USB_PRODUCT
|
||||
|
|
@ -52,7 +51,6 @@
|
|||
#define sDELAY 100
|
||||
#define DELAY 1200
|
||||
|
||||
|
||||
// =========================================================================================
|
||||
// Serial port speed
|
||||
// =========================================================================================
|
||||
|
|
@ -62,7 +60,7 @@
|
|||
// PUN: device configuration
|
||||
// =========================================================================================
|
||||
#ifdef USE_PUN
|
||||
File32 pun_dev;
|
||||
FILE_TYPE pun_dev;
|
||||
int pun_open = FALSE;
|
||||
#endif
|
||||
|
||||
|
|
@ -70,16 +68,16 @@ int pun_open = FALSE;
|
|||
// LST: device configuration
|
||||
// =========================================================================================
|
||||
#ifdef USE_LST
|
||||
File32 lst_dev;
|
||||
FILE_TYPE lst_dev;
|
||||
int lst_open = FALSE;
|
||||
#endif
|
||||
|
||||
#include "ram.h"
|
||||
#include "console.h"
|
||||
#include "cpm.h"
|
||||
#include "cpu.h"
|
||||
#include "disk.h"
|
||||
#include "host.h"
|
||||
#include "cpm.h"
|
||||
#include "ram.h"
|
||||
#ifdef CCP_INTERNAL
|
||||
#include "ccp.h"
|
||||
#endif
|
||||
|
|
@ -88,7 +86,6 @@ void setup(void) {
|
|||
pinMode(LED, OUTPUT);
|
||||
digitalWrite(LED, LOW ^ LEDinv);
|
||||
|
||||
|
||||
// =========================================================================================
|
||||
// Serial Port Definition
|
||||
// =========================================================================================
|
||||
|
|
@ -119,7 +116,7 @@ void setup(void) {
|
|||
// _clrscr();
|
||||
// _puts("Opening serial-port...\r\n");
|
||||
Serial.begin(SERIALSPD);
|
||||
while (!Serial) { // Wait until serial is connected
|
||||
while (!Serial) { // Wait until serial is connected
|
||||
digitalWrite(LED, HIGH ^ LEDinv);
|
||||
delay(sDELAY);
|
||||
digitalWrite(LED, LOW ^ LEDinv);
|
||||
|
|
@ -131,7 +128,6 @@ void setup(void) {
|
|||
_sys_deletefile((uint8 *)LogName);
|
||||
#endif
|
||||
|
||||
|
||||
// =========================================================================================
|
||||
// Printing the Startup-Messages
|
||||
// =========================================================================================
|
||||
|
|
@ -139,10 +135,12 @@ void setup(void) {
|
|||
_clrscr();
|
||||
|
||||
// if (bootup_press == 1)
|
||||
// { _puts("Recognized " TEXT_BOLD "#" TEXT_NORMAL " key as pressed! :)\r\n\r\n");
|
||||
// { _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("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");
|
||||
|
|
@ -166,61 +164,63 @@ void setup(void) {
|
|||
_puts("" TEXT_NORMAL "]banks\r\n");
|
||||
#endif
|
||||
|
||||
// Serial.printf("Free Memory [" TEXT_BOLD "%d bytes" TEXT_NORMAL "]\r\n", freeMemory());
|
||||
// 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");
|
||||
_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("----------------------------------------------");
|
||||
_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;
|
||||
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();
|
||||
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();
|
||||
_ccp();
|
||||
#endif
|
||||
if (Status == 1)
|
||||
break;
|
||||
if (Status == 1)
|
||||
break;
|
||||
#ifdef USE_PUN
|
||||
if (pun_dev)
|
||||
_sys_fflush(pun_dev);
|
||||
if (pun_dev)
|
||||
_sys_fflush(pun_dev);
|
||||
#endif
|
||||
#ifdef USE_LST
|
||||
if (lst_dev)
|
||||
_sys_fflush(lst_dev);
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
// 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