The USB capture/replay interface now uses timestamps rather than intervals;

this makes the hardware considerably simpler and more reliable (as I don't need
to spend time resetting the timers between pulses). Still doesn't help writes,
though. Simplify and improve clock detection; add an abortive attempt at an FM
decoder (turns out that the Brother doesn't use FM).
This commit is contained in:
David Given
2018-10-13 01:19:17 +02:00
parent 333fd989de
commit b46d55cc07
10 changed files with 394 additions and 95 deletions

View File

@@ -2542,27 +2542,27 @@
<CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFolderSerialize" version="3">
<CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtBaseContainerSerialize" version="1">
<CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="REPLAY_COUNTER" persistent="">
<Hidden v="False" />
<Hidden v="True" />
</CyGuid_31768f72-0253-412b-af77-e7dba74d1330>
<CyGuid_0820c2e7-528d-4137-9a08-97257b946089 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemListSerialize" version="2">
<dependencies>
<CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1">
<CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="REPLAY_COUNTER.c" persistent="Generated_Source\PSoC5\REPLAY_COUNTER.c">
<Hidden v="False" />
<Hidden v="True" />
</CyGuid_31768f72-0253-412b-af77-e7dba74d1330>
<build_action v="SOURCE_C;CortexM3;;;" />
<PropertyDeltas />
</CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b>
<CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1">
<CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="REPLAY_COUNTER.h" persistent="Generated_Source\PSoC5\REPLAY_COUNTER.h">
<Hidden v="False" />
<Hidden v="True" />
</CyGuid_31768f72-0253-412b-af77-e7dba74d1330>
<build_action v="HEADER;;;;" />
<PropertyDeltas />
</CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b>
<CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1">
<CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="REPLAY_COUNTER_PM.c" persistent="Generated_Source\PSoC5\REPLAY_COUNTER_PM.c">
<Hidden v="False" />
<Hidden v="True" />
</CyGuid_31768f72-0253-412b-af77-e7dba74d1330>
<build_action v="SOURCE_C;CortexM3;;;" />
<PropertyDeltas />
@@ -2841,6 +2841,39 @@
</CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8>
<filters />
</CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0>
<CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFolderSerialize" version="3">
<CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtBaseContainerSerialize" version="1">
<CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="REPLAY_TIMESTAMP" persistent="">
<Hidden v="False" />
</CyGuid_31768f72-0253-412b-af77-e7dba74d1330>
<CyGuid_0820c2e7-528d-4137-9a08-97257b946089 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemListSerialize" version="2">
<dependencies>
<CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1">
<CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="REPLAY_TIMESTAMP.h" persistent="Generated_Source\PSoC5\REPLAY_TIMESTAMP.h">
<Hidden v="False" />
</CyGuid_31768f72-0253-412b-af77-e7dba74d1330>
<build_action v="HEADER;;;;" />
<PropertyDeltas />
</CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b>
<CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1">
<CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="REPLAY_TIMESTAMP.c" persistent="Generated_Source\PSoC5\REPLAY_TIMESTAMP.c">
<Hidden v="False" />
</CyGuid_31768f72-0253-412b-af77-e7dba74d1330>
<build_action v="SOURCE_C;CortexM3;;;" />
<PropertyDeltas />
</CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b>
<CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1">
<CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="REPLAY_TIMESTAMP_PM.c" persistent="Generated_Source\PSoC5\REPLAY_TIMESTAMP_PM.c">
<Hidden v="False" />
</CyGuid_31768f72-0253-412b-af77-e7dba74d1330>
<build_action v="SOURCE_C;CortexM3;;;" />
<PropertyDeltas />
</CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b>
</dependencies>
</CyGuid_0820c2e7-528d-4137-9a08-97257b946089>
</CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8>
<filters />
</CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0>
</dependencies>
</CyGuid_0820c2e7-528d-4137-9a08-97257b946089>
</CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8>

View File

Binary file not shown.

View File

@@ -351,7 +351,7 @@ static void init_replay_dma(void)
CyDmaTdSetConfiguration(td[i], BUFFER_SIZE, td[nexti],
CY_DMA_TD_INC_SRC_ADR | REPLAY_DMA__TD_TERMOUT_EN);
CyDmaTdSetAddress(td[i], LO16((uint32)&dma_buffer[i]), LO16((uint32)REPLAY_COUNTER_COUNTER_LSB_PTR));
CyDmaTdSetAddress(td[i], LO16((uint32)&dma_buffer[i]), LO16((uint32)REPLAY_TIMESTAMP_Control_PTR));
}
}
@@ -373,8 +373,6 @@ static void cmd_write(struct write_frame* f)
int packets = f->bytes_to_write / FRAME_SIZE;
int count_read = 0;
int count_written = 0;
REPLAY_COUNTER_Start();
REPLAY_COUNTER_WriteCounter(0);
dma_writing_to_td = 0;
dma_reading_from_td = -1;
dma_underrun = false;
@@ -468,7 +466,6 @@ static void cmd_write(struct write_frame* f)
CyDmaChSetRequest(dma_channel, CY_DMA_CPU_TERM_CHAIN);
while (CyDmaChGetRequest(dma_channel))
;
REPLAY_COUNTER_Stop();
CyDmaChDisable(dma_channel);
}
@@ -481,7 +478,6 @@ static void cmd_write(struct write_frame* f)
print(" packets read\r");
}
DECLARE_REPLY_FRAME(struct write_reply_frame, F_FRAME_WRITE_REPLY);
r.bytes_actually_written = count_written*FRAME_SIZE;

View File

@@ -12,6 +12,7 @@ SRCS = \
cmd_read.c \
cmd_write.c \
cmd_mfmdecode.c \
cmd_fmdecode.c \
cmd_testpattern.c \
cmd_fluxdump.c \

256
cmd_fmdecode.c Normal file
View File

@@ -0,0 +1,256 @@
#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");
}

View File

@@ -100,90 +100,6 @@ static void open_files(void)
atexit(close_files);
}
static void find_clock(void)
{
/* 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.
*/
uint32_t buckets[256] = {};
for (;;)
{
if (cursor >= inputlen)
return;
uint8_t data = inputbuffer[cursor++];
elapsed_ticks += data;
if (data >= 2)
buckets[data-2] += CLOCK_LOCK_BOOST * 1 / 3;
if (data >= 1)
buckets[data-1] += CLOCK_LOCK_BOOST * 2 / 3;
buckets[data] += CLOCK_LOCK_BOOST;
if (data <= 0x7e)
buckets[data+1] += CLOCK_LOCK_BOOST * 2 / 3;
if (data <= 0x7d)
buckets[data+2] += CLOCK_LOCK_BOOST * 1 / 3;
/* The bucket list slowly decays. */
for (int i=0; i<128; i++)
if (buckets[i] > 0)
buckets[i] -= CLOCK_LOCK_DECAY;
/*
* After 10 bytes, we'll have seen 80 bits. So there should be a nice
* sharp peak in our distribution. The amplitude is chosen by trial and
* error. *
*/
uint32_t maxvalue = 0;
clock_period = 0;
for (int i=0; i<128; i++)
{
if (buckets[i] > maxvalue)
{
maxvalue = buckets[i];
clock_period = i;
}
}
if (maxvalue > CLOCK_DETECTOR_AMPLITUDE_THRESHOLD)
{
/*
* 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.
*/
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;
return;
}
}
}
static void queue_bit(bool bit)
{
fifo <<= 1;
@@ -301,11 +217,13 @@ static bool process_byte(uint8_t 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)
{
@@ -387,7 +305,44 @@ static void decode_track_cb(int track, int side, const struct fluxmap* fluxmap)
{
while (cursor < inputlen)
{
find_clock();
/* 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.
*/
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 short transition. We're also going
* to assume that this was a 0 bit and set the phase, god help us.
*/
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())

View File

@@ -37,3 +37,35 @@ void fluxmap_append_intervals(struct fluxmap* fluxmap, const uint8_t* intervals,
fluxmap->length_us = fluxmap->length_ticks / (TICK_FREQUENCY / 1000000);
}
int fluxmap_seek_clock(const struct fluxmap* fluxmap, int* cursor, int pulses)
{
int count = 0;
int value = 0;
while (*cursor < fluxmap->bytes)
{
uint8_t t = fluxmap->intervals[(*cursor)++];
if (value == 0)
value = t;
else
{
count++;
int delta = abs(value - t) / 3 + 1;
if (delta > (value / 8))
count = value = 0;
else
{
if (t < value)
value -= delta;
else
value += delta;
}
}
if (count >= pulses)
return value;
}
return 1;
}

View File

@@ -41,6 +41,7 @@ extern struct fluxmap* create_fluxmap(void);
extern void free_fluxmap(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 int fluxmap_seek_clock(const struct fluxmap* fluxmap, int* cursor, int pulses);
extern void error(const char* message, ...);
extern double gettime(void);
@@ -62,6 +63,7 @@ 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);

2
main.c
View File

@@ -76,6 +76,8 @@ 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)

24
usb.c
View File

@@ -147,6 +147,17 @@ struct fluxmap* usb_read(int side)
uint8_t buffer[1024*1024];
int len = large_bulk_transfer(FLUXENGINE_DATA_IN_EP, buffer, sizeof(buffer));
/* Convert from absolute timestamps to intervals. */
uint8_t now = 0;
for (int i=0; i<len; i++)
{
uint8_t t = buffer[i];
buffer[i] = t - now;
now = t;
}
fluxmap_append_intervals(fluxmap, buffer, len);
await_reply(F_FRAME_READ_REPLY);
@@ -157,6 +168,17 @@ struct fluxmap* usb_read(int side)
int usb_write(int side, struct fluxmap* fluxmap)
{
int safelen = fluxmap->bytes & ~(FRAME_SIZE-1);
/* Convert from intervals to absolute timestamps. */
uint8_t buffer[1024*1024];
uint8_t clock = 0;
for (int i=0; i<safelen; i++)
{
clock += fluxmap->intervals[i];
buffer[i] = clock;
}
struct write_frame f = {
.f = { .type = F_FRAME_WRITE_CMD, .size = sizeof(f) },
.side = side,
@@ -164,7 +186,7 @@ int usb_write(int side, struct fluxmap* fluxmap)
};
usb_cmd_send(&f, f.f.size);
large_bulk_transfer(FLUXENGINE_DATA_OUT_EP, fluxmap->intervals, safelen);
large_bulk_transfer(FLUXENGINE_DATA_OUT_EP, buffer, safelen);
struct write_reply_frame* r = await_reply(F_FRAME_WRITE_REPLY);
return r->bytes_actually_written;