mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
Radically rework decoding: there's now a simple statistical clock guesser, and
then we read the pulsetrain into nice tidy bits with a proper clock, which makes the decoder's job way easier. We can actually get rid of the entire MFM decoder state machine. Also, after sorting out the magic bit patterns at the beginning of records, we can now reliably pull them out of the bitstream without needing to know anything about the records themselves.
This commit is contained in:
2
Makefile
2
Makefile
@@ -12,10 +12,10 @@ SRCS = \
|
||||
cmd_read.c \
|
||||
cmd_write.c \
|
||||
cmd_mfmdecode.c \
|
||||
cmd_fmdecode.c \
|
||||
cmd_testpattern.c \
|
||||
cmd_fluxdump.c \
|
||||
cmd_calibrate.c \
|
||||
cmd_getclock.c \
|
||||
|
||||
OBJS = $(patsubst %.c, .objs/%.o, $(SRCS))
|
||||
|
||||
|
||||
256
cmd_fmdecode.c
256
cmd_fmdecode.c
@@ -1,256 +0,0 @@
|
||||
#include "globals.h"
|
||||
#include "sql.h"
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
|
||||
#define DEBOUNCE_TICKS 0x28
|
||||
|
||||
#define CLOCK_LOCK_BOOST 6 /* arbitrary */
|
||||
#define CLOCK_LOCK_DECAY 1 /* arbitrary */
|
||||
#define CLOCK_DETECTOR_AMPLITUDE_THRESHOLD 60 /* arbi4rary */
|
||||
#define CLOCK_ERROR_BOUNDS 0.50
|
||||
|
||||
static const char* inputfilename = NULL;
|
||||
static const char* outputfilename = NULL;
|
||||
static bool verbose = false;
|
||||
static sqlite3* indb;
|
||||
static sqlite3* outdb;
|
||||
|
||||
static const uint8_t* inputbuffer;
|
||||
static int inputlen;
|
||||
static int cursor;
|
||||
static int elapsed_ticks;
|
||||
|
||||
static int clock_period; /* mfm cell width */
|
||||
static int period0; /* lower limit for a short transition */
|
||||
static int period1; /* between short and long transitions */
|
||||
static int period2; /* upper limit for a long transition */
|
||||
|
||||
static uint8_t outputbuffer[5*1024];
|
||||
static int outputbufferpos;
|
||||
static uint8_t fifo = 0;
|
||||
static int bitcount = 0;
|
||||
static bool phaselocked = false;
|
||||
|
||||
static int thislength = 0;
|
||||
static int nextlength = 0;
|
||||
|
||||
static void syntax_error(void)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"syntax: fluxclient fmdecode <options>:\n"
|
||||
" -i <filename> input filename (.flux)\n"
|
||||
" -o <filename> output filename (.rec)\n"
|
||||
" -v verbose decoding\n"
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static char* const* parse_options(char* const* argv)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
switch (getopt(countargs(argv), argv, "+i:o:v"))
|
||||
{
|
||||
case -1:
|
||||
return argv + optind - 1;
|
||||
|
||||
case 'i':
|
||||
inputfilename = optarg;
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
outputfilename = optarg;
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
syntax_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void close_files(void)
|
||||
{
|
||||
if (indb)
|
||||
sqlite3_close(indb);
|
||||
if (outdb)
|
||||
sqlite3_close(outdb);
|
||||
}
|
||||
|
||||
static void open_files(void)
|
||||
{
|
||||
indb = sql_open(inputfilename, SQLITE_OPEN_READONLY);
|
||||
outdb = sql_open(outputfilename, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
|
||||
sql_prepare_record(outdb);
|
||||
atexit(close_files);
|
||||
}
|
||||
|
||||
static void queue_bit(bool bit)
|
||||
{
|
||||
fifo <<= 1;
|
||||
fifo |= bit;
|
||||
bitcount++;
|
||||
}
|
||||
|
||||
static bool read_bit(void)
|
||||
{
|
||||
/*
|
||||
* FM is incredibly stupid. Every cell has a pulse at the beginning;
|
||||
* cells which contain a 1 have a pulse in the middle; cells which don't
|
||||
* have a 0. Therefore we have these two states:
|
||||
*
|
||||
* Data: 0 1
|
||||
* Signal: 10 11
|
||||
*
|
||||
* Therefore, a *long* interval represents a 0, and two *short* intervals
|
||||
* represent a 1.
|
||||
*/
|
||||
|
||||
if (cursor >= inputlen)
|
||||
return false;
|
||||
uint8_t t = inputbuffer[cursor++];
|
||||
elapsed_ticks += t;
|
||||
|
||||
if ((t < period0) || (t > period2))
|
||||
{
|
||||
/* Garbage data: our clock's wrong. */
|
||||
phaselocked = false;
|
||||
return false;
|
||||
}
|
||||
else if (t < period1)
|
||||
{
|
||||
/* That was the first short interval of a 1: now consume any additional
|
||||
* until we reach the clock pulse again. */
|
||||
if (cursor >= inputlen)
|
||||
return false;
|
||||
while (t < period1)
|
||||
{
|
||||
if (cursor >= inputlen)
|
||||
return false;
|
||||
uint8_t tt = inputbuffer[cursor++];
|
||||
elapsed_ticks += tt;
|
||||
t += tt;
|
||||
}
|
||||
|
||||
/* If the clock pulse didn't show up, give up. */
|
||||
if (t > period2)
|
||||
{
|
||||
phaselocked = false;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* A long transition. */
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t read_byte(void)
|
||||
{
|
||||
while (phaselocked && (bitcount < 8))
|
||||
queue_bit(read_bit());
|
||||
bitcount = 0;
|
||||
return fifo;
|
||||
}
|
||||
|
||||
static void log_record(char type)
|
||||
{
|
||||
if (verbose)
|
||||
printf("\n % 8.3fms [0x%05x]: ",
|
||||
(double)elapsed_ticks / (TICKS_PER_US*1000.0), cursor);
|
||||
putchar(type);
|
||||
}
|
||||
|
||||
static bool process_byte(uint8_t b)
|
||||
{
|
||||
outputbuffer[outputbufferpos++] = b;
|
||||
if (outputbufferpos == sizeof(outputbuffer))
|
||||
goto abandon_record;
|
||||
|
||||
error("unimplemented --- can't handle FM yet");
|
||||
|
||||
abandon_record:
|
||||
if (verbose && (outputbufferpos > 4))
|
||||
printf(" misread");
|
||||
phaselocked = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void decode_track_cb(int track, int side, const struct fluxmap* fluxmap)
|
||||
{
|
||||
printf("Track %02d side %d: ", track, side);
|
||||
|
||||
inputbuffer = fluxmap->intervals;
|
||||
inputlen = fluxmap->bytes;
|
||||
cursor = 0;
|
||||
elapsed_ticks = 0;
|
||||
int record = 0;
|
||||
|
||||
while (cursor < inputlen)
|
||||
{
|
||||
if (!phaselocked)
|
||||
{
|
||||
while (cursor < inputlen)
|
||||
{
|
||||
clock_period = fluxmap_seek_clock(fluxmap, &cursor, 16);
|
||||
|
||||
/*
|
||||
* Okay, this looks good. We'll assume this is clock/2 --- 250kHz
|
||||
* for HD floppies; this is one long transition.
|
||||
*/
|
||||
|
||||
double short_time = clock_period / 2.0;
|
||||
double long_time = short_time * 2.0;
|
||||
|
||||
period0 = short_time - short_time * CLOCK_ERROR_BOUNDS;
|
||||
period1 = (short_time + long_time) / 2.0;
|
||||
period2 = long_time + long_time * CLOCK_ERROR_BOUNDS;
|
||||
phaselocked = true;
|
||||
|
||||
while (phaselocked && (cursor < inputlen))
|
||||
{
|
||||
if (read_bit())
|
||||
goto found_byte;
|
||||
}
|
||||
}
|
||||
found_byte:;
|
||||
fifo = 1;
|
||||
bitcount = 1;
|
||||
outputbufferpos = 0;
|
||||
}
|
||||
|
||||
if (process_byte(read_byte()))
|
||||
{
|
||||
sql_write_record(outdb, track, side, record, outputbuffer, outputbufferpos);
|
||||
record++;
|
||||
}
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
printf("\n ");
|
||||
printf(" = %d records\n", record);
|
||||
}
|
||||
|
||||
void cmd_fmdecode(char* const* argv)
|
||||
{
|
||||
argv = parse_options(argv);
|
||||
if (countargs(argv) != 1)
|
||||
syntax_error();
|
||||
if (!inputfilename)
|
||||
error("you must supply a filename to read from");
|
||||
if (!outputfilename)
|
||||
error("you must supply a filename to write to");
|
||||
|
||||
open_files();
|
||||
sql_stmt(outdb, "BEGIN");
|
||||
sql_stmt(outdb, "DELETE FROM records");
|
||||
sql_for_all_flux_data(indb, decode_track_cb);
|
||||
sql_stmt(outdb, "COMMIT");
|
||||
}
|
||||
|
||||
109
cmd_getclock.c
Normal file
109
cmd_getclock.c
Normal file
@@ -0,0 +1,109 @@
|
||||
#include "globals.h"
|
||||
#include "sql.h"
|
||||
#include <unistd.h>
|
||||
|
||||
static const char* input_filename = NULL;
|
||||
static int track = 0;
|
||||
static int side = 0;
|
||||
static int threshold = 100;
|
||||
static sqlite3* db;
|
||||
|
||||
static void syntax_error(void)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"syntax: fluxclient getclock <options>:\n"
|
||||
" -i <filename> input filename (.flux)\n"
|
||||
" -t <track> track to analyse\n"
|
||||
" -0 analyse side 0\n"
|
||||
" -1 analyse side 1\n"
|
||||
" -T <threshold> noise threshold (default: 100)\n"
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static char* const* parse_options(char* const* argv)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
switch (getopt(countargs(argv), argv, "+i:t:01T:"))
|
||||
{
|
||||
case -1:
|
||||
return argv + optind - 1;
|
||||
|
||||
case 'i':
|
||||
input_filename = optarg;
|
||||
break;
|
||||
|
||||
case 't':
|
||||
track = atoi(optarg);
|
||||
break;
|
||||
|
||||
case '0':
|
||||
side = 0;
|
||||
break;
|
||||
|
||||
case '1':
|
||||
side = 1;
|
||||
break;
|
||||
|
||||
case 'T':
|
||||
threshold = atoi(optarg);
|
||||
break;
|
||||
|
||||
default:
|
||||
syntax_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void close_file(void)
|
||||
{
|
||||
sqlite3_close(db);
|
||||
}
|
||||
|
||||
static void open_file(void)
|
||||
{
|
||||
db = sql_open(input_filename, SQLITE_OPEN_READONLY);
|
||||
atexit(close_file);
|
||||
}
|
||||
|
||||
void cmd_getclock(char* const* argv)
|
||||
{
|
||||
argv = parse_options(argv);
|
||||
if (countargs(argv) != 1)
|
||||
syntax_error();
|
||||
if (!input_filename)
|
||||
error("you must supply a filename to read from");
|
||||
|
||||
open_file();
|
||||
struct fluxmap* fluxmap = sql_read_flux(db, track, side);
|
||||
if (!fluxmap)
|
||||
error("no data for that track in the file");
|
||||
|
||||
uint32_t buckets[256] = {};
|
||||
for (int i=0; i<fluxmap->bytes; i++)
|
||||
buckets[fluxmap->intervals[i]]++;
|
||||
|
||||
bool skipping = false;
|
||||
for (int i=0; i<256; i++)
|
||||
{
|
||||
uint32_t v = buckets[i];
|
||||
if (v < threshold)
|
||||
{
|
||||
if (!skipping)
|
||||
{
|
||||
skipping = true;
|
||||
printf("...\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
skipping = false;
|
||||
printf("(0x%02x) % 6.2fus: %d\n", i, (double)i / (double)TICKS_PER_US, v);
|
||||
}
|
||||
}
|
||||
|
||||
nanoseconds_t estimated_clock = fluxmap_guess_clock(fluxmap);
|
||||
printf("Estimated clock: %6.2fus\n", estimated_clock/1000.0);
|
||||
}
|
||||
|
||||
330
cmd_mfmdecode.c
330
cmd_mfmdecode.c
@@ -23,28 +23,13 @@ static bool verbose = false;
|
||||
static sqlite3* indb;
|
||||
static sqlite3* outdb;
|
||||
|
||||
static const uint8_t* inputbuffer;
|
||||
static int inputlen;
|
||||
static int cursor;
|
||||
static int elapsed_ticks;
|
||||
|
||||
static int clock_period; /* mfm cell width */
|
||||
static int period0; /* lower limit for a short transition */
|
||||
static int period1; /* between short and medium transitions */
|
||||
static int period2; /* between medium and long transitions */
|
||||
static int period3; /* upper limit for a long transition */
|
||||
|
||||
static uint8_t outputbuffer[5*1024];
|
||||
static nanoseconds_t elapsed_time;
|
||||
static uint8_t outputbuffer[20*1024];
|
||||
static int outputbufferpos;
|
||||
static uint8_t fifo = 0;
|
||||
static uint8_t outputfifo = 0;
|
||||
static int bitcount = 0;
|
||||
static bool phaselocked = false;
|
||||
static bool phase = false;
|
||||
static bool queued = false;
|
||||
static bool has_queued = false;
|
||||
|
||||
static int thislength = 0;
|
||||
static int nextlength = 0;
|
||||
|
||||
static void syntax_error(void)
|
||||
{
|
||||
@@ -100,268 +85,107 @@ static void open_files(void)
|
||||
atexit(close_files);
|
||||
}
|
||||
|
||||
static void queue_bit(bool bit)
|
||||
static void write_bit(bool bit)
|
||||
{
|
||||
fifo <<= 1;
|
||||
fifo |= bit;
|
||||
outputfifo = (outputfifo << 1) | bit;
|
||||
bitcount++;
|
||||
}
|
||||
|
||||
static bool read_bit(void)
|
||||
{
|
||||
if (has_queued)
|
||||
if (bitcount == 8)
|
||||
{
|
||||
has_queued = false;
|
||||
return queued;
|
||||
}
|
||||
|
||||
/*
|
||||
* MFM divides the signal into two-bit cells, which can be either x0
|
||||
* (representing 0) and 01 (representing 1). x can be any value; the rules
|
||||
* set it to 0 of the previous cell contained 01, and 1 otherwise.
|
||||
*
|
||||
* However, all we have are the intervals and so we don't know where a cell
|
||||
* begins. Consider:
|
||||
*
|
||||
* Data: 1 1 0 0 0 1 0 0 1 1 1 0 1 0 1 0 1 0
|
||||
* Signal: 01 01 00 10 10 01 00 10 01 01 01 00 01 00 01 00 01 00
|
||||
*
|
||||
* If our signal is offset half a cell, we get this:
|
||||
*
|
||||
* Data: 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0
|
||||
* Signal: 0 10 10 01 01 00 10 01 00 10 10 10 00 10 00 10 00 10 0
|
||||
*
|
||||
* However! This violates the rules. We have 10 00. Both of these
|
||||
* encode zeros, which means the second 00 should have been a 10.
|
||||
* So, after a long transition, we know that the cell sequence
|
||||
* must have been (assuming correct encoding) 00 01, encoding 0
|
||||
* and 1, and that the next interval will be at the start of a
|
||||
* cell.
|
||||
*
|
||||
* Data: 0 0 1 1 0 0 1 0 0 0 0 0 | 1 0 1 0 1 0
|
||||
* Signal: 0 10 10 01 01 00 10 01 00 10 10 10 00 | x1 00 01 00 01 00
|
||||
* \ resync
|
||||
*/
|
||||
|
||||
if (cursor >= inputlen)
|
||||
return false;
|
||||
uint8_t t = inputbuffer[cursor++];
|
||||
elapsed_ticks += t;
|
||||
|
||||
if ((t < period0) || (t > period3))
|
||||
{
|
||||
/* Garbage data: our clock's wrong. */
|
||||
phaselocked = false;
|
||||
return false;
|
||||
}
|
||||
else if (t < period1)
|
||||
{
|
||||
/* Short transition: either (1),01, or ,(1)0,1. */
|
||||
return !phase;
|
||||
}
|
||||
else if (t > period2)
|
||||
{
|
||||
/* Long transition: either (1),00,01 or ,(1)0,00,1.
|
||||
* The latter is illegal but occurs inside marker bytes. */
|
||||
|
||||
if (!phase)
|
||||
{
|
||||
queued = true;
|
||||
has_queued = true;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
queued = false;
|
||||
has_queued = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Medium transition: either (1),00,1 or ,(1)0,01. */
|
||||
if (!phase)
|
||||
{
|
||||
phase = true;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
queued = true;
|
||||
has_queued = true;
|
||||
phase = false;
|
||||
return false;
|
||||
}
|
||||
outputbuffer[outputbufferpos++] = outputfifo;
|
||||
bitcount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t read_byte(void)
|
||||
{
|
||||
while (phaselocked && (bitcount < 8))
|
||||
queue_bit(read_bit());
|
||||
bitcount = 0;
|
||||
return fifo;
|
||||
}
|
||||
|
||||
static void log_record(char type)
|
||||
static void log_record(void)
|
||||
{
|
||||
if (verbose)
|
||||
printf("\n % 8.3fms [0x%05x]: ",
|
||||
(double)elapsed_ticks / (TICKS_PER_US*1000.0), cursor);
|
||||
putchar(type);
|
||||
}
|
||||
|
||||
static bool process_byte(uint8_t b)
|
||||
{
|
||||
outputbuffer[outputbufferpos++] = b;
|
||||
if (outputbufferpos == sizeof(outputbuffer))
|
||||
goto abandon_record;
|
||||
|
||||
#if 0
|
||||
if (outputbufferpos < 4)
|
||||
{
|
||||
if ((b != 0xA1) && (b != 0xC2))
|
||||
goto abandon_record;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (outputbufferpos == 4)
|
||||
{
|
||||
switch (outputbuffer[3])
|
||||
{
|
||||
case IAM:
|
||||
thislength = IAM_LEN;
|
||||
log_record('T');
|
||||
break;
|
||||
|
||||
case IDAM:
|
||||
thislength = IDAM_LEN;
|
||||
log_record('H');
|
||||
break;
|
||||
|
||||
case DAM1:
|
||||
case DAM2:
|
||||
thislength = nextlength;
|
||||
nextlength = 0;
|
||||
/* Sector with a header? */
|
||||
if (thislength == 0)
|
||||
goto abandon_record;
|
||||
log_record('D');
|
||||
break;
|
||||
|
||||
default:
|
||||
goto abandon_record;
|
||||
}
|
||||
}
|
||||
|
||||
if (outputbufferpos == thislength)
|
||||
{
|
||||
/* We've read a complete record. */
|
||||
|
||||
if (verbose)
|
||||
printf(" %d bytes ", thislength);
|
||||
|
||||
switch (outputbuffer[3])
|
||||
{
|
||||
case IDAM:
|
||||
if (verbose)
|
||||
printf(" C%02d H%01d S%02d", outputbuffer[4], outputbuffer[5], outputbuffer[6]);
|
||||
nextlength = (1<<(outputbuffer[7] + 7)) + DAM_LEN;
|
||||
break;
|
||||
|
||||
case DAM1:
|
||||
case DAM2:
|
||||
if (verbose)
|
||||
printf(" (%d bytes user data)", thislength - DAM_LEN);
|
||||
break;
|
||||
}
|
||||
|
||||
phaselocked = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
abandon_record:
|
||||
if (verbose && (outputbufferpos > 4))
|
||||
printf(" misread");
|
||||
phaselocked = false;
|
||||
return false;
|
||||
printf("\n % 8.3fms [0x%05x] ",
|
||||
(double)elapsed_time / 1e6, cursor);
|
||||
else
|
||||
putchar('.');
|
||||
}
|
||||
|
||||
static void decode_track_cb(int track, int side, const struct fluxmap* fluxmap)
|
||||
{
|
||||
printf("Track %02d side %d: ", track, side);
|
||||
|
||||
inputbuffer = fluxmap->intervals;
|
||||
inputlen = fluxmap->bytes;
|
||||
nanoseconds_t clock_period = fluxmap_guess_clock(fluxmap);
|
||||
printf("% 4.1fus ", (double)clock_period/(double)1000);
|
||||
|
||||
/* On MFM, the data clock is half the detected clock (which is the cell length). */
|
||||
clock_period /= 2;
|
||||
struct encoding_buffer* decoded = fluxmap_decode(fluxmap, clock_period);
|
||||
|
||||
cursor = 0;
|
||||
elapsed_ticks = 0;
|
||||
uint64_t inputfifo = 0;
|
||||
bool reading = false;
|
||||
int record = 0;
|
||||
|
||||
while (cursor < inputlen)
|
||||
while (cursor < decoded->length_pulses)
|
||||
{
|
||||
if (!phaselocked)
|
||||
elapsed_time = cursor * decoded->pulselength_ns;
|
||||
bool bit = decoded->bitmap[cursor++];
|
||||
inputfifo = (inputfifo << 1) | bit;
|
||||
|
||||
/*
|
||||
* The IAM record, which is the first one on the disk (and is optional), uses
|
||||
* a distorted 0xC2 0xC2 0xC2 marker to identify it. Unfortunately, if this is
|
||||
* shifted out of phase, it becomes a legal encoding, so if we're looking at
|
||||
* real data we can't honour this.
|
||||
*
|
||||
* 0xC2 is:
|
||||
* data: 1 1 0 0 0 0 1 0
|
||||
* mfm: 01 01 00 10 10 10 01 00 = 0x5254
|
||||
* special: 01 01 00 10 00 10 01 00 = 0x5224
|
||||
* ^^^^
|
||||
* shifted: 10 10 01 00 01 00 10 0. = legal, and might happen in real data
|
||||
*
|
||||
* Therefore, when we've read the marker, the input fifo will contain
|
||||
* 0xXXXX522252225222.
|
||||
*
|
||||
* All other records use 0xA1 as a marker:
|
||||
*
|
||||
* 0xA1 is:
|
||||
* data: 1 0 1 0 0 0 0 1
|
||||
* mfm: 01 00 01 00 10 10 10 01 = 0x44A9
|
||||
* special: 01 00 01 00 10 00 10 01 = 0x4489
|
||||
* ^^^^^
|
||||
* shifted: 10 00 10 01 00 01 00 1
|
||||
*
|
||||
* When this is shifted out of phase, we get an illegal encoding (you
|
||||
* can't do 10 00). So, if we ever see 0x448944894489 in the input
|
||||
* fifo, we know we've landed at the beginning of a new record.
|
||||
*/
|
||||
|
||||
uint64_t masked = inputfifo & 0xFFFFFFFFFFFFLL;
|
||||
if ((!reading && (masked == 0x522452245224LL)) || (masked == 0x448944894489LL))
|
||||
{
|
||||
while (cursor < inputlen)
|
||||
{
|
||||
/* Somewhere in the bitstream there'll be a sync sequence of 16 0 bytes
|
||||
* followed by a special index byte, beginning with a 1 bit.
|
||||
*
|
||||
* These zeroes will be encoded as 10 10 10 10..., so forming a nice
|
||||
* simple signal that should be easy to detect. This routine scans the
|
||||
* bitstream until it finds one of these windows, and sets the clock
|
||||
* accordingly. Remember that the routine can be easily spoofed by bad
|
||||
* data, so you need to check for the marker byte afterwards.
|
||||
*
|
||||
* ...btw, the standard fill byte is 0x4e:
|
||||
*
|
||||
* 0 1 0 0 1 1 1 0
|
||||
* 10 01 00 10 01 01 01 00
|
||||
*
|
||||
* That's four medium transitions vs two short ones. So we know we're going
|
||||
* to miscalculate the clock the first time round.
|
||||
*/
|
||||
log_record();
|
||||
|
||||
clock_period = fluxmap_seek_clock(fluxmap, &cursor, 16);
|
||||
if (reading)
|
||||
sql_write_record(outdb, track, side, record++, outputbuffer, outputbufferpos);
|
||||
|
||||
/*
|
||||
* Okay, this looks good. We'll assume this is clock/2 --- 250kHz
|
||||
* for HD floppies; this is one short transition. We're also going
|
||||
* to assume that this was a 0 bit and set the phase, god help us.
|
||||
*/
|
||||
if (!reading)
|
||||
memcpy(outputbuffer, "\xc2\xc2\xc2", 3);
|
||||
else
|
||||
memcpy(outputbuffer, "\xa1\xa1\xa1", 3);
|
||||
|
||||
double short_time = clock_period;
|
||||
double medium_time = short_time * 1.5;
|
||||
double long_time = short_time * 2.0;
|
||||
|
||||
period0 = short_time - short_time * CLOCK_ERROR_BOUNDS;
|
||||
period1 = (short_time + medium_time) / 2.0;
|
||||
period2 = (medium_time + long_time) / 2.0;
|
||||
period3 = long_time + long_time * CLOCK_ERROR_BOUNDS;
|
||||
phase = true;
|
||||
phaselocked = true;
|
||||
has_queued = false;
|
||||
|
||||
while (phaselocked && (cursor < inputlen))
|
||||
{
|
||||
if (read_bit())
|
||||
goto found_byte;
|
||||
}
|
||||
}
|
||||
found_byte:;
|
||||
fifo = 1;
|
||||
bitcount = 1;
|
||||
outputbufferpos = 0;
|
||||
reading = true;
|
||||
outputbufferpos = 3;
|
||||
bitcount = 0;
|
||||
phase = 0;
|
||||
}
|
||||
|
||||
if (process_byte(read_byte()))
|
||||
else if (reading)
|
||||
{
|
||||
sql_write_record(outdb, track, side, record, outputbuffer, outputbufferpos);
|
||||
record++;
|
||||
if (phase)
|
||||
write_bit(bit);
|
||||
phase = !phase;
|
||||
}
|
||||
}
|
||||
|
||||
if (reading)
|
||||
sql_write_record(outdb, track, side, record++, outputbuffer, outputbufferpos);
|
||||
|
||||
if (verbose)
|
||||
printf("\n ");
|
||||
printf(" = %d records\n", record);
|
||||
|
||||
@@ -81,13 +81,13 @@ static void open_file(void)
|
||||
|
||||
static void write_pulsetrain(struct encoding_buffer* buffer, int cursor_ms, int length_ms, int width_us)
|
||||
{
|
||||
int cursor_us = cursor_ms*1000;
|
||||
int length_us = length_ms*1000;
|
||||
while (length_us > 0)
|
||||
int cursor_ns = cursor_ms*1000000;
|
||||
int length_ns = length_ms*1000000;
|
||||
while (length_ns > 0)
|
||||
{
|
||||
encoding_buffer_pulse(buffer, cursor_us);
|
||||
length_us -= width_us;
|
||||
cursor_us += width_us;
|
||||
encoding_buffer_pulse(buffer, cursor_ns);
|
||||
length_ns -= width_us*1000;
|
||||
cursor_ns += width_us*1000;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ void cmd_testpattern(char* const* argv)
|
||||
if (start_track > end_track)
|
||||
error("writing to track %d to track %d makes no sense", start_track, end_track);
|
||||
|
||||
struct encoding_buffer* buffer = create_encoding_buffer(track_length_ms*1000);
|
||||
struct encoding_buffer* buffer = create_encoding_buffer(1000, track_length_ms*1000);
|
||||
|
||||
int cursor_ms = 0;
|
||||
while (cursor_ms < track_length_ms)
|
||||
|
||||
26
encoder.c
26
encoder.c
@@ -2,11 +2,12 @@
|
||||
|
||||
#define MAX_INTERVAL_US (128/(TICK_FREQUENCY/1000000))
|
||||
|
||||
struct encoding_buffer* create_encoding_buffer(int length_us)
|
||||
struct encoding_buffer* create_encoding_buffer(int pulselength_ns, int length_pulses)
|
||||
{
|
||||
struct encoding_buffer* buffer = calloc(1, sizeof(*buffer));
|
||||
buffer->length_us = length_us;
|
||||
buffer->bitmap = calloc(1, length_us);
|
||||
buffer->pulselength_ns = pulselength_ns;
|
||||
buffer->length_pulses = length_pulses;
|
||||
buffer->bitmap = calloc(1, length_pulses);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@@ -16,10 +17,11 @@ void free_encoding_buffer(struct encoding_buffer* buffer)
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
void encoding_buffer_pulse(struct encoding_buffer* buffer, int timestamp_us)
|
||||
void encoding_buffer_pulse(struct encoding_buffer* buffer, int timestamp_ns)
|
||||
{
|
||||
if (timestamp_us < buffer->length_us)
|
||||
buffer->bitmap[timestamp_us] = 1;
|
||||
int pulse = timestamp_ns / buffer->pulselength_ns;
|
||||
if (pulse < buffer->length_pulses)
|
||||
buffer->bitmap[pulse] = 1;
|
||||
}
|
||||
|
||||
struct fluxmap* encoding_buffer_encode(const struct encoding_buffer* buffer)
|
||||
@@ -28,12 +30,18 @@ struct fluxmap* encoding_buffer_encode(const struct encoding_buffer* buffer)
|
||||
|
||||
int lastpulse = 0;
|
||||
int cursor = 1;
|
||||
while (cursor < buffer->length_us)
|
||||
while (cursor < buffer->length_pulses)
|
||||
{
|
||||
if ((buffer->bitmap[cursor]) || (cursor-lastpulse == MAX_INTERVAL_US))
|
||||
{
|
||||
uint8_t interval = (cursor - lastpulse) * (TICK_FREQUENCY/1000000);
|
||||
fluxmap_append_intervals(fluxmap, &interval, 1);
|
||||
int delta_ticks = (cursor - lastpulse) / TICKS_PER_NS;
|
||||
while (delta_ticks >= 0xfe)
|
||||
{
|
||||
fluxmap_append_interval(fluxmap, 0xfe);
|
||||
delta_ticks -= 0xfe;
|
||||
}
|
||||
|
||||
fluxmap_append_interval(fluxmap, delta_ticks);
|
||||
lastpulse = cursor;
|
||||
}
|
||||
cursor++;
|
||||
|
||||
78
fluxmap.c
78
fluxmap.c
@@ -48,6 +48,11 @@ void fluxmap_append_intervals(struct fluxmap* fluxmap, const uint8_t* intervals,
|
||||
fluxmap->length_us = fluxmap->length_ticks / (TICK_FREQUENCY / 1000000);
|
||||
}
|
||||
|
||||
void fluxmap_append_interval(struct fluxmap* fluxmap, uint8_t interval)
|
||||
{
|
||||
fluxmap_append_intervals(fluxmap, &interval, 1);
|
||||
}
|
||||
|
||||
int fluxmap_seek_clock(const struct fluxmap* fluxmap, int* cursor, int pulses)
|
||||
{
|
||||
int count = 0;
|
||||
@@ -80,6 +85,48 @@ int fluxmap_seek_clock(const struct fluxmap* fluxmap, int* cursor, int pulses)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Tries to guess the clock by finding the smallest common interval.
|
||||
* Returns nanoseconds.
|
||||
*/
|
||||
nanoseconds_t fluxmap_guess_clock(const struct fluxmap* fluxmap)
|
||||
{
|
||||
uint32_t buckets[256] = {};
|
||||
for (int i=0; i<fluxmap->bytes; i++)
|
||||
buckets[fluxmap->intervals[i]]++;
|
||||
|
||||
int peaklo = 0;
|
||||
while (peaklo < 256)
|
||||
{
|
||||
if (buckets[peaklo] > 100)
|
||||
break;
|
||||
peaklo++;
|
||||
}
|
||||
|
||||
int peakmaxindex = peaklo;
|
||||
int peakmaxvalue = buckets[peakmaxindex];
|
||||
int peakhi = peaklo;
|
||||
while (peakhi < 256)
|
||||
{
|
||||
uint32_t v = buckets[peakhi];
|
||||
if (buckets[peakhi] < 50)
|
||||
break;
|
||||
if (v > peakmaxvalue)
|
||||
{
|
||||
peakmaxindex = peakhi;
|
||||
peakmaxvalue = v;
|
||||
}
|
||||
peakhi++;
|
||||
}
|
||||
|
||||
/*
|
||||
* Okay, peakmaxindex should now be a good candidate for the (or a) clock.
|
||||
* How this maps onto the actual clock rate depends on the encoding.
|
||||
*/
|
||||
|
||||
return peakmaxindex * NS_PER_TICK;
|
||||
}
|
||||
|
||||
void fluxmap_precompensate(struct fluxmap* fluxmap, int threshold_ticks, int amount_ticks)
|
||||
{
|
||||
uint8_t junk = 0xff;
|
||||
@@ -103,3 +150,34 @@ void fluxmap_precompensate(struct fluxmap* fluxmap, int threshold_ticks, int amo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct encoding_buffer* fluxmap_decode(const struct fluxmap* fluxmap, nanoseconds_t clock_period)
|
||||
{
|
||||
int pulses = (fluxmap->length_us*1000) / clock_period;
|
||||
nanoseconds_t lower_threshold = clock_period * 0.75;
|
||||
|
||||
struct encoding_buffer* buffer = create_encoding_buffer(clock_period, pulses);
|
||||
int count = 0;
|
||||
int cursor = 0;
|
||||
nanoseconds_t timestamp = 0;
|
||||
for (;;)
|
||||
{
|
||||
while (timestamp < lower_threshold)
|
||||
{
|
||||
if (cursor >= fluxmap->bytes)
|
||||
goto abort;
|
||||
uint8_t interval = fluxmap->intervals[cursor++];
|
||||
timestamp += interval * NS_PER_TICK;
|
||||
}
|
||||
|
||||
int clocks = (timestamp + clock_period/2) / clock_period;
|
||||
count += clocks;
|
||||
if (count >= buffer->length_pulses)
|
||||
goto abort;
|
||||
buffer->bitmap[count] = true;
|
||||
timestamp = 0;
|
||||
}
|
||||
abort:
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
16
globals.h
16
globals.h
@@ -16,16 +16,19 @@
|
||||
|
||||
#include "protocol.h"
|
||||
|
||||
typedef int nanoseconds_t; /* maximum value is over 2s, which is fine */
|
||||
|
||||
struct encoding_buffer
|
||||
{
|
||||
int length_us;
|
||||
uint8_t* bitmap;
|
||||
int pulselength_ns;
|
||||
int length_pulses;
|
||||
bool* bitmap;
|
||||
};
|
||||
|
||||
extern struct encoding_buffer* create_encoding_buffer(int length_us);
|
||||
extern struct encoding_buffer* create_encoding_buffer(int pulselength_ns, int length_pulses);
|
||||
extern void free_encoding_buffer(struct encoding_buffer* buffer);
|
||||
|
||||
extern void encoding_buffer_pulse(struct encoding_buffer* buffer, int timestamp_us);
|
||||
extern void encoding_buffer_pulse(struct encoding_buffer* buffer, int timestamp_ns);
|
||||
extern struct fluxmap* encoding_buffer_encode(const struct encoding_buffer* buffer);
|
||||
|
||||
struct fluxmap
|
||||
@@ -42,8 +45,11 @@ extern void free_fluxmap(struct fluxmap* fluxmap);
|
||||
extern struct fluxmap* copy_fluxmap(const struct fluxmap* fluxmap);
|
||||
extern void fluxmap_clear(struct fluxmap* fluxmap);
|
||||
extern void fluxmap_append_intervals(struct fluxmap* fluxmap, const uint8_t* intervals, int count);
|
||||
extern void fluxmap_append_interval(struct fluxmap* fluxmap, uint8_t interval);
|
||||
extern int fluxmap_seek_clock(const struct fluxmap* fluxmap, int* cursor, int pulses);
|
||||
extern nanoseconds_t fluxmap_guess_clock(const struct fluxmap* fluxmap);
|
||||
extern void fluxmap_precompensate(struct fluxmap* fluxmap, int threshold_ticks, int amount_ticks);
|
||||
extern struct encoding_buffer* fluxmap_decode(const struct fluxmap* fluxmap, int clock_ns);
|
||||
|
||||
extern void error(const char* message, ...);
|
||||
extern double gettime(void);
|
||||
@@ -65,9 +71,9 @@ extern void cmd_usbbench(char* const* argv);
|
||||
extern void cmd_read(char* const* argv);
|
||||
extern void cmd_write(char* const* argv);
|
||||
extern void cmd_mfmdecode(char* const* argv);
|
||||
extern void cmd_fmdecode(char* const* argv);
|
||||
extern void cmd_testpattern(char* const* argv);
|
||||
extern void cmd_fluxdump(char* const* argv);
|
||||
extern void cmd_calibrate(char* const* argv);
|
||||
extern void cmd_getclock(char* const* argv);
|
||||
|
||||
#endif
|
||||
|
||||
4
main.c
4
main.c
@@ -76,14 +76,14 @@ int main(int argc, char* const* argv)
|
||||
cmd_write(argv);
|
||||
else if (strcmp(argv[0], "mfmdecode") == 0)
|
||||
cmd_mfmdecode(argv);
|
||||
else if (strcmp(argv[0], "fmdecode") == 0)
|
||||
cmd_fmdecode(argv);
|
||||
else if (strcmp(argv[0], "testpattern") == 0)
|
||||
cmd_testpattern(argv);
|
||||
else if (strcmp(argv[0], "fluxdump") == 0)
|
||||
cmd_fluxdump(argv);
|
||||
else if (strcmp(argv[0], "calibrate") == 0)
|
||||
cmd_calibrate(argv);
|
||||
else if (strcmp(argv[0], "getclock") == 0)
|
||||
cmd_getclock(argv);
|
||||
else
|
||||
syntax_error();
|
||||
|
||||
|
||||
@@ -28,11 +28,13 @@ enum
|
||||
FRAME_SIZE = 64,
|
||||
TICK_FREQUENCY = 12000000,
|
||||
TICKS_PER_US = TICK_FREQUENCY / 1000000,
|
||||
NS_PER_TICK = 1000000000 / TICK_FREQUENCY,
|
||||
TICKS_PER_NS = TICK_FREQUENCY / 1000,
|
||||
|
||||
PRECOMPENSATION_THRESHOLD_TICKS = (int)(2.25 * TICKS_PER_US),
|
||||
};
|
||||
|
||||
#define NS_PER_TICK ((double)1000000000 / (double)TICK_FREQUENCY)
|
||||
|
||||
enum
|
||||
{
|
||||
F_FRAME_ERROR = 0, /* any_frame */
|
||||
|
||||
Reference in New Issue
Block a user