diff --git a/FluxEngine.cydsn/FluxEngine.cydwr b/FluxEngine.cydsn/FluxEngine.cydwr
index 0012f5ca..4c96cd04 100644
--- a/FluxEngine.cydsn/FluxEngine.cydwr
+++ b/FluxEngine.cydsn/FluxEngine.cydwr
@@ -871,6 +871,7 @@
+
@@ -4161,6 +4162,11 @@
+
+
+
+
+
diff --git a/FluxEngine.cydsn/FluxEngine.cyprj b/FluxEngine.cydsn/FluxEngine.cyprj
index d9b0575d..24724f66 100644
--- a/FluxEngine.cydsn/FluxEngine.cyprj
+++ b/FluxEngine.cydsn/FluxEngine.cyprj
@@ -2847,6 +2847,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FluxEngine.cydsn/TopDesign/TopDesign.cysch b/FluxEngine.cydsn/TopDesign/TopDesign.cysch
index 1f147cc9..22bbabb2 100644
Binary files a/FluxEngine.cydsn/TopDesign/TopDesign.cysch and b/FluxEngine.cydsn/TopDesign/TopDesign.cysch differ
diff --git a/FluxEngine.cydsn/main.c b/FluxEngine.cydsn/main.c
index daa2f38a..0892cc58 100644
--- a/FluxEngine.cydsn/main.c
+++ b/FluxEngine.cydsn/main.c
@@ -13,8 +13,8 @@
#define DISKSTATUS_WPT 1
#define DISKSTATUS_DSKCHG 2
-#define STEP_TOWARDS0 1
-#define STEP_AWAYFROM0 0
+#define STEP_TOWARDS0 0
+#define STEP_AWAYFROM0 1
static bool drive0_present;
static bool drive1_present;
@@ -249,6 +249,8 @@ static void seek_to(int track)
CyWdtClear();
}
CyDelay(STEP_SETTLING_TIME);
+
+ TK43_REG_Write(track < 43); /* high if 0..42, low if 43 or up */
print("finished seek");
}
@@ -381,8 +383,9 @@ static void init_capture_dma(void)
static void cmd_read(struct read_frame* f)
{
- SIDE_REG_Write(f->side);
seek_to(current_track);
+ SIDE_REG_Write(f->side);
+ STEP_REG_Write(f->side); /* for drives which multiplex SIDE and DIR */
/* Do slow setup *before* we go into the real-time bit. */
@@ -478,6 +481,7 @@ abort:;
wait_until_writeable(FLUXENGINE_DATA_IN_EP_NUM);
deinit_dma();
+ STEP_REG_Write(0);
if (saved_dma_underrun)
{
print("underrun after %d packets");
@@ -523,9 +527,11 @@ static void cmd_write(struct write_frame* f)
return;
}
- SEQUENCER_CONTROL_Write(1); /* put the sequencer into reset */
-
+ seek_to(current_track);
SIDE_REG_Write(f->side);
+ STEP_REG_Write(f->side); /* for drives which multiplex SIDE and DIR */
+
+ SEQUENCER_CONTROL_Write(1); /* put the sequencer into reset */
{
uint8_t i = CyEnterCriticalSection();
REPLAY_FIFO_SET_LEVEL_MID;
@@ -533,7 +539,6 @@ static void cmd_write(struct write_frame* f)
REPLAY_FIFO_SINGLE_BUFFER_UNSET;
CyExitCriticalSection(i);
}
- seek_to(current_track);
init_replay_dma();
bool writing = false; /* to the disk */
@@ -638,6 +643,7 @@ abort:
deinit_dma();
+ STEP_REG_Write(0);
if (dma_underrun)
{
print("underrun!");
diff --git a/doc/building.md b/doc/building.md
index 6a2dfdc2..a0ed9b6e 100644
--- a/doc/building.md
+++ b/doc/building.md
@@ -292,7 +292,7 @@ INDEX300 ---+ 3.0| | GND+--------------------------+
+----+ +----+ +--+--+ |
INDEX360 ---+ 3.1| | 1.7+------ DISKCHG --+34+33+--+
+----+ +----+ +--+--+
- + 3.2| | 1.6+------- SIDE1 ---+32+31+
+ TK43 ---+ 3.2| | 1.6+------- SIDE1 ---+32+31+
+----+ +----+ +--+--+
+ 3.3| | 1.5+------- RDATA ---+30+29+
+----+ +----+ +--+--+
@@ -306,7 +306,7 @@ INDEX360 ---+ 3.1| | 1.7+------ DISKCHG --+34+33+--+
+----+ +----+ +--+--+
+15.0| | 1.0+------- STEP ----+20+19+
+----+ +----+ +--+--+
- +15.1| |12.0+-------- DIR ----+18+17+
+ +15.1| |12.0+--- DIR/SIDE1 ---+18+17+
+----+ +----+ +--+--+
+15.2| |12.1+------- MOTEB ---+16+15+
+----+ +----+ +--+--+
@@ -343,6 +343,10 @@ INDEX360 ---+ 3.1| | 1.7+------ DISKCHG --+34+33+--+
Notes:
+ - `DIR/SIDE1` is the step direction pin. During reads or writes, `SIDE1` is
+ also multiplexed onto it, because some drives expect this. This is harmless
+ on other drives because the `DIR` pin is ignored during reads or writes.
+
- `TX` is the debug UART port. It's on pin 12.7 because the board routes it
to the USB serial port on the programmer, so you can get debug information
from the FluxEngine by just plugging the programming end into a USB port
@@ -365,6 +369,10 @@ Notes:
rather exotic things. See the section on flippy disks [in the FAQ](faq.md)
for more details; you can normally ignore these.
+ - `TK43` is an optional output pin which goes low when the drive is seeking
+ to track 43 or above. This is useful when using 8" floppy drives, which
+ require reduced write current when writing to these tracks.
+
## Next steps
You should now be ready to go. You'll want to read [the client
diff --git a/doc/disk-trs80.md b/doc/disk-trs80.md
index 7bcfe338..f2eed8d0 100644
--- a/doc/disk-trs80.md
+++ b/doc/disk-trs80.md
@@ -32,3 +32,11 @@ If you've got a 40-track disk, use `-s :t=0-79x2`.
If you've got a single density disk, use `--read-fm=true`. (Double density is
the default.)
+
+
+Useful references
+-----------------
+
+ - [The JV3 file format](https://www.tim-mann.org/trs80/dskspec.html):
+ documents the most popular emulator disk image.
+
diff --git a/doc/using.md b/doc/using.md
index f82bfd44..f0a185ce 100644
--- a/doc/using.md
+++ b/doc/using.md
@@ -136,6 +136,10 @@ exact format varies according to the extension:
format due to the weird layout of Mac GCR disks, but it can also support
720kB and 1440kB IBM disks (although there's no real benefit).
+ - `.jv3`: a disk image format mainly used by the TRS-80. These images can be
+ read, but not yet written. You only get the data; the density and DAM bits
+ are ignored.
+
### High density disks
High density disks use a different magnetic medium to low and double density
@@ -228,21 +232,16 @@ directory.
format in a non-backwards-compatible way; this tool will upgrade flux files
to the new format.
- - `fluxengine convert`: converts flux files from various formats to various
- other formats. You can use this to convert Catweasel flux files to
+ - `fluxengine convert`: converts files from various formats to various other
+ formats. The main use of this is probably `fluxengine convert image`, which
+ will convert a disk image from one format to another.
+
+ There are also subcommands for converting Catweasel flux files to
FluxEngine's native format, FluxEngine flux files to various other formats
useful for debugging (including VCD which can be loaded into
[sigrok](http://sigrok.org)), and bidirectional conversion to and from
Supercard Pro `.scp` format.
- **Important SCP note:** import (`fluxengine convert scptoflux`) should be
- fairly robust, but export (`fluxengine convert fluxtoscp`) should only be
- done with great caution as FluxEngine files contain features which can't be
- represented very well in `.scp` format and they're probably pretty dubious.
- As ever, please [get in
- touch](https://github.com/davidgiven/fluxengine/issues/new) with any
- reports.
-
Commands which normally take `--source` or `--dest` get a sensible default if
left unspecified. `fluxengine read ibm` on its own will read drive 0 and
write an `ibm.img` file.
diff --git a/lib/imagereader/imagereader.cc b/lib/imagereader/imagereader.cc
index be29d491..5b8895c3 100644
--- a/lib/imagereader/imagereader.cc
+++ b/lib/imagereader/imagereader.cc
@@ -15,6 +15,8 @@ std::map ImageReader::formats =
{".diskcopy", ImageReader::createDiskCopyImageReader},
{".img", ImageReader::createImgImageReader},
{".ima", ImageReader::createImgImageReader},
+ {".jv1", ImageReader::createImgImageReader},
+ {".jv3", ImageReader::createJv3ImageReader},
};
static bool ends_with(const std::string& value, const std::string& ending)
@@ -46,7 +48,7 @@ std::unique_ptr ImageReader::create(const ImageSpec& spec)
void ImageReader::verifyImageSpec(const ImageSpec& spec)
{
if (!findConstructor(spec))
- Error() << "unrecognised image filename extension";
+ Error() << "unrecognised input image filename extension";
}
ImageReader::ImageReader(const ImageSpec& spec):
diff --git a/lib/imagereader/imagereader.h b/lib/imagereader/imagereader.h
index 048ba0e7..69e61f41 100644
--- a/lib/imagereader/imagereader.h
+++ b/lib/imagereader/imagereader.h
@@ -25,6 +25,7 @@ private:
static std::unique_ptr createDiskCopyImageReader(const ImageSpec& spec);
static std::unique_ptr createImgImageReader(const ImageSpec& spec);
+ static std::unique_ptr createJv3ImageReader(const ImageSpec& spec);
static Constructor findConstructor(const ImageSpec& spec);
diff --git a/lib/imagereader/jv3imagereader.cc b/lib/imagereader/jv3imagereader.cc
new file mode 100644
index 00000000..06b38dd6
--- /dev/null
+++ b/lib/imagereader/jv3imagereader.cc
@@ -0,0 +1,141 @@
+#include "globals.h"
+#include "flags.h"
+#include "dataspec.h"
+#include "sector.h"
+#include "sectorset.h"
+#include "imagereader/imagereader.h"
+#include "fmt/format.h"
+#include
+#include
+#include
+
+/* JV3 files are kinda weird. There's a fixed layout for up to 2901 sectors, which may appear
+ * in any order, followed by the same again for more sectors. To find the second data block
+ * you need to know the size of the first data block, which requires parsing it.
+ *
+ * https://www.tim-mann.org/trs80/dskspec.html
+ *
+ * typedef struct {
+ * SectorHeader headers1[2901];
+ * unsigned char writeprot;
+ * unsigned char data1[];
+ * SectorHeader headers2[2901];
+ * unsigned char padding;
+ * unsigned char data2[];
+ * } JV3;
+ *
+ * typedef struct {
+ * unsigned char track;
+ * unsigned char sector;
+ * unsigned char flags;
+ * } SectorHeader;
+ */
+
+struct SectorHeader
+{
+ uint8_t track;
+ uint8_t sector;
+ uint8_t flags;
+};
+
+#define JV3_DENSITY 0x80 /* 1=dden, 0=sden */
+#define JV3_DAM 0x60 /* data address mark code; see below */
+#define JV3_SIDE 0x10 /* 0=side 0, 1=side 1 */
+#define JV3_ERROR 0x08 /* 0=ok, 1=CRC error */
+#define JV3_NONIBM 0x04 /* 0=normal, 1=short */
+#define JV3_SIZE 0x03 /* in used sectors: 0=256,1=128,2=1024,3=512
+ in free sectors: 0=512,1=1024,2=128,3=256 */
+
+#define JV3_FREE 0xFF /* in track and sector fields of free sectors */
+#define JV3_FREEF 0xFC /* in flags field, or'd with size code */
+
+static unsigned getSectorSize(uint8_t flags)
+{
+ if ((flags & JV3_FREEF) == JV3_FREEF)
+ {
+ switch (flags & JV3_SIZE)
+ {
+ case 0: return 512;
+ case 1: return 1024;
+ case 2: return 128;
+ case 3: return 256;
+ }
+ }
+ else
+ {
+ switch (flags & JV3_SIZE)
+ {
+ case 0: return 256;
+ case 1: return 128;
+ case 2: return 1024;
+ case 3: return 512;
+ }
+ }
+ Error() << "not reachable";
+}
+
+class Jv3ImageReader : public ImageReader
+{
+public:
+ Jv3ImageReader(const ImageSpec& spec):
+ ImageReader(spec)
+ {}
+
+ SectorSet readImage()
+ {
+ std::ifstream inputFile(spec.filename, std::ios::in | std::ios::binary);
+ if (!inputFile.is_open())
+ Error() << "cannot open input file";
+
+ inputFile.seekg( 0, std::ios::end);
+ unsigned inputFileSize = inputFile.tellg();
+ unsigned headerPtr = 0;
+ SectorSet sectors;
+ for (;;)
+ {
+ unsigned dataPtr = headerPtr + 2901*3 + 1;
+ if (dataPtr >= inputFileSize)
+ break;
+
+ for (unsigned i=0; i<2901; i++)
+ {
+ SectorHeader header = {0, 0, 0xff};
+ inputFile.seekg(headerPtr);
+ inputFile.read((char*) &header, 3);
+ unsigned sectorSize = getSectorSize(header.flags);
+ if ((header.flags & JV3_FREEF) != JV3_FREEF)
+ {
+ Bytes data(sectorSize);
+ inputFile.seekg(dataPtr);
+ inputFile.read((char*) data.begin(), sectorSize);
+
+ unsigned head = !!(header.flags & JV3_SIDE);
+ std::unique_ptr& sector = sectors.get(header.track, head, header.sector);
+ sector.reset(new Sector);
+ sector->status = Sector::OK;
+ sector->logicalTrack = sector->physicalTrack = header.track;
+ sector->logicalSide = sector->physicalSide = head;
+ sector->logicalSector = header.sector;
+ sector->data = data;
+ }
+
+ headerPtr += 3;
+ dataPtr += sectorSize;
+ }
+
+ /* dataPtr is now pointing at the beginning of the next chunk. */
+
+ headerPtr = dataPtr;
+ }
+
+ return sectors;
+ }
+};
+
+std::unique_ptr ImageReader::createJv3ImageReader(
+ const ImageSpec& spec)
+{
+ return std::unique_ptr(new Jv3ImageReader(spec));
+}
+
+
diff --git a/lib/imagewriter/imagewriter.cc b/lib/imagewriter/imagewriter.cc
index e0f9fec3..73699a21 100644
--- a/lib/imagewriter/imagewriter.cc
+++ b/lib/imagewriter/imagewriter.cc
@@ -47,7 +47,7 @@ std::unique_ptr ImageWriter::create(const SectorSet& sectors, const
void ImageWriter::verifyImageSpec(const ImageSpec& spec)
{
if (!findConstructor(spec))
- Error() << "unrecognised image filename extension";
+ Error() << "unrecognised output image filename extension";
}
ImageWriter::ImageWriter(const SectorSet& sectors, const ImageSpec& spec):
diff --git a/mkninja.sh b/mkninja.sh
index 8748f688..72ba94bf 100644
--- a/mkninja.sh
+++ b/mkninja.sh
@@ -154,6 +154,7 @@ buildlibrary libbackend.a \
lib/imagereader/diskcopyimagereader.cc \
lib/imagereader/imagereader.cc \
lib/imagereader/imgimagereader.cc \
+ lib/imagereader/jv3imagereader.cc \
lib/imagewriter/d64imagewriter.cc \
lib/imagewriter/diskcopyimagewriter.cc \
lib/imagewriter/imagewriter.cc \
@@ -214,6 +215,7 @@ buildlibrary libfrontend.a \
src/fe-fluxtoau.cc \
src/fe-fluxtoscp.cc \
src/fe-fluxtovcd.cc \
+ src/fe-image.cc \
src/fe-inspect.cc \
src/fe-readadfs.cc \
src/fe-readaeslanier.cc \
diff --git a/src/fe-image.cc b/src/fe-image.cc
new file mode 100644
index 00000000..126d261b
--- /dev/null
+++ b/src/fe-image.cc
@@ -0,0 +1,38 @@
+#include "globals.h"
+#include "flags.h"
+#include "dataspec.h"
+#include "sector.h"
+#include "sectorset.h"
+#include "imagereader/imagereader.h"
+#include "imagewriter/imagewriter.h"
+#include "fmt/format.h"
+#include
+
+static FlagGroup flags { };
+
+static void syntax()
+{
+ std::cout << "Syntax: fluxengine convert image \n";
+ exit(0);
+}
+
+int mainConvertImage(int argc, const char* argv[])
+{
+ auto filenames = flags.parseFlagsWithFilenames(argc, argv);
+ if (filenames.size() != 2)
+ syntax();
+
+ DataSpec ids(filenames[0]);
+ ImageSpec iis(ids);
+ SectorSet sectors = ImageReader::create(iis)->readImage();
+
+ DataSpec ods(filenames[1]);
+ ImageSpec ois(ods);
+ auto writer = ImageWriter::create(sectors, ois);
+ writer->adjustGeometry();
+ writer->printMap();
+ writer->writeImage();
+
+ return 0;
+}
+
diff --git a/src/fluxengine.cc b/src/fluxengine.cc
index 0cebf860..be3de3c9 100644
--- a/src/fluxengine.cc
+++ b/src/fluxengine.cc
@@ -7,6 +7,7 @@ extern command_cb mainConvertCwfToFlux;
extern command_cb mainConvertFluxToAu;
extern command_cb mainConvertFluxToScp;
extern command_cb mainConvertFluxToVcd;
+extern command_cb mainConvertImage;
extern command_cb mainConvertScpToFlux;
extern command_cb mainInspect;
extern command_cb mainReadADFS;
@@ -103,6 +104,7 @@ static std::vector convertables =
{ "fluxtoau", mainConvertFluxToAu, "Converts (one track of a) flux file to an .au audio file.", },
{ "fluxtoscp", mainConvertFluxToScp, "Converrt a flux file to a Supercard Pro file.", },
{ "fluxtovcd", mainConvertFluxToVcd, "Converts (one track of a) flux file to a VCD file.", },
+ { "image", mainConvertImage, "Converts one disk image to another.", },
};
static std::vector testables =