Переглянути джерело

Merge pull request #226 from ZuluSCSI/dev_cdrom_eject_buttons

Add platform support for CD-ROM eject buttons
Alex Perez 2 роки тому
батько
коміт
77b226c9aa

+ 21 - 0
README.md

@@ -107,6 +107,27 @@ ZuluSCSI RP2040 DIP switch settings are:
 - TERMINATION: Enable SCSI termination
 - BOOTLOADER: Enable built-in USB bootloader, this DIP switch MUST remain off during normal operation.
 
+Physical eject button for CDROM
+-------------------------------
+CD-ROM drives can be configured to eject when a physical button is pressed.
+If multiple image files are configured with `IMG0`..`IMG9` config settings, ejection will switch between them.
+Two separate buttons are supported and they can eject different drives.
+
+    [SCSI1]
+    Type=2 # CDROM drive
+    IMG0 = img0.iso
+    IMG1 = ...
+    EjectButton = 1
+
+On GD32-based ZuluSCSI models (V1.0 and V1.1), buttons are connected to J303 12-pin expansion header.
+Button 1 is connected between `PE5` and `GND`, and button 2 is connected between `PE6` and `GND`.
+Pin locations are also shown in [this image](docs/ZuluSCSI_v1_1_buttons.jpg).
+
+On RP2040-based ZuluSCSI models, buttons are connected to the I2C pins.
+Button 1 is connected between `SDA` and `GND` and button 2 is connected between `SCL` and `GND`.
+On full-size models, the pins are available on expansion header J303 ([image](docs/ZuluSCSI_RP2040_buttons.jpg)).
+On compact model, pins are available on 4-pin I2C header J305 ([image](docs/ZuluSCSI_RP2040_compact_buttons.jpg)).
+
 SCSI initiator mode
 -------------------
 The RP2040 model supports SCSI initiator mode for reading SCSI drives.

BIN
docs/ZuluSCSI_RP2040_buttons.jpg


BIN
docs/ZuluSCSI_RP2040_compact_buttons.jpg


BIN
docs/ZuluSCSI_v1_1_buttons.jpg


+ 25 - 1
lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_platform.cpp

@@ -218,6 +218,10 @@ void platform_init()
     gpio_bit_set(LED_PORT, LED_PINS);
     gpio_init(LED_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_2MHZ, LED_PINS);
 
+    // Ejection buttons
+    gpio_init(EJECT_1_PORT, GPIO_MODE_IPU, 0, EJECT_1_PIN);
+    gpio_init(EJECT_2_PORT, GPIO_MODE_IPU, 0, EJECT_2_PIN);
+
     // SWO trace pin on PB3
     gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_3);
 }
@@ -495,7 +499,27 @@ void platform_poll()
 
 uint8_t platform_get_buttons()
 {
-    return 0;
+    // Buttons are active low: internal pull-up is enabled,
+    // and when button is pressed the pin goes low.
+    uint8_t buttons = 0;
+    if (!gpio_input_bit_get(EJECT_1_PORT, EJECT_1_PIN))   buttons |= 1;
+    if (!gpio_input_bit_get(EJECT_2_PORT, EJECT_2_PIN))   buttons |= 2;
+
+    // Simple debouncing logic: handle button releases after 100 ms delay.
+    static uint32_t debounce;
+    static uint8_t buttons_debounced = 0;
+
+    if (buttons != 0)
+    {
+        buttons_debounced = buttons;
+        debounce = millis();
+    }
+    else if ((uint32_t)(millis() - debounce) > 100)
+    {
+        buttons_debounced = 0;
+    }
+
+    return buttons_debounced;
 }
 
 /***********************/

+ 8 - 0
lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_v1_0_gpio.h

@@ -138,3 +138,11 @@
 #define LED_PINS     (LED_I_PIN | LED_E_PIN)
 #define LED_ON()     gpio_bit_reset(LED_PORT, LED_PINS)
 #define LED_OFF()    gpio_bit_set(LED_PORT, LED_PINS)
+
+// Ejection buttons are available on expansion header J303.
+// PE5 = channel 1, PE6 = channel 2
+// Connect button between GPIO and GND pin.
+#define EJECT_1_PORT    GPIOE
+#define EJECT_1_PIN     GPIO_PIN_5
+#define EJECT_2_PORT    GPIOE
+#define EJECT_2_PIN     GPIO_PIN_6

+ 9 - 1
lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_v1_1_gpio.h

@@ -182,4 +182,12 @@
 #define LED_E_PIN    GPIO_PIN_5
 #define LED_PINS     (LED_I_PIN | LED_E_PIN)
 #define LED_ON()     gpio_bit_reset(LED_PORT, LED_PINS)
-#define LED_OFF()    gpio_bit_set(LED_PORT, LED_PINS)
+#define LED_OFF()    gpio_bit_set(LED_PORT, LED_PINS)
+
+// Ejection buttons are available on expansion header J303.
+// PE5 = channel 1, PE6 = channel 2
+// Connect button between GPIO and GND pin.
+#define EJECT_1_PORT    GPIOE
+#define EJECT_1_PIN     GPIO_PIN_5
+#define EJECT_2_PORT    GPIOE
+#define EJECT_2_PIN     GPIO_PIN_6

+ 24 - 6
lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform.cpp

@@ -621,14 +621,32 @@ void platform_poll()
 
 uint8_t platform_get_buttons()
 {
-#ifdef ENABLE_AUDIO_OUTPUT
-    uint8_t pins = 0x00;
+    uint8_t buttons = 0;
+
+#if defined(ENABLE_AUDIO_OUTPUT)
     // pulled to VCC via resistor, sinking when pressed
-    if (!gpio_get(GPIO_EXP_SPARE)) pins |= 0x01;
-    return pins;
-#else
-    return 0;
+    if (!gpio_get(GPIO_EXP_SPARE)) buttons |= 1;
+#elif defined(GPIO_I2C_SDA)
+    // SDA = button 1, SCL = button 2
+    if (!gpio_get(GPIO_I2C_SDA)) buttons |= 1;
+    if (!gpio_get(GPIO_I2C_SCL)) buttons |= 2;
 #endif
+
+    // Simple debouncing logic: handle button releases after 100 ms delay.
+    static uint32_t debounce;
+    static uint8_t buttons_debounced = 0;
+
+    if (buttons != 0)
+    {
+        buttons_debounced = buttons;
+        debounce = millis();
+    }
+    else if ((uint32_t)(millis() - debounce) > 100)
+    {
+        buttons_debounced = 0;
+    }
+
+    return buttons_debounced;
 }
 
 /*****************************************/

+ 17 - 4
src/ZuluSCSI_cdrom.cpp

@@ -817,9 +817,22 @@ void cdromPerformEject(image_config_t &img)
     // terminate audio playback if active on this target (MMC-1 Annex C)
     audio_stop(target);
 #endif
-    dbgmsg("------ CDROM open tray on ID ", (int)target);
-    img.ejected = true;
-    img.cdrom_events = 3; // Media removal
+    if (!img.ejected)
+    {
+        dbgmsg("------ CDROM open tray on ID ", (int)target);
+        img.ejected = true;
+        img.cdrom_events = 3; // Media removal
+    }
+    else
+    {
+        dbgmsg("------ CDROM close tray on ID ", (int)target);
+        if (!cdromSwitchNextImage(img))
+        {
+            // Reinsert the single image
+            img.ejected = false;
+            img.cdrom_events = 2; // New media
+        }
+    }
 }
 
 // Reinsert any ejected CDROMs on reboot
@@ -904,7 +917,7 @@ static void doGetEventStatusNotification(bool immed)
         scsiDev.phase = DATA_IN;
         img.cdrom_events = 0;
 
-        if (img.ejected)
+        if (img.ejected && img.reinsert_after_eject)
         {
             // We are now reporting to host that the drive is open.
             // Simulate a "close" for next time the host polls.

+ 19 - 5
src/ZuluSCSI_disk.cpp

@@ -174,6 +174,8 @@ void scsiDiskCloseSDCardImages()
         {
             g_DiskImages[i].file.close();
         }
+
+        g_DiskImages[i].cuesheetfile.close();
     }
 }
 
@@ -326,6 +328,7 @@ static void setDefaultDriveInfo(int target_idx)
 bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_id, int scsi_lun, int blocksize, S2S_CFG_TYPE type)
 {
     image_config_t &img = g_DiskImages[target_idx];
+    img.cuesheetfile.close();
     img.file = ImageBackingStore(filename, blocksize);
 
     if (img.file.isOpen())
@@ -478,6 +481,7 @@ static void scsiDiskLoadConfig(int target_idx, const char *section)
     img.rightAlignStrings = ini_getbool(section, "RightAlignStrings", 0, CONFIGFILE);
     img.prefetchbytes = ini_getl(section, "PrefetchBytes", img.prefetchbytes, CONFIGFILE);
     img.reinsert_on_inquiry = ini_getbool(section, "ReinsertCDOnInquiry", 1, CONFIGFILE);
+    img.reinsert_after_eject = ini_getbool(section, "ReinsertAfterEject", 1, CONFIGFILE);
     img.ejectButton = ini_getl(section, "EjectButton", 0, CONFIGFILE);
 
     char tmp[32];
@@ -559,18 +563,25 @@ image_config_t &scsiDiskGetImageConfig(int target_idx)
 
 static void diskEjectAction(uint8_t buttonId)
 {
-    logmsg("Eject button pressed for channel ", buttonId);
+    bool found = false;
     for (uint8_t i = 0; i < S2S_MAX_TARGETS; i++)
     {
-        image_config_t img = g_DiskImages[i];
+        image_config_t &img = g_DiskImages[i];
         if (img.ejectButton == buttonId)
         {
             if (img.deviceType == S2S_CFG_OPTICAL)
             {
+                found = true;
+                logmsg("Eject button ", (int)buttonId, " pressed, passing to CD drive SCSI", (int)i);
                 cdromPerformEject(img);
             }
         }
     }
+
+    if (!found)
+    {
+        logmsg("Eject button ", (int)buttonId, " pressed, but no drives with EjectButton=", (int)buttonId, " setting found!");
+    }
 }
 
 uint8_t diskEjectButtonUpdate(bool immediate)
@@ -897,9 +908,12 @@ static int doTestUnitReady()
         scsiDev.target->sense.asc = MEDIUM_NOT_PRESENT;
         scsiDev.phase = STATUS;
 
-        // We are now reporting to host that the drive is open.
-        // Simulate a "close" for next time the host polls.
-        cdromSwitchNextImage(img);
+        if (img.reinsert_after_eject)
+        {
+            // We are now reporting to host that the drive is open.
+            // Simulate a "close" for next time the host polls.
+            cdromSwitchNextImage(img);
+        }
     }
     else if (unlikely(!(blockDev.state & DISK_PRESENT)))
     {

+ 6 - 1
src/ZuluSCSI_disk.h

@@ -42,12 +42,17 @@ extern "C" {
 // Extended configuration stored alongside the normal SCSI2SD target information
 struct image_config_t: public S2S_TargetCfg
 {
+    // There should be only one global instance of this struct per device, so disallow copy constructor.
+    image_config_t() = default;
+    image_config_t(const image_config_t&) = delete;
+
     ImageBackingStore file;
 
     // For CD-ROM drive ejection
     bool ejected;
     uint8_t cdrom_events;
-    bool reinsert_on_inquiry;
+    bool reinsert_on_inquiry; // Reinsert on Inquiry command (to reinsert automatically after boot)
+    bool reinsert_after_eject; // Reinsert next image after ejection
 
     // selects a physical button channel that will cause an eject action
     // default option of '0' disables this functionality

+ 2 - 0
zuluscsi.ini

@@ -41,6 +41,8 @@
 #RightAlignStrings = 0 # Right-align SCSI vendor / product strings, defaults on if Quirks = 1
 #PrefetchBytes = 8192 # Maximum number of bytes to prefetch after a read request, 0 to disable
 #ReinsertCDOnInquiry = 1 # Reinsert any ejected CD-ROM image on Inquiry command
+#ReinsertAfterEject = 1 # Reinsert next CD image after eject, if multiple images configured.
+#EjectButton = 0 # Enable eject by button 1 or 2, or set 0 to disable
 
 # Settings can be overridden for individual devices.
 #[SCSI2]