mirror of
https://github.com/LIV2/amiberry.git
synced 2025-12-06 06:32:45 +00:00
enhancement: updated FloppyBridge to v1.6.4
This commit is contained in:
parent
2401021c5b
commit
5d3915ea9b
@ -270,8 +270,8 @@ bool ArduinoFloppyDiskBridge::setCurrentCylinder(const unsigned int cylinder) {
|
||||
if (!m_io.getFirwareVersion().fullControlMod) ignoreDiskCheck |= !isReadyForManualDiskCheck();
|
||||
|
||||
// Go! - and don't ask
|
||||
ArduinoFloppyReader::DiagnosticResponse dr = m_io.selectTrack(cylinder, ArduinoFloppyReader::TrackSearchSpeed::tssFast, ignoreDiskCheck);
|
||||
if (dr != ArduinoFloppyReader::DiagnosticResponse::drOK) dr = m_io.selectTrack(cylinder, ArduinoFloppyReader::TrackSearchSpeed::tssNormal, ignoreDiskCheck);
|
||||
ArduinoFloppyReader::DiagnosticResponse dr = m_io.selectTrack(cylinder, ArduinoFloppyReader::TrackSearchSpeed::tssVeryFast, ignoreDiskCheck);
|
||||
if (dr != ArduinoFloppyReader::DiagnosticResponse::drOK) dr = m_io.selectTrack(cylinder, ArduinoFloppyReader::TrackSearchSpeed::tssFast, ignoreDiskCheck);
|
||||
if (dr != ArduinoFloppyReader::DiagnosticResponse::drOK) dr = m_io.selectTrack(cylinder, ArduinoFloppyReader::TrackSearchSpeed::tssNormal, ignoreDiskCheck);
|
||||
if (dr != ArduinoFloppyReader::DiagnosticResponse::drOK) dr = m_io.selectTrack(cylinder, ArduinoFloppyReader::TrackSearchSpeed::tssNormal, ignoreDiskCheck);
|
||||
if (dr == ArduinoFloppyReader::DiagnosticResponse::drOK) {
|
||||
@ -306,6 +306,19 @@ CommonBridgeTemplate::ReadResponse ArduinoFloppyDiskBridge::readData(PLL::Bridge
|
||||
}
|
||||
}
|
||||
|
||||
// Called for a direct read. This does not match up a rotation and should be used with the pll initialized with the LinearExtractor
|
||||
// pll: required
|
||||
// Returns: ReadResponse, explains its self
|
||||
CommonBridgeTemplate::ReadResponse ArduinoFloppyDiskBridge::readLinearData(PLL::BridgePLL& pll) {
|
||||
ArduinoFloppyReader::DiagnosticResponse result = m_io.readData(pll);
|
||||
|
||||
switch (result) {
|
||||
case ArduinoFloppyReader::DiagnosticResponse::drOK: return ReadResponse::rrOK;
|
||||
case ArduinoFloppyReader::DiagnosticResponse::drNoDiskInDrive: return ReadResponse::rrNoDiskInDrive;
|
||||
default: return ReadResponse::rrError;
|
||||
}
|
||||
}
|
||||
|
||||
// Called when a cylinder revolution should be written to the disk.
|
||||
// Parameters are: rawMFMData The raw data to be written. This is an actual MFM stream, going from MSB to LSB for each byte
|
||||
// numBytes Number of bits in the buffer to write
|
||||
|
||||
@ -111,6 +111,11 @@ protected:
|
||||
virtual ReadResponse readData(PLL::BridgePLL& pll, const unsigned int maxBufferSize, RotationExtractor::MFMSample* buffer, RotationExtractor::IndexSequenceMarker& indexMarker,
|
||||
std::function<bool(RotationExtractor::MFMSample* mfmData, const unsigned int dataLengthInBits)> onRotation) override;
|
||||
|
||||
// Called for a direct read. This does not match up a rotation and should be used with the pll initialized with the LinearExtractor
|
||||
// pll: required
|
||||
// Returns: ReadResponse, explains its self
|
||||
virtual ReadResponse readLinearData(PLL::BridgePLL& pll) override;
|
||||
|
||||
// Called when a cylinder revolution should be written to the disk.
|
||||
// Parameters are: rawMFMData The raw data to be written. This is an actual MFM stream, going from MSB to LSB for each byte
|
||||
// numBytes Number of bits in the buffer to write
|
||||
@ -137,4 +142,4 @@ public:
|
||||
|
||||
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
199
external/floppybridge/src/ArduinoInterface.cpp
vendored
199
external/floppybridge/src/ArduinoInterface.cpp
vendored
@ -186,6 +186,7 @@ ArduinoInterface::ArduinoInterface() {
|
||||
|
||||
// Free me
|
||||
ArduinoInterface::~ArduinoInterface() {
|
||||
if (m_tempBuffer) free(m_tempBuffer);
|
||||
abortReadStreaming();
|
||||
closePort();
|
||||
}
|
||||
@ -991,6 +992,202 @@ DiagnosticResponse ArduinoInterface::readCurrentTrack(RawTrackDataDD& trackData,
|
||||
return readCurrentTrack(&trackData, RAW_TRACKDATA_LENGTH_DD, readFromIndexPulse);
|
||||
}
|
||||
|
||||
// Reads just enough data to fulfill most extractions needed, but doesnt care about rotation position or index - pll should have the LinearExtractor configured
|
||||
DiagnosticResponse ArduinoInterface::readData(PLL::BridgePLL& pll) {
|
||||
pll.rotationExtractor()->reset(m_isHDMode);
|
||||
LinearExtractor* linearExtractor = dynamic_cast<LinearExtractor*>(pll.rotationExtractor());
|
||||
if (!linearExtractor) return DiagnosticResponse::drError;
|
||||
|
||||
if (!m_tempBuffer) {
|
||||
m_tempBuffer = malloc(RAW_TRACKDATA_LENGTH_HD);
|
||||
if (!m_tempBuffer) return DiagnosticResponse::drError;
|
||||
}
|
||||
|
||||
const uint32_t sizeRequired = m_isHDMode ? RAW_TRACKDATA_LENGTH_HD : RAW_TRACKDATA_LENGTH_DD;
|
||||
DiagnosticResponse response = readCurrentTrack(m_tempBuffer, sizeRequired, false);
|
||||
if (response != DiagnosticResponse::drOK) return response;
|
||||
linearExtractor->copyToBuffer(m_tempBuffer, sizeRequired);
|
||||
return response;
|
||||
|
||||
m_lastCommand = LastCommand::lcReadTrackStream;
|
||||
|
||||
if (m_version.major == 1 && m_version.minor < 8) {
|
||||
m_lastError = DiagnosticResponse::drOldFirmware;
|
||||
return m_lastError;
|
||||
}
|
||||
|
||||
const bool highPrecisionMode = !m_isHDMode && m_version.deviceFlags1 & FLAGS_HIGH_PRECISION_SUPPORT;
|
||||
char mode = highPrecisionMode ? COMMAND_READTRACKSTREAM_HIGHPRECISION : COMMAND_READTRACKSTREAM;
|
||||
|
||||
if (mode == COMMAND_READTRACKSTREAM_HIGHPRECISION && m_version.deviceFlags1 & FLAGS_FLUX_READ) mode = COMMAND_READTRACKSTREAM_HALFPLL;
|
||||
m_lastError = runCommand(mode);
|
||||
if (m_lastError != DiagnosticResponse::drOK) return m_lastError;
|
||||
m_isStreaming = true;
|
||||
|
||||
applyCommTimeouts(true);
|
||||
unsigned char tempReadBuffer[4096] = { 0 };
|
||||
|
||||
// Let the class know we're doing some streaming stuff
|
||||
m_abortStreaming = false;
|
||||
m_abortSignalled = false;
|
||||
|
||||
// Number of times we failed to read anything
|
||||
int32_t readFail = 0;
|
||||
|
||||
// Sliding window for abort
|
||||
char slidingWindow[5] = { 0,0,0,0,0 };
|
||||
bool timeout = false;
|
||||
bool dataState = false;
|
||||
bool isFirstByte = true;
|
||||
unsigned char mfmSequences = 0;
|
||||
|
||||
MFMExtractionTarget* extractor = pll.rotationExtractor();
|
||||
|
||||
for (;;) {
|
||||
|
||||
// More efficient to read several bytes in one go
|
||||
unsigned int bytesAvailable = m_comPort.getBytesWaiting();
|
||||
if (bytesAvailable < 1) bytesAvailable = 1;
|
||||
if (bytesAvailable > sizeof tempReadBuffer) bytesAvailable = sizeof tempReadBuffer;
|
||||
unsigned int bytesRead = m_comPort.read(tempReadBuffer, bytesAvailable);
|
||||
for (size_t a = 0; a < bytesRead; a++) {
|
||||
const unsigned char byteRead = tempReadBuffer[a];
|
||||
if (m_abortSignalled) {
|
||||
// Make space
|
||||
for (int s = 0; s < 4; s++) slidingWindow[s] = slidingWindow[s + 1];
|
||||
// Append the new byte
|
||||
slidingWindow[4] = byteRead;
|
||||
|
||||
// Watch the sliding window for the pattern we need
|
||||
if (slidingWindow[0] == 'X' && slidingWindow[1] == 'Y' && slidingWindow[2] == 'Z' && slidingWindow[3] == SPECIAL_ABORT_CHAR && slidingWindow[4] == '1') {
|
||||
m_isStreaming = false;
|
||||
m_comPort.purgeBuffers();
|
||||
m_lastError = timeout ? DiagnosticResponse::drError : DiagnosticResponse::drOK;
|
||||
applyCommTimeouts(false);
|
||||
return m_lastError;
|
||||
}
|
||||
}
|
||||
else {
|
||||
unsigned char tmp;
|
||||
RotationExtractor::MFMSequenceInfo sequence{};
|
||||
if (m_isHDMode) {
|
||||
for (int i = 6; i >= 0; i -= 2) {
|
||||
tmp = byteRead >> i & 0x03;
|
||||
sequence.mfm = (RotationExtractor::MFMSequence)((tmp == 0x03 ? 0 : tmp) + 1);
|
||||
sequence.timeNS = 2000 + (unsigned int)sequence.mfm * 2000;
|
||||
extractor->submitSequence(sequence, tmp == 0x03);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (highPrecisionMode) {
|
||||
if (isFirstByte) {
|
||||
// Throw away the first byte
|
||||
isFirstByte = false;
|
||||
if (byteRead != 0xC3) {
|
||||
// This should never happen.
|
||||
abortReadStreaming();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (dataState) {
|
||||
|
||||
if (mode == COMMAND_READTRACKSTREAM_HALFPLL) {
|
||||
const int32_t sequences[4] = { (mfmSequences >> 6) & 0x03, (mfmSequences >> 4) & 0x03, (mfmSequences >> 2) & 0x03, mfmSequences & 0x03 };
|
||||
|
||||
// First remove any flux time that us 000 as thats always fixed
|
||||
int32_t readSpeed = (unsigned int)(byteRead & 0x7F) * 2000 / 128;
|
||||
|
||||
// Output the fluxes
|
||||
for (int a = 0; a < 4; a++) {
|
||||
if (sequences[a] == 0) {
|
||||
sequence.mfm = RotationExtractor::MFMSequence::mfm000;
|
||||
sequence.timeNS = 5000 + readSpeed;
|
||||
}
|
||||
else {
|
||||
sequence.mfm = (RotationExtractor::MFMSequence)sequences[a];
|
||||
sequence.timeNS = 1000 + readSpeed + (((int)sequence.mfm) * 2000);
|
||||
}
|
||||
sequence.pllTimeNS = sequence.timeNS;
|
||||
extractor->submitSequence(sequence, ((byteRead & 0x80) != 0) && (a == 0));
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
int32_t readSpeed = (unsigned int)(byteRead & 0x7F) * 2000 / 128;
|
||||
tmp = mfmSequences >> 6 & 0x03;
|
||||
sequence.mfm = tmp == 0 ? RotationExtractor::MFMSequence::mfm000 : (RotationExtractor::MFMSequence)(tmp);
|
||||
sequence.timeNS = 1000 + (unsigned int)sequence.mfm * 2000 + (sequence.mfm == RotationExtractor::MFMSequence::mfm000 ? -2000 : readSpeed);
|
||||
extractor->submitSequence(sequence, (byteRead & 0x80) != 0);
|
||||
|
||||
tmp = mfmSequences >> 4 & 0x03;
|
||||
sequence.mfm = tmp == 0 ? RotationExtractor::MFMSequence::mfm000 : (RotationExtractor::MFMSequence)(tmp);
|
||||
sequence.timeNS = 1000 + (unsigned int)sequence.mfm * 2000 + (sequence.mfm == RotationExtractor::MFMSequence::mfm000 ? -2000 : readSpeed);
|
||||
extractor->submitSequence(sequence, false);
|
||||
|
||||
tmp = mfmSequences >> 2 & 0x03;
|
||||
sequence.mfm = tmp == 0 ? RotationExtractor::MFMSequence::mfm000 : (RotationExtractor::MFMSequence)(tmp);
|
||||
sequence.timeNS = 1000 + (unsigned int)sequence.mfm * 2000 + (sequence.mfm == RotationExtractor::MFMSequence::mfm000 ? -2000 : readSpeed);
|
||||
extractor->submitSequence(sequence, false);
|
||||
|
||||
tmp = mfmSequences & 0x03;
|
||||
sequence.mfm = tmp == 0 ? RotationExtractor::MFMSequence::mfm000 : (RotationExtractor::MFMSequence)(tmp);
|
||||
sequence.timeNS = 1000 + (unsigned int)sequence.mfm * 2000 + (sequence.mfm == RotationExtractor::MFMSequence::mfm000 ? -2000 : readSpeed);
|
||||
extractor->submitSequence(sequence, false);
|
||||
}
|
||||
|
||||
dataState = false;
|
||||
}
|
||||
else {
|
||||
// in 'false' mode we get the flux data
|
||||
dataState = true;
|
||||
mfmSequences = byteRead;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
unsigned short readSpeed = (unsigned long long)((unsigned int)((byteRead & 0x07) * 16) * 2000) / 128;
|
||||
|
||||
// Now packet up the data in the format the rotation extractor expects it to be in
|
||||
tmp = byteRead >> 5 & 0x03;
|
||||
sequence.mfm = tmp == 0 ? RotationExtractor::MFMSequence::mfm000 : (RotationExtractor::MFMSequence)(tmp);
|
||||
sequence.timeNS = 1000 + (unsigned int)sequence.mfm * 2000 + readSpeed;
|
||||
|
||||
extractor->submitSequence(sequence, (byteRead & 0x80) != 0);
|
||||
|
||||
tmp = byteRead >> 3 & 0x03;
|
||||
sequence.mfm = tmp == 0 ? RotationExtractor::MFMSequence::mfm000 : (RotationExtractor::MFMSequence)(tmp);
|
||||
sequence.timeNS = 1000 + (unsigned int)sequence.mfm * 2000 + readSpeed;
|
||||
|
||||
extractor->submitSequence(sequence, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Is it ready to extract? - takes 15-16ms to abort so take that into account
|
||||
if ((extractor->canExtract() || extractor->totalTimeReceived()>(220000000 - 14000000))) {
|
||||
if (!m_abortSignalled) abortReadStreaming();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bytesRead < 1) {
|
||||
readFail++;
|
||||
if (readFail > 30) {
|
||||
m_abortStreaming = false; // force the 'abort' command to be sent
|
||||
abortReadStreaming();
|
||||
m_lastError = DiagnosticResponse::drReadResponseFailed;
|
||||
m_isStreaming = false;
|
||||
applyCommTimeouts(false);
|
||||
return m_lastError;
|
||||
}
|
||||
else {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
else {
|
||||
readFail = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read RAW data from the current track and surface HD mode
|
||||
DiagnosticResponse ArduinoInterface::readCurrentTrack(void* trackData, const int dataLength, const bool readFromIndexPulse) {
|
||||
m_lastCommand = LastCommand::lcReadTrack;
|
||||
@ -1161,7 +1358,7 @@ DiagnosticResponse ArduinoInterface::readCurrentTrack(void* trackData, const int
|
||||
|
||||
// Reads a complete rotation of the disk, and returns it using the callback function which can return FALSE to stop
|
||||
// An instance of RotationExtractor is required. This is purely to save on re-allocations. It is internally reset each time
|
||||
DiagnosticResponse ArduinoInterface::readRotation(RotationExtractor& extractor, const unsigned int maxOutputSize, RotationExtractor::MFMSample* firstOutputBuffer, RotationExtractor::IndexSequenceMarker& startBitPatterns, std::function<bool(RotationExtractor::MFMSample** mfmData, const unsigned int dataLengthInBits)> onRotation, bool useHalfPLL) {
|
||||
DiagnosticResponse ArduinoInterface::readRotation(MFMExtractionTarget& extractor, const unsigned int maxOutputSize, RotationExtractor::MFMSample* firstOutputBuffer, RotationExtractor::IndexSequenceMarker& startBitPatterns, std::function<bool(RotationExtractor::MFMSample** mfmData, const unsigned int dataLengthInBits)> onRotation, bool useHalfPLL) {
|
||||
m_lastCommand = LastCommand::lcReadTrackStream;
|
||||
|
||||
if (m_version.major == 1 && m_version.minor < 8) {
|
||||
|
||||
6
external/floppybridge/src/ArduinoInterface.h
vendored
6
external/floppybridge/src/ArduinoInterface.h
vendored
@ -171,6 +171,7 @@ namespace ArduinoFloppyReader {
|
||||
bool m_isStreaming;
|
||||
bool m_isHDMode;
|
||||
std::mutex m_protectAbort;
|
||||
void* m_tempBuffer = nullptr;
|
||||
|
||||
// Read a desired number of bytes into the target pointer
|
||||
bool deviceRead(void* target, const unsigned int numBytes, const bool failIfNotAllRead = false);
|
||||
@ -248,7 +249,7 @@ namespace ArduinoFloppyReader {
|
||||
|
||||
// Reads a complete rotation of the disk, and returns it using the callback function which can return FALSE to stop
|
||||
// An instance of BridgePLL is required.
|
||||
DiagnosticResponse readRotation(RotationExtractor& extractor, const unsigned int maxOutputSize, RotationExtractor::MFMSample* firstOutputBuffer, RotationExtractor::IndexSequenceMarker& startBitPatterns, std::function<bool(RotationExtractor::MFMSample** mfmData, const unsigned int dataLengthInBits)> onRotation, bool useHalfPLL);
|
||||
DiagnosticResponse readRotation(MFMExtractionTarget& extractor, const unsigned int maxOutputSize, RotationExtractor::MFMSample* firstOutputBuffer, RotationExtractor::IndexSequenceMarker& startBitPatterns, std::function<bool(RotationExtractor::MFMSample** mfmData, const unsigned int dataLengthInBits)> onRotation, bool useHalfPLL);
|
||||
// Same as the above, but this uses the newer much more accurate flux read
|
||||
DiagnosticResponse readFlux(PLL::BridgePLL& pll, const unsigned int maxOutputSize, RotationExtractor::MFMSample* firstOutputBuffer, RotationExtractor::IndexSequenceMarker& startBitPatterns, std::function<bool(RotationExtractor::MFMSample** mfmData, const unsigned int dataLengthInBits)> onRotation);
|
||||
|
||||
@ -289,6 +290,9 @@ namespace ArduinoFloppyReader {
|
||||
// Choose which surface of the disk to read from
|
||||
DiagnosticResponse selectSurface(const DiskSurface side);
|
||||
|
||||
// Reads just enough data to fulfill most extractions needed, but doesnt care about rotation position or index - pll should have the LinearExtractor configured
|
||||
DiagnosticResponse readData(PLL::BridgePLL& pll);
|
||||
|
||||
// Read RAW data from the current track and surface selected - this works properly with the HD and DD options
|
||||
// dataLength must be either RAW_TRACKDATA_LENGTH or RAW_TRACKDATA_LENGTH_HD. If you mismatch the type then the function will return an error
|
||||
DiagnosticResponse readCurrentTrack(void* trackData, const int dataLength, const bool readFromIndexPulse);
|
||||
|
||||
@ -807,7 +807,8 @@ bool CommonBridgeTemplate::isMFMDataAvailable() {
|
||||
}
|
||||
|
||||
// Requests an entire track of data. Returns 0 if the track is not available
|
||||
// The return value is the wrap point in bits (last byte is shifted to MSB)
|
||||
// The return value is the wrap point in bits (last byte is shifted to MSB) or in Direct mode, just the number of bits received
|
||||
// resyncRotation is ignored in direct mode
|
||||
int CommonBridgeTemplate::getMFMTrack(bool side, unsigned int track, bool resyncRotation, const int bufferSizeInBytes, void* output) {
|
||||
if (m_directMode) {
|
||||
threadLockControl(true);
|
||||
@ -831,37 +832,22 @@ int CommonBridgeTemplate::getMFMTrack(bool side, unsigned int track, bool resync
|
||||
setActiveSurface(_side);
|
||||
}
|
||||
|
||||
MFMCache& trackData = m_mfmRead[m_actualCurrentCylinder][(int)m_actualFloppySide].next;
|
||||
trackData.amountReadInBits = 0;
|
||||
trackData.ready = false;
|
||||
m_linearExtractor.setOutputBuffer(output, bufferSizeInBytes);
|
||||
// Switch to linear extractor
|
||||
m_pll.setRotationExtractor(&m_linearExtractor);
|
||||
|
||||
m_extractor.setAlwaysUseIndex(resyncRotation);
|
||||
ReadResponse r = readLinearData(m_pll);
|
||||
// put it back!
|
||||
m_pll.setRotationExtractor(&m_extractor);
|
||||
|
||||
// Grab full revolutions if possible.
|
||||
ReadResponse r = readData(m_pll, bufferSizeInBytes, trackData.mfmBuffer,
|
||||
m_mfmRead[m_actualCurrentCylinder][(int)m_actualFloppySide].startBitPatterns,
|
||||
[this, &trackData](RotationExtractor::MFMSample* mfmData, const unsigned int dataLengthInBits) -> bool {
|
||||
trackData.amountReadInBits = dataLengthInBits;
|
||||
return false; // stop reading
|
||||
});
|
||||
// Prevent disk check while we're doing this
|
||||
m_lastDiskCheckTime = std::chrono::steady_clock::now();
|
||||
|
||||
// Release thread
|
||||
threadLockControl(false);
|
||||
|
||||
RotationExtractor::MFMSample* sample = trackData.mfmBuffer;
|
||||
const int bitsRemaining = trackData.amountReadInBits;
|
||||
const int bytesToCopy = std::min((bitsRemaining + 7) / 8, bufferSizeInBytes);
|
||||
unsigned char* outByte = (unsigned char*)output;
|
||||
|
||||
for (int byte = 0; byte < bytesToCopy; byte++) {
|
||||
*outByte++ = sample->mfmData;
|
||||
sample++;
|
||||
}
|
||||
|
||||
// Clear the data read
|
||||
trackData.amountReadInBits = 0;
|
||||
|
||||
return bitsRemaining;
|
||||
// Finalise the output and get the mumber of bits
|
||||
return m_linearExtractor.finaliseAndGetNumBits();
|
||||
}
|
||||
|
||||
// Be in the right place
|
||||
@ -1388,6 +1374,7 @@ bool CommonBridgeTemplate::initialise() {
|
||||
internalSetMotorStatus(false);
|
||||
m_autoCacheMotorStatus = false;
|
||||
setActiveSurface(m_actualFloppySide);
|
||||
setCurrentCylinder(0);
|
||||
|
||||
// Get some real disk status values
|
||||
if (supportsDiskChange()) {
|
||||
@ -1595,6 +1582,9 @@ bool CommonBridgeTemplate::writeMFMTrackToBuffer(bool side, unsigned int track,
|
||||
m_writePending = false;
|
||||
m_writeComplete = true;
|
||||
|
||||
// Prevent disk check while we're doing this
|
||||
m_lastDiskCheckTime = std::chrono::steady_clock::now();
|
||||
|
||||
// Release thread
|
||||
threadLockControl(false);
|
||||
|
||||
|
||||
@ -126,6 +126,9 @@ private:
|
||||
// For extracting rotations from disks
|
||||
RotationExtractor m_extractor;
|
||||
|
||||
// For doing the same as above, but no searching for rotations, just get the data
|
||||
LinearExtractor m_linearExtractor;
|
||||
|
||||
// In direct mode FloppyBridge doesnt do some functions in the background
|
||||
// Its useful if you're using FloppyBridge just for direct access to the interfaces
|
||||
bool m_directMode;
|
||||
@ -409,6 +412,11 @@ protected:
|
||||
virtual ReadResponse readData(PLL::BridgePLL& pll, const unsigned int maxBufferSize, RotationExtractor::MFMSample* buffer, RotationExtractor::IndexSequenceMarker& indexMarker,
|
||||
std::function<bool(RotationExtractor::MFMSample* mfmData, const unsigned int dataLengthInBits)> onRotation) = 0;
|
||||
|
||||
// Called for a direct read. This does not match up a rotation and should be used with the pll initialized with the LinearExtractor
|
||||
// pll: required
|
||||
// Returns: ReadResponse, explains its self
|
||||
virtual ReadResponse readLinearData(PLL::BridgePLL& pll) = 0;
|
||||
|
||||
// Called when a cylinder revolution should be written to the disk.
|
||||
// Parameters are: rawMFMData The raw data to be written. This is an actual MFM stream, going from MSB to LSB for each byte
|
||||
// numBytes Number of bits in the buffer to write
|
||||
|
||||
58
external/floppybridge/src/FloppyBridge.cpp
vendored
58
external/floppybridge/src/FloppyBridge.cpp
vendored
@ -41,7 +41,8 @@
|
||||
#include <WinSock2.h>
|
||||
#include "bridgeProfileListEditor.h"
|
||||
#include "bridgeProfileEditor.h"
|
||||
|
||||
#include <WinDNS.h>
|
||||
#pragma comment(lib, "Dnsapi.lib")
|
||||
HINSTANCE hInstance;
|
||||
|
||||
#else
|
||||
@ -154,31 +155,56 @@ void handleAbout(bool checkForUpdates, FloppyBridge::BridgeAbout** output) {
|
||||
// Start winsock
|
||||
WSADATA data;
|
||||
WSAStartup(MAKEWORD(2, 0), &data);
|
||||
#endif
|
||||
// Fetch version from 'A' record in the DNS record
|
||||
|
||||
DNS_RECORD* dnsRecord;
|
||||
std::wstring versionString;
|
||||
|
||||
// Look up TXT record
|
||||
DNS_STATUS error = DnsQuery(L"floppybridge-amiga.robsmithdev.co.uk", DNS_TYPE_TEXT, DNS_QUERY_BYPASS_CACHE, NULL, &dnsRecord, NULL);
|
||||
if (!error) {
|
||||
const DNS_RECORD* record = dnsRecord;
|
||||
while (record) {
|
||||
if (record->wType == DNS_TYPE_TEXT) {
|
||||
const DNS_TXT_DATAW* pData = &record->Data.TXT;
|
||||
for (DWORD string = 0; string < pData->dwStringCount; string++) {
|
||||
const std::wstring text = pData->pStringArray[string];
|
||||
if ((text.length() >= 9) && (text.substr(0, 2) == L"v=")) versionString = text.substr(2);
|
||||
}
|
||||
}
|
||||
record = record->pNext;
|
||||
}
|
||||
DnsRecordListFree(dnsRecord, DnsFreeRecordList); //DnsFreeRecordListDeep
|
||||
}
|
||||
|
||||
if (!versionString.empty()) {
|
||||
// A little hacky to convert a.b.c.d into an array
|
||||
sockaddr_in tmp;
|
||||
INT len = sizeof(tmp);
|
||||
if (WSAStringToAddress((wchar_t*)versionString.c_str(), AF_INET, NULL, (sockaddr*)&tmp, &len) == 0) {
|
||||
const in_addr add = tmp.sin_addr;
|
||||
BridgeInformationUpdate.updateMajorVersion = add.S_un.S_un_b.s_b1;
|
||||
BridgeInformationUpdate.updateMinorVersion = add.S_un.S_un_b.s_b2;
|
||||
BridgeInformation.isUpdateAvailable = ((BridgeInformation.majorVersion < BridgeInformation.updateMajorVersion) ||
|
||||
((BridgeInformation.majorVersion == BridgeInformation.updateMajorVersion) && (BridgeInformation.minorVersion < BridgeInformation.updateMinorVersion))) ? 1 : 0;
|
||||
BridgeInformationUpdate.isUpdateAvailable = BridgeInformation.isUpdateAvailable;;
|
||||
}
|
||||
}
|
||||
#else
|
||||
// Fetch version from 'A' record in the DNS record - this is probably not used by anything anyway
|
||||
hostent* address = gethostbyname("floppybridge-amiga.robsmithdev.co.uk");
|
||||
if ((address) && (address->h_addrtype == AF_INET)) {
|
||||
if (address->h_addr_list[0] != 0) {
|
||||
in_addr add = *((in_addr*)address->h_addr_list[0]);
|
||||
|
||||
#ifdef _WIN32
|
||||
// BridgeInformation.updateMajorVersion = add.S_un.S_un_b.s_b1;
|
||||
// BridgeInformation.updateMinorVersion = add.S_un.S_un_b.s_b2;
|
||||
BridgeInformationUpdate.updateMajorVersion = add.S_un.S_un_b.s_b1;
|
||||
BridgeInformationUpdate.updateMinorVersion = add.S_un.S_un_b.s_b2;
|
||||
#else
|
||||
uint32_t bytes = htonl(add.s_addr);
|
||||
/// BridgeInformation.updateMajorVersion = bytes >> 24;
|
||||
// BridgeInformation.updateMinorVersion = (bytes >> 16) & 0xFF;
|
||||
BridgeInformationUpdate.updateMajorVersion = bytes >> 24;
|
||||
BridgeInformationUpdate.updateMinorVersion = (bytes >> 16) & 0xFF;
|
||||
#endif
|
||||
|
||||
BridgeInformation.isUpdateAvailable = ((BridgeInformation.majorVersion < BridgeInformation.updateMajorVersion) ||
|
||||
((BridgeInformation.majorVersion == BridgeInformation.updateMajorVersion) && (BridgeInformation.minorVersion < BridgeInformation.updateMinorVersion))) ? 1 : 0;
|
||||
|
||||
BridgeInformationUpdate.isUpdateAvailable = BridgeInformation.isUpdateAvailable;;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#ifdef _WIN32
|
||||
if (output)
|
||||
@ -422,7 +448,7 @@ extern "C" {
|
||||
// Returns a pointer to a string containing the details for a profile
|
||||
FLOPPYBRIDGE_API bool CALLING_CONVENSION BRIDGE_GetProfileConfigFromString(unsigned int profileID, char** profilesConfigString) {
|
||||
if (!profilesConfigString) return false;
|
||||
auto f = profileList.find(profileID);
|
||||
const auto f = profileList.find(profileID);
|
||||
if (f == profileList.end()) return false;
|
||||
|
||||
f->second->toString(profilesConfigString);
|
||||
@ -550,7 +576,7 @@ extern "C" {
|
||||
// Creates an instance of a driver from a config string. This will automatically choose the correct driver index
|
||||
FLOPPYBRIDGE_API bool CALLING_CONVENSION BRIDGE_CreateDriverFromProfileID(unsigned int profileID, BridgeOpened** bridgeDriverHandle) {
|
||||
if (!bridgeDriverHandle) return false;
|
||||
const auto f = profileList.find(profileID);
|
||||
auto f = profileList.find(profileID);
|
||||
if (f == profileList.end()) return false;
|
||||
|
||||
if (!BRIDGE_CreateDriver(f->second->bridgeIndex, bridgeDriverHandle)) return false;
|
||||
|
||||
37
external/floppybridge/src/GreaseWeazleBridge.cpp
vendored
37
external/floppybridge/src/GreaseWeazleBridge.cpp
vendored
@ -225,14 +225,14 @@ bool GreaseWeazleDiskBridge::performNoClickSeek() {
|
||||
if (!m_io.supportsDiskChange()) return true;
|
||||
|
||||
switch (m_io.performNoClickSeek()) {
|
||||
case GWResponse::drOK:
|
||||
updateLastManualCheckTime();
|
||||
return true;
|
||||
case GWResponse::drOldFirmware:
|
||||
return false;
|
||||
case GWResponse::drError:
|
||||
m_wasIOError = true;
|
||||
return false;
|
||||
case GWResponse::drOK:
|
||||
updateLastManualCheckTime();
|
||||
return true;
|
||||
case GWResponse::drOldFirmware:
|
||||
return false;
|
||||
case GWResponse::drError:
|
||||
m_wasIOError = true;
|
||||
return false;
|
||||
|
||||
}
|
||||
return false;
|
||||
@ -277,10 +277,25 @@ CommonBridgeTemplate::ReadResponse GreaseWeazleDiskBridge::readData(PLL::BridgeP
|
||||
case GWResponse::drOK: return ReadResponse::rrOK;
|
||||
case GWResponse::drNoDiskInDrive: return ReadResponse::rrNoDiskInDrive;
|
||||
default: return ReadResponse::rrError;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Called for a direct read. This does not match up a rotation and should be used with the pll initialized with the LinearExtractor
|
||||
// pll: required
|
||||
// Returns: ReadResponse, explains its self
|
||||
CommonBridgeTemplate::ReadResponse GreaseWeazleDiskBridge::readLinearData(PLL::BridgePLL& pll) {
|
||||
GWResponse result = m_io.readData(pll);
|
||||
m_motorTurnOnTime = std::chrono::steady_clock::now();
|
||||
|
||||
switch (result) {
|
||||
case GWResponse::drOK: return ReadResponse::rrOK;
|
||||
case GWResponse::drNoDiskInDrive: return ReadResponse::rrNoDiskInDrive;
|
||||
default: return ReadResponse::rrError;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Called when a cylinder revolution should be written to the disk.
|
||||
// Parameters are: rawMFMData The raw data to be written. This is an actual MFM stream, going from MSB to LSB for each byte
|
||||
// numBytes Number of bits in the buffer to write
|
||||
@ -316,4 +331,4 @@ void GreaseWeazleDiskBridge::poll() {
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@ -116,6 +116,11 @@ protected:
|
||||
virtual ReadResponse readData(PLL::BridgePLL& pll, const unsigned int maxBufferSize, RotationExtractor::MFMSample* buffer, RotationExtractor::IndexSequenceMarker& indexMarker,
|
||||
std::function<bool(RotationExtractor::MFMSample* mfmData, const unsigned int dataLengthInBits)> onRotation) override;
|
||||
|
||||
// Called for a direct read. This does not match up a rotation and should be used with the pll initialized with the LinearExtractor
|
||||
// pll: required
|
||||
// Returns: ReadResponse, explains its self
|
||||
virtual ReadResponse readLinearData(PLL::BridgePLL& pll) override;
|
||||
|
||||
// Called when a cylinder revolution should be written to the disk.
|
||||
// Parameters are: rawMFMData The raw data to be written. This is an actual MFM stream, going from MSB to LSB for each byte
|
||||
// numBytes Number of bits in the buffer to write
|
||||
@ -128,7 +133,6 @@ protected:
|
||||
// It's virtual as the default method issues a read and looks for data. If you have a better implementation then override this
|
||||
virtual bool attemptToDetectDiskChange() override;
|
||||
|
||||
|
||||
public:
|
||||
GreaseWeazleDiskBridge(FloppyBridge::BridgeMode bridgeMode, FloppyBridge::BridgeDensityMode bridgeDensity, bool enableAutoCache, bool useSmartSpeed, bool autoDetectComPort, char* comPort, FloppyBridge::DriveSelection drive);
|
||||
|
||||
@ -143,4 +147,4 @@ public:
|
||||
|
||||
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@ -915,20 +915,103 @@ inline void unpackStreamQueue(std::queue<unsigned char>& queue, PLLData& pllData
|
||||
|
||||
}
|
||||
|
||||
// Reads "enough" data to extract data from the disk. This doesnt care about creating a perfect revolution - pll should have the LinearExtractor configured
|
||||
GWResponse GreaseWeazleInterface::readData(PLL::BridgePLL& pll) {
|
||||
GWReadFlux header;
|
||||
|
||||
// 220ms is long enough for even the worst drives. If a revolution takes longer than this then theres a problem anyway
|
||||
header.ticks = nSecToTicks(220 * 1000 * 1000, m_gwVersionInformation.sample_freq);
|
||||
header.max_index = 0;
|
||||
header.max_index_linger = 0;
|
||||
|
||||
m_shouldAbortReading = false;
|
||||
|
||||
// Sample storage
|
||||
std::queue<unsigned char> queue;
|
||||
|
||||
// Reset ready for extraction
|
||||
pll.rotationExtractor()->reset(m_inHDMode);
|
||||
|
||||
selectDrive(true);
|
||||
|
||||
// Write request
|
||||
Ack response = Ack::Okay;
|
||||
if (!sendCommand(Cmd::ReadFlux, (void*)&header, sizeof(header), response)) {
|
||||
if (!m_motorIsEnabled) selectDrive(false);
|
||||
return GWResponse::drReadResponseFailed;
|
||||
}
|
||||
int32_t failCount = 0;
|
||||
|
||||
// Buffer to read into
|
||||
unsigned char tempReadBuffer[64] = { 0 };
|
||||
bool zeroDetected = false;
|
||||
|
||||
applyCommTimeouts(true);
|
||||
|
||||
PLLData pllData;
|
||||
pllData.freq = m_gwVersionInformation.sample_freq;
|
||||
|
||||
do {
|
||||
// More efficient to read several bytes in one go
|
||||
uint32_t bytesAvailable = m_comPort.getBytesWaiting();
|
||||
if (bytesAvailable < 1) bytesAvailable = 1;
|
||||
if (bytesAvailable > sizeof(tempReadBuffer)) bytesAvailable = sizeof(tempReadBuffer);
|
||||
uint32_t bytesRead = m_comPort.read(tempReadBuffer, m_shouldAbortReading ? 1 : bytesAvailable);
|
||||
if (bytesRead < 1) {
|
||||
failCount++;
|
||||
if (failCount > 10) break;
|
||||
}
|
||||
else failCount = 0;
|
||||
|
||||
// If theres this many we can process as this is the maximum we need to process
|
||||
if ((!m_shouldAbortReading) && (bytesRead)) {
|
||||
|
||||
for (uint32_t a = 0; a < bytesRead; a++) {
|
||||
queue.push(tempReadBuffer[a]);
|
||||
zeroDetected |= tempReadBuffer[a] == 0;
|
||||
}
|
||||
|
||||
unpackStreamQueue(queue, pllData, pll, m_inHDMode);
|
||||
}
|
||||
else {
|
||||
for (uint32_t a = 0; a < bytesRead; a++) zeroDetected |= tempReadBuffer[a] == 0;
|
||||
}
|
||||
} while (!zeroDetected);
|
||||
|
||||
applyCommTimeouts(false);
|
||||
|
||||
// Check for errors
|
||||
response = Ack::Okay;
|
||||
sendCommand(Cmd::GetFluxStatus, nullptr, 0, response);
|
||||
|
||||
if (!m_motorIsEnabled) selectDrive(false);
|
||||
|
||||
// Update this flag
|
||||
m_diskInDrive = response != Ack::NoIndex;
|
||||
|
||||
switch (response) {
|
||||
case Ack::FluxOverflow: return GWResponse::drSerialOverrun;
|
||||
case Ack::NoIndex: return GWResponse::drNoDiskInDrive;
|
||||
case Ack::Okay: return GWResponse::drOK;
|
||||
default: return GWResponse::drReadResponseFailed;
|
||||
}
|
||||
}
|
||||
|
||||
// Reads a complete rotation of the disk, and returns it using the callback function which can return FALSE to stop
|
||||
// An instance of RotationExtractor is required. This is purely to save on re-allocations. It is internally reset each time
|
||||
// This is slower than the above because this one focuses on an accurate rotation image of the data rather than just a stream
|
||||
GWResponse GreaseWeazleInterface::readRotation(PLL::BridgePLL& pll, const unsigned int maxOutputSize, RotationExtractor::MFMSample* firstOutputBuffer, RotationExtractor::IndexSequenceMarker& startBitPatterns,
|
||||
std::function<bool(RotationExtractor::MFMSample** mfmData, const unsigned int dataLengthInBits)> onRotation) {
|
||||
GWReadFlux header;
|
||||
|
||||
const uint32_t extraTime = OVERLAP_SEQUENCE_MATCHES * (OVERLAP_EXTRA_BUFFER) * 8000;
|
||||
const uint32_t extraTime = OVERLAP_SEQUENCE_MATCHES * (OVERLAP_EXTRA_BUFFER) * 8000; // this is approx 49mS
|
||||
if ((pll.rotationExtractor()->isInIndexMode()) || (!pll.rotationExtractor()->hasLearntRotationSpeed())) {
|
||||
header.ticks = 0;
|
||||
header.max_index = 1;
|
||||
header.max_index_linger = nSecToTicks((212 * 1000 * 1000) + extraTime, m_gwVersionInformation.sample_freq);
|
||||
header.max_index_linger = nSecToTicks((210 * 1000 * 1000) + extraTime, m_gwVersionInformation.sample_freq);
|
||||
}
|
||||
else {
|
||||
header.ticks = nSecToTicks((212 * 1000 * 1000) + extraTime, m_gwVersionInformation.sample_freq);
|
||||
header.ticks = nSecToTicks((210 * 1000 * 1000) + extraTime, m_gwVersionInformation.sample_freq);
|
||||
header.max_index = 0;
|
||||
header.max_index_linger = 0;
|
||||
}
|
||||
|
||||
@ -211,6 +211,9 @@ namespace GreaseWeazle {
|
||||
GWResponse readRotation(PLL::BridgePLL& pll, const unsigned int maxOutputSize, RotationExtractor::MFMSample* firstOutputBuffer, RotationExtractor::IndexSequenceMarker& startBitPatterns,
|
||||
std::function<bool(RotationExtractor::MFMSample** mfmData, const unsigned int dataLengthInBits)> onRotation);
|
||||
|
||||
// Reads "enough" data to extract data from the disk. This doesn't care about creating a perfect revolution - pll should have the LinearExtractor configured
|
||||
GWResponse readData(PLL::BridgePLL& pll);
|
||||
|
||||
// Turns on and off the reading interface. dontWait disables the GW timeout waiting, so you must instead. Returns drOK, drError,
|
||||
GWResponse enableMotor(const bool enable, const bool dontWait = false);
|
||||
|
||||
|
||||
72
external/floppybridge/src/RotationExtractor.cpp
vendored
72
external/floppybridge/src/RotationExtractor.cpp
vendored
@ -30,6 +30,7 @@
|
||||
// This isn't 100% perfect but does seem to work.
|
||||
|
||||
#include "RotationExtractor.h"
|
||||
#include <cstring>
|
||||
|
||||
RotationExtractor::RotationExtractor() : m_sequences(new MFMSequenceInfo[MAX_REVOLUTION_SEQUENCES]),
|
||||
m_initialSequences(
|
||||
@ -506,3 +507,74 @@ bool RotationExtractor::extractRotation(MFMSample* output, uint32_t& outputBits,
|
||||
// and success!
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Reset this back to "empty"
|
||||
void LinearExtractor::reset(bool isHD) {
|
||||
m_totalTime = 0;
|
||||
m_currentPosition = m_outputBuffer;
|
||||
m_outputStreamPos = 0;
|
||||
m_outputStreamBit = 0;
|
||||
}
|
||||
|
||||
// Set where the data should be saved to
|
||||
void LinearExtractor::setOutputBuffer(void* outputBuffer, const uint32_t bufferSizeInBytes) {
|
||||
m_outputBuffer = (uint8_t*)outputBuffer;
|
||||
reset(false);
|
||||
m_totalSize = bufferSizeInBytes;
|
||||
}
|
||||
|
||||
// Write a 0 or 1 to the bit stream
|
||||
inline void LinearExtractor::writeLinearBit(const bool value) {
|
||||
if (!m_currentPosition) return;
|
||||
(*m_currentPosition) <<= 1;
|
||||
if (value) *m_currentPosition |= 1;
|
||||
m_outputStreamBit++;
|
||||
if (m_outputStreamBit >= 8) {
|
||||
m_outputStreamBit = 0;
|
||||
m_outputStreamPos++;
|
||||
if (m_outputStreamPos >= m_totalSize)
|
||||
m_currentPosition = nullptr;
|
||||
else m_currentPosition++;
|
||||
}
|
||||
}
|
||||
|
||||
// Copies the supplied buffer directly in
|
||||
void LinearExtractor::copyToBuffer(void* data, const uint32_t dataSize) {
|
||||
uint32_t amountToCopy = dataSize;
|
||||
if (amountToCopy > m_totalSize) amountToCopy = m_totalSize;
|
||||
if (!m_outputBuffer) return;
|
||||
if (!data) return;
|
||||
#ifdef _WIN32
|
||||
memcpy_s(m_outputBuffer, m_totalSize, data, amountToCopy);
|
||||
#else
|
||||
memcpy(m_outputBuffer, data, amountToCopy);
|
||||
#endif
|
||||
m_outputStreamPos = dataSize;
|
||||
m_outputStreamBit = 0;
|
||||
m_currentPosition = nullptr;
|
||||
}
|
||||
|
||||
|
||||
// Submit a single sequence to the list - abstract function
|
||||
void LinearExtractor::submitSequence(const MFMSequenceInfo& sequence, bool isIndex, bool discardEarlySamples) {
|
||||
if (!m_currentPosition) return;
|
||||
|
||||
m_totalTime += sequence.timeNS;
|
||||
|
||||
// And write the output stream
|
||||
uint32_t bitsToWrite = (uint32_t)sequence.mfm;
|
||||
if (bitsToWrite > 3) bitsToWrite = 3;
|
||||
for (uint32_t s = 0; s < bitsToWrite; s++) writeLinearBit(false);
|
||||
if (sequence.mfm != MFMSequence::mfm000) writeLinearBit(true);
|
||||
}
|
||||
|
||||
// Finalise the buffer (shifting the bits for the current byte into place) and returns the total number of bits received
|
||||
uint32_t LinearExtractor::finaliseAndGetNumBits() {
|
||||
// Shift the remaining bits into place
|
||||
if (m_outputStreamBit && m_currentPosition) {
|
||||
(*m_currentPosition) <<= 8 - m_outputStreamBit;
|
||||
m_currentPosition = nullptr;
|
||||
}
|
||||
return (m_outputStreamPos * 8) + m_outputStreamBit;
|
||||
}
|
||||
|
||||
100
external/floppybridge/src/RotationExtractor.h
vendored
100
external/floppybridge/src/RotationExtractor.h
vendored
@ -61,11 +61,9 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
// Class to extract a single rotation from an incoming mfm data sequence.
|
||||
class RotationExtractor {
|
||||
// A class that can receive data
|
||||
class MFMExtractionTarget {
|
||||
public:
|
||||
|
||||
// Enum for the possible sequences we support - mfm1 is not normally allowed.
|
||||
enum class MFMSequence : unsigned char { mfm1 = 0, mfm01 = 1, mfm001 = 2, mfm0001 = 3, mfm000 = 4 };
|
||||
|
||||
@ -109,6 +107,38 @@ public:
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
// Return the total amount of time data received so far
|
||||
[[nodiscard]] virtual uint32_t totalTimeReceived() const = 0;
|
||||
|
||||
// Get and set the sequence identified as data round the INDEX pulse so that next time we get consistent revolution starting points
|
||||
virtual void setIndexSequence(const IndexSequenceMarker& sequence) = 0;
|
||||
virtual void getIndexSequence(IndexSequenceMarker& sequence) const = 0;
|
||||
|
||||
// Reset this back to "empty"
|
||||
virtual void reset(bool isHD) = 0;
|
||||
|
||||
// Submit a single sequence to the list - abstract function
|
||||
virtual void submitSequence(const MFMSequenceInfo& sequence, bool isIndex, bool discardEarlySamples = true) = 0;
|
||||
|
||||
// Returns TRUE if we are readt to extract (eg: full revolution or buffer full)
|
||||
[[nodiscard]] virtual bool canExtract() const = 0;
|
||||
|
||||
// Returns TRUE if this has learnt the time of a disk revolution
|
||||
[[nodiscard]] virtual bool hasLearntRotationSpeed() const = 0;
|
||||
|
||||
// Returns TRUE if we're in INDEX mode
|
||||
[[nodiscard]] virtual bool isInIndexMode() const = 0;
|
||||
|
||||
// Extracts the data we have so far. Might need canExtract to be true depending on the implementation
|
||||
[[nodiscard]] virtual bool extractRotation(MFMSample* output, uint32_t& outputBits, uint32_t maxBufferSizeBytes, bool usePLLTime = false) = 0;
|
||||
|
||||
// I want the destructor virtual
|
||||
virtual ~MFMExtractionTarget() {};
|
||||
};
|
||||
|
||||
|
||||
// Class to extract a single rotation from an incoming mfm data sequence and ensure it's a perfect MFM rotation
|
||||
class RotationExtractor : public MFMExtractionTarget {
|
||||
private:
|
||||
// How long a revolution is
|
||||
uint32_t m_revolutionTime = 0;
|
||||
@ -158,11 +188,11 @@ public:
|
||||
virtual ~RotationExtractor();
|
||||
|
||||
// Get and set the sequence identified as data round the INDEX pulse so that next time we get consistent revolution starting points
|
||||
void setIndexSequence(const IndexSequenceMarker& sequence) { m_indexSequence = sequence; }
|
||||
void getIndexSequence(IndexSequenceMarker& sequence) const { sequence = m_indexSequence; }
|
||||
virtual void setIndexSequence(const IndexSequenceMarker& sequence) override { m_indexSequence = sequence; }
|
||||
virtual void getIndexSequence(IndexSequenceMarker& sequence) const override { sequence = m_indexSequence; }
|
||||
|
||||
// Reset this back to "empty"
|
||||
void reset(bool isHD);
|
||||
virtual void reset(bool isHD) override;
|
||||
|
||||
// Return TRUE if we're in HD mode
|
||||
[[nodiscard]] bool isHD() const { return m_isHD; }
|
||||
@ -182,13 +212,13 @@ public:
|
||||
void setRevolutionTime(const uint32_t time) { m_revolutionTime = time; m_revolutionTimeNearlyComplete = (uint32_t)(time * 0.9f); }
|
||||
|
||||
// Return the total amount of time data received so far
|
||||
[[nodiscard]] uint32_t totalTimeReceived() const { return m_timeReceived; }
|
||||
[[nodiscard]] virtual uint32_t totalTimeReceived() const override { return m_timeReceived; };
|
||||
|
||||
// Returns TRUE if this has learnt the time of a disk revolution
|
||||
[[nodiscard]] bool hasLearntRotationSpeed() const { return m_revolutionTime > (m_isHD ? 300000000U : 150000000U); }
|
||||
[[nodiscard]] bool hasLearntRotationSpeed() const override { return m_revolutionTime > (m_isHD ? 300000000U : 150000000U); }
|
||||
|
||||
// Returns TRUE if we're in INDEX mode
|
||||
[[nodiscard]] bool isInIndexMode() const { return m_useIndex; }
|
||||
[[nodiscard]] bool isInIndexMode() const override { return m_useIndex; }
|
||||
|
||||
// Sets the code so it always uses the index marker when finding revolutions
|
||||
void setAlwaysUseIndex(bool useIndex) { m_useIndex = useIndex; }
|
||||
@ -200,15 +230,59 @@ public:
|
||||
[[nodiscard]] bool isNearlyReady() const { return (m_revolutionTimeNearlyComplete) && (m_currentTime >= m_revolutionTimeNearlyComplete) && (!m_useIndex); }
|
||||
|
||||
// Submit a single sequence to the list
|
||||
void submitSequence(const MFMSequenceInfo& sequence, bool isIndex, bool discardEarlySamples = true);
|
||||
virtual void submitSequence(const MFMSequenceInfo& sequence, bool isIndex, bool discardEarlySamples = true) override;
|
||||
|
||||
// Returns TRUE if we should be able to extract a revolution
|
||||
[[nodiscard]] bool canExtract() const { return (m_revolutionReadyAt != INDEX_NOT_FOUND) && (m_revolutionReady) && (m_sequencePos>100); }
|
||||
[[nodiscard]] virtual bool canExtract() const override { return (m_revolutionReadyAt != INDEX_NOT_FOUND) && (m_revolutionReady) && (m_sequencePos>100); }
|
||||
|
||||
// Extracts a single rotation and updates the buffer to remove it. Returns FALSE if no rotation is available
|
||||
// If calculateSpeedFactor is true, we're in INDEX mode, and HIGH_RESOLUTION_MODE is defined then this will output time in NS rather than the speed factor value
|
||||
[[nodiscard]] bool extractRotation(MFMSample* output, uint32_t& outputBits, uint32_t maxBufferSizeBytes, bool usePLLTime = false);
|
||||
[[nodiscard]] virtual bool extractRotation(MFMSample* output, uint32_t& outputBits, uint32_t maxBufferSizeBytes, bool usePLLTime = false) override;
|
||||
};
|
||||
|
||||
|
||||
// Simple class to just receive that data being sent from the PLL into a linear MFM buffer. It doesn't try to find rotations
|
||||
class LinearExtractor : public MFMExtractionTarget {
|
||||
private:
|
||||
uint8_t* m_outputBuffer = nullptr;
|
||||
uint8_t* m_currentPosition = nullptr;
|
||||
uint32_t m_outputStreamPos = 0;
|
||||
uint32_t m_outputStreamBit = 0;
|
||||
uint32_t m_totalSize = 0;
|
||||
uint32_t m_totalTime = 0;
|
||||
|
||||
// Write a 0 or 1 to the bit stream
|
||||
inline void writeLinearBit(const bool value);
|
||||
public:
|
||||
// Dont care about this
|
||||
virtual void setIndexSequence(const IndexSequenceMarker& sequence) override {};
|
||||
virtual void getIndexSequence(IndexSequenceMarker& sequence) const override {};
|
||||
virtual bool hasLearntRotationSpeed() const override { return true; };
|
||||
virtual bool isInIndexMode() const override { return false; };
|
||||
virtual bool extractRotation(MFMSample* output, uint32_t& outputBits, uint32_t maxBufferSizeBytes, bool usePLLTime = false) override { return false; };
|
||||
|
||||
// Returns TRUE if we are readt to extract (eg: full revolution or buffer full)
|
||||
virtual bool canExtract() const override { return m_outputStreamPos >= m_totalSize; };
|
||||
|
||||
// Set where the data should be saved to
|
||||
void setOutputBuffer(void* outputBuffer, const uint32_t bufferSizeInBytes);
|
||||
|
||||
// Finalise the buffer (shifting the bits for the current byte into place) and returns the total number of bits received
|
||||
uint32_t finaliseAndGetNumBits();
|
||||
|
||||
// Return the total amount of time data received so far
|
||||
virtual uint32_t totalTimeReceived() const override { return m_totalTime; };
|
||||
|
||||
// Reset this back to "empty"
|
||||
virtual void reset(bool isHD) override;
|
||||
|
||||
// Copies the supplied buffer directly in
|
||||
void copyToBuffer(void* data, const uint32_t dataSize);
|
||||
|
||||
// Submit a single sequence to the list - abstract function
|
||||
virtual void submitSequence(const MFMSequenceInfo& sequence, bool isIndex, bool discardEarlySamples = true) override;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
19
external/floppybridge/src/SuperCardProBridge.cpp
vendored
19
external/floppybridge/src/SuperCardProBridge.cpp
vendored
@ -230,10 +230,25 @@ CommonBridgeTemplate::ReadResponse SupercardProDiskBridge::readData(PLL::BridgeP
|
||||
case SCPErr::scpOK: return ReadResponse::rrOK;
|
||||
case SCPErr::scpNoDiskInDrive: return ReadResponse::rrNoDiskInDrive;
|
||||
default: return ReadResponse::rrError;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Called for a direct read. This does not match up a rotation and should be used with the pll initialized with the LinearExtractor
|
||||
// pll: required
|
||||
// Returns: ReadResponse, explains its self
|
||||
CommonBridgeTemplate::ReadResponse SupercardProDiskBridge::readLinearData(PLL::BridgePLL& pll) {
|
||||
SCPErr result = m_io.readData(pll);
|
||||
m_motorTurnOnTime = std::chrono::steady_clock::now();
|
||||
|
||||
switch (result) {
|
||||
case SCPErr::scpOK: return ReadResponse::rrOK;
|
||||
case SCPErr::scpNoDiskInDrive: return ReadResponse::rrNoDiskInDrive;
|
||||
default: return ReadResponse::rrError;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Called when a cylinder revolution should be written to the disk.
|
||||
// Parameters are: rawMFMData The raw data to be written. This is an actual MFM stream, going from MSB to LSB for each byte
|
||||
// numBytes Number of bits in the buffer to write
|
||||
|
||||
@ -116,6 +116,11 @@ protected:
|
||||
virtual ReadResponse readData(PLL::BridgePLL& pll, const unsigned int maxBufferSize, RotationExtractor::MFMSample* buffer, RotationExtractor::IndexSequenceMarker& indexMarker,
|
||||
std::function<bool(RotationExtractor::MFMSample* mfmData, const unsigned int dataLengthInBits)> onRotation) override;
|
||||
|
||||
// Called for a direct read. This does not match up a rotation and should be used with the pll initialized with the LinearExtractor
|
||||
// pll: required
|
||||
// Returns: ReadResponse, explains its self
|
||||
virtual ReadResponse readLinearData(PLL::BridgePLL& pll) override;
|
||||
|
||||
// Called when a cylinder revolution should be written to the disk.
|
||||
// Parameters are: rawMFMData The raw data to be written. This is an actual MFM stream, going from MSB to LSB for each byte
|
||||
// numBytes Number of bits in the buffer to write
|
||||
@ -141,6 +146,5 @@ public:
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
127
external/floppybridge/src/SuperCardProInterface.cpp
vendored
127
external/floppybridge/src/SuperCardProInterface.cpp
vendored
@ -265,8 +265,8 @@ bool SCPInterface::enableMotor(const bool enable, const bool dontWait) {
|
||||
if (dontWait) {
|
||||
payload[0] = htons(1000); // Drive select delay (microseconds)
|
||||
payload[1] = htons(5000); // Step command delay (microseconds)
|
||||
payload[2] = htons(1); // Motor on delay (milliseconds)
|
||||
payload[3] = htons(1); // Track Seek Delay (Milliseconds)
|
||||
payload[2] = htons(150); // Motor on delay (milliseconds) - for some reason if less than 100 then seeking doesnt work properly
|
||||
payload[3] = htons(5); // Track Seek Delay (Milliseconds)
|
||||
payload[4] = htons(MOTOR_AUTOOFF_DELAY); // Before turning off the motor automatically (milliseconds)
|
||||
}
|
||||
else {
|
||||
@ -315,9 +315,10 @@ bool SCPInterface::performNoClickSeek() {
|
||||
|
||||
// Select the track, this makes the motor seek to this position
|
||||
bool SCPInterface::selectTrack(const unsigned char trackIndex, bool ignoreDiskInsertCheck) {
|
||||
if (m_currentTrack < 0) return false;
|
||||
//if (m_currentTrack < 0) return false;
|
||||
if (trackIndex < 0) return false;
|
||||
if (trackIndex > 81) return false;
|
||||
if (trackIndex > 83) return false;
|
||||
|
||||
|
||||
selectDrive(true);
|
||||
SCPResponse response;
|
||||
@ -918,6 +919,124 @@ SCPErr SCPInterface::readRotation(PLL::BridgePLL& pll, const unsigned int maxOut
|
||||
}
|
||||
}
|
||||
|
||||
// Reads just enough data to fulfill most extractions needed, but doesnt care about rotation position or index - pll should have the LinearExtractor configured
|
||||
SCPErr SCPInterface::readData(PLL::BridgePLL& pll) {
|
||||
SCPResponse response;
|
||||
|
||||
pll.rotationExtractor()->reset(m_isHDMode);
|
||||
|
||||
// Issue a read-request
|
||||
selectDrive(true);
|
||||
|
||||
// Request real-time flux stream, 8-bit resolution, and 50ns timings.
|
||||
const unsigned char payload[1] = { STREAMFLAGS_RESOLUTION_50NS | FLAGS_BITSIZE_8BIT | STREAMFLAGS_IMMEDIATEREAD | STREAMFLAGS_RAWFLUX };
|
||||
bool ret = sendCommand(SCPCommand::DoCMD_STARTSTREAM, payload, 1, response);
|
||||
if (!ret) {
|
||||
if (!m_motorIsEnabled) selectDrive(false);
|
||||
if ((response == SCPResponse::pr_NotReady) || (response == SCPResponse::pr_NoDisk)) {
|
||||
m_diskInDrive = false;
|
||||
return SCPErr::scpNoDiskInDrive;
|
||||
}
|
||||
return SCPErr::scpUnknownError;
|
||||
}
|
||||
|
||||
applyCommTimeouts(true);
|
||||
unsigned char tempReadBuffer[4096] = { 0 };
|
||||
|
||||
m_isStreaming = true;
|
||||
m_abortStreaming = false;
|
||||
m_abortSignalled = false;
|
||||
|
||||
// Number of times we failed to read anything
|
||||
int readFail = 0;
|
||||
|
||||
// Sliding window for abort
|
||||
unsigned char slidingWindow[4] = { 0,0,0,0 };
|
||||
bool timeout = false;
|
||||
bool dataState = false;
|
||||
bool isFirstByte = true;
|
||||
unsigned char mfmSequences = 0;
|
||||
int hitIndex = 0;
|
||||
uint32_t tickInNS = 0;
|
||||
|
||||
for (;;) {
|
||||
|
||||
// More efficient to read several bytes in one go
|
||||
unsigned int bytesAvailable = m_comPort.getBytesWaiting();
|
||||
if (bytesAvailable < 1) bytesAvailable = 1;
|
||||
if (bytesAvailable > sizeof(tempReadBuffer)) bytesAvailable = sizeof(tempReadBuffer);
|
||||
unsigned int bytesRead = m_comPort.read(tempReadBuffer, m_abortSignalled ? 1 : bytesAvailable);
|
||||
for (size_t a = 0; a < bytesRead; a++) {
|
||||
const unsigned char byteRead = tempReadBuffer[a];
|
||||
|
||||
if (m_abortSignalled) {
|
||||
for (int s = 0; s < 3; s++) slidingWindow[s] = slidingWindow[s + 1];
|
||||
slidingWindow[3] = byteRead;
|
||||
|
||||
// Watch the sliding window for the pattern we need
|
||||
if ((slidingWindow[0] == 0xDE) && (slidingWindow[1] == 0xAD) && (slidingWindow[2] == (unsigned char)SCPCommand::DoCMD_STOPSTREAM)) {
|
||||
m_isStreaming = false;
|
||||
m_comPort.purgeBuffers();
|
||||
applyCommTimeouts(false);
|
||||
if (!m_diskInDrive) return SCPErr::scpNoDiskInDrive;
|
||||
if (timeout) return SCPErr::scpUnknownError;
|
||||
if (slidingWindow[3] == (unsigned char)SCPResponse::pr_Overrun) return SCPErr::scpOverrun;
|
||||
return SCPErr::scpOK;
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (byteRead) {
|
||||
case 0xFF:
|
||||
hitIndex = 1;
|
||||
break;
|
||||
case 0x00:
|
||||
if (hitIndex == 1) hitIndex = 2; else {
|
||||
tickInNS += m_isHDMode ? (256 * 100) : (256 * 50);
|
||||
hitIndex = 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
pll.submitFlux(tickInNS + (m_isHDMode ? (byteRead * 100UL) : (byteRead * 50UL)), hitIndex == 2);
|
||||
tickInNS = 0;
|
||||
hitIndex = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Is it ready to extract?
|
||||
if ((pll.canExtract()) || (pll.totalTimeReceived() > 220000000)) {
|
||||
if (!m_abortSignalled) abortReadStreaming();
|
||||
}
|
||||
else
|
||||
if (pll.totalTimeReceived() > (m_isHDMode ? 1200000000U : 600000000U)) {
|
||||
// No data, stop
|
||||
abortReadStreaming();
|
||||
timeout = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bytesRead < 1) {
|
||||
readFail++;
|
||||
if (readFail > 30) {
|
||||
if (!m_abortStreaming) {
|
||||
abortReadStreaming();
|
||||
readFail = 0;
|
||||
m_diskInDrive = false;
|
||||
}
|
||||
else {
|
||||
m_isStreaming = false;
|
||||
applyCommTimeouts(false);
|
||||
return SCPErr::scpUnknownError;
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
else {
|
||||
readFail = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stops the read streaming immediately and any data in the buffer will be discarded.
|
||||
bool SCPInterface::abortReadStreaming() {
|
||||
|
||||
@ -177,6 +177,9 @@ namespace SuperCardPro {
|
||||
SCPErr readRotation(PLL::BridgePLL& pll, const unsigned int maxOutputSize, RotationExtractor::MFMSample* firstOutputBuffer, RotationExtractor::IndexSequenceMarker& startBitPatterns,
|
||||
std::function<bool(RotationExtractor::MFMSample** mfmData, const unsigned int dataLengthInBits)> onRotation);
|
||||
|
||||
// Reads just enough data to fulfill most extractions needed, but doesnt care about rotation position or index - pll should have the LinearExtractor configured
|
||||
SCPErr readData(PLL::BridgePLL& pll);
|
||||
|
||||
// Turns on and off the reading interface. dontWait disables the GW timeout waiting, so you must instead.
|
||||
bool enableMotor(const bool enable, const bool dontWait = false);
|
||||
|
||||
@ -216,4 +219,4 @@ namespace SuperCardPro {
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
typedef void* BridgeDriverHandle;
|
||||
|
||||
namespace FloppyBridge {
|
||||
@ -77,4 +78,4 @@ namespace FloppyBridge {
|
||||
|
||||
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
@ -20,7 +20,6 @@
|
||||
#include <locale>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
6
external/floppybridge/src/pll.h
vendored
6
external/floppybridge/src/pll.h
vendored
@ -55,7 +55,7 @@ namespace PLL {
|
||||
const bool m_enabled;
|
||||
|
||||
// Rotation extractor
|
||||
RotationExtractor* m_extractor = nullptr;
|
||||
MFMExtractionTarget* m_extractor = nullptr;
|
||||
|
||||
// Clock
|
||||
int32_t m_clock = 0;
|
||||
@ -93,14 +93,14 @@ namespace PLL {
|
||||
void prepareExtractor(bool isHD, const RotationExtractor::IndexSequenceMarker& indexSequence);
|
||||
|
||||
// Change the rotation extractor
|
||||
void setRotationExtractor(RotationExtractor* extractor) { m_extractor = extractor; }
|
||||
void setRotationExtractor(MFMExtractionTarget* extractor) { m_extractor = extractor; }
|
||||
|
||||
// Re-plays the data back into the rotation extractor but with (random) +/- 64ns of jitter
|
||||
void rePlayData(const unsigned int maxBufferSize, RotationExtractor::MFMSample* buffer, RotationExtractor::IndexSequenceMarker& indexMarker,
|
||||
std::function<bool(RotationExtractor::MFMSample* mfmData, const unsigned int dataLengthInBits)> onRotation);
|
||||
|
||||
// Return the active rotation extractor
|
||||
RotationExtractor* rotationExtractor() { return m_extractor; }
|
||||
MFMExtractionTarget* rotationExtractor() { return m_extractor; }
|
||||
|
||||
// Pass on some functions from the extractor
|
||||
bool canExtract() { return m_extractor->canExtract(); }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user