200 lines
8.0 KiB
C
200 lines
8.0 KiB
C
|
|
#ifndef READERWRITER_ROTATION_EXTRACTOR
|
||
|
|
#define READERWRITER_ROTATION_EXTRACTOR
|
||
|
|
/* ArduinoFloppyReader (and writer) - Rotation Extractor
|
||
|
|
*
|
||
|
|
* Copyright (C) 2017-2021 Robert Smith (@RobSmithDev)
|
||
|
|
* https://amiga.robsmithdev.co.uk
|
||
|
|
*
|
||
|
|
* This library is free software; you can redistribute it and/or
|
||
|
|
* modify it under the terms of the GNU Library General Public
|
||
|
|
* License as published by the Free Software Foundation; either
|
||
|
|
* version 3 of the License, or (at your option) any later version.
|
||
|
|
*
|
||
|
|
* This library is distributed in the hope that it will be useful,
|
||
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
|
* Library General Public License for more details.
|
||
|
|
*
|
||
|
|
* You should have received a copy of the GNU Library General Public
|
||
|
|
* License along with this library; if not, see http://www.gnu.org/licenses/
|
||
|
|
*/
|
||
|
|
|
||
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
// Class to manage finding *exact* disk revolutions //
|
||
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
//
|
||
|
|
// Purpose:
|
||
|
|
// The class attempts to guess where an exact disk revolution occurs, and then re-aligns
|
||
|
|
// it such that it starts at the index pulse. This means we dont need to wait for an index
|
||
|
|
// pulse to work out a revolution of the disk. The first time a disk is used we calculate
|
||
|
|
// the time of a single revolution and then use that as a guide to how long a revolution will
|
||
|
|
// take in the future.
|
||
|
|
//
|
||
|
|
// This isn't 100% perfect but does seem to work.
|
||
|
|
|
||
|
|
// With this defined you get speed data per bit. Undefined, per 8 bits. Nothing seems to notice much
|
||
|
|
// Inside *UAE it uses one speed value for 16 bits of MFM data so that's probably why.
|
||
|
|
// and besides, with this *undefined* we save about 700k of ram
|
||
|
|
#define HIGH_RESOLUTION_MODE
|
||
|
|
|
||
|
|
// Instead of outputting "speed" values this will output bit-times in ns
|
||
|
|
#define OUTPUT_TIME_IN_NS
|
||
|
|
|
||
|
|
// So worse case is the disk takes 210ms to spin, and every sequence is VERY fast, and every sequence is 01, this number is highly unlikely though
|
||
|
|
#define MAX_REVOLUTION_SEQUENCES 110000
|
||
|
|
|
||
|
|
// Number of sequences to match to find the index overlap position or the rotation overlap position
|
||
|
|
// The higher the number, the more chance of perfect revolution alignment, but higher processing required at the end of each revolution
|
||
|
|
#define OVERLAP_SEQUENCE_MATCHES 1024
|
||
|
|
|
||
|
|
// Extra window either side. this allows more of a search range
|
||
|
|
#define OVERLAP_EXTRA_BUFFER 6
|
||
|
|
|
||
|
|
// Signal for index was not found
|
||
|
|
#define INDEX_NOT_FOUND 0xFFFFFFFF
|
||
|
|
|
||
|
|
|
||
|
|
// Class to extract a single rotation from an incoming mfm data sequence.
|
||
|
|
class RotationExtractor {
|
||
|
|
public:
|
||
|
|
|
||
|
|
// Enum for the possible sequences we support
|
||
|
|
enum class MFMSequence : unsigned char { mfm01 = 0, mfm001 = 1, mfm0001 = 2, mfm0000 = 3 };
|
||
|
|
|
||
|
|
// A single sequence of MFM data
|
||
|
|
struct MFMSequenceInfo {
|
||
|
|
// Total time it took to read this in NS
|
||
|
|
unsigned short timeNS;
|
||
|
|
|
||
|
|
// The MFM sequence discovered
|
||
|
|
MFMSequence mfm;
|
||
|
|
};
|
||
|
|
|
||
|
|
// Decoded version of the above
|
||
|
|
struct MFMSample {
|
||
|
|
#ifdef OUTPUT_TIME_IN_NS
|
||
|
|
// This is the time for each 'bit'
|
||
|
|
unsigned short bittime[8];
|
||
|
|
#else
|
||
|
|
#ifdef HIGH_RESOLUTION_MODE
|
||
|
|
// This is the speed of each 'bit' as a %
|
||
|
|
unsigned short speed[8];
|
||
|
|
#else
|
||
|
|
// This is the average speed of all 8 bits as a %
|
||
|
|
unsigned short speed;
|
||
|
|
#endif
|
||
|
|
#endif
|
||
|
|
|
||
|
|
// This is the raw MFM bit-data
|
||
|
|
unsigned char mfmData;
|
||
|
|
};
|
||
|
|
|
||
|
|
// Struct for tracking what the index start looks like so we get it perfect (or at least consistant)
|
||
|
|
struct IndexSequenceMarker {
|
||
|
|
// Sequences found
|
||
|
|
MFMSequence sequences[OVERLAP_SEQUENCE_MATCHES];
|
||
|
|
|
||
|
|
// If this is actually valid
|
||
|
|
bool valid = false;
|
||
|
|
};
|
||
|
|
|
||
|
|
private:
|
||
|
|
// How long a revolution is
|
||
|
|
unsigned int m_revolutionTime = 0;
|
||
|
|
// An amount of time whereby we 'nearly' have a complete revolution of data
|
||
|
|
unsigned int m_revolutionTimeNearlyComplete = 0;
|
||
|
|
// Used while working out the above
|
||
|
|
unsigned int m_revolutionTimeCounting = 0;
|
||
|
|
// Where the first index pulse was discovered
|
||
|
|
unsigned int m_sequenceIndex = INDEX_NOT_FOUND;
|
||
|
|
// Where the second index pulse was discovered
|
||
|
|
unsigned int m_nextSequenceIndex = INDEX_NOT_FOUND;
|
||
|
|
// Where we were when we reached m_revolutionTime worth of data
|
||
|
|
unsigned int m_revolutionReadyAt = INDEX_NOT_FOUND;
|
||
|
|
// And a flag to set this as good
|
||
|
|
bool m_revolutionReady = false;
|
||
|
|
// If we should always use the index marker when finding revolutions
|
||
|
|
bool m_useIndex = false;
|
||
|
|
// Current amount of data in the buffer in ns
|
||
|
|
unsigned int m_currentTime = 0;
|
||
|
|
// Current position of the buffer
|
||
|
|
unsigned int m_sequencePos = 0;
|
||
|
|
// Used to track exactly how much data has been submitted
|
||
|
|
unsigned int m_timeReceived = 0;
|
||
|
|
// Sequences received thus far
|
||
|
|
MFMSequenceInfo m_sequences[MAX_REVOLUTION_SEQUENCES];
|
||
|
|
// In index mode, this holds the initial sequences before the first index marker
|
||
|
|
MFMSequenceInfo m_initialSequences[OVERLAP_SEQUENCE_MATCHES * OVERLAP_EXTRA_BUFFER];
|
||
|
|
// Length of the above datat in use
|
||
|
|
unsigned int m_initialSequencesLength = 0;
|
||
|
|
// Where we're writing to as its a circular buffer
|
||
|
|
unsigned int m_initialSequencesWritePos = 0;
|
||
|
|
// Sequences discovered around the index marker
|
||
|
|
IndexSequenceMarker m_indexSequence;
|
||
|
|
|
||
|
|
// Finds the overlap between the start of the data and where we currently are
|
||
|
|
unsigned int getOverlapPosition() const;
|
||
|
|
|
||
|
|
// is almost identical
|
||
|
|
const unsigned int getTrueIndexPosition(const unsigned int revolutionEnd, const unsigned int startingPoint = INDEX_NOT_FOUND);
|
||
|
|
public:
|
||
|
|
// Get and set the sequence identified as data round the INDEX pulse so that next time we get consistant revolution starting points
|
||
|
|
void setIndexSequence(const IndexSequenceMarker& sequence) { m_indexSequence = sequence; }
|
||
|
|
void getIndexSequence(IndexSequenceMarker& sequence) const { sequence = m_indexSequence; }
|
||
|
|
|
||
|
|
// Reset this back to "empty"
|
||
|
|
inline void reset() {
|
||
|
|
m_indexSequence.valid = false;
|
||
|
|
m_revolutionReadyAt = INDEX_NOT_FOUND;
|
||
|
|
m_sequencePos = 0;
|
||
|
|
m_sequenceIndex = INDEX_NOT_FOUND;
|
||
|
|
m_nextSequenceIndex = INDEX_NOT_FOUND;
|
||
|
|
m_currentTime = 0;
|
||
|
|
m_revolutionReady = false;
|
||
|
|
m_initialSequencesLength = 0;
|
||
|
|
m_initialSequencesWritePos = 0;
|
||
|
|
m_timeReceived = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Signal new disk, or maybe a motor restarted. Need to re-calculate rotation speed
|
||
|
|
inline void newDisk() {
|
||
|
|
reset();
|
||
|
|
m_revolutionTime = 0;
|
||
|
|
m_revolutionTimeCounting = 0;
|
||
|
|
m_revolutionTimeNearlyComplete = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Return the current revolution time
|
||
|
|
inline unsigned int getRevolutionTime() const { return m_revolutionTime; };
|
||
|
|
|
||
|
|
// Set the current revolution time
|
||
|
|
inline void setRevolutionTime(const unsigned int time) { m_revolutionTime = time; m_revolutionTimeNearlyComplete = (unsigned int)(time * 0.9f); };
|
||
|
|
|
||
|
|
// Return the total amount of time data received so far
|
||
|
|
inline unsigned int totalTimeReceived() const { return m_timeReceived; };
|
||
|
|
|
||
|
|
// Returns TRUE if this has learnt the time of a disk revolution
|
||
|
|
inline bool hasLearntRotationSpeed() const { return m_revolutionTime > 150000000; };
|
||
|
|
|
||
|
|
// Returns TRUE if we're in INDEX mode
|
||
|
|
inline bool isInIndexMode() const { return m_useIndex; };
|
||
|
|
|
||
|
|
// Sets the code so it always uses the index marker when finding revolutions
|
||
|
|
void setAlwaysUseIndex(bool useIndex) { m_useIndex = useIndex; };
|
||
|
|
|
||
|
|
// If this is about to spit out a revolution in a very small amount of time
|
||
|
|
inline bool isNearlyReady() const { return (m_revolutionTimeNearlyComplete) && (m_currentTime >= m_revolutionTimeNearlyComplete) && (!m_useIndex); }
|
||
|
|
|
||
|
|
// Submit a single sequence to the list
|
||
|
|
void submitSequence(const MFMSequenceInfo& sequence, const bool isIndex);
|
||
|
|
|
||
|
|
// Returns TRUE if we should be able to extract a revolution
|
||
|
|
inline bool canExtract() const { return (m_revolutionReadyAt != INDEX_NOT_FOUND) && (m_revolutionReady); };
|
||
|
|
|
||
|
|
// 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
|
||
|
|
bool extractRotation(MFMSample* output, unsigned int& outputBits, const unsigned int maxBufferSizeBytes);
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
#endif
|