Files
floppinator/floppinator/floppinator.ino

411 lines
9.3 KiB
C++

/*
Hirnfrei Floppinator - Standalone-Version (Pico only)
Der Arduino Mega samt UART-Backend ist raus. Die SD-Karte haengt jetzt
direkt per SPI0 am Pico (das neue Modul mit 3V3/CS/MOSI/CLK/MISO/GND).
Pinbelegung SD-Modul -> Pico (SPI0, alternatives Pinset):
3V3 -> 3V3 (out)
GND -> GND
CS -> GP5
MOSI -> GP7
CLK -> GP6
MISO -> GP4
Hinweis: GP16-19 (Standard-SPI0-Pins) wurden bewusst NICHT verwendet.
GP16/17 waren im Frankenstein-Aufbau als UART-Verbindung zum Mega im
Einsatz (5V-Logik!) und haben sich beim Testen als unzuverlaessig fuer
SD-Kommunikation erwiesen - vermutlich durch die 5V-Belastung in
Mitleidenschaft gezogen. GP4-7 sind unbelastete, funktionierende Pins.
Das Display haengt weiterhin an SPI1 wie bisher, unveraendert.
Serial1 / UART wird nicht mehr gebraucht und ist komplett entfernt.
*/
#include <Adafruit_TinyUSB.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SPI.h>
#include <SdFat.h>
#define TFT_CS_PIN 9
#define TFT_DC_PIN 8
#define TFT_RST_PIN 12
#define TFT_BL_PIN 13
#define BTN_PREV_PIN 14
#define BTN_NEXT_PIN 15
#define BTN_SEL_PIN 20
#define DEBOUNCE_MS 50
#define SD_CS_PIN 5
#define SD_MISO_PIN 4
#define SD_MOSI_PIN 7
#define SD_SCK_PIN 6
// Debug-Schalter: true = Serial-Ausgaben aktiv, false = komplett
// abgeschaltet (keine Ausgaben, kein Warten auf den seriellen Monitor
// beim Start). Das USB-CDC-Interface selbst bleibt so oder so im
// USB-Deskriptor bestehen, das liegt an der TinyUSB-Konfiguration.
#define DEBUG_ENABLED false
#if DEBUG_ENABLED
#define DBG_PRINT(...) do { if (Serial) { Serial.print(__VA_ARGS__); } } while (0)
#define DBG_PRINTLN(...) do { if (Serial) { Serial.println(__VA_ARGS__); } } while (0)
#else
#define DBG_PRINT(...) do {} while (0)
#define DBG_PRINTLN(...) do {} while (0)
#endif
#define MAX_IMAGES 16
struct FloppyImage
{
char name[32];
};
FloppyImage images[MAX_IMAGES];
uint8_t imageCount = 0;
int8_t browseIndex = 0;
int8_t mountedIndex = -1;
Adafruit_USBD_MSC usb_msc;
SdFat32 sd;
File32 currentFile;
String currentFilename = "";
class TftDisplay : public Adafruit_ST7735
{
public:
TftDisplay(SPIClass *spiClass, int8_t cs, int8_t dc, int8_t rst)
: Adafruit_ST7735(spiClass, cs, dc, rst) {}
using Adafruit_ST77xx::setColRowStart;
};
TftDisplay tft(&SPI1, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);
struct Button
{
uint8_t pin;
bool lastReading = HIGH;
bool stableState = HIGH;
uint32_t lastChangeMs = 0;
};
Button btnPrev { BTN_PREV_PIN };
Button btnNext { BTN_NEXT_PIN };
Button btnSel { BTN_SEL_PIN };
bool buttonPressed(Button &b)
{
bool reading = digitalRead(b.pin);
if (reading != b.lastReading)
{
b.lastChangeMs = millis();
b.lastReading = reading;
}
if ((millis() - b.lastChangeMs) > DEBOUNCE_MS && reading != b.stableState)
{
b.stableState = reading;
if (b.stableState == LOW)
{
return true;
}
}
return false;
}
// --- USB MSC Callbacks: jetzt direkt auf die SD-Karte ---
int32_t msc_read_cb(uint32_t lba, void *buffer, uint32_t bufsize)
{
if (!currentFile)
{
return -1;
}
if (!currentFile.seekSet((uint32_t)lba * 512UL))
{
DBG_PRINT("SEEK FEHLER (read) lba=");
DBG_PRINTLN(lba);
return -1;
}
int n = currentFile.read(buffer, bufsize);
if (n != (int)bufsize)
{
DBG_PRINT("READ FEHLER lba=");
DBG_PRINT(lba);
DBG_PRINT(" n=");
DBG_PRINTLN(n);
return -1;
}
return bufsize;
}
int32_t msc_write_cb(uint32_t lba, uint8_t *buffer, uint32_t bufsize)
{
if (!currentFile)
{
return -1;
}
if (!currentFile.seekSet((uint32_t)lba * 512UL))
{
DBG_PRINT("SEEK FEHLER (write) lba=");
DBG_PRINTLN(lba);
return -1;
}
int n = currentFile.write(buffer, bufsize);
if (n != (int)bufsize)
{
DBG_PRINT("WRITE FEHLER lba=");
DBG_PRINTLN(lba);
return -1;
}
return bufsize;
}
void msc_flush_cb(void)
{
if (currentFile)
{
currentFile.sync();
}
}
// --- Display ---
void updateDisplay()
{
tft.fillScreen(ST77XX_BLACK);
tft.setTextSize(1);
tft.setTextColor(ST77XX_WHITE);
tft.setCursor(4, 4);
tft.println("Floppy A:");
tft.setCursor(4, 24);
if (mountedIndex >= 0)
{
tft.setTextColor(ST77XX_GREEN);
tft.println(images[mountedIndex].name);
}
else
{
tft.setTextColor(ST77XX_RED);
tft.println("-- leer --");
}
tft.setCursor(4, 55);
tft.setTextColor(ST77XX_YELLOW);
tft.print("> ");
if (imageCount > 0)
{
tft.println(images[browseIndex].name);
}
else
{
tft.println("(Suche .img...)");
}
}
// --- SD-Verzeichnis einlesen ---
void scanImages()
{
imageCount = 0;
File32 root = sd.open("/");
DBG_PRINT("root.open('/') Ergebnis: ");
DBG_PRINTLN(root ? "OK" : "FEHLGESCHLAGEN");
if (!root)
{
DBG_PRINTLN("-> FEHLER: Root-Verzeichnis '/' konnte nicht geoeffnet werden!");
return;
}
File32 file = root.openNextFile();
while (file)
{
char nameBuf[32];
file.getName(nameBuf, sizeof(nameBuf));
if (!file.isDirectory())
{
String lowerName = String(nameBuf);
lowerName.toLowerCase();
if (lowerName.endsWith(".img") && imageCount < MAX_IMAGES)
{
strncpy(images[imageCount].name, nameBuf, sizeof(images[imageCount].name) - 1);
images[imageCount].name[sizeof(images[imageCount].name) - 1] = '\0';
imageCount++;
}
}
file.close();
file = root.openNextFile();
}
root.close();
DBG_PRINT(imageCount);
DBG_PRINTLN(" .img-Datei(en) gefunden.");
for (uint8_t i = 0; i < imageCount; i++)
{
DBG_PRINT(" ["); DBG_PRINT(i); DBG_PRINT("] ");
DBG_PRINTLN(images[i].name);
}
}
void mountImage(int8_t idx)
{
if (idx < 0 || idx >= imageCount)
{
return;
}
String fname = String(images[idx].name);
if (!fname.startsWith("/"))
{
fname = "/" + fname;
}
DBG_PRINT("Mounte: '");
DBG_PRINT(fname);
DBG_PRINTLN("'");
// USB-Verbindung kurz trennen, damit das Betriebssystem das Medium neu einliest
usb_msc.setUnitReady(false);
TinyUSBDevice.detach();
delay(200);
if (currentFile)
{
currentFile.close();
}
currentFile = sd.open(fname.c_str(), O_RDWR);
bool ok = (bool)currentFile;
DBG_PRINT("-> Mount ");
DBG_PRINTLN(ok ? "OK" : "FEHLGESCHLAGEN");
if (!ok)
{
TinyUSBDevice.attach();
return;
}
currentFilename = fname;
mountedIndex = idx;
// 1.44 MB Standard Disketten-Geometrie (2880 Sektoren à 512 Byte)
usb_msc.setCapacity(2880, 512);
usb_msc.setUnitReady(true);
TinyUSBDevice.attach();
}
void setup()
{
Serial.begin(115200);
#if DEBUG_ENABLED
delay(2000); // Zeit, um den Seriellen Monitor rechtzeitig zu oeffnen
#endif
DBG_PRINTLN();
DBG_PRINTLN("=== Bochser Floppy-Emulator (standalone) startet ===");
pinMode(BTN_PREV_PIN, INPUT_PULLUP);
pinMode(BTN_NEXT_PIN, INPUT_PULLUP);
pinMode(BTN_SEL_PIN, INPUT_PULLUP);
pinMode(TFT_BL_PIN, OUTPUT);
digitalWrite(TFT_BL_PIN, HIGH);
DBG_PRINTLN("Initialisiere Display...");
SPI1.setRX(28);
SPI1.setSCK(10);
SPI1.setTX(11);
tft.initR(INITR_BLACKTAB);
tft.setColRowStart(1, 0);
tft.setRotation(1);
updateDisplay();
DBG_PRINTLN("Display initialisiert.");
DBG_PRINTLN("Initialisiere SD-Karte (SPI0)...");
SPI.setRX(SD_MISO_PIN);
SPI.setTX(SD_MOSI_PIN);
SPI.setSCK(SD_SCK_PIN);
if (!sd.begin(SD_CS_PIN, SD_SCK_MHZ(4)))
{
DBG_PRINTLN("SD-Karte nicht gefunden!");
}
else
{
DBG_PRINTLN("SD-Karte OK.");
scanImages();
}
updateDisplay();
// USB MSC konfigurieren
DBG_PRINTLN("Initialisiere USB-MSC...");
usb_msc.setID("Hirnfrei", "Bochser", "1.0");
usb_msc.setReadWriteCallback(msc_read_cb, msc_write_cb, msc_flush_cb);
usb_msc.setCapacity(2880, 512);
usb_msc.setUnitReady(false);
usb_msc.begin();
DBG_PRINTLN("=== Setup abgeschlossen ===");
}
void loop()
{
if (imageCount == 0)
{
return;
}
if (buttonPressed(btnPrev))
{
browseIndex = (browseIndex - 1 + imageCount) % imageCount;
if (Serial)
{
DBG_PRINT("Zurueck -> Index "); DBG_PRINT(browseIndex);
DBG_PRINT(" ("); DBG_PRINT(images[browseIndex].name); DBG_PRINTLN(")");
}
updateDisplay();
}
if (buttonPressed(btnNext))
{
browseIndex = (browseIndex + 1) % imageCount;
if (Serial)
{
DBG_PRINT("Weiter -> Index "); DBG_PRINT(browseIndex);
DBG_PRINT(" ("); DBG_PRINT(images[browseIndex].name); DBG_PRINTLN(")");
}
updateDisplay();
}
if (buttonPressed(btnSel))
{
DBG_PRINT("Auswahl gedrueckt, Index "); DBG_PRINTLN(browseIndex);
if (browseIndex != mountedIndex)
{
mountImage(browseIndex);
updateDisplay();
}
else
{
DBG_PRINTLN("Bereits gemountet, keine Aktion.");
}
}
}