Lauffähiger Floppinator-Code
This commit is contained in:
410
floppinator/floppinator.ino
Normal file
410
floppinator/floppinator.ino
Normal file
@@ -0,0 +1,410 @@
|
||||
/*
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user