diff --git a/include/cbm_defines.h b/include/cbm_defines.h index 5d67d002d..33c960b7c 100644 --- a/include/cbm_defines.h +++ b/include/cbm_defines.h @@ -140,7 +140,7 @@ typedef enum #define CLEAR 0x0000 // clear all flags #define CLEAR_LOW 0xFF00 // clear low byte #define ERROR (1 << 0) // if this flag is set, something went wrong -#define ATN_PULLED (1 << 1) // might be set by iec_receive +#define ATN_ASSERTED (1 << 1) // might be set by iec_receive #define EOI_RECVD (1 << 2) #define EMPTY_STREAM (1 << 3) @@ -161,7 +161,7 @@ typedef enum #define TIMING_Ts 70 // BIT SET-UP TALKER 71us 20us 70us - #define TIMING_Ts0 40 // BIT SET-UP LISTENER PRE 57us 47us -#define TIMING_Ts1 35 // BIT SET-UP LISTENER POST 18us 24us +#define TIMING_Ts1 30 // BIT SET-UP LISTENER POST 18us 24us #define TIMING_Tv 20 // DATA VALID VIC20 76us 26us 20us 20us - (Tv and Tpr minimum must be 60μ s for external device to be a talker. ) #define TIMING_Tv64 80 // DATA VALID C64 @@ -187,9 +187,9 @@ typedef enum // OTHER #define TIMING_EMPTY 512 // SIGNAL EMPTY STREAM -#define TIMEOUT_ATNCLK 70 // WAIT FOR CLK AFTER ATN IS PULLED +#define TIMEOUT_ATNCLK 70 // WAIT FOR CLK AFTER ATN IS ASSERTED #define TIMEOUT_Ttlta 65 // TALKER/LISTENER TURNAROUND TIMEOUT -#define TIMING_Ttcp 13 // TALKER TURNAROUND CLOCK PULL +#define TIMING_Ttcp 13 // TALKER TURNAROUND CLOCK ASSERT // SPECIAL #define TIMING_PROTOCOL_DETECT 218 // SAUCEDOS/JIFFYDOS CAPABLE DELAY @@ -200,18 +200,7 @@ typedef enum #define TIMED_OUT -1 #define FOREVER 5000000 // 0 -#ifndef IEC_INVERTED_LINES -// Not Inverted -#define PULLED true -#define RELEASED false -#define LOW 0x00 -#define HIGH 0x01 -#else -// Inverted -#define PULLED false -#define RELEASED true -#define LOW 0x01 -#define HIGH 0x00 -#endif +#define IEC_ASSERTED true +#define IEC_RELEASED false #endif // CBMDEFINES_H diff --git a/include/pinmap.h b/include/pinmap.h index 63cc211ff..1af232386 100644 --- a/include/pinmap.h +++ b/include/pinmap.h @@ -3,6 +3,8 @@ #define PINMAP_H #ifdef ESP_PLATFORM +#include + #include "pinmap/a2_fn10.h" #include "pinmap/a2_rev0.h" #include "pinmap/a2_d32pro.h" diff --git a/include/pinmap/iec-d32pro.h b/include/pinmap/iec-d32pro.h index bc502f0b0..33e369852 100644 --- a/include/pinmap/iec-d32pro.h +++ b/include/pinmap/iec-d32pro.h @@ -42,28 +42,18 @@ #define PIN_LED_BT GPIO_NUM_5 // LOLIN D32 PRO #endif -/* Audio Output */ -#define PIN_DAC1 GPIO_NUM_25 // samlib.h - /* Commodore IEC Pins */ #define IEC_HAS_RESET // Reset line is available #define PIN_IEC_RESET GPIO_NUM_34 #define PIN_IEC_ATN GPIO_NUM_32 #define PIN_IEC_CLK_IN GPIO_NUM_33 -#define PIN_IEC_CLK_OUT GPIO_NUM_33 +#define PIN_IEC_CLK_OUT PIN_IEC_CLK_IN #define PIN_IEC_DATA_IN GPIO_NUM_25 -#define PIN_IEC_DATA_OUT GPIO_NUM_25 +#define PIN_IEC_DATA_OUT PIN_IEC_DATA_IN #define PIN_IEC_SRQ GPIO_NUM_26 -/* Color Computer */ -#define PIN_CASS_MOTOR GPIO_NUM_34 // Second motor pin is tied to +3V -#define PIN_CASS_DATA_IN GPIO_NUM_33 -#define PIN_CASS_DATA_OUT GPIO_NUM_26 - -#define PIN_SERIAL_CD GPIO_NUM_32 -#define PIN_SERIAL_RX GPIO_NUM_9 // fnUartBUS -#define PIN_SERIAL_TX GPIO_NUM_10 +#define PIN_DEBUG PIN_IEC_SRQ #endif // PINMAP_IEC_D32PRO -#endif // PINMAP_LOLIN_D32_PRO_H \ No newline at end of file +#endif // PINMAP_LOLIN_D32_PRO_H diff --git a/lib/bus/iec/iec.cpp b/lib/bus/iec/iec.cpp index 891df3b87..8474c6958 100644 --- a/lib/bus/iec/iec.cpp +++ b/lib/bus/iec/iec.cpp @@ -13,7 +13,9 @@ #include "../../include/pinmap.h" #include "../../include/cbm_defines.h" #include "led.h" +#include "protocol/_protocol.h" #include "protocol/cpbstandardserial.h" +#include "protocol/jiffydos.h" #include "string_utils.h" #include "utils.h" @@ -21,39 +23,157 @@ #define MAIN_PRIORITY 20 #define MAIN_CPUAFFINITY 1 +#define IEC_ALLDEV 31 +#define IEC_SET_STATE(x) ({ _state = x; }) + using namespace Protocol; systemBus IEC; -static void IRAM_ATTR cbm_on_atn_isr_handler(void *arg) +static void IRAM_ATTR cbm_on_atn_isr_forwarder(void *arg) { systemBus *b = (systemBus *)arg; - //b->pull(PIN_IEC_SRQ); + b->cbm_on_atn_isr_handler(); +} +void IRAM_ATTR systemBus::cbm_on_atn_isr_handler() +{ + if (IEC_IS_ASSERTED(PIN_IEC_ATN)) { // Go to listener mode and get command - b->release(PIN_IEC_CLK_OUT); - b->pull(PIN_IEC_DATA_OUT); + IEC_RELEASE(PIN_IEC_CLK_OUT); + IEC_ASSERT(PIN_IEC_DATA_OUT); - b->flags = CLEAR; - b->flags |= ATN_PULLED; - b->state = BUS_ACTIVE; + flags = CLEAR; + flags |= ATN_ASSERTED; + IEC_SET_STATE(BUS_ACTIVE); - //b->release(PIN_IEC_SRQ); + // Commands are always sent using standard serial + if (detected_protocol != PROTOCOL_SERIAL) { + detected_protocol = PROTOCOL_SERIAL; + protocol = selectProtocol(); + } + } + else if (_state == BUS_RELEASE) { + releaseLines(); + IEC_SET_STATE(BUS_IDLE); + } } -static void IRAM_ATTR cbm_on_clk_isr_handler(void *arg) +static void IRAM_ATTR cbm_on_clk_isr_forwarder(void *arg) { systemBus *b = (systemBus *)arg; - //b->pull(PIN_IEC_SRQ); + b->cbm_on_clk_isr_handler(); +} + +void IRAM_ATTR systemBus::cbm_on_clk_isr_handler() +{ + int atn, val; + int cmd, dev; + + + if (_state < BUS_ACTIVE) + return; + + atn = IEC_IS_ASSERTED(PIN_IEC_ATN); + gpio_intr_disable(PIN_IEC_CLK_IN); + + val = protocol->receiveByte(); + if (flags & ERROR) + goto done; + + if (atn) { + cmd = val & 0xe0; + dev = val & 0x1f; + + switch (cmd) { + case IEC_LISTEN: + case IEC_TALK: + if (dev == IEC_ALLDEV || !isDeviceEnabled(dev)) { + if (dev == IEC_ALLDEV) { + // Handle releaseLines() when ATN is released outside of this + // interrupt to prevent watchdog timeout + IEC_SET_STATE(BUS_RELEASE); + } + else { + IEC_SET_STATE(BUS_IDLE); + protocol->transferDelaySinceLast(TIMING_Tbb); + releaseLines(); + } + sendInput(); + } + else { + newIO(val); + if (flags & JIFFYDOS_ACTIVE) { + Debug_printf(" IEC: [JD][%.2X]", val); + detected_protocol = PROTOCOL_JIFFYDOS; + protocol = selectProtocol(); + } + } + break; + + case IEC_REOPEN: + /* Take a break driver 8. We can reach our destination, but we're still a ways away */ + if (iec_curCommand) { + channelIO(val); + if (iec_curCommand->primary == IEC_TALK) { + IEC_SET_STATE(BUS_IDLE); + turnAround(); + sendInput(); + } + } + break; + + case IEC_CLOSE: + if (iec_curCommand) { + channelIO(val); + if (dev == 0x00) + sendInput(); + } + break; + + default: + break; + } + } + else if (iec_curCommand) + iec_curCommand->payload += val & 0xff; + + done: + gpio_intr_enable(PIN_IEC_CLK_IN); + return; +} + +void IRAM_ATTR systemBus::newIO(int val) +{ + iec_curCommand = new IECData(); + iec_curCommand->primary = val & 0xe0; + iec_curCommand->device = val & 0x1f; + iec_curCommand->payload = ""; + + return; +} + +void IRAM_ATTR systemBus::channelIO(int val) +{ + iec_curCommand->secondary = val & 0xf0; + iec_curCommand->channel = val & 0x0f; + return; +} + +void IRAM_ATTR systemBus::sendInput(void) +{ + BaseType_t woken; + - // get bit - b->byte >>= 1; - if ( !b->status ( PIN_IEC_DATA_IN ) ) b->byte |= 0x80; - b->bit++; + //IEC_ASSERT(PIN_DEBUG); + if (iec_curCommand) + xQueueSendFromISR(iec_commandQueue, &iec_curCommand, &woken); + iec_curCommand = nullptr; + //IEC_RELEASE(PIN_DEBUG); - //b->release(PIN_IEC_SRQ); + return; } /** @@ -71,7 +191,7 @@ static void onTimer(void *info) #if 0 static void ml_iec_intr_task(void* arg) { - while ( true ) + while ( true ) { if ( IEC.enabled ) IEC.service(); @@ -88,42 +208,10 @@ void systemBus::init_gpio(gpio_num_t _pin) gpio_set_direction(_pin, GPIO_MODE_INPUT); gpio_pullup_en(_pin); gpio_set_pull_mode(_pin, GPIO_PULLUP_ONLY); - gpio_set_level(_pin, LOW); + gpio_set_level(_pin, 0); return; } -// true => PULL => LOW -void IRAM_ATTR systemBus::pull ( uint8_t _pin ) -{ - int _reg = GPIO_ENABLE_REG; - if (_pin > 31) - { - _reg = GPIO_ENABLE1_REG, _pin -= 32; - } - REG_SET_BIT(_reg, 1ULL << _pin); // GPIO_MODE_OUTPUT -} - -// false => RELEASE => HIGH -void IRAM_ATTR systemBus::release ( uint8_t _pin ) -{ - int _reg = GPIO_ENABLE_REG; - if (_pin > 31) - { - _reg = GPIO_ENABLE1_REG, _pin -= 32; - } - REG_CLR_BIT(_reg, 1ULL << _pin); // GPIO_MODE_INPUT -} - -bool IRAM_ATTR systemBus::status ( uint8_t _pin ) -{ - int _reg = GPIO_IN_REG; - if (_pin > 31) - { - _reg = GPIO_IN1_REG, _pin -= 32; - } - return (REG_READ(_reg) & BIT(_pin)) ? RELEASED : PULLED; -} - bool IRAM_ATTR systemBus::status () { // uint64_t pin_states; @@ -181,6 +269,10 @@ void systemBus::setup() init_gpio(PIN_IEC_RESET); #endif +#ifdef IEC_INVERTED_LINES +#warning intr_type likely needs to be fixed! +#endif + // Start task // xTaskCreatePinnedToCore(ml_iec_intr_task, "ml_iec_intr_task", 4096, NULL, 20, NULL, 1); @@ -190,10 +282,10 @@ void systemBus::setup() .mode = GPIO_MODE_INPUT, // set as input mode .pull_up_en = GPIO_PULLUP_DISABLE, // disable pull-up mode .pull_down_en = GPIO_PULLDOWN_DISABLE, // disable pull-down mode - .intr_type = GPIO_INTR_NEGEDGE // interrupt of falling edge + .intr_type = GPIO_INTR_ANYEDGE // interrupt of any edge }; gpio_config(&io_conf); - gpio_isr_handler_add((gpio_num_t)PIN_IEC_ATN, cbm_on_atn_isr_handler, this); + gpio_isr_handler_add((gpio_num_t)PIN_IEC_ATN, cbm_on_atn_isr_forwarder, this); // Setup interrupt config for CLK io_conf = { @@ -204,385 +296,34 @@ void systemBus::setup() .intr_type = GPIO_INTR_POSEDGE // interrupt of rising edge }; gpio_config(&io_conf); - gpio_isr_handler_add((gpio_num_t)PIN_IEC_CLK_IN, cbm_on_clk_isr_handler, this); + gpio_isr_handler_add((gpio_num_t)PIN_IEC_CLK_IN, cbm_on_clk_isr_forwarder, this); + + iec_commandQueue = xQueueCreate(10, sizeof(IECData *)); // Start SRQ timer service timer_start(); } - void IRAM_ATTR systemBus::service() { - // pull( PIN_IEC_SRQ ); - - // Disable Interrupt - // gpio_intr_disable((gpio_num_t)PIN_IEC_ATN); - - if (state < BUS_ACTIVE) - { - // debugTiming(); - - // Handle SRQ for devices - for (auto devicep : _daisyChain) - { - for (unsigned char i=0;i<16;i++) - devicep->poll_interrupt(i); - } - - return; - } - -#ifdef IEC_HAS_RESET - // Check if CBM is sending a reset (setting the RESET line high). This is typically - // when the CBM is reset itself. In this case, we are supposed to reset all states to initial. - bool pin_reset = status(PIN_IEC_RESET); - if (pin_reset == PULLED) - { - if (status(PIN_IEC_ATN) == PULLED) - { - // If RESET & ATN are both PULLED then CBM is off - state = BUS_OFFLINE; - // gpio_intr_enable((gpio_num_t)PIN_IEC_ATN); - return; - } - - //Debug_printf("IEC Reset! reset[%d]\r\n", pin_reset); - data.init(); // Clear bus data - releaseLines(); - state = BUS_IDLE; - //Debug_printv("bus init"); - - // Reset virtual devices - reset_all_our_devices(); - // gpio_intr_enable((gpio_num_t)PIN_IEC_ATN); - return; - } -#endif - - // Command or Data Mode - do - { - if (state == BUS_ACTIVE) - { - //pull ( PIN_IEC_SRQ ); - - // Switch to standard serial protocol - if ( detected_protocol != PROTOCOL_SERIAL) - { - detected_protocol = PROTOCOL_SERIAL; - protocol = selectProtocol(); - } - - // *** IMPORTANT! This helps keep us in sync! - // Sometimes the C64 pulls ATN but doesn't pull CLOCK right away - protocol->timeoutWait ( PIN_IEC_CLK_IN, PULLED, TIMEOUT_ATNCLK, false ); - - // Read bus command bytes - //Debug_printv("command"); - read_command(); - - //release ( PIN_IEC_SRQ ); - } - - if (state == BUS_PROCESS) - { - // Reset bit/byte - bit = 0; - byte = 0; - - //Debug_printv("data"); - //pull ( PIN_IEC_SRQ ); - - // Data Mode - Get Command or Data - if (data.primary == IEC_LISTEN) - { - //Debug_printv("calling deviceListen()\r\n"); - //pull ( PIN_IEC_SRQ ); - deviceListen(); - //release ( PIN_IEC_SRQ ); - } - else if (data.primary == IEC_TALK) - { - //Debug_printv("calling deviceTalk()\r\n"); - //pull ( PIN_IEC_SRQ ); - deviceTalk(); - //release ( PIN_IEC_SRQ ); - Debug_printf(" (%.2X %s %.2d CHANNEL)\r\n", data.secondary, data.action.c_str(), data.channel); - } - else if (data.primary == IEC_UNLISTEN) - { - state = BUS_RELEASE; - } - - // Switch to detected protocol - if (data.secondary == IEC_OPEN || data.secondary == IEC_REOPEN) - { - //pull ( PIN_IEC_SRQ ); - if ( detected_protocol != PROTOCOL_SERIAL) - { - protocol = selectProtocol(); - } - //release ( PIN_IEC_SRQ ); - } - - // Queue control codes and command in specified device - //pull ( PIN_IEC_SRQ ); - auto d = deviceById(data.device); - if (d != nullptr) - { - d->queue_command(data); - - //fnLedManager.set(eLed::LED_BUS, true); - - //Debug_printv("bus[%d] device[%d]", state, device_state); - // for (auto devicep : _daisyChain) - // { - d->process(); - if ( data.primary == IEC_TALK ) - data.init(); - // } - } - - //Debug_printv("bus[%d] device[%d] flags[%d]", state, device_state, flags); - - - //release ( PIN_IEC_SRQ ); - } - - if ( state == BUS_RELEASE ) - break; - - // Let's check ATN again before we exit and clean up - if ( status ( PIN_IEC_ATN ) ) - { - state = BUS_ACTIVE; - } - - } while( state > BUS_IDLE ); - - // Clean Up - if ( state == BUS_RELEASE ) - { - releaseLines(); - data.init(); - } - - //Debug_printv ( "primary[%.2X] secondary[%.2X] bus[%d] flags[%d]", data.primary, data.secondary, state, flags ); - //Debug_printv ( "device[%d] channel[%d]", data.device, data.channel); - - Debug_printv("bus[%d] flags[%d]", state, flags); - Debug_printf("Heap: %lu\r\n",esp_get_free_internal_heap_size()); - - //release( PIN_IEC_SRQ ); - //fnLedStrip.stopRainbow(); - //fnLedManager.set(eLed::LED_BUS, false); -} - -void systemBus::read_command() -{ - //pull( PIN_IEC_SRQ ); - uint8_t c = receiveByte(); - //release( PIN_IEC_SRQ ); - - // Check for error - if ( flags & ERROR ) - { - Debug_printv("Error reading command. flags[%d] c[%X]", flags, c); - state = BUS_ERROR; - - return; - } - else if ( flags & EMPTY_STREAM) - { - state = BUS_RELEASE; - } - else - { - if ( flags & JIFFYDOS_ACTIVE ) - { - Debug_printf(" IEC: [JD][%.2X]", c); - detected_protocol = PROTOCOL_JIFFYDOS; - } - else - { - Debug_printf(" IEC: [%.2X]", c); - } - - // Decode command byte - uint8_t command = c & 0x60; - if (c == IEC_UNLISTEN) - command = IEC_UNLISTEN; - if (c == IEC_UNTALK) - command = IEC_UNTALK; - - //Debug_printv ( "device[%d] channel[%d]", data.device, data.channel); - //Debug_printv ("command[%.2X]", command); - - switch (command) - { - // case IEC_GLOBAL: - // data.primary = IEC_GLOBAL; - // data.device = c ^ IEC_GLOBAL; - // state = BUS_IDLE; - // Debug_printf(" (00 GLOBAL %.2d COMMAND)\r\n", data.device); - // break; - - case IEC_LISTEN: - data.primary = IEC_LISTEN; - data.device = c ^ IEC_LISTEN; - data.secondary = IEC_REOPEN; // Default secondary command - data.channel = CHANNEL_COMMAND; // Default channel - data.payload = ""; - state = BUS_ACTIVE; - Debug_printf(" (20 LISTEN %.2d DEVICE)\r\n", data.device); - break; - - case IEC_UNLISTEN: - data.primary = IEC_UNLISTEN; - state = BUS_PROCESS; - Debug_printf(" (3F UNLISTEN)\r\n"); - break; - - case IEC_TALK: - data.primary = IEC_TALK; - data.device = c ^ IEC_TALK; - data.secondary = IEC_REOPEN; // Default secondary command - data.channel = CHANNEL_COMMAND; // Default channel - state = BUS_ACTIVE; - Debug_printf(" (40 TALK %.2d DEVICE)\r\n", data.device); - break; - - case IEC_UNTALK: - data.primary = IEC_UNTALK; - data.secondary = 0x00; - state = BUS_RELEASE; - Debug_printf(" (5F UNTALK)\r\n"); - break; + IECData *received; - default: - //pull ( PIN_IEC_SRQ ); - std::string secondary; - state = BUS_PROCESS; - - command = c & 0xF0; - switch ( command ) - { - case IEC_OPEN: - data.secondary = IEC_OPEN; - data.channel = c ^ IEC_OPEN; - data.action = "OPEN"; - break; - - case IEC_REOPEN: - data.secondary = IEC_REOPEN; - data.channel = c ^ IEC_REOPEN; - data.action = "DATA"; - break; - - case IEC_CLOSE: - data.secondary = IEC_CLOSE; - data.channel = c ^ IEC_CLOSE; - data.action = "CLOSE"; - break; - - default: - state = BUS_IDLE; - } - - if ( data.primary != IEC_TALK ) - Debug_printf(" (%.2X %s %.2d CHANNEL)\r\n", data.secondary, data.action.c_str(), data.channel); - } - } - - if ( state == BUS_ACTIVE ) // Normal behaviour is to ignore everything if it's not for us - //if ( state == BUS_PROCESS ) // Use this to sniff the secondary commands - { - if ( !isDeviceEnabled( data.device ) ) - { - // Is this command for us? - state = BUS_RELEASE; // NOPE! - } - } - - if ( state == BUS_PROCESS ) - { - // *** IMPORTANT! This helps keep us in sync! - // Sometimes ATN isn't released immediately. Wait for ATN to be - // released before trying to process the command. - // Long ATN delay (>1.5ms) seems to occur more frequently with VIC-20. - //pull ( PIN_IEC_SRQ ); - protocol->timeoutWait ( PIN_IEC_ATN, RELEASED, TIMEOUT_DEFAULT, false ); - - // Delay after ATN is RELEASED - //protocol->wait( TIMING_Ttk, false ); - //release ( PIN_IEC_SRQ ); - } - - -#ifdef PARALLEL_BUS - // Switch to Parallel if detected - if ( PARALLEL.state == PBUS_PROCESS ) - { - if ( data.primary == IEC_LISTEN || data.primary == IEC_TALK ) - detected_protocol = PROTOCOL_SPEEDDOS; - else if ( data.primary == IEC_OPEN || data.primary == IEC_REOPEN ) - detected_protocol = PROTOCOL_DOLPHINDOS; - - // Switch to parallel protocol - protocol = selectProtocol(); - - if ( data.primary == IEC_LISTEN ) - PARALLEL.setMode( MODE_RECEIVE ); - else - PARALLEL.setMode( MODE_SEND ); - - // Acknowledge parallel mode - PARALLEL.handShake(); - } -#endif - - //Debug_printv ( "code[%.2X] primary[%.2X] secondary[%.2X] bus[%d] flags[%d]", c, data.primary, data.secondary, state, flags ); - //Debug_printv ( "device[%d] channel[%d]", data.device, data.channel); - - //release( PIN_IEC_SRQ ); -} - -void systemBus::read_payload() -{ - // Record the command string until ATN is PULLED - // NOTE: string is just a container, it may contain arbitrary bytes but a LOT of code treats payload as a string - std::string listen_command = ""; + if (!xQueueReceive(iec_commandQueue, &received, 0)) + return; - // ATN might get pulled right away if there is no command string to send - //pull ( PIN_IEC_SRQ ); - while (status(PIN_IEC_ATN) != PULLED) - { - //pull ( PIN_IEC_SRQ ); - int16_t c = protocol->receiveByte(); - //Debug_printv("c[%2X]", c); - //release ( PIN_IEC_SRQ ); + received->debugPrint(); - if (flags & EMPTY_STREAM || flags & ERROR) - { - Debug_printv("flags[%02X]", flags); - state = BUS_ERROR; - //release ( PIN_IEC_SRQ ); - return; - } + auto d = deviceById(received->device); + if (d != nullptr) { + d->commanddata = *received; + d->process(); + } - //if (c != 0xFFFFFFFF ) // && c != 0x0D) // Leave 0x0D to be stripped later - //{ - listen_command += (uint8_t)c; - //} + // Command was processed, clear it out + delete received; - if (flags & EOI_RECVD) - break; - } - data.payload = listen_command; - - state = BUS_IDLE; - //release ( PIN_IEC_SRQ ); + return; } /** @@ -614,10 +355,10 @@ void systemBus::timer_stop() } } -std::shared_ptr systemBus::selectProtocol() +std::shared_ptr systemBus::selectProtocol() { //Debug_printv("protocol[%d]", detected_protocol); - + switch(detected_protocol) { case PROTOCOL_JIFFYDOS: @@ -653,35 +394,27 @@ device_state_t virtualDevice::process() switch ((bus_command_t)commanddata.primary) { case bus_command_t::IEC_LISTEN: - state = DEVICE_LISTEN; - break; - case bus_command_t::IEC_UNLISTEN: - state = DEVICE_PROCESS; - break; - case bus_command_t::IEC_TALK: - state = DEVICE_TALK; - break; - default: - break; - } - - switch ((bus_command_t)commanddata.secondary) - { - case bus_command_t::IEC_OPEN: - payload = commanddata.payload; - break; - case bus_command_t::IEC_CLOSE: - reset_state(); - break; - case bus_command_t::IEC_REOPEN: - if (state == DEVICE_TALK) - { - } - else if (state == DEVICE_LISTEN) + switch (commanddata.secondary) { + case bus_command_t::IEC_OPEN: payload = commanddata.payload; + state = openChannel(/*commanddata.channel, payload*/); + break; + case bus_command_t::IEC_CLOSE: + state = closeChannel(/*commanddata.channel*/); + break; + case bus_command_t::IEC_REOPEN: + payload = commanddata.payload; + state = writeChannel(/*commanddata.channel, payload*/); + break; } break; + + case bus_command_t::IEC_TALK: + if (commanddata.secondary == bus_command_t::IEC_REOPEN) + state = readChannel(/*commanddata.channel*/); + break; + default: break; } @@ -707,198 +440,85 @@ void virtualDevice::dumpData() void systemBus::assert_interrupt() { if (interruptSRQ) - pull(PIN_IEC_SRQ); + IEC_ASSERT(PIN_IEC_SRQ); else - release(PIN_IEC_SRQ); -} - -uint8_t systemBus::receiveByte() -{ - //pull( PIN_IEC_SRQ ); - uint8_t b = protocol->receiveByte(); -#ifdef DATA_STREAM - Serial.printf("%.2X ", (int16_t)b); -#endif - - if ( flags & ERROR ) - { - Debug_printv("error"); - } - //release( PIN_IEC_SRQ ); - return b; -} - -std::string systemBus::receiveBytes() -{ - std::string s; - - do - { - uint8_t b = receiveByte(); - if ( !(flags & ERROR) ) - s += b; - }while(!(flags & EOI_RECVD)); - return s; + IEC_RELEASE(PIN_IEC_SRQ); } bool systemBus::sendByte(const char c, bool eoi) { - gpio_intr_disable( PIN_IEC_CLK_IN ); - if (!protocol->sendByte(c, eoi)) - { - if (!(flags & ATN_PULLED)) - { - flags |= ERROR; - Debug_printv("error"); - gpio_intr_enable( PIN_IEC_CLK_IN ); - return false; - } - } - -#ifdef DATA_STREAM - if (eoi) - { - Serial.printf("%.2X [eoi]\r\n", c); - } - else - { - if (!(flags & ATN_PULLED)) - Serial.printf("%.2X ", c); - } -#endif - gpio_intr_enable( PIN_IEC_CLK_IN ); - return true; + return protocol->sendByte(c, eoi); } -bool systemBus::sendBytes(const char *buf, size_t len, bool eoi) +size_t systemBus::sendBytes(const char *buf, size_t len, bool eoi) { - bool success = false; + size_t i; - for (size_t i = 0; i < len; i++) + for (i = 0; i < len; i++) { - if (i == (len - 1) && eoi) - success = sendByte(buf[i], true); - else - success = sendByte(buf[i], false); - - if ( IEC.status ( PIN_IEC_ATN ) ) - { - return true; - } + if (!sendByte(buf[i], eoi && i == (len - 1))) + break; } - return success; + return i; } -bool systemBus::sendBytes(std::string s, bool eoi) +size_t systemBus::sendBytes(std::string s, bool eoi) { return sendBytes(s.c_str(), s.size(), eoi); } -void systemBus::process_cmd() +bool IRAM_ATTR systemBus::turnAround() { - // fnLedManager.set(eLed::LED_BUS, true); + /* + TURNAROUND + An unusual sequence takes place following ATN if the computer + wishes the remote device to become a talker. This will usually + take place only after a Talk command has been sent. Immediately + after ATN is RELEASED, the selected device will be behaving like a + listener. After all, it's been listening during the ATN cycle, and + the computer has been a talker. At this instant, we have "wrong + way" logic; the device is asserting the Data line, and the + computer is asserting the Clock line. We must turn this around. - // TODO implement + Here's the sequence: - // fnLedManager.set(eLed::LED_BUS, false); -} + 1. The computer asserts the Data line (it's already there), as + well as releases the Clock line. -void systemBus::process_queue() -{ - // TODO IMPLEMENT -} + 2. When the device sees the Clock line is releaseed, it releases + the Data line (which stays asserted anyway since the computer + is asserting it) and then asserts the Clock line. -void IRAM_ATTR systemBus::deviceListen() -{ - // If the command is SECONDARY and it is not to expect just a small command on the command channel, then - // we're into something more heavy. Otherwise read it all out right here until UNLISTEN is received. - if (data.secondary == IEC_REOPEN && data.channel != CHANNEL_COMMAND) - { - // A heapload of data might come now, too big for this context to handle so the caller handles this, we're done here. - // Debug_printf(" (%.2X SECONDARY) (%.2X CHANNEL)\r\n", data.primary, data.channel); - Debug_printf("REOPEN on non-command channel.\r\n"); - state = BUS_ACTIVE; - } - - // OPEN or DATA - else if (data.secondary == IEC_OPEN || data.secondary == IEC_REOPEN) - { - read_payload(); - std::string s = mstr::toHex(data.payload); - Serial.printf("Device #%02d:%02d {%s} [%s]\r\n", data.device, data.channel, data.payload.c_str(), s.c_str()); - } - - // CLOSE Named Channel - else if (data.secondary == IEC_CLOSE) - { - // Debug_printf(" (E0 CLOSE) (%d CHANNEL)\r\n", data.channel); - state = BUS_PROCESS; - } - - // Unknown - else - { - Debug_printf(" OTHER (%.2X COMMAND) (%.2X CHANNEL) ", data.secondary, data.channel); - state = BUS_ERROR; - } -} + We're now in our starting position, with the talker (that's the + device) asserting the Clock, and the listener (the computer) + asserting the Data line. The computer watches for this state; + only when it has gone through the cycle correctly will it be ready + to receive data. And data will be signalled, of course, with the + usual sequence: the talker releases the Clock line to signal that + it's ready to send. + */ -void IRAM_ATTR systemBus::deviceTalk() -{ - // Now do bus turnaround - //pull(PIN_IEC_SRQ); - if (!turnAround()) + // Wait for ATN to be released + if (protocol->waitForSignals(PIN_IEC_ATN, IEC_RELEASED, 0, 0, FOREVER) == TIMED_OUT) { - Debug_printv("error flags[%d]", flags); - state = BUS_ERROR; - return; + flags |= ERROR; + return false; } - //release(PIN_IEC_SRQ); - - // We have recieved a CMD and we should talk now: - state = BUS_PROCESS; -} - -bool IRAM_ATTR systemBus::turnAround() -{ - /* - TURNAROUND - An unusual sequence takes place following ATN if the computer wishes the remote device to - become a talker. This will usually take place only after a Talk command has been sent. - Immediately after ATN is RELEASED, the selected device will be behaving like a listener. After all, it's - been listening during the ATN cycle, and the computer has been a talker. At this instant, we - have "wrong way" logic; the device is holding down the Data line, and the computer is holding the - Clock line. We must turn this around. - - Here's the sequence: - 1. The computer pulls the Data line to true (it's already there), as well as releases the Clock line to false. - 2. When the device sees the Clock line is releaseed, it releases the Data line (which stays true anyway since - the computer is now holding it down) and then pulls down the Clock line. - - We're now in our starting position, with the talker (that's the device) holding the Clock true, and - the listener (the computer) holding the Data line true. The computer watches for this state; only when it has - gone through the cycle correctly will it be ready to receive data. And data will be signalled, of course, with - the usual sequence: the talker releases the Clock line to signal that it's ready to send. - */ // Wait for CLK to be released - //pull ( PIN_IEC_SRQ ); - if (protocol->timeoutWait(PIN_IEC_CLK_IN, RELEASED, TIMEOUT_Ttlta) == TIMEOUT_Ttlta) + if (protocol->waitForSignals(PIN_IEC_CLK_IN, IEC_RELEASED, 0, 0, TIMEOUT_Ttlta) == TIMED_OUT) { - Debug_printv("Wait until the computer releases the CLK line\r\n"); - Debug_printv("IEC: TURNAROUND TIMEOUT\r\n"); flags |= ERROR; return false; // return error because timeout } - release ( PIN_IEC_DATA_OUT ); - protocol->wait( TIMING_Ttcp ); - pull ( PIN_IEC_CLK_OUT ); - //release ( PIN_IEC_SRQ ); + + IEC_RELEASE( PIN_IEC_DATA_OUT ); + IEC_ASSERT( PIN_IEC_CLK_OUT ); // 80us minimum delay after TURNAROUND // *** IMPORTANT! - protocol->wait( TIMING_Tda ); + usleep(TIMING_Tda); return true; } // turnAround @@ -911,44 +531,41 @@ void systemBus::reset_all_our_devices() void systemBus::setBitTiming(std::string set, int p1, int p2, int p3, int p4) { uint8_t i = 0; // Send - if (mstr::equals(set, (char *) "r")) i = 1; + if (mstr::equals(set, (char *) "r")) i = 1; if (p1) protocol->bit_pair_timing[i][0] = p1; if (p2) protocol->bit_pair_timing[i][1] = p2; if (p3) protocol->bit_pair_timing[i][2] = p3; if (p4) protocol->bit_pair_timing[i][3] = p4; Debug_printv("i[%d] timing[%d][%d][%d][%d]", i, - protocol->bit_pair_timing[i][0], - protocol->bit_pair_timing[i][1], - protocol->bit_pair_timing[i][2], + protocol->bit_pair_timing[i][0], + protocol->bit_pair_timing[i][1], + protocol->bit_pair_timing[i][2], protocol->bit_pair_timing[i][3]); } void IRAM_ATTR systemBus::releaseLines(bool wait) { - //pull ( PIN_IEC_SRQ ); - // Release lines - release(PIN_IEC_CLK_OUT); - release(PIN_IEC_DATA_OUT); + IEC_RELEASE(PIN_IEC_CLK_OUT); + IEC_RELEASE(PIN_IEC_DATA_OUT); + IEC_SET_STATE(BUS_IDLE); // Wait for ATN to release and quit if (wait) { Debug_printv("Waiting for ATN to release"); - protocol->timeoutWait ( PIN_IEC_ATN, RELEASED, TIMEOUT_DEFAULT, false ); + protocol->waitForSignals(PIN_IEC_ATN, IEC_RELEASED, 0, 0, TIMEOUT_DEFAULT); } - - //release ( PIN_IEC_SRQ ); } void IRAM_ATTR systemBus::senderTimeout() { releaseLines(); - this->state = BUS_ERROR; + IEC_SET_STATE(BUS_ERROR); - protocol->wait( TIMING_EMPTY ); - pull( PIN_IEC_DATA_OUT ); + usleep(TIMING_EMPTY); + IEC_ASSERT( PIN_IEC_DATA_OUT ); } // senderTimeout void systemBus::addDevice(virtualDevice *pDevice, int device_id) @@ -1023,45 +640,46 @@ void systemBus::shutdown() Debug_printf("All devices shut down.\r\n"); } +enum { + IECOpenChannel = 0, + IECCloseChannel, + IECReadChannel, + IECWriteChannel, +}; + +static const char *IECCommandNames[] = { + "UNKNOWN", "LISTEN", "TALK", "REOPEN", + "OPEN", "CLOSE", "READ", "WRITE", +}; +int IECData::channelCommand() +{ + switch (primary) { + case IEC_LISTEN: + switch (secondary) { + case IEC_OPEN: + return IECOpenChannel; + case IEC_CLOSE: + return IECCloseChannel; + case IEC_REOPEN: + return IECWriteChannel; + } + break; + + case IEC_TALK: + return IECReadChannel; + } + + return 0; +} -void systemBus::debugTiming() +void IECData::debugPrint() { - int pin = PIN_IEC_ATN; - pull(pin); - protocol->wait(10); - release(pin); - protocol->wait(10); - - pin = PIN_IEC_CLK_OUT; - pull(pin); - protocol->wait(20); - release(pin); - protocol->wait(20); - - pin = PIN_IEC_DATA_OUT; - pull(pin); - protocol->wait(30); - release(pin); - protocol->wait(30); - - pin = PIN_IEC_SRQ; - pull(pin); - protocol->wait(40); - release(pin); - protocol->wait(40); - - // pin = PIN_IEC_ATN; - // pull(pin); - // protocol->wait(100); // 100 - // release(pin); - // protocol->wait(1); - - // pin = PIN_IEC_CLK_OUT; - // pull(pin); - // protocol->wait(200); // 200 - // release(pin); - // protocol->wait(1); + Debug_printf("IEC %2i %-5s %2i: %-6s [%02X %02X]\r\n", + device, IECCommandNames[channelCommand() + 4], channel, + IECCommandNames[primary >> 5], primary, secondary); + if (payload.size()) + Debug_printf("%s", util_hexdump(payload.data(), payload.size()).c_str()); } #endif /* BUILD_IEC */ diff --git a/lib/bus/iec/iec.h b/lib/bus/iec/iec.h index 92c8fb03a..969d72a10 100644 --- a/lib/bus/iec/iec.h +++ b/lib/bus/iec/iec.h @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -52,12 +53,6 @@ #include #include "fnSystem.h" -#include "protocol/_protocol.h" -#include "protocol/jiffydos.h" -#ifdef PARALLEL_BUS -#include "protocol/dolphindos.h" -#endif - #include #include "../../../include/debug.h" @@ -95,7 +90,7 @@ typedef enum BUS_ERROR = -2, // A problem occoured, reset communication BUS_RELEASE = -1, // Clean Up BUS_IDLE = 0, // Nothing recieved of our concern - BUS_ACTIVE = 1, // ATN is pulled and a command byte is expected + BUS_ACTIVE = 1, // ATN is asserted and a command byte is expected BUS_PROCESS = 2, // A command is ready to be processed } bus_state_t; @@ -138,8 +133,6 @@ typedef enum { PROTOCOL_IEEE488 } bus_protocol_t; -using namespace Protocol; - /** * @class IECData * @brief the IEC command data passed to devices @@ -167,27 +160,21 @@ class IECData * @brief the device command */ std::string payload = ""; - /** - * @brief the raw bytes received for the command - */ - std::vector payload_raw; - /** - * @brief secondary action description - */ - std::string action =""; + /** * @brief clear and initialize IEC command data */ void init(void) { - //primary = 0; + primary = 0; device = 0; secondary = 0; channel = 0; payload = ""; - payload_raw.clear(); - action = ""; } + + int channelCommand(); + void debugPrint(); }; /** @@ -202,6 +189,12 @@ class systemBus; class virtualDevice { private: + /** + * @brief All IEC devices repeatedly call this routine to fan out to other methods for each command. + * This is typcially implemented as a switch() statement. + * @return new device state. + */ + virtual device_state_t process(); protected: friend systemBus; /* Because we connect to it. */ @@ -283,12 +276,10 @@ class virtualDevice return state; } - /** - * @brief All IEC devices repeatedly call this routine to fan out to other methods for each command. - * This is typcially implemented as a switch() statement. - * @return new device state. - */ - virtual device_state_t process(); + virtual device_state_t openChannel(/*int chan, IECPayload &payload*/) = 0; + virtual device_state_t closeChannel(/*int chan*/) = 0; + virtual device_state_t readChannel(/*int chan*/) = 0; + virtual device_state_t writeChannel(/*int chan, IECPayload &payload*/) = 0; /** * @brief poll whether interrupt should be wiggled @@ -370,13 +361,36 @@ class virtualDevice } }; +namespace Protocol { + class IECProtocol; + class CPBStandardSerial; + class JiffyDOS; +} +class oiecstream; + /** * @class systemBus * @brief the system bus that all virtualDevices attach to. */ class systemBus { +friend Protocol::IECProtocol; +friend Protocol::CPBStandardSerial; +friend Protocol::JiffyDOS; +friend oiecstream; + private: + /** + * @brief current bus state + */ + bus_state_t _state; + + /** + * @brief data about current bus transaction + */ + IECData *iec_curCommand; + QueueHandle_t iec_commandQueue; + /** * @brief The chain of devices on the bus. */ @@ -405,48 +419,27 @@ class systemBus /** * @brief the active bus protocol */ - std::shared_ptr protocol = nullptr; + std::shared_ptr protocol = nullptr; /** * @brief Switch to detected bus protocol */ - std::shared_ptr selectProtocol(); + std::shared_ptr selectProtocol(); /** - * IEC LISTEN received + * @brief bus flags */ - void deviceListen(); + uint16_t flags = 0;//CLEAR; - /** - * IEC TALK requested - */ - void deviceTalk(); + void newIO(int val); + void channelIO(int val); + void sendInput(); /** * BUS TURNAROUND (switch from listener to talker) */ bool turnAround(); - /** - * @brief called to process the next command - */ - void process_cmd(); - - /** - * @brief called to process a queue item (such as disk swap) - */ - void process_queue(); - - /** - * @brief called to read bus command bytes - */ - void read_command(); - - /** - * @brief called to read bus payload bytes - */ - void read_payload(); - /** * ESP timer handle for the Interrupt rate limiting timer */ @@ -468,21 +461,11 @@ class systemBus void timer_stop(); public: - /** - * @brief bus flags - */ - uint16_t flags = CLEAR; - /** * @brief bus enabled */ bool enabled = true; - /** - * @brief current bus state - */ - bus_state_t state; - /** * @brief vic20 mode enables faster valid bit timing */ @@ -492,12 +475,7 @@ class systemBus * Toggled by the rate limiting timer to indicate that the SRQ interrupt should * be pulsed. */ - bool interruptSRQ = false; - - /** - * @brief data about current bus transaction - */ - IECData data; + bool interruptSRQ = false; /** * @brief Enabled device bits @@ -549,7 +527,7 @@ class systemBus * @param eoi Send EOI? * @return true on success, false on error */ - bool sendBytes(const char *buf, size_t len, bool eoi = true); + size_t sendBytes(const char *buf, size_t len, bool eoi = true); /** * @brief Send string to bus @@ -557,19 +535,7 @@ class systemBus * @param eoi Send EOI? * @return true on success, false on error */ - bool sendBytes(std::string s, bool eoi = true); - - /** - * @brief Receive Byte from bus - * @return Byte received from bus, or -1 for error - */ - uint8_t receiveByte(); - - /** - * @brief Receive String from bus - * @return std::string received from bus - */ - std::string receiveBytes(); + size_t sendBytes(std::string s, bool eoi = true); /** * @brief called in response to RESET pin being asserted. @@ -631,20 +597,16 @@ class systemBus */ void senderTimeout(); - - uint8_t bit = 0; - uint8_t byte = 0; - - bool pin_atn = false; - bool pin_clk = false; - bool pin_data = false; - bool pin_srq = false; - bool pin_reset = false; + // FIXME - these should be private + void cbm_on_atn_isr_handler(); + void cbm_on_clk_isr_handler(); void init_gpio(gpio_num_t _pin); - void pull ( uint8_t _pin ); +#if IEC_ASSERT_RELEASE_AS_FUNCTIONS + void assert ( uint8_t _pin ); void release ( uint8_t _pin ); bool status ( uint8_t _pin ); +#endif bool status (); void debugTiming(); diff --git a/lib/bus/iec/protocol/_protocol.cpp b/lib/bus/iec/protocol/_protocol.cpp index e94d1041e..191313394 100644 --- a/lib/bus/iec/protocol/_protocol.cpp +++ b/lib/bus/iec/protocol/_protocol.cpp @@ -24,9 +24,9 @@ IECProtocol::IECProtocol() { esp_timer_create_args_t args = { .callback = onTimer, .arg = this, - .dispatch_method = ESP_TIMER_TASK, + .dispatch_method = ESP_TIMER_TASK, .name = "onTimer", - .skip_unhandled_events = 0, + .skip_unhandled_events = 0, }; esp_timer_create(&args, &timer_handle); }; @@ -53,145 +53,51 @@ void IECProtocol::timer_stop() //IEC.release( PIN_IEC_SRQ ); } - -// int16_t IRAM_ATTR IECProtocol::timeoutWait(uint8_t pin, bool target_status, size_t wait_us, bool watch_atn) -// { -// IEC.pull ( PIN_IEC_SRQ ); -// uint64_t start = esp_timer_get_time(); -// uint64_t current = 0; -// timer_start( wait_us ); - -// #ifndef IEC_SPLIT_LINES -// IEC.release ( pin ); -// #endif - -// while ( !timer_timedout ) -// { -// IEC.pull ( PIN_IEC_SRQ ); -// if ( IEC.status ( pin ) == target_status ) -// { -// timer_stop(); -// current = esp_timer_get_time(); -// IEC.release ( PIN_IEC_SRQ ); -// return ( current - start ); -// } -// usleep( 2 ); -// if ( watch_atn ) -// { -// if ( IEC.status ( PIN_IEC_ATN ) ) -// { -// IEC.release ( PIN_IEC_SRQ ); -// return -1; -// } -// } -// IEC.release ( PIN_IEC_SRQ ); -// usleep( 2 ); -// } -// IEC.release ( PIN_IEC_SRQ ); -// return wait_us; -// } - -int16_t IRAM_ATTR IECProtocol::timeoutWait(uint8_t pin, bool target_status, size_t wait_us, bool watch_atn) +int IRAM_ATTR IECProtocol::waitForSignals(int pin1, int state1, + int pin2, int state2, + int timeout) { - uint64_t start = 0; - uint64_t current = 0; - uint64_t elapsed = 0; - -#ifndef IEC_SPLIT_LINES - IEC.release ( pin ); -#endif - - // Quick check to see if the target status is already set - if ( IEC.status ( pin ) == target_status ) - return elapsed; - - if ( pin == PIN_IEC_ATN ) - { - watch_atn = false; - } - else - { -#ifndef IEC_SPLIT_LINES - IEC.release ( PIN_IEC_ATN ); -#endif - - // Sample ATN and set flag to indicate COMMAND or DATA mode - if( IEC.status ( PIN_IEC_ATN ) ) - IEC.flags |= ATN_PULLED; + uint64_t start, now, elapsed; + int abort = 0; + + + start = esp_timer_get_time(); + for (;;) { + if (IEC_IS_ASSERTED(pin1) == state1) + break; + if (pin2 && IEC_IS_ASSERTED(pin2) == state2) + break; + + now = esp_timer_get_time(); + elapsed = now - start; + if (elapsed >= timeout) { + abort = 1; + break; } + } - //IEC.pull ( PIN_IEC_SRQ ); - start = esp_timer_get_time(); - while ( IEC.status ( pin ) != target_status ) - { - current = esp_timer_get_time(); - elapsed = ( current - start ); - - if ( elapsed >= wait_us && wait_us != FOREVER ) - { - //IEC.release ( PIN_IEC_SRQ ); - if ( wait_us == TIMEOUT_DEFAULT ) - return -1; - - return wait_us; - } - - if ( watch_atn ) - { - if ( IEC.flags & ATN_PULLED ) - return -1; - } - - if ( IEC.state < BUS_ACTIVE || elapsed > FOREVER ) - { - // Something is messed up. Get outta here. - // FOREVER really isn't forever - Debug_printv("wth? bus_state[%d]", IEC.state); - Debug_printv("pin[%d] target_status[%d] wait[%d] elapsed[%d]", pin, target_status, wait_us, elapsed); - return -1; - } - } - //IEC.release ( PIN_IEC_SRQ ); - - // Debug_printv("pin[%d] state[%d] wait[%d] step[%d] t[%d]", pin, target_status, wait, elapsed); - return elapsed; -} - -bool IRAM_ATTR IECProtocol::wait(size_t wait_us, bool watch_atn) -{ - return wait(wait_us, 0, watch_atn); + return abort ? TIMED_OUT : 0; } -bool IRAM_ATTR IECProtocol::wait(size_t wait_us, uint64_t start, bool watch_atn) +void IECProtocol::transferDelaySinceLast(size_t minimumDelay) { - uint64_t current, elapsed; - current = 0; - elapsed = 0; - - if ( wait_us == 0 ) return true; - wait_us--; // Shave 1us for overhead - - //IEC.pull ( PIN_IEC_SRQ ); - if ( start == 0 ) start = esp_timer_get_time(); - while ( elapsed <= wait_us ) - { - current = esp_timer_get_time(); - elapsed = current - start; - - if ( watch_atn ) - { - if ( IEC.status ( PIN_IEC_ATN ) ) - { - IEC.flags |= ATN_PULLED; - //IEC.release ( PIN_IEC_SRQ ); - //Debug_printv("wait[%d] elapsed[%d] start[%d] current[%d]", wait, elapsed, start, current); - return false; - } - } + uint64_t now, elapsed; + int64_t remaining; + + + now = esp_timer_get_time(); + elapsed = now - _transferEnded; + if (minimumDelay > 0) { + remaining = minimumDelay; + remaining -= elapsed; + if (remaining > 0) { + usleep(remaining); + now = esp_timer_get_time(); } - //IEC.release ( PIN_IEC_SRQ ); + } - return true; + _transferEnded = now; + return; } #endif /* BUILD_IEC */ diff --git a/lib/bus/iec/protocol/_protocol.h b/lib/bus/iec/protocol/_protocol.h index e581aafaf..d1bfa3aaf 100644 --- a/lib/bus/iec/protocol/_protocol.h +++ b/lib/bus/iec/protocol/_protocol.h @@ -9,6 +9,35 @@ #include "../../include/cbm_defines.h" +#define IEC_RELEASE(pin) ({ \ + uint32_t _pin = pin; \ + uint32_t _mask = 1 << (_pin % 32); \ + if (_pin >= 32) \ + GPIO.enable1_w1tc.val = _mask; \ + else \ + GPIO.enable_w1tc = _mask; \ + }) +#define IEC_ASSERT(pin) ({ \ + uint32_t _pin = pin; \ + uint32_t _mask = 1 << (_pin % 32); \ + if (_pin >= 32) \ + GPIO.enable1_w1ts.val = _mask; \ + else \ + GPIO.enable_w1ts = _mask; \ + }) + +#ifndef IEC_INVERTED_LINES +#define IEC_IS_ASSERTED(pin) ({ \ + uint32_t _pin = pin; \ + !((_pin >= 32 ? GPIO.in1.val : GPIO.in) & (1 << (_pin % 32))); \ + }) +#else +#define IEC_IS_ASSERTED(pin) ({ \ + uint32_t _pin = pin; \ + !!(_pin >= 32 ? GPIO.in1.val : GPIO.in) & (1 << (_pin % 32)); \ + }) +#endif /* !IEC_INVERTED_LINES */ + namespace Protocol { /** @@ -16,6 +45,9 @@ namespace Protocol */ class IECProtocol { + private: + uint64_t _transferEnded = 0; + public: // 2bit Fast Loader Pair Timing @@ -65,26 +97,11 @@ namespace Protocol void timer_start(uint64_t timeout); void timer_stop(); - /** - * @brief Wait until target status, or timeout is reached. - * @param pin IEC pin to watch - * @param target_status break if target state reached - * @param wait_us timeout period in microseconds (default is 1 millisecond) - * @param watch_atn also abort if ATN status changes (default is true) - * @return elapsed time in microseconds, or -1 if ATN pulled, or -1 if timeout breached. - * - */ - virtual int16_t timeoutWait(uint8_t pin, bool target_status, size_t wait_us = TIMEOUT_DEFAULT, bool watch_atn = true); - - /** - * @brief Wait for specified milliseconds, or until ATN status changes - * @param wait_us # of milliseconds to wait - * @param start The previously set start millisecond time. - * @param watch_atn also abort if ATN status changes? (default is false) - */ - virtual bool wait(size_t wait_us, bool watch_atn = false); - virtual bool wait(size_t wait_us, uint64_t start, bool watch_atn = false); + int waitForSignals(int pin1, int state1, int pin2, int state2, int timeout); + void transferDelaySinceLast(size_t minimumDelay); }; }; -#endif /* IECPROTOCOLBASE_H */ \ No newline at end of file +#define transferEnd() transferDelaySinceLast(0) + +#endif /* IECPROTOCOLBASE_H */ diff --git a/lib/bus/iec/protocol/cpbstandardserial.cpp b/lib/bus/iec/protocol/cpbstandardserial.cpp index 95590daee..b8d6211f6 100644 --- a/lib/bus/iec/protocol/cpbstandardserial.cpp +++ b/lib/bus/iec/protocol/cpbstandardserial.cpp @@ -17,6 +17,8 @@ #ifdef BUILD_IEC +#define DYNAMIC_DELAY + #include "cpbstandardserial.h" #include "bus.h" @@ -25,594 +27,350 @@ #include "../../../include/debug.h" #include "../../../include/pinmap.h" -using namespace Protocol; - - -/** - * Callback function to sendbits - */ -static void onSendBits(void *arg) -{ -// IECProtocol *p = (CPBStandardSerial *)arg; - - uint8_t Tv = TIMING_Tv64; // C64 data valid timing - - // We can send faster if in VIC20 Mode - if ( IEC.vic20_mode ) - { - Tv = TIMING_Tv; // VIC-20 data valid timing - } - - // Send bits - for ( uint8_t n = 0; n < 8; n++ ) - { - // set bit - usleep ( TIMING_Ts0 ); - ( IEC.byte & 1 ) ? IEC.release ( PIN_IEC_DATA_OUT ) : IEC.pull ( PIN_IEC_DATA_OUT ); - IEC.byte >>= 1; // shift to next bit - usleep ( TIMING_Ts1 ); - - // tell listener bit is ready to read - //IEC.pull ( PIN_IEC_SRQ ); - IEC.release ( PIN_IEC_CLK_OUT ); - usleep ( Tv ); - - // tell listener to wait for next bit - IEC.pull ( PIN_IEC_CLK_OUT ); - //IEC.release ( PIN_IEC_SRQ ); - IEC.bit++; - } - IEC.bit++; - - // Release DATA after byte sent - IEC.release ( PIN_IEC_DATA_OUT ); -} - -CPBStandardSerial::CPBStandardSerial() -{ - const esp_timer_create_args_t args = { - .callback = onSendBits, - .arg = this, - .dispatch_method = ESP_TIMER_ISR, - .name = "onSendBits", - .skip_unhandled_events = 0, - }; - esp_timer_create(&args, &timer_send_h); - //Debug_printv("send_timer_create"); -}; - -CPBStandardSerial::~CPBStandardSerial() -{ - esp_timer_stop(timer_send_h); - esp_timer_delete(timer_send_h); - //Debug_printv("send_timer_delete"); -} +#define IEC_SET_STATE(x) ({IEC._state = x;}) +using namespace Protocol; // STEP 1: READY TO RECEIVE -// Sooner or later, the talker will want to talk, and send a character. -// When it's ready to go, it releases the Clock line to false. This signal change might be -// translated as "I'm ready to send a character." The listener must detect this and respond, -// but it doesn't have to do so immediately. The listener will respond to the talker's -// "ready to send" signal whenever it likes; it can wait a long time. If it's -// a printer chugging out a line of print, or a disk drive with a formatting job in progress, -// it might holdback for quite a while; there's no time limit. +// Sooner or later, the talker will want to talk, and send a +// character. When it's ready to go, it releases the Clock line. +// This signal change might be translated as "I'm ready to send a +// character." The listener must detect this and respond, but it +// doesn't have to do so immediately. The listener will respond to the +// talker's "ready to send" signal whenever it likes; it can wait a +// long time. If it's a printer chugging out a line of print, or a +// disk drive with a formatting job in progress, it might holdback for +// quite a while; there's no time limit. uint8_t CPBStandardSerial::receiveByte() { - bool atn_status = false; - IEC.flags &= CLEAR_LOW; - - // Sample ATN and set flag to indicate COMMAND or DATA mode - atn_status = IEC.status ( PIN_IEC_ATN ); - if ( atn_status ) - IEC.flags |= ATN_PULLED; - - // Wait for talker ready - //IEC.pull ( PIN_IEC_SRQ ); - if ( timeoutWait ( PIN_IEC_CLK_IN, RELEASED, FOREVER, false ) == TIMED_OUT ) - { - Debug_printv ( "Wait for talker ready" ); - IEC.flags |= ERROR; - //IEC.release ( PIN_IEC_SRQ ); - return 0; // return error because timeout - } - //IEC.release ( PIN_IEC_SRQ ); - - // Say we're ready - // STEP 2: READY FOR DATA - // When the listener is ready to listen, it releases the Data - // line to false. Suppose there is more than one listener. The Data line will go false - // only when all listeners have RELEASED it - in other words, when all listeners are ready - // to accept data. What happens next is variable. - - // Release Data and wait for all other devices to release the data line too - //IEC.release( PIN_IEC_DATA_IN ); - if ( timeoutWait ( PIN_IEC_DATA_IN, RELEASED, FOREVER, false ) == TIMED_OUT ) - { - Debug_printv ( "Wait for all other devices to release the data line" ); - IEC.flags |= ERROR; - return 0; // return error because timeout + int abort; + int idx, data; + uint64_t start, now; + int elapsed = 0; + + + IEC.flags &= CLEAR_LOW; + if (IEC_IS_ASSERTED(PIN_IEC_ATN)) + IEC.flags |= ATN_ASSERTED; + + portDISABLE_INTERRUPTS(); + +#ifdef DYNAMIC_DELAY + transferDelaySinceLast(TIMING_Tf); +#endif + + // STEP 2: READY FOR DATA + // line. Suppose there is more than one listener. The Data line + // will be reelased only when all listeners have RELEASED it - in + // other words, when all listeners are ready to accept data. What + // happens next is variable. + + // Release Data and wait for all other devices to release the data line too + + IEC_RELEASE(PIN_IEC_DATA_OUT); + + // Either the talker will assert the Clock line back to asserted + // in less than 200 microseconds - usually within 60 microseconds + // - or it will do nothing. The listener should be watching, and + // if 200 microseconds pass without the Clock line being asserted, + // it has a special task to perform: note EOI. + start = esp_timer_get_time(); + for (abort = 0; !abort && !IEC_IS_ASSERTED(PIN_IEC_CLK_IN); ) { + now = esp_timer_get_time(); + elapsed = now - start; + + if (!(IEC.flags & EOI_RECVD) && elapsed >= TIMING_Tye) { + // INTERMISSION: EOI + // If the Ready for Data signal isn't acknowledged by the + // talker within 200 microseconds, the listener knows that the + // talker is trying to signal EOI. EOI, which formally stands + // for "End of Indicator," means "this character will be the + // last one." If it's a sequential disk file, don't ask for + // more: there will be no more. If it's a relative record, + // that's the end of the record. The character itself will + // still be coming, but the listener should note: here comes + // the last character. So if the listener sees the 200 + // microsecond time-out, it must signal "OK, I noticed the + // EOI" back to the talker, It does this by asserting the Data + // line for at least 60 microseconds, and then releasing it. + // The talker will then revert to transmitting the character + // in the usual way; within 60 microseconds it will assert the + // Clock line, and transmission will continue. At this point, + // the Clock line is asserted whether or not we have gone + // through the EOI sequence; we're back to a common + // transmission sequence. + IEC_ASSERT(PIN_IEC_DATA_OUT); + usleep(TIMING_Tei); + IEC_RELEASE(PIN_IEC_DATA_OUT); + IEC.flags |= EOI_RECVD; } - // Either the talker will pull the - // Clock line back to true in less than 200 microseconds - usually within 60 microseconds - or it - // will do nothing. The listener should be watching, and if 200 microseconds pass - // without the Clock line going to true, it has a special task to perform: note EOI. - - //IEC.pull ( PIN_IEC_SRQ ); - //if ( timeoutWait ( PIN_IEC_CLK_IN, PULLED, TIMING_Tye, false ) == TIMING_Tye ) - portDISABLE_INTERRUPTS(); - timer_start( TIMING_Tye ); - while ( IEC.status(PIN_IEC_CLK_IN) != PULLED ) - { - // INTERMISSION: EOI - // If the Ready for Data signal isn't acknowledged by the talker within 200 microseconds, the - // listener knows that the talker is trying to signal EOI. EOI, which formally - // stands for "End of Indicator," means "this character will be the last one." - // If it's a sequential disk file, don't ask for more: there will be no more. If it's - // a relative record, that's the end of the record. The character itself will still be coming, but - // the listener should note: here comes the last character. So if the listener sees the 200 microsecond - // time-out, it must signal "OK, I noticed the EOI" back to the talker, It does this - // by pulling the Data line true for at least 60 microseconds, and then releasing it. - // The talker will then revert to transmitting the character in the usual way; within 60 microseconds - // it will pull the Clock line true, and transmission will continue. At this point, the Clock - // line is true whether or not we have gone through the EOI sequence; we're back to a common - // transmission sequence. - - //IEC.pull ( PIN_IEC_SRQ ); - - if ( timer_timedout ) - { - timer_timedout = false; - IEC.flags |= EOI_RECVD; - - // Acknowledge by pull down data more than 60us - //wait ( TIMING_Th ); - IEC.pull ( PIN_IEC_DATA_OUT ); - wait ( TIMING_Tei ); - IEC.release ( PIN_IEC_DATA_OUT ); - } - - // Wait for clock line to be pulled - //timeoutWait ( PIN_IEC_CLK_IN, PULLED, TIMING_Tye, false ); - // IEC.pull( PIN_IEC_SRQ ); - // usleep( 1 ); - // IEC.release( PIN_IEC_SRQ ); - // usleep( 1 ); + if (elapsed > 100000) { + abort = 1; + break; } - timer_stop(); - portENABLE_INTERRUPTS(); - //IEC.release ( PIN_IEC_SRQ ); - - // Has ATN status changed? - if ( atn_status != IEC.status ( PIN_IEC_ATN ) ) - { - Debug_printv ( "ATN status changed!" ); - IEC.flags |= ATN_PULLED; - return 0; + } + + // STEP 3: RECEIVING THE BITS + // The talker has eight bits to send. They will go out without + // handshake; in other words, the listener had better be there to + // catch them, since the talker won't wait to hear from the listener. + // At this point, the talker controls both lines, Clock and Data. At + // the beginning of the sequence, it is holding the Clock asserted, + // while the Data line is RELEASED. the Data line will change soon, + // since we'll sendthe data over it. The eights bits will go out from + // the character one at a time, with the least significant bit going + // first. For example, if the character is the ASCII question mark, + // which is written in binary as 00011111, the ones will go out first, + // followed by the zeros. Now, for each bit, we set the Data line + // released (one) or asserted (zero) according to whether the bit is + // one or zero. As soon as that's set, the Clock line is RELEASED, + // signalling "data ready." The talker will typically have a bit in + // place and be signalling ready in 70 microseconds or less. Once the + // talker has signalled "data ready," it will hold the two lines + // steady for at least 20 microseconds timing needs to be increased to + // 60 microseconds if the Commodore 64 is listening, since the 64's + // video chip may interrupt the processor for 42 microseconds at a + // time, and without the extra wait the 64 might completely miss a + // bit. The listener plays a passive role here; it sends nothing, and + // just watches. As soon as it sees the Clock line released, it grabs + // the bit from the Data line and puts it away. It then waits for the + // clock line to be asserted, in order to prepare for the next + // bit. When the talker figures the data has been held for a + // sufficient length of time, it asserts the Clock line and releases + // the Data line. Then it starts to prepare the next bit. + for (idx = data = 0; !abort && idx < 7; idx++) { + if ((abort = waitForSignals(PIN_IEC_CLK_IN, IEC_RELEASED, 0, 0, TIMEOUT_DEFAULT))) + break; + + if (!IEC_IS_ASSERTED(PIN_IEC_DATA_IN)) + data |= 1 << idx; + + if (waitForSignals(PIN_IEC_CLK_IN, IEC_ASSERTED, 0, 0, TIMEOUT_DEFAULT)) { + if (idx < 7) + abort = 1; } - - // STEP 3: RECEIVING THE BITS - //IEC.pull ( PIN_IEC_SRQ ); - uint8_t data = receiveBits(); - //IEC.release ( PIN_IEC_SRQ ); - - // STEP 4: FRAME HANDSHAKE - // After the eighth bit has been sent, it's the listener's turn to acknowledge. At this moment, the Clock line is true - // and the Data line is false. The listener must acknowledge receiving the byte OK by pulling the Data - // line to true. The talker is now watching the Data line. If the listener doesn't pull the Data line true within - // one millisecond - one thousand microseconds - it will know that something's wrong and may alarm appropriately. - - // Acknowledge byte received - //IEC.pull ( PIN_IEC_SRQ ); - wait ( TIMING_Tf ); - IEC.pull ( PIN_IEC_DATA_OUT ); - //IEC.release ( PIN_IEC_SRQ ); - - // STEP 5: START OVER - // We're finished, and back where we started. The talker is holding the Clock line true, - // and the listener is holding the Data line true. We're ready for step 1; we may send another character - unless EOI has - // happened. If EOI was sent or received in this last transmission, both talker and listener "letgo." After a suitable pause, - // the Clock and Data lines are RELEASED to false and transmission stops. - - // Lines will be released when exiting the service loop - - //IEC.release ( PIN_IEC_SRQ ); - return data; + } + + // If there is a 218us delay before bit 7, the controller uses JiffyDOS + if (waitForSignals(PIN_IEC_CLK_IN, IEC_RELEASED, 0, 0, + TIMING_PROTOCOL_DETECT) == TIMED_OUT) { + // acknowledge we support JiffyDOS + IEC_ASSERT(PIN_IEC_DATA_OUT); + usleep(TIMING_PROTOCOL_ACK); + IEC_RELEASE(PIN_IEC_DATA_OUT); + IEC.flags |= JIFFYDOS_ACTIVE; + + abort = waitForSignals(PIN_IEC_CLK_IN, IEC_RELEASED, 0, 0, TIMEOUT_DEFAULT); + } + + if (!abort) { + // JiffyDOS check complete, Get last bit + if (!IEC_IS_ASSERTED(PIN_IEC_DATA_IN)) + data |= 1 << idx; + + waitForSignals(PIN_IEC_CLK_IN, IEC_ASSERTED, 0, 0, TIMEOUT_DEFAULT); + } + + portENABLE_INTERRUPTS(); + + // STEP 4: FRAME HANDSHAKE + // After the eighth bit has been sent, it's the listener's turn to + // acknowledge. At this moment, the Clock line is asserted and the + // Data line is released. The listener must acknowledge receiving + // the byte OK by asserting the Data line. The talker is now + // watching the Data line. If the listener doesn't assert the Data + // line within one millisecond - one thousand microseconds - it + // will know that something's wrong and may alarm appropriately. + IEC_ASSERT(PIN_IEC_DATA_OUT); + +#ifdef DYNAMIC_DELAY + transferEnd(); +#else + //usleep(TIMING_Tf); +#endif + + // STEP 5: START OVER + // We're finished, and back where we started. The talker is + // asserting the Clock line, and the listener is asserting the + // Data line. We're ready for step 1; we may send another + // character - unless EOI has happened. If EOI was sent or + // received in this last transmission, both talker and listener + // "letgo." After a suitable pause, the Clock and Data lines are + // RELEASED and transmission stops. + + if (abort) + return -1; + + return data; } - -// STEP 3: RECEIVING THE BITS -// The talker has eight bits to send. They will go out without handshake; in other words, -// the listener had better be there to catch them, since the talker won't wait to hear from the listener. At this -// point, the talker controls both lines, Clock and Data. At the beginning of the sequence, it is holding the -// Clock true, while the Data line is RELEASED to false. the Data line will change soon, since we'll sendthe data -// over it. The eights bits will go out from the character one at a time, with the least significant bit going first. -// For example, if the character is the ASCII question mark, which is written in binary as 00011111, the ones -// will go out first, followed by the zeros. Now, for each bit, we set the Data line true or false according -// to whether the bit is one or zero. As soon as that'sset, the Clock line is RELEASED to false, signalling "data ready." -// The talker will typically have a bit in place and be signalling ready in 70 microseconds or less. Once -// the talker has signalled "data ready," it will hold the two lines steady for at least 20 microseconds timing needs -// to be increased to 60 microseconds if the Commodore 64 is listening, since the 64's video chip may -// interrupt the processor for 42 microseconds at a time, and without the extra wait the 64 might completely miss a -// bit. The listener plays a passive role here; it sends nothing, and just watches. As soon as it sees the Clock line -// false, it grabs the bit from the Data line and puts it away. It then waits for the clock line to go true, in order -// to prepare for the next bit. When the talker figures the data has been held for a sufficient length of time, it -// pulls the Clock line true and releases the Data line to false. Then it starts to prepare the next bit. - -uint8_t CPBStandardSerial::receiveBits () -{ - IEC.bit = 0; - IEC.byte = 0; - - timer_start( TIMEOUT_DEFAULT ); - while ( IEC.bit < 7 ) - { - if ( timer_timedout ) - { - Debug_printv ( "Timeout bit[%d]", IEC.bit ); - IEC.flags |= ERROR; - return 0; - } - - IEC.pull( PIN_IEC_SRQ ); - usleep( 1 ); - IEC.release( PIN_IEC_SRQ ); - usleep( 1 ); - } - timer_stop(); - - // If there is a 218us delay before bit 7, the controller uses JiffyDOS - timer_start( TIMING_PROTOCOL_DETECT ); - while ( IEC.bit < 8 ) - { - // Are we in COMMAND mode? - if ( IEC.flags & ATN_PULLED ) - { - // Have we timed out? - if ( timer_timedout ) - { - // Check LISTEN & TALK - uint8_t device = (IEC.byte >> 1) & 0x1F; // LISTEN - if ( device > 30 ) - device = (IEC.byte >> 1 ) & 0x3F; // TALK - - if ( IEC.isDeviceEnabled ( device ) ) - { - // acknowledge we support JiffyDOS - IEC.pull(PIN_IEC_DATA_OUT); - wait( TIMING_PROTOCOL_ACK, false ); - IEC.release(PIN_IEC_DATA_OUT); - - IEC.flags |= JIFFYDOS_ACTIVE; - } - timer_timedout = false; - } - } - - IEC.pull( PIN_IEC_SRQ ); - usleep( 1 ); - IEC.release( PIN_IEC_SRQ ); - usleep( 1 ); - } - timer_stop(); - - // Wait for CLK to be pulled after last bit - if ( timeoutWait ( PIN_IEC_CLK_IN, PULLED, FOREVER, false ) == TIMED_OUT ) - { - Debug_printv ( "Wait for talker to finish" ); - } - - return IEC.byte; -} - - -// uint8_t CPBStandardSerial::receiveBits () -// { -// // Listening for bits -// uint8_t data = 0; -// int16_t bit_time = 0; // Used to detect JiffyDOS - -// //IEC.pull ( PIN_IEC_SRQ ); -// #ifndef IEC_SPLIT_LINES -// IEC.release(PIN_IEC_CLK_IN); -// IEC.release(PIN_IEC_DATA_IN); // Set DATA IN back to input -// #endif - -// uint8_t n = 0; -// for ( n = 0; n < 8; n++ ) -// { -// // Time the release of the clock line to detect JiffyDOS -// //IEC.pull ( PIN_IEC_SRQ ); -// //bit_time = timeoutWait ( PIN_IEC_CLK_IN, RELEASED, TIMING_PROTOCOL_DETECT, false ); -// while ( IEC.status(PIN_IEC_CLK_IN) != RELEASED ); -// //IEC.pull ( PIN_IEC_SRQ ); -// //IEC.release ( PIN_IEC_SRQ ); - -// #ifdef JIFFYDOS -// // If there is a 218us delay before bit 7, the controller uses JiffyDOS -// if ( bit_time >= TIMING_PROTOCOL_DETECT ) -// { -// //IEC.pull ( PIN_IEC_SRQ ); -// if ( n == 7 && IEC.status( PIN_IEC_ATN ) ) -// { -// // Check LISTEN & TALK -// uint8_t device = (data >> 1) & 0x1F; // LISTEN -// if ( device > 30 ) -// device = (data >> 1 ) & 0x3F; // TALK - -// if ( device < 31 ) -// { -// if ( IEC.isDeviceEnabled ( device ) ) -// { -// // acknowledge we support JiffyDOS -// IEC.pull(PIN_IEC_DATA_OUT); -// wait( TIMING_PROTOCOL_ACK, false ); -// IEC.release(PIN_IEC_DATA_OUT); - -// IEC.flags |= JIFFYDOS_ACTIVE; -// } -// } -// } -// //IEC.release ( PIN_IEC_SRQ ); - -// // wait for bit to be ready to read -// //IEC.pull ( PIN_IEC_SRQ ); -// if ( timeoutWait ( PIN_IEC_CLK_IN, RELEASED ) == TIMED_OUT ) -// { -// if ( n == 0 ) -// { -// Debug_printv ( "empty stream signaled" ); -// IEC.flags |= EMPTY_STREAM; -// } -// else -// { -// Debug_printv ( "bit %d timeout", n ); -// IEC.flags |= ERROR; -// } -// return data; -// } -// //IEC.release ( PIN_IEC_SRQ ); -// } -// #endif - -// // get bit -// data >>= 1; -// if ( !IEC.status ( PIN_IEC_DATA_IN ) ) data |= 0x80; -// //IEC.release ( PIN_IEC_SRQ ); - -// // wait for talker to finish sending bit -// // if ( timeoutWait ( PIN_IEC_CLK_IN, PULLED ) == TIMED_OUT ) -// // { -// // Debug_printv ( "wait for talker to finish sending bit n[%d]", n ); -// // IEC.flags |= ERROR; -// // return data; // return error because timeout -// // } -// while ( IEC.status(PIN_IEC_CLK_IN) != PULLED ); -// IEC.release ( PIN_IEC_SRQ ); -// } -// //IEC.release ( PIN_IEC_SRQ ); - -// return data; -// } - - // STEP 1: READY TO SEND -// Sooner or later, the talker will want to talk, and send a character. -// When it's ready to go, it releases the Clock line to false. This signal change might be -// translated as "I'm ready to send a character." The listener must detect this and respond, -// but it doesn't have to do so immediately. The listener will respond to the talker's -// "ready to send" signal whenever it likes; it can wait a long time. If it's -// a printer chugging out a line of print, or a disk drive with a formatting job in progress, -// it might holdback for quite a while; there's no time limit. +// Sooner or later, the talker will want to talk, and send a +// character. When it's ready to go, it releases the Clock line. This +// signal change might be translated as "I'm ready to send a +// character." The listener must detect this and respond, but it +// doesn't have to do so immediately. The listener will respond to the +// talker's "ready to send" signal whenever it likes; it can wait a +// long time. If it's a printer chugging out a line of print, or a +// disk drive with a formatting job in progress, it might holdback for +// quite a while; there's no time limit. bool CPBStandardSerial::sendByte(uint8_t data, bool eoi) { - //IEC.pull ( PIN_IEC_SRQ ); - - IEC.flags &= CLEAR_LOW; - - // Say we're ready - IEC.release ( PIN_IEC_CLK_OUT ); - - // Wait for listener to be ready - // STEP 2: READY FOR DATA - // When the listener is ready to listen, it releases the Data - // line to false. Suppose there is more than one listener. The Data line will go false - // only when all listeners have RELEASED it - in other words, when all listeners are ready - // to accept data. - //IEC.pull ( PIN_IEC_SRQ ); - if ( timeoutWait ( PIN_IEC_DATA_IN, RELEASED, FOREVER ) == TIMED_OUT ) - { - if ( !(IEC.flags & ATN_PULLED) ) - { - Debug_printv ( "Wait for listener to be ready [%02X]", data ); - IEC.flags |= ERROR; - } - - return false; // return error because of ATN or timeout - } - //IEC.release ( PIN_IEC_SRQ ); - - // What happens next is variable. Either the talker will pull the - // Clock line back to true in less than 200 microseconds - usually within 60 microseconds - or it - // will do nothing. The listener should be watching, and if 200 microseconds pass - // without the Clock line going to true, it has a special task to perform: note EOI. - if ( eoi ) - { - // INTERMISSION: EOI - // If the Ready for Data signal isn't acknowledged by the talker within 200 microseconds, the - // listener knows that the talker is trying to signal EOI. EOI, which formally - // stands for "End of Indicator," means "this character will be the last one." - // If it's a sequential disk file, don't ask for more: there will be no more. If it's - // a relative record, that's the end of the record. The character itself will still be coming, but - // the listener should note: here comes the last character. So if the listener sees the 200 microsecond - // time-out, it must signal "OK, I noticed the EOI" back to the talker, It does this - // by pulling the Data line true for at least 60 microseconds, and then releasing it. - // The talker will then revert to transmitting the character in the usual way; within 60 microseconds - // it will pull the Clock line true, and transmission will continue. At this point, the Clock - // line is true whether or not we have gone through the EOI sequence; we're back to a common - // transmission sequence. - - // Wait for EOI ACK - // This will happen after appx 250us - if ( timeoutWait ( PIN_IEC_DATA_IN, PULLED ) == TIMED_OUT ) - { - Debug_printv ( "EOI ACK: Listener didn't PULL DATA [%02X]", data ); - IEC.flags |= ERROR; - return false; // return error because timeout - } - - // Sender ACK? - // 1541 release CLK in the middle of the EOI ACK - usleep ( TIMING_Tpr ); - IEC.pull ( PIN_IEC_CLK_OUT ); - - if ( timeoutWait ( PIN_IEC_DATA_IN, RELEASED ) == TIMED_OUT ) - { - Debug_printv ( "EOI ACK: Listener didn't RELEASE DATA [%02X]", data ); - IEC.flags |= ERROR; - return false; // return error because timeout - } + int len; + int abort = 0; + uint8_t Tv = TIMING_Tv64; + + + if (0) //(IEC.vic20_mode) + Tv = TIMING_Tv; + + if (IEC_IS_ASSERTED(PIN_IEC_ATN) || IEC._state > BUS_IDLE) { + Debug_printv("Abort"); + return 0; + } + + //IEC_ASSERT(PIN_IEC_SRQ);//Debug + gpio_intr_disable(PIN_IEC_CLK_IN); + portDISABLE_INTERRUPTS(); + +#ifdef DYNAMIC_DELAY + transferDelaySinceLast(TIMING_Tbb); +#endif + + IEC_RELEASE(PIN_IEC_CLK_OUT); + + // STEP 2: READY FOR DATA + // When the listener is ready to listen, it releases the Data + // line. Suppose there is more than one listener. The Data line + // will be released only when ALL listeners have RELEASED it - in + // other words, when all listeners are ready to accept data. + // IEC_ASSERT( PIN_IEC_SRQ ); + + // FIXME - Can't wait FOREVER because watchdog will get + // mad. Probably need to configure DATA GPIO with POSEDGE + // interrupt and not do portDISABLE_INTERRUPTS(). Without + // interrupts disabled though there is a big risk of false + // EOI being sent. Maybe the DATA ISR needs to handle EOI + // signaling too? + + if ((abort = waitForSignals(PIN_IEC_DATA_IN, IEC_RELEASED, PIN_IEC_ATN, IEC_ASSERTED, FOREVER))) { + Debug_printv("data released abort"); + } + + /* Because interrupts are disabled it's possible to miss the ATN pause signal */ + if (IEC_IS_ASSERTED(PIN_IEC_ATN)) { + abort = 1; + Debug_printv("ATN abort"); + } + + // What happens next is variable. Either the talker will assert + // the Clock line in less than 200 microseconds - usually within + // 60 microseconds - or it will do nothing. The listener should be + // watching, and if 200 microseconds pass without the Clock line + // being asserted, it has a special task to perform: note EOI. + if (!abort && eoi) { + // INTERMISSION: EOI + // If the Ready for Data signal isn't acknowledged by the + // talker within 200 microseconds, the listener knows that the + // talker is trying to signal EOI. EOI, which formally stands + // for "End of Indicator," means "this character will be the + // last one." If it's a sequential disk file, don't ask for + // more: there will be no more. If it's a relative record, + // that's the end of the record. The character itself will + // still be coming, but the listener should note: here comes + // the last character. So if the listener sees the 200 + // microsecond time-out, it must signal "OK, I noticed the + // EOI" back to the talker, It does this by asserting the Data + // line for at least 60 microseconds, and then releasing + // it. The talker will then revert to transmitting the + // character in the usual way; within 60 microseconds it will + // assert the Clock line, and transmission will continue. At + // this point, the Clock line is asserted whether or not we + // have gone through the EOI sequence; we're back to a common + // transmission sequence. + + // Wait for EOI ACK + // This will happen after appx 250us + if ((abort = waitForSignals(PIN_IEC_DATA_IN, IEC_ASSERTED, PIN_IEC_ATN, IEC_ASSERTED, TIMEOUT_Tf))) { + Debug_printv("EOI ack abort"); } - // *** IMPORTANT!!! - // Delay before sending bits - // ATN might get pulled here - if ( !wait ( TIMING_Tne, true ) ) return false; - IEC.pull ( PIN_IEC_CLK_OUT ); - usleep ( TIMING_Tna ); - - // STEP 3: SENDING THE BITS - //IEC.pull ( PIN_IEC_SRQ ); - sendBits( data ); - //IEC.release ( PIN_IEC_SRQ ); - - // STEP 4: FRAME HANDSHAKE - // After the eighth bit has been sent, it's the listener's turn to acknowledge. At this moment, the Clock line is true - // and the Data line is false. The listener must acknowledge receiving the byte OK by pulling the Data - // line to true. The talker is now watching the Data line. If the listener doesn't pull the Data line true within - // one millisecond - one thousand microseconds - it will know that something's wrong and may alarm appropriately. - - // Wait for listener to accept data - //IEC.pull ( PIN_IEC_SRQ ); - if ( timeoutWait ( PIN_IEC_DATA_IN, PULLED, TIMEOUT_Tf ) == TIMEOUT_Tf ) - { - // RECIEVER TIMEOUT - // If no receiver pulls DATA within 1000 µs at the end of the transmission of a byte (after step 28), a receiver timeout is raised. - Debug_printv ( "Wait for listener to acknowledge byte received (pull data) [%02X]", data ); - Debug_printv ( "RECEIVER TIMEOUT" ); - IEC.flags |= ERROR; - //IEC.release ( PIN_IEC_SRQ ); - return false; // return error because timeout + if (!abort && + (abort = waitForSignals(PIN_IEC_DATA_IN, IEC_RELEASED, PIN_IEC_ATN, IEC_ASSERTED, TIMEOUT_Tne))) { + Debug_printv("EOI ackack abort"); } - //IEC.release ( PIN_IEC_SRQ ); - //IEC.pull ( PIN_IEC_SRQ ); - // timer_start( TIMEOUT_Tf ); - // while ( IEC.status ( PIN_IEC_DATA_IN ) != PULLED ) - // { - // if ( timer_timedout ) - // { - // // RECIEVER TIMEOUT - // // If no receiver pulls DATA within 1000 µs at the end of the transmission of a byte (after step 28), a receiver timeout is raised. - // Debug_printv ( "Wait for listener to acknowledge byte received (pull data) [%02X]", data ); - // Debug_printv ( "RECEIVER TIMEOUT" ); - // IEC.flags |= ERROR; - // //IEC.release ( PIN_IEC_SRQ ); - // return false; // return error because timeout - // } - // } - //IEC.release ( PIN_IEC_SRQ ); - - // STEP 5: START OVER - // We're finished, and back where we started. The talker is holding the Clock line true, - // and the listener is holding the Data line true. We're ready for step 1; we may send another character - unless EOI has - // happened. If EOI was sent or received in this last transmission, both talker and listener "letgo." After a suitable pause, - // the Clock and Data lines are RELEASED to false and transmission stops. - - // Lines will be released when exiting the service loop - usleep ( TIMING_Tbb ); - - return true; -} + } -// STEP 3: SENDING THE BITS -// The talker has eight bits to send. They will go out without handshake; in other words, -// the listener had better be there to catch them, since the talker won't wait to hear from the listener. At this -// point, the talker controls both lines, Clock and Data. At the beginning of the sequence, it is holding the -// Clock true, while the Data line is RELEASED to false. the Data line will change soon, since we'll sendthe data -// over it. The eights bits will go out from the character one at a time, with the least significant bit going first. -// For example, if the character is the ASCII question mark, which is written in binary as 00011111, the ones -// will go out first, followed by the zeros. Now, for each bit, we set the Data line true or false according -// to whether the bit is one or zero. As soon as that'sset, the Clock line is RELEASED to false, signalling "data ready." -// The talker will typically have a bit in place and be signalling ready in 70 microseconds or less. Once -// the talker has signalled "data ready," it will hold the two lines steady for at least 20 microseconds timing needs -// to be increased to 60 microseconds if the Commodore 64 is listening, since the 64's video chip may -// interrupt the processor for 42 microseconds at a time, and without the extra wait the 64 might completely miss a -// bit. The listener plays a passive role here; it sends nothing, and just watches. As soon as it sees the Clock line -// false, it grabs the bit from the Data line and puts it away. It then waits for the clock line to go true, in order -// to prepare for the next bit. When the talker figures the data has been held for a sufficient length of time, it -// pulls the Clock line true and releases the Data line to false. Then it starts to prepare the next bit. - - -// bool CPBStandardSerial::sendBits ( uint8_t data ) -// { -// // uint8_t b = 255; -// // timer_start( TIMEOUT_DEFAULT ); -// // IEC.pull ( PIN_IEC_SRQ ); -// IEC.bit = 0; -// IEC.byte = data; - -// esp_timer_start_once(timer_send_h, 5); -// while ( IEC.bit < 8 ) -// { -// // if ( timer_timedout ) -// // { -// // IEC.flags |= ERROR; -// // return false; -// // } - -// usleep( 10 ); -// } - -// // IEC.release ( PIN_IEC_SRQ ); -// esp_intr_enable_source( PIN_IEC_CLK_IN ); -// return true; -// } - -bool CPBStandardSerial::sendBits ( uint8_t data ) -{ - uint8_t Tv = TIMING_Tv64; // C64 data valid timing + IEC_ASSERT(PIN_IEC_CLK_OUT); + usleep(TIMING_Tpr); - // We can send faster if in VIC20 Mode - if ( IEC.vic20_mode ) - { - Tv = TIMING_Tv; // VIC-20 data valid timing + // STEP 3: SENDING THE BITS + for (len = 0; !abort && len < 8; len++, data >>= 1) { + if (IEC_IS_ASSERTED(PIN_IEC_ATN)) { + Debug_printv("ATN 2 abort"); + abort = 1; + break; } - // Send bits - for ( uint8_t n = 0; n < 8; n++ ) - { - // set bit - usleep ( TIMING_Ts0 ); - ( data & 1 ) ? IEC.release ( PIN_IEC_DATA_OUT ) : IEC.pull ( PIN_IEC_DATA_OUT ); - data >>= 1; // shift to next bit - usleep ( TIMING_Ts1 ); - - // tell listener bit is ready to read - IEC.release ( PIN_IEC_CLK_OUT ); - usleep ( Tv ); - - // tell listener to wait for next bit - IEC.pull ( PIN_IEC_CLK_OUT ); - - // Release DATA after bit sent - IEC.release ( PIN_IEC_DATA_OUT ); + if (data & 1) + IEC_RELEASE(PIN_IEC_DATA_OUT); + else + IEC_ASSERT(PIN_IEC_DATA_OUT); + + usleep(TIMING_Ts0 + TIMING_Ts1); + IEC_RELEASE(PIN_IEC_CLK_OUT); + usleep(Tv); + IEC_RELEASE(PIN_IEC_DATA_OUT); + IEC_ASSERT(PIN_IEC_CLK_OUT); + } + + // STEP 4: FRAME HANDSHAKE + // After the eighth bit has been sent, it's the listener's turn to + // acknowledge. At this moment, the Clock line is asserted and the + // Data line is released. The listener must acknowledge receiving + // the byte OK by asserting the Data line. The talker is now + // watching the Data line. If the listener doesn't assert the Data + // line within one millisecond - one thousand microseconds - it + // will know that something's wrong and may alarm appropriately. + + // Wait for listener to accept data + if (!abort && + (abort = waitForSignals(PIN_IEC_DATA_IN, IEC_ASSERTED, PIN_IEC_ATN, IEC_ASSERTED, TIMEOUT_Tf))) { + // RECIEVER TIMEOUT + // If no receiver asserts DATA within 1000 µs at the end of + // the transmission of a byte (after step 28), a receiver + // timeout is raised. + if (!IEC_IS_ASSERTED(PIN_IEC_ATN)) { + abort = 0; } - - // Release DATA after byte sent - //IEC.release ( PIN_IEC_DATA_OUT ); - - return true; -} // sendBits + else { + IEC_SET_STATE(BUS_IDLE); + } + } + portENABLE_INTERRUPTS(); + gpio_intr_enable(PIN_IEC_CLK_IN); + //IEC_RELEASE(PIN_IEC_SRQ);//Debug + +#ifdef DYNAMIC_DELAY + transferEnd(); +#else + //usleep(TIMING_Tbb); +#endif + + // STEP 5: START OVER + // We're finished, and back where we started. The talker is + // asserting the Clock line, and the listener is asserting the + // Data line. We're ready for step 1; we may send another + // character - unless EOI has happened. If EOI was sent or + // received in this last transmission, both talker and listener + // "letgo." After a suitable pause, the Clock and Data lines are + // RELEASED and transmission stops. + + if (abort && IEC_IS_ASSERTED(PIN_IEC_ATN)) + IEC_RELEASE(PIN_IEC_CLK_OUT); + + return !abort; +} #endif // BUILD_IEC diff --git a/lib/bus/iec/protocol/cpbstandardserial.h b/lib/bus/iec/protocol/cpbstandardserial.h index ea59998eb..2946628bc 100644 --- a/lib/bus/iec/protocol/cpbstandardserial.h +++ b/lib/bus/iec/protocol/cpbstandardserial.h @@ -33,28 +33,8 @@ namespace Protocol { class CPBStandardSerial : public IECProtocol { - private: - uint8_t receiveBits(); - bool sendBits(uint8_t data); - - public: - /** - * ESP timer handle for the Interrupt rate limiting timer - */ - esp_timer_handle_t timer_send_h = nullptr; - - /** - * @brief ctor - */ - CPBStandardSerial(); - - /** - * @brief dtor - */ - virtual ~CPBStandardSerial(); - /** * @brief receive byte from bus * @return The byte received from bus. @@ -71,4 +51,4 @@ namespace Protocol }; }; -#endif // PROTOCOL_CPBSTANDARDSERIAL_H \ No newline at end of file +#endif // PROTOCOL_CPBSTANDARDSERIAL_H diff --git a/lib/bus/iec/protocol/jiffydos.cpp b/lib/bus/iec/protocol/jiffydos.cpp index a9b609f60..7ae0758f4 100644 --- a/lib/bus/iec/protocol/jiffydos.cpp +++ b/lib/bus/iec/protocol/jiffydos.cpp @@ -52,9 +52,9 @@ JiffyDOS::JiffyDOS() { esp_timer_create_args_t args = { .callback = onTimer, .arg = this, - .dispatch_method = ESP_TIMER_TASK, + .dispatch_method = ESP_TIMER_TASK, .name = nullptr, - .skip_unhandled_events = 0, + .skip_unhandled_events = 0, }; esp_timer_create(&args, &timer_handle); }; @@ -73,56 +73,56 @@ uint8_t JiffyDOS::receiveByte () // Release the Data line to signal we are ready #ifndef IEC_SPLIT_LINE - //IEC.release(PIN_IEC_CLK_IN); - IEC.release(PIN_IEC_DATA_IN); + //IEC_RELEASE(PIN_IEC_CLK_IN); + IEC_RELEASE(PIN_IEC_DATA_IN); #endif // Wait for talker ready - while ( IEC.status( PIN_IEC_CLK_IN ) == PULLED ); + while ( IEC_IS_ASSERTED( PIN_IEC_CLK_IN ) ); // RECEIVING THE BITS // As soon as the talker releases the Clock line we are expected to receive the bits - // Bits are inverted so use IEC.status() to get pulled/released status + // Bits are inverted so use IEC_IS_ASSERTED() to get asserted/released status - //IEC.pull ( PIN_IEC_SRQ ); + //IEC_ASSERT( PIN_IEC_SRQ ); // get bits 4,5 usleep ( bit_pair_timing[0][0] ); // Includes setup delay - if ( IEC.status ( PIN_IEC_CLK_IN ) ) data |= 0b00010000; // 0 - if ( IEC.status ( PIN_IEC_DATA_IN ) ) data |= 0b00100000; // 1 - IEC.pull ( PIN_IEC_SRQ ); + if ( IEC_IS_ASSERTED( PIN_IEC_CLK_IN ) ) data |= 0b00010000; // 0 + if ( IEC_IS_ASSERTED( PIN_IEC_DATA_IN ) ) data |= 0b00100000; // 1 + IEC_ASSERT( PIN_IEC_SRQ ); // get bits 6,7 usleep ( bit_pair_timing[0][1] ); - if ( IEC.status ( PIN_IEC_CLK_IN ) ) data |= 0b01000000; // 0 - if ( IEC.status ( PIN_IEC_DATA_IN ) ) data |= 0b10000000; // 0 - IEC.release ( PIN_IEC_SRQ ); + if ( IEC_IS_ASSERTED( PIN_IEC_CLK_IN ) ) data |= 0b01000000; // 0 + if ( IEC_IS_ASSERTED( PIN_IEC_DATA_IN ) ) data |= 0b10000000; // 0 + IEC_RELEASE( PIN_IEC_SRQ ); // get bits 3,1 usleep ( bit_pair_timing[0][2] ); - if ( IEC.status ( PIN_IEC_CLK_IN ) ) data |= 0b00001000; // 0 - if ( IEC.status ( PIN_IEC_DATA_IN ) ) data |= 0b00000010; // 0 - IEC.pull ( PIN_IEC_SRQ ); + if ( IEC_IS_ASSERTED( PIN_IEC_CLK_IN ) ) data |= 0b00001000; // 0 + if ( IEC_IS_ASSERTED( PIN_IEC_DATA_IN ) ) data |= 0b00000010; // 0 + IEC_ASSERT( PIN_IEC_SRQ ); // get bits 2,0 usleep ( bit_pair_timing[0][3] ); - if ( IEC.status ( PIN_IEC_CLK_IN ) ) data |= 0b00000100; // 1 - if ( IEC.status ( PIN_IEC_DATA_IN ) ) data |= 0b00000001; // 0 - IEC.release ( PIN_IEC_SRQ ); + if ( IEC_IS_ASSERTED( PIN_IEC_CLK_IN ) ) data |= 0b00000100; // 1 + if ( IEC_IS_ASSERTED( PIN_IEC_DATA_IN ) ) data |= 0b00000001; // 0 + IEC_RELEASE( PIN_IEC_SRQ ); // Check CLK for EOI usleep ( 12 ); - if ( IEC.status ( PIN_IEC_CLK_IN ) ) + if ( IEC_IS_ASSERTED( PIN_IEC_CLK_IN ) ) IEC.flags |= EOI_RECVD; - IEC.pull ( PIN_IEC_SRQ ); + IEC_ASSERT( PIN_IEC_SRQ ); // Acknowledge byte received // If we want to indicate an error we can release DATA usleep ( 60 ); - IEC.pull ( PIN_IEC_DATA_OUT ); + IEC_ASSERT( PIN_IEC_DATA_OUT ); usleep ( 10 ); - //IEC.release ( PIN_IEC_SRQ ); + //IEC_RELEASE( PIN_IEC_SRQ ); //Debug_printv("data[%02X] eoi[%d]", data, eoi); // $ = 0x24 @@ -144,58 +144,58 @@ bool JiffyDOS::sendByte ( uint8_t data, bool signalEOI ) // Release the Data line to signal we are ready #ifndef IEC_SPLIT_LINE - IEC.release(PIN_IEC_CLK_IN); - IEC.release(PIN_IEC_DATA_IN); + IEC_RELEASE(PIN_IEC_CLK_IN); + IEC_RELEASE(PIN_IEC_DATA_IN); #endif // Wait for listener ready - while ( IEC.status( PIN_IEC_DATA_IN ) == PULLED ); + while ( IEC_IS_ASSERTED( PIN_IEC_DATA_IN ) ); // STEP 2: SENDING THE BITS // As soon as the listener releases the DATA line we are expected to send the bits - // Bits are inverted so use IEC.status() to get pulled/released status + // Bits are inverted so use IEC_IS_ASSERTED() to get asserted/released status - IEC.pull ( PIN_IEC_SRQ ); + IEC_ASSERT( PIN_IEC_SRQ ); // Start timer usleep ( bit_pair_timing[1][0] ); // set bits 0,1 - //IEC.pull ( PIN_IEC_SRQ ); - ( data & 1 ) ? IEC.release ( PIN_IEC_CLK_OUT ) : IEC.pull ( PIN_IEC_CLK_OUT ); + //IEC_ASSERT( PIN_IEC_SRQ ); + ( data & 1 ) ? IEC_RELEASE( PIN_IEC_CLK_OUT ) : IEC_ASSERT( PIN_IEC_CLK_OUT ); data >>= 1; // shift to next bit - ( data & 1 ) ? IEC.release ( PIN_IEC_DATA_OUT ) : IEC.pull ( PIN_IEC_DATA_OUT ); + ( data & 1 ) ? IEC_RELEASE( PIN_IEC_DATA_OUT ) : IEC_ASSERT( PIN_IEC_DATA_OUT ); usleep ( bit_pair_timing[1][1] ); // set bits 2,3 data >>= 1; // shift to next bit - ( data & 1 ) ? IEC.release ( PIN_IEC_CLK_OUT ) : IEC.pull ( PIN_IEC_CLK_OUT ); + ( data & 1 ) ? IEC_RELEASE( PIN_IEC_CLK_OUT ) : IEC_ASSERT( PIN_IEC_CLK_OUT ); data >>= 1; // shift to next bit - ( data & 1 ) ? IEC.release ( PIN_IEC_DATA_OUT ) : IEC.pull ( PIN_IEC_DATA_OUT ); + ( data & 1 ) ? IEC_RELEASE( PIN_IEC_DATA_OUT ) : IEC_ASSERT( PIN_IEC_DATA_OUT ); usleep ( bit_pair_timing[1][2] ); // set bits 4,5 data >>= 1; // shift to next bit - ( data & 1 ) ? IEC.release ( PIN_IEC_CLK_OUT ) : IEC.pull ( PIN_IEC_CLK_OUT ); + ( data & 1 ) ? IEC_RELEASE( PIN_IEC_CLK_OUT ) : IEC_ASSERT( PIN_IEC_CLK_OUT ); data >>= 1; // shift to next bit - ( data & 1 ) ? IEC.release ( PIN_IEC_DATA_OUT ) : IEC.pull ( PIN_IEC_DATA_OUT ); + ( data & 1 ) ? IEC_RELEASE( PIN_IEC_DATA_OUT ) : IEC_ASSERT( PIN_IEC_DATA_OUT ); usleep ( bit_pair_timing[1][3] ); // set bits 6,7 data >>= 1; // shift to next bit - ( data & 1 ) ? IEC.release ( PIN_IEC_CLK_OUT ) : IEC.pull ( PIN_IEC_CLK_OUT ); + ( data & 1 ) ? IEC_RELEASE( PIN_IEC_CLK_OUT ) : IEC_ASSERT( PIN_IEC_CLK_OUT ); data >>= 1; // shift to next bit - ( data & 1 ) ? IEC.release ( PIN_IEC_DATA_OUT ) : IEC.pull ( PIN_IEC_DATA_OUT ); + ( data & 1 ) ? IEC_RELEASE( PIN_IEC_DATA_OUT ) : IEC_ASSERT( PIN_IEC_DATA_OUT ); usleep ( bit_pair_timing[1][4] ); // Check CLK for EOI - ( signalEOI ) ? IEC.pull ( PIN_IEC_CLK_OUT ) : IEC.release ( PIN_IEC_CLK_OUT ); + ( signalEOI ) ? IEC_ASSERT( PIN_IEC_CLK_OUT ) : IEC_RELEASE( PIN_IEC_CLK_OUT ); usleep ( 13 ); - //IEC.release ( PIN_IEC_SRQ ); + //IEC_RELEASE( PIN_IEC_SRQ ); // Acknowledge byte received // If we want to indicate an error we can release DATA -// bool error = IEC.status ( PIN_IEC_DATA_IN ); +// bool error = IEC_IS_ASSERTED( PIN_IEC_DATA_IN ); return true; } // sendByte diff --git a/lib/device/iec/clock.cpp b/lib/device/iec/clock.cpp index d1ef01194..4b1d6564a 100644 --- a/lib/device/iec/clock.cpp +++ b/lib/device/iec/clock.cpp @@ -25,6 +25,31 @@ void iecClock::set_timestamp_format(std::string s) tf = s; } +#if 1 +device_state_t iecClock::openChannel(/*int chan, IECPayload &payload*/) +{ + iec_open(); + return state; +} + +device_state_t iecClock::closeChannel(/*int chan*/) +{ + iec_close(); + return state; +} + +device_state_t iecClock::readChannel(/*int chan*/) +{ + iec_reopen(); + return state; +} + +device_state_t iecClock::writeChannel(/*int chan, IECPayload &payload*/) +{ + iec_reopen(); + return state; +} +#else device_state_t iecClock::process() { virtualDevice::process(); @@ -46,6 +71,7 @@ device_state_t iecClock::process() return state; } +#endif void iecClock::iec_open() { @@ -116,4 +142,4 @@ void iecClock::iec_reopen_talk() IEC.sendBytes(s, true); } -#endif /* BUILD_IEC */ \ No newline at end of file +#endif /* BUILD_IEC */ diff --git a/lib/device/iec/clock.h b/lib/device/iec/clock.h index aaf5d8c52..47ca01621 100644 --- a/lib/device/iec/clock.h +++ b/lib/device/iec/clock.h @@ -8,16 +8,24 @@ class iecClock : public virtualDevice { private: - + time_t ts; std::string tf; +protected: + virtual device_state_t openChannel(/*int chan, IECPayload &payload*/) override; + virtual device_state_t closeChannel(/*int chan*/) override; + virtual device_state_t readChannel(/*int chan*/) override; + virtual device_state_t writeChannel(/*int chan, IECPayload &payload*/) override; + public: iecClock(); ~iecClock(); +#if 0 device_state_t process(); +#endif void iec_open(); void iec_close(); @@ -30,4 +38,4 @@ class iecClock : public virtualDevice }; -#endif /* IECCLOCK_H */ \ No newline at end of file +#endif /* IECCLOCK_H */ diff --git a/lib/device/iec/cpm.cpp b/lib/device/iec/cpm.cpp index 0b0f658b1..8acd505c1 100644 --- a/lib/device/iec/cpm.cpp +++ b/lib/device/iec/cpm.cpp @@ -120,6 +120,7 @@ void iecCpm::iec_reopen_listen() return; } +#if 0 while (!(IEC.flags & EOI_RECVD)) { int16_t b = IEC.receiveByte(); @@ -132,6 +133,9 @@ void iecCpm::iec_reopen_listen() xQueueSend(txq,&b,portMAX_DELAY); } +#else +#warning FIXME - use payload +#endif } void iecCpm::iec_reopen() @@ -147,6 +151,31 @@ void iecCpm::iec_reopen() } } +#if 1 +device_state_t iecCpm::openChannel(/*int chan, IECPayload &payload*/) +{ + iec_open(); + return state; +} + +device_state_t iecCpm::closeChannel(/*int chan*/) +{ + iec_close(); + return state; +} + +device_state_t iecCpm::readChannel(/*int chan*/) +{ + iec_reopen(); + return state; +} + +device_state_t iecCpm::writeChannel(/*int chan, IECPayload &payload*/) +{ + iec_reopen(); + return state; +} +#else device_state_t iecCpm::process() { // Call base class @@ -169,5 +198,6 @@ device_state_t iecCpm::process() return state; } +#endif -#endif /* BUILD_IEC */ \ No newline at end of file +#endif /* BUILD_IEC */ diff --git a/lib/device/iec/cpm.h b/lib/device/iec/cpm.h index e40f15274..3474a283b 100644 --- a/lib/device/iec/cpm.h +++ b/lib/device/iec/cpm.h @@ -24,13 +24,19 @@ class iecCpm : public virtualDevice */ ~iecCpm(); +#if 0 /** * @brief Process command fanned out from bus * @return new device state */ device_state_t process() override; +#endif protected: + virtual device_state_t openChannel(/*int chan, IECPayload &payload*/) override; + virtual device_state_t closeChannel(/*int chan*/) override; + virtual device_state_t readChannel(/*int chan*/) override; + virtual device_state_t writeChannel(/*int chan, IECPayload &payload*/) override; private: @@ -45,4 +51,4 @@ class iecCpm : public virtualDevice void iec_reopen_listen(); }; -#endif /* IECCPM_H */ \ No newline at end of file +#endif /* IECCPM_H */ diff --git a/lib/device/iec/drive.cpp b/lib/device/iec/drive.cpp index 0b58da58a..31905880a 100644 --- a/lib/device/iec/drive.cpp +++ b/lib/device/iec/drive.cpp @@ -94,7 +94,56 @@ bool iecDrive::write_blank(FILE *f, uint16_t sectorSize, uint16_t numSectors) return false; } +#if 1 +device_state_t iecDrive::openChannel(/*int chan, IECPayload &payload*/) +{ + if (commanddata.channel == CHANNEL_COMMAND) + iec_command(); + else + iec_open(); + + return state; +} + +device_state_t iecDrive::closeChannel(/*int chan*/) +{ + if (_base == nullptr) { + IEC.senderTimeout(); + return state; + } + + closeStream(commanddata.channel); + return state; +} +device_state_t iecDrive::readChannel(/*int chan*/) +{ + if (commanddata.channel == CHANNEL_COMMAND) + iec_talk_command_buffer_status(); + else { + Debug_printv( "_base[%s] _last_file[%s]", _base->url.c_str(), _last_file.c_str() ); + + if (commanddata.channel == CHANNEL_LOAD && _base->isDirectory()) + sendListing(); + else + sendFile(); + } + + return state; +} + +device_state_t iecDrive::writeChannel(/*int chan, IECPayload &payload*/) +{ + if (_base == nullptr) { + IEC.senderTimeout(); + return state; + } + Debug_printv("url[%s]", _base->url.c_str()); + + saveFile(); + return state; +} +#else // Process command device_state_t iecDrive::process() { @@ -122,6 +171,7 @@ device_state_t iecDrive::process() // Debug_printv("url[%s] file[%s] state[%d]", _base->url.c_str(), _last_file.c_str(), state); return state; } +#endif void iecDrive::process_load() { @@ -177,6 +227,7 @@ void iecDrive::process_command() } } +#if 0 void iecDrive::process_channel() { //Debug_printv("secondary[%.2X]", commanddata.secondary); @@ -195,7 +246,7 @@ void iecDrive::process_channel() break; } } - +#endif void iecDrive::iec_open() { @@ -302,6 +353,7 @@ void iecDrive::iec_reopen_save() saveFile(); } +#if 0 void iecDrive::iec_reopen_channel() { //Debug_printv("primary[%.2X]", commanddata.primary); @@ -316,12 +368,12 @@ void iecDrive::iec_reopen_channel() } } - void iecDrive::iec_reopen_channel_listen() { std::string s = IEC.receiveBytes(); Debug_printv("{%s}", s.c_str() ); } +#endif void iecDrive::iec_reopen_channel_talk() { @@ -767,7 +819,8 @@ uint16_t iecDrive::sendLine(uint16_t blocks, const char *format, ...) { // Debug_printv("bus[%d]", IEC.state); - // Exit if ATN is PULLED while sending +#if 0 + // Exit if ATN is ASSERTED while sending // Exit if there is an error while sending if ( IEC.state == BUS_ERROR ) { @@ -776,6 +829,7 @@ uint16_t iecDrive::sendLine(uint16_t blocks, const char *format, ...) //setDeviceStatus(74); return 0; } +#endif // Format our string va_list args; @@ -791,36 +845,26 @@ uint16_t iecDrive::sendLine(uint16_t blocks, char *text) { Serial.printf("%d %s ", blocks, text); - // Exit if ATN is PULLED while sending - // Exit if there is an error while sending - if ( IEC.flags & ERROR ) {Debug_printv("line[%s]", text); return 0;}; - // Get text length uint8_t len = strlen(text); // Send that pointer // No basic line pointer is used in the directory listing set to 0x0101 - IEC.sendByte(0x01); // IEC.sendByte(basicPtr bitand 0xFF); - if ( IEC.flags & ERROR ) {Debug_printv("line[%s]", text); return 0;}; - IEC.sendByte(0x01); // IEC.sendByte(basicPtr >> 8); - if ( IEC.flags & ERROR ) {Debug_printv("line[%s]", text); return 0;}; + if (!IEC.sendByte(0x01)) {Debug_printv("line[%s]", text); return 0;}; + if (!IEC.sendByte(0x01)) {Debug_printv("line[%s]", text); return 0;}; // Send blocks - IEC.sendByte(blocks bitand 0xFF); - if ( IEC.flags & ERROR ) {Debug_printv("line[%s]", text); return 0;}; - IEC.sendByte(blocks >> 8); - if ( IEC.flags & ERROR ) {Debug_printv("line[%s]", text); return 0;}; + if (!IEC.sendByte(blocks bitand 0xFF)) {Debug_printv("line[%s]", text); return 0;}; + if (!IEC.sendByte(blocks >> 8)) {Debug_printv("line[%s]", text); return 0;}; // Send line contents for (uint8_t i = 0; i < len; i++) { - IEC.sendByte(text[i]); - if ( IEC.flags & ERROR ) {Debug_printv("line[%s]", text); return 0;}; + if (!IEC.sendByte(text[i])) {Debug_printv("line[%s]", text); return 0;}; } // Finish line - IEC.sendByte(0); - if ( IEC.flags & ERROR ) {Debug_printv("line[%s]", text); return 0;}; + if (!IEC.sendByte(0)) {Debug_printv("line[%s]", text); return 0;}; Serial.println(""); @@ -849,8 +893,11 @@ uint16_t iecDrive::sendHeader(std::string header, std::string id) //Debug_printv("header[%s] id[%s] space_cnt[%d]", header.c_str(), id.c_str(), space_cnt); - byte_count += sendLine(0, CBM_REVERSE_ON "\"%*s%s%*s\" %s", space_cnt, "", header.c_str(), space_cnt, "", id.c_str()); - if ( IEC.flags & ERROR ) return 0; + size_t written; + + written = sendLine(0, CBM_REVERSE_ON "\"%*s%s%*s\" %s", space_cnt, "", header.c_str(), space_cnt, "", id.c_str()); + if ( !written ) return 0; + byte_count += written; //byte_count += sendLine(basicPtr, 0, "\x12\"%*s%s%*s\" %.02d 2A", space_cnt, "", PRODUCT_ID, space_cnt, "", device_config.device()); //byte_count += sendLine(basicPtr, 0, CBM_REVERSE_ON "%s", header.c_str()); @@ -858,47 +905,57 @@ uint16_t iecDrive::sendHeader(std::string header, std::string id) // Send Extra INFO if (url.size()) { - byte_count += sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, "[URL]"); - if ( IEC.flags & ERROR ) return 0; - byte_count += sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, url.c_str()); - if ( IEC.flags & ERROR ) return 0; + written = sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, "[URL]"); + if (!written) return 0; + byte_count += written; + written = sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, url.c_str()); + if (!written) return 0; + byte_count += written; sent_info = true; } if (path.size() > 1) { - byte_count += sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, "[PATH]"); - if ( IEC.flags & ERROR ) return 0; - byte_count += sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, path.c_str()); - if ( IEC.flags & ERROR ) return 0; + written = sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, "[PATH]"); + if (!written) return 0; + byte_count += written; + written = sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, path.c_str()); + if (!written) return 0; + byte_count += written; sent_info = true; } if (archive.size() > 1) { - byte_count += sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, "[ARCHIVE]"); - if ( IEC.flags & ERROR ) return 0; - byte_count += sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, archive.c_str()); - if ( IEC.flags & ERROR ) return 0; + written = sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, "[ARCHIVE]"); + if (!written) return 0; + byte_count += written; + written = sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, archive.c_str()); + if (!written) return 0; + byte_count += written; sent_info = true; } if (image.size()) { - byte_count += sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, "[IMAGE]"); - if ( IEC.flags & ERROR ) return 0; - byte_count += sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, image.c_str()); - if ( IEC.flags & ERROR ) return 0; + written = sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, "[IMAGE]"); + if (!written) return 0; + byte_count += written; + written = sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, image.c_str()); + if (!written) return 0; + byte_count += written; sent_info = true; } if (sent_info) { - byte_count += sendLine(0, "%*s\"-------------------\" NFO", 0, ""); - if ( IEC.flags & ERROR ) return 0; + written = sendLine(0, "%*s\"-------------------\" NFO", 0, ""); + if (!written) return 0; + byte_count += written; } // If SD Card is available ad we are at the root path show it as a directory at the top if (fnSDFAT.running() && _base->url.size() < 2) { - byte_count += sendLine(0, "%*s\"SD\" DIR", 3, ""); - if ( IEC.flags & ERROR ) return 0; + written = sendLine(0, "%*s\"SD\" DIR", 3, ""); + if (!written) return 0; + byte_count += written; } return byte_count; @@ -939,7 +996,7 @@ void iecDrive::sendListing() closeStream( commanddata.channel ); bool isOpen = registerStream(commanddata.channel); - if(isOpen) + if(isOpen) { sendFile(); } @@ -947,19 +1004,20 @@ void iecDrive::sendListing() { sendFileNotFound(); } - + return; } //fnLedStrip.startRainbow(300); // Send load address - IEC.sendByte(CBM_BASIC_START & 0xff); - IEC.sendByte((CBM_BASIC_START >> 8) & 0xff); - byte_count += 2; + size_t written = 0; + written += IEC.sendByte(CBM_BASIC_START & 0xff); + written += IEC.sendByte((CBM_BASIC_START >> 8) & 0xff); + byte_count += written; // If there has been a error don't try to send any more bytes - if ( IEC.flags & ERROR ) + if (written != 2) { Debug_printv(":("); return; @@ -972,9 +1030,8 @@ void iecDrive::sendListing() { // Send device default listing header char buf[7] = { '\0' }; - sprintf(buf, "%.02d 2A", IEC.data.device); + sprintf(buf, "%.02d 2A", _devnum); byte_count += sendHeader(PRODUCT_ID, buf); - if ( IEC.flags & ERROR ) return; } else { @@ -983,7 +1040,6 @@ void iecDrive::sendListing() _base->media_header = mstr::toPETSCII2( _base->media_header ); byte_count += sendHeader(_base->media_header.c_str(), _base->media_id.c_str()); - if ( IEC.flags & ERROR ) return; } // Send Directory Items @@ -1044,18 +1100,7 @@ void iecDrive::sendListing() if (name[0]!='.') { - // Exit if ATN is PULLED while sending - // Exit if there is an error while sending - if ( IEC.state == BUS_ERROR ) - { - // Save file pointer position - // streamUpdate(byte_count); - //setDeviceStatus(74); - return; - } - byte_count += sendLine(block_cnt, "%*s\"%s\"%*s %s", block_spc, "", name.c_str(), space_cnt, "", extension.c_str()); - if ( IEC.flags & ERROR ) return; } entry.reset(_base->getNextFileInDir()); @@ -1065,7 +1110,6 @@ void iecDrive::sendListing() // Send Listing Footer byte_count += sendFooter(); - if ( IEC.flags & ERROR ) return; // End program with two zeros after last line. Last zero goes out as EOI. IEC.sendByte(0); @@ -1081,15 +1125,10 @@ void iecDrive::sendListing() bool iecDrive::sendFile() { - uint32_t count = 0; + uint32_t count = 0, startpos; bool success_rx = true; - bool success_tx = true; - uint8_t b; // byte -// uint8_t nb; // next byte -#if 0 - uint8_t bi = 0; -#endif + uint8_t buf[128]; uint16_t load_address = 0; uint16_t sys_address = 0; @@ -1130,6 +1169,7 @@ bool iecDrive::sendFile() bool eoi = false; uint32_t size = istream->size(); uint32_t avail = istream->available(); + size_t written = 0, len; uint64_t t_start = esp_timer_get_time(), t_end; //fnLedStrip.startRainbow(300); @@ -1137,20 +1177,18 @@ bool iecDrive::sendFile() if( commanddata.channel == CHANNEL_LOAD && istream->position() == 0 ) { // Get/Send file load address - istream->read(&b, 1); - success_tx = IEC.sendByte(b); - load_address = b & 0x00FF; // low byte - istream->read(&b, 1); - success_tx = IEC.sendByte(b); - load_address = load_address | b << 8; // high byte + istream->read(buf, 2); + written = IEC.sendBytes((const char *) buf, 2, size <= 2); + load_address = buf[0] & 0x00FF; // low byte + load_address = load_address | buf[1] << 8; // high byte sys_address = load_address; Serial.printf( "load_address[$%.4X] sys_address[%d]", load_address, sys_address ); // Get SYSLINE } - - Serial.printf("\r\nsendFile: [$%.4X] pos[%d]\r\n=================================\r\n", load_address, istream->position()); + startpos = istream->position(); + Serial.printf("\r\nsendFile: [$%.4X] pos[%d]\r\n=================================\r\n", load_address, startpos); while( success_rx && !istream->error() ) { //Debug_printv("b[%02X] nb[%02X] success_rx[%d] error[%d] count[%d] avail[%d]", b, nb, success_rx, istream->error(), count, avail); @@ -1162,10 +1200,8 @@ bool iecDrive::sendFile() } #endif - // Read byte - success_rx = istream->read(&b, 1); - //Debug_printv("b[%02X] success[%d]", b, success_rx); - + // Read bytes + len = istream->read(buf, sizeof(buf)); count = istream->position(); avail = istream->available(); @@ -1179,28 +1215,13 @@ bool iecDrive::sendFile() eoi = true; } - // Send Byte - success_tx = IEC.sendByte(b, eoi); - if ( !success_tx ) - { - Debug_printv("Error sending byte.") - break; - } - - // Exit if ATN is PULLED while sending - if ( !eoi && IEC.flags & ATN_PULLED ) - { - //IEC.pull ( PIN_IEC_SRQ ); - //Serial.println(); - //Debug_printv("ATN pulled while sending. b[%.2X]", b); -#ifdef DATA_STREAM - Serial.printf("[atn]\r\n"); -#endif - - // Save file pointer position - istream->seek( -1, SEEK_CUR); - //IEC.release ( PIN_IEC_SRQ ); - break; + // Send Bytes + written = IEC.sendBytes((const char *) buf, len, eoi); + if (written != len) { + Debug_printv("Short write: %i expected: %i cur: %i", written, len, count); + count -= len - written; + istream->seek(count, SEEK_SET); + break; } uint32_t t = 0; @@ -1226,7 +1247,7 @@ bool iecDrive::sendFile() // // Toggle LED // if (i % 50 == 0) // { - // fnLedManager.toggle(eLed::LED_BUS); + // fnLedManager.toggle(eLed::LED_BUS); // } if ( eoi ) @@ -1235,11 +1256,10 @@ bool iecDrive::sendFile() } } - t_end = esp_timer_get_time(); t_end -= t_start; double seconds = t_end / 1000000.0; - double cps = count / seconds; + double cps = (count - startpos) / seconds; Serial.printf("=================================\r\n%d bytes sent of %d @ %0.2fcps[SYS%d]\r\n\r\n", count, size, cps, sys_address); //Debug_printv("len[%d] avail[%d] success_rx[%d]", len, avail, success_rx); @@ -1247,7 +1267,7 @@ bool iecDrive::sendFile() //fnLedManager.set(eLed::LED_BUS, false); //fnLedStrip.stopRainbow(); - if ( istream->error() || !success_tx ) + if ( istream->error() ) { Serial.println("sendFile: Transfer aborted!"); IEC.senderTimeout(); @@ -1262,16 +1282,7 @@ bool iecDrive::saveFile() { size_t i = 0; bool success = true; - bool done = false; - -#if 0 - size_t bi = 0; -#endif size_t load_address = 0; - size_t b_len = 1; - uint8_t b[b_len]; - uint8_t ll[b_len]; - uint8_t lh[b_len]; #ifdef DATA_STREAM char ba[9]; @@ -1294,70 +1305,16 @@ bool iecDrive::saveFile() if ( i == 0 ) { // Get file load address - ll[0] = IEC.receiveByte(); - load_address = *ll & 0x00FF; // low byte - lh[0] = IEC.receiveByte(); - load_address = load_address | *lh << 8; // high byte + load_address = (payload[1] << 8) | payload[0]; } Serial.printf("saveFile: [$%.4X]\r\n=================================\r\n", load_address); - // Recieve bytes until a EOI is detected - do - { - // Save Load Address - if (i == 0) - { - Serial.print("["); - ostream->write(ll, b_len); - ostream->write(lh, b_len); - i += 2; - Serial.print("]"); - } - -#ifdef DATA_STREAM - if (bi == 0) - { - Serial.printf(":%.4X ", load_address); - load_address += 8; - } -#endif - - b[0] = IEC.receiveByte(); - ostream->write(b, b_len); - i++; - - uint16_t f = IEC.flags; - done = (f & EOI_RECVD) or (f & ERROR); - - // Exit if ATN is PULLED while sending - if ( f & ATN_PULLED ) - { - // Save file pointer position - // streamUpdate(ostream->position()); - //setDeviceStatus(74); - break; - } - -#ifdef DATA_STREAM - // Show ASCII Data - if (b[0] < 32 || b[0] >= 127) - ba[bi++] = 46; - else - ba[bi++] = b[0]; - - if(bi == 8) - { - Serial.printf(" %s (%d)\r\n", ba, i); - bi = 0; - } -#endif - - } while (not done); + ostream->write((const uint8_t *) payload.data(), payload.size()); } - Serial.printf("=================================\r\n%d bytes saved\r\n", i); + Serial.printf("=================================\r\n%d bytes saved\r\n", ostream->position()); // TODO: Handle errorFlag diff --git a/lib/device/iec/drive.h b/lib/device/iec/drive.h index 49257057f..10754e341 100644 --- a/lib/device/iec/drive.h +++ b/lib/device/iec/drive.h @@ -74,11 +74,18 @@ class iecDrive : public virtualDevice void format(); protected: +#if 0 /** * @brief Process command fanned out from bus * @return new device state */ device_state_t process() override; +#else + virtual device_state_t openChannel(/*int chan, IECPayload &payload*/) override; + virtual device_state_t closeChannel(/*int chan*/) override; + virtual device_state_t readChannel(/*int chan*/) override; + virtual device_state_t writeChannel(/*int chan, IECPayload &payload*/) override; +#endif /** * @brief process command for channel 0 (load) @@ -95,10 +102,12 @@ class iecDrive : public virtualDevice */ void process_command(); +#if 0 /** * @brief process every other channel (2-14) */ void process_channel(); +#endif /** * @brief called to open a connection to a protocol @@ -120,6 +129,7 @@ class iecDrive : public virtualDevice */ void iec_reopen_save(); +#if 0 /** * @brief called when REOPEN (to send/receive data) */ @@ -129,6 +139,7 @@ class iecDrive : public virtualDevice * @brief called when channel needs to listen for data from c= */ void iec_reopen_channel_listen(); +#endif /** * @brief called when channel needs to talk data to c= @@ -185,4 +196,4 @@ class iecDrive : public virtualDevice ~iecDrive(); }; -#endif // DRIVE_H \ No newline at end of file +#endif // DRIVE_H diff --git a/lib/device/iec/fuji.cpp b/lib/device/iec/fuji.cpp index d3e2902b1..e8abe6f3d 100644 --- a/lib/device/iec/fuji.cpp +++ b/lib/device/iec/fuji.cpp @@ -1,6 +1,7 @@ #ifdef BUILD_IEC #include "fuji.h" +#include "../../../include/cbm_defines.h" #include @@ -133,6 +134,98 @@ void logResponse(const void* data, size_t length) } +#if 1 +device_state_t iecFuji::openChannel(/*int chan, IECPayload &payload*/) +{ + writeChannel(); + return state; +} + +device_state_t iecFuji::closeChannel(/*int chan*/) +{ + return state; +} + +device_state_t iecFuji::readChannel(/*int chan*/) +{ + if (commanddata.channel != CHANNEL_COMMAND) { + Debug_printf("Meatloaf device only accepts on channel 15. Sending NOTFOUND.\r\n"); + IEC.senderTimeout(); + return state; + } + + { + // Debug_printv("TALK/REOPEN:\r\ncurrent_fuji_cmd: %02x\r\n%s\r\n", current_fuji_cmd, util_hexdump(&payload.c_str()[0], payload.size()).c_str()); + +#ifdef DEBUG + // if (!response.empty() && !is_raw_command) logResponse(response.data(), response.size()); + // if (!responseV.empty() && is_raw_command) logResponse(responseV.data(), responseV.size()); + // Debug_printf("\n"); +#endif + + // only send raw back for a raw command, thus code can set "response", but we won't send it back as that's BASIC response + if (!responseV.empty() && is_raw_command) { + IEC.sendBytes(reinterpret_cast(responseV.data()), responseV.size()); + } + + // only send string response back for basic command + if(!response.empty() && !is_raw_command) { + IEC.sendBytes(const_cast(response.c_str()), response.size()); + } + + // ensure responses are cleared for next command in case they were set but didn't match the command type (i.e. basic or raw) + responseV.clear(); + response = ""; + } + + return state; +} + +device_state_t iecFuji::writeChannel(/*int chan, IECPayload &payload*/) +{ + if (commanddata.channel != CHANNEL_COMMAND) { + Debug_printf("Meatloaf device only accepts on channel 15. Sending NOTFOUND.\r\n"); + IEC.senderTimeout(); + return state; + } + + { + // Debug_printv("UNLISTEN/(RE)OPEN:\r\ncurrent_fuji_cmd: %02x\r\n%s\r\n", current_fuji_cmd, util_hexdump(&payload.c_str()[0], payload.size()).c_str()); + + if (current_fuji_cmd == -1) { + // this is a new command being sent + is_raw_command = (payload.size() == 2 && payload[0] == 0x01); // marker byte + if (is_raw_command) { + if (!is_supported(payload[1])) { + Debug_printv("ERROR: Unsupported cmd: x%02x, ignoring\n", payload[1]); + last_command = payload[1]; + set_fuji_iec_status(DEVICE_ERROR, "Unrecognised command"); + // IEC.senderTimeout(); + return state; + } + // Debug_printf("RAW COMMAND - trying immediate action on it\r\n"); + // if it is an immediate command (no parameters), current_fuji_cmd will be reset to -1, + // otherwise, it stays set until further data is received to process it + current_fuji_cmd = payload[1]; + last_command = current_fuji_cmd; + process_immediate_raw_cmds(); + } else if (payload.size() > 0) { + // "IEC: [EF] (E0 CLOSE 15 CHANNEL)" happens with an UNLISTEN, which has no payload, so we can skip it to save trying BASIC commands + // Debug_printf("BASIC COMMAND\r\n"); + process_basic_commands(); + } + } + else { + // we're in the middle of some data, let's continue + // Debug_printf("IN CMD, processing data\r\n"); + process_raw_cmd_data(); + } + + } + + return state; +} +#else device_state_t iecFuji::process() { virtualDevice::process(); @@ -222,6 +315,7 @@ device_state_t iecFuji::process() return state; } +#endif // COMMODORE SPECIFIC CONVENIENCE COMMANDS ///////////////////// @@ -2536,4 +2630,4 @@ void iecFuji::hash_clear() } -#endif /* BUILD_IEC */ \ No newline at end of file +#endif /* BUILD_IEC */ diff --git a/lib/device/iec/fuji.h b/lib/device/iec/fuji.h index f3025bc62..e29aa20e1 100644 --- a/lib/device/iec/fuji.h +++ b/lib/device/iec/fuji.h @@ -327,7 +327,14 @@ class iecFuji : public virtualDevice // Commodore specific void local_ip(); +#if 0 device_state_t process() override; +#else + virtual device_state_t openChannel(/*int chan, IECPayload &payload*/) override; + virtual device_state_t closeChannel(/*int chan*/) override; + virtual device_state_t readChannel(/*int chan*/) override; + virtual device_state_t writeChannel(/*int chan, IECPayload &payload*/) override; +#endif void shutdown() override; @@ -378,4 +385,4 @@ class iecFuji : public virtualDevice extern iecFuji theFuji; -#endif // FUJI_H \ No newline at end of file +#endif // FUJI_H diff --git a/lib/device/iec/network.cpp b/lib/device/iec/network.cpp index d743476ab..76052c82a 100644 --- a/lib/device/iec/network.cpp +++ b/lib/device/iec/network.cpp @@ -8,6 +8,7 @@ #include #include "network.h" +#include "../../include/cbm_defines.h" #include "../../include/debug.h" #include "../../hardware/led.h" @@ -261,6 +262,7 @@ void iecNetwork::iec_reopen_save() return; } +#if 0 while (!(IEC.flags & EOI_RECVD)) { int16_t b = IEC.receiveByte(); @@ -273,6 +275,9 @@ void iecNetwork::iec_reopen_save() channel_data.transmitBuffer.push_back(b); } +#else +#warning FIXME - use payload +#endif // force incoming data from HOST to fixed ascii // Debug_printv("[1] DATA: >%s< [%s]", channel_data.transmitBuffer.c_str(), mstr::toHex(channel_data.transmitBuffer).c_str()); @@ -322,6 +327,7 @@ void iecNetwork::iec_reopen_channel_listen() // Debug_printv("Receiving data from computer...\r\n"); +#if 0 while (!(IEC.flags & EOI_RECVD)) { int16_t b = IEC.receiveByte(); @@ -334,6 +340,9 @@ void iecNetwork::iec_reopen_channel_listen() channel_data.transmitBuffer.push_back(b); } +#else +#warning FIXME - use payload +#endif // force incoming data from HOST to fixed ascii // Debug_printv("[1] DATA: >%s< [%s]", channel_data.transmitBuffer.c_str(), mstr::toHex(channel_data.transmitBuffer).c_str()); @@ -394,18 +403,18 @@ void iecNetwork::iec_reopen_channel_talk() set_eoi = true; } - IEC.sendByte(b, set_eoi); - - if ( IEC.flags & ERROR ) + if (!IEC.sendByte(b, set_eoi)) { - Debug_printv("TALK ERROR! flags[%d]\n", IEC.flags); + //Debug_printv("TALK ERROR! flags[%d]\n", IEC.flags); return; } - if ( !(IEC.flags & ATN_PULLED) ) +#if 0 + if ( !(IEC.flags & ATN_ASSERTED) ) channel_data.receiveBuffer.erase(0, 1); +#endif - } while( !(IEC.flags & ATN_PULLED) && !set_eoi ); + } while( /*!(IEC.flags & ATN_ASSERTED) &&*/ !set_eoi ); } void iecNetwork::set_login_password() @@ -1266,6 +1275,33 @@ void iecNetwork::set_open_params() } +#if 1 +device_state_t iecNetwork::openChannel(/*int chan, IECPayload &payload*/) +{ + process_channel(); + return state; +} + +device_state_t iecNetwork::closeChannel(/*int chan*/) +{ + return state; +} + +device_state_t iecNetwork::readChannel(/*int chan*/) +{ + if (commanddata.channel == CHANNEL_COMMAND) + process_command(); + else + process_load(); + return state; +} + +device_state_t iecNetwork::writeChannel(/*int chan, IECPayload &payload*/) +{ + process_save(); + return state; +} +#else device_state_t iecNetwork::process() { // Call base class @@ -1299,6 +1335,7 @@ device_state_t iecNetwork::process() return state; } +#endif void iecNetwork::process_load() { @@ -1398,4 +1435,4 @@ void iecNetwork::process_command() } -#endif /* BUILD_IEC */ \ No newline at end of file +#endif /* BUILD_IEC */ diff --git a/lib/device/iec/network.h b/lib/device/iec/network.h index ebec5f9bf..5ca9a80ec 100644 --- a/lib/device/iec/network.h +++ b/lib/device/iec/network.h @@ -64,12 +64,13 @@ class iecNetwork : public virtualDevice // */ // NetworkProtocol *protocol[NUM_CHANNELS]; - +#if 0 /** * @brief Process command fanned out from bus * @return new device state */ device_state_t process() override; +#endif /** * @brief Check to see if SRQ needs to be asserted. @@ -77,6 +78,12 @@ class iecNetwork : public virtualDevice */ virtual void poll_interrupt(unsigned char c) override; +protected: + virtual device_state_t openChannel(/*int chan, IECPayload &payload*/) override; + virtual device_state_t closeChannel(/*int chan*/) override; + virtual device_state_t readChannel(/*int chan*/) override; + virtual device_state_t writeChannel(/*int chan, IECPayload &payload*/) override; + private: /** * @brief flag to indicate if the status result should be binary or string @@ -308,4 +315,4 @@ class iecNetwork : public virtualDevice }; -#endif /* NETWORK_H */ \ No newline at end of file +#endif /* NETWORK_H */ diff --git a/lib/device/iec/printer.cpp b/lib/device/iec/printer.cpp index d7947a5c1..c7e2c63ac 100755 --- a/lib/device/iec/printer.cpp +++ b/lib/device/iec/printer.cpp @@ -28,6 +28,7 @@ iecPrinter::~iecPrinter() // write for W commands void iecPrinter::write(uint8_t channel) { +#if 0 // Receive data from computer while (!(IEC.flags & EOI_RECVD)) { @@ -41,6 +42,9 @@ void iecPrinter::write(uint8_t channel) _pptr->process(1, commanddata.channel, 0); _last_ms = fnSystem.millis(); } +#else +#warning FIXME - use paylaod +#endif } /** @@ -140,6 +144,28 @@ iecPrinter::printer_type iecPrinter::match_modelname(std::string model_name) return (printer_type)i; } +#if 1 +device_state_t iecPrinter::openChannel(/*int chan, IECPayload &payload*/) +{ + return state; +} + +device_state_t iecPrinter::closeChannel(/*int chan*/) +{ + return state; +} + +device_state_t iecPrinter::readChannel(/*int chan*/) +{ + return state; +} + +device_state_t iecPrinter::writeChannel(/*int chan, IECPayload &payload*/) +{ + write(commanddata.channel); + return state; +} +#else // Process command device_state_t iecPrinter::process() { @@ -152,5 +178,6 @@ device_state_t iecPrinter::process() return state; } +#endif -#endif /* BUILD_IEC */ \ No newline at end of file +#endif /* BUILD_IEC */ diff --git a/lib/device/iec/printer.h b/lib/device/iec/printer.h index f52a20787..bba9b21c0 100755 --- a/lib/device/iec/printer.h +++ b/lib/device/iec/printer.h @@ -20,7 +20,14 @@ class iecPrinter : public virtualDevice std::string buffer; void write(uint8_t channel); void status(); +#if 0 device_state_t process() override; +#else + virtual device_state_t openChannel(/*int chan, IECPayload &payload*/) override; + virtual device_state_t closeChannel(/*int chan*/) override; + virtual device_state_t readChannel(/*int chan*/) override; + virtual device_state_t writeChannel(/*int chan, IECPayload &payload*/) override; +#endif void shutdown() override; printer_emu *_pptr = nullptr; diff --git a/lib/meatloaf/wrappers/iec_buffer.cpp b/lib/meatloaf/wrappers/iec_buffer.cpp index abb67f68c..0176f5c53 100644 --- a/lib/meatloaf/wrappers/iec_buffer.cpp +++ b/lib/meatloaf/wrappers/iec_buffer.cpp @@ -1,5 +1,6 @@ #ifdef BUILD_IEC #include "iec_buffer.h" +#include "../../../include/cbm_defines.h" oiecstream iecStream; @@ -14,7 +15,7 @@ oiecstream iecStream; * SAVE ops, pipe mode = _S_out, uses get area ********************************************************/ size_t oiecstream::receiveBytesViaIEC() { - // we are in a SAVE operation here, so we are pulling bytes from C64 to file, by reading from IEC + // we are in a SAVE operation here, so we are moving bytes from C64 to file, by reading from IEC // underflow happened to our get buffer and this function was called, it has to read bytes from IEC // put them in gbuff and setg to point to them @@ -58,7 +59,7 @@ size_t oiecstream::sendBytesViaIEC() { //Serial.printf("%c[%.2X]",*b, *b); bool sendSuccess = m_iec->sendByte(*b); //bool sendSuccess = true; - if(sendSuccess && !(IEC.flags bitand ATN_PULLED) ) written++; + if(sendSuccess && !(IEC.flags bitand ATN_ASSERTED) ) written++; else if(!sendSuccess) { // JAIME: what should happen here? should the badbit be set when send returns false? setstate(badbit); @@ -67,9 +68,9 @@ size_t oiecstream::sendBytesViaIEC() { return written; } else { - // ATN was pulled + // ATN was asserted setp(data+written, data+IEC_BUFFER_SIZE); // set pbase to point to next unwritten char - Debug_printv("IEC acknowledged %d bytes, then ATN was pulled\n", written); + Debug_printv("IEC acknowledged %d bytes, then ATN was asserted\n", written); return written; } } diff --git a/lib/utils/string_utils.cpp b/lib/utils/string_utils.cpp index c3530263f..d75b986f6 100644 --- a/lib/utils/string_utils.cpp +++ b/lib/utils/string_utils.cpp @@ -681,4 +681,4 @@ namespace mstr { return parent + "/" + plus; } -} \ No newline at end of file +}