mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
Merge.
This commit is contained in:
21
README.md
21
README.md
@@ -4,11 +4,8 @@ FluxEngine
|
||||
(If you're reading this on GitHub, the formatting's a bit messed up. [Try the
|
||||
version on cowlark.com instead.](http://cowlark.com/fluxengine/))
|
||||
|
||||
**Breaking news!** As of 2022-09-09, there's new [filesystem
|
||||
support](doc/filesystem.md). Read (and sometimes write) files directly from
|
||||
(and to) your disks, with eight different file systems! It works in the GUI,
|
||||
too, which is available for Linux (and other Unix clones), Windows and OSX. See
|
||||
the details below.
|
||||
**Breaking news!** As of 2024-10-01, the FluxEngine client software works
|
||||
(to a point) with [Applesauce](doc/applesauce.md) hardware.
|
||||
|
||||
<div style="text-align: center">
|
||||
<a href="doc/screenshot.jpg"><img src="doc/screenshot.jpg" style="width:60%" alt="screenshot of the GUI in action"></a>
|
||||
@@ -35,12 +32,14 @@ Don't believe me? Watch the demo reel!
|
||||
</div>
|
||||
|
||||
**New!** The FluxEngine client software now works with
|
||||
[Greaseweazle](https://github.com/keirf/Greaseweazle/wiki) hardware. So, if you
|
||||
can't find a PSoC5 development kit, or don't want to use the Cypress Windows
|
||||
tools for programming it, you can use one of these instead. Very nearly all
|
||||
FluxEngine features are available with the Greaseweazle and it works out-of-the
|
||||
box. See the [dedicated Greaseweazle documentation page](doc/greaseweazle.md)
|
||||
for more information.
|
||||
[Greaseweazle](https://github.com/keirf/Greaseweazle/wiki) and
|
||||
[Applesauce](https://applesaucefdc.com/) hardware. So, if you can't find a PSoC5
|
||||
development kit, or don't want to use the Cypress Windows tools for programming
|
||||
it, you can use one of these instead. Very nearly all FluxEngine features are
|
||||
available with the Greaseweazle and it works out-of-the box; the Applesauce is a
|
||||
bit less supported but still works. See the [dedicated Greaseweazle
|
||||
documentation page](doc/greaseweazle.md) or the [Applesauce
|
||||
page](doc/applesauce.md) for more information.
|
||||
|
||||
Where?
|
||||
------
|
||||
|
||||
3
build.py
3
build.py
@@ -85,6 +85,8 @@ cxxlibrary(
|
||||
"./lib/proto.cc",
|
||||
"./lib/readerwriter.cc",
|
||||
"./lib/sector.cc",
|
||||
"./lib/usb/applesauce.cc",
|
||||
"./lib/usb/applesauceusb.cc",
|
||||
"./lib/usb/fluxengineusb.cc",
|
||||
"./lib/usb/greaseweazle.cc",
|
||||
"./lib/usb/greaseweazleusb.cc",
|
||||
@@ -180,6 +182,7 @@ cxxlibrary(
|
||||
"lib/proto.h": "./lib/proto.h",
|
||||
"lib/readerwriter.h": "./lib/readerwriter.h",
|
||||
"lib/sector.h": "./lib/sector.h",
|
||||
"lib/usb/applesauce.h": "./lib/usb/applesauce.h",
|
||||
"lib/usb/greaseweazle.h": "./lib/usb/greaseweazle.h",
|
||||
"lib/usb/usb.h": "./lib/usb/usb.h",
|
||||
"lib/usb/usbfinder.h": "./lib/usb/usbfinder.h",
|
||||
|
||||
72
doc/applesauce.md
Normal file
72
doc/applesauce.md
Normal file
@@ -0,0 +1,72 @@
|
||||
Using the FluxEngine client software with Applesauce hardware
|
||||
===============================================================
|
||||
|
||||
The FluxEngine isn't the only project which does this; another one is the
|
||||
[Applesauce](https://applesaucefdc.com/), a proprietary but feature-rich
|
||||
off-the-shelf imaging device. Its native client (which is a lot better than
|
||||
FluxEngine) only works on OSX, so if you want to use it anywhere else,
|
||||
the FluxEngine client works.
|
||||
|
||||
The Applesauce works rather differently to the FluxEngine hardware or the
|
||||
[Greaseweazle](greaseweazle.md), so there are some caveats.
|
||||
|
||||
- Rather than streaming the flux data from the device to the PC, the Applesauce
|
||||
has a fixed buffer in RAM used to capture a complete image of a track. This is
|
||||
then downloaded later. The advantage is that USB bandwidth isn't an issue; the
|
||||
downside is that the buffer can only hold so much data. In fact, the Applesauce
|
||||
can only capture 1.25 revolutions or 2.25 revolutions, nothing else. When used
|
||||
with the FluxEngine the capture time will be ignored apart from used to
|
||||
determine whether you want a 'long' or 'short' capture.
|
||||
|
||||
- The current (v2) firmware only supports reading, not writing (via clients
|
||||
other than the official one, of course). The new (v3) firmware will support
|
||||
writing, but it's not out yet, so for the time being the FluxEngine client is
|
||||
read only.
|
||||
|
||||
- You can only do synchronous reads, i.e., reads starting from the index mark.
|
||||
|
||||
Other than this, the FluxEngine software supports the Applesauce almost
|
||||
out-of-the-box --- just plug it in and nearly everything should work. The
|
||||
FluxEngine software will autodetect it. If you have more than one device plugged
|
||||
in, use `--usb.serial=` to specify which one you want to use.
|
||||
|
||||
I am aware that having _software_ called FluxEngine and _hardware_ called
|
||||
FluxEngine makes things complicated when you're not using the FluxEngine client
|
||||
software with a FluxEngine board, but I'm afraid it's too late to change that
|
||||
now. Sorry.
|
||||
|
||||
What works
|
||||
----------
|
||||
|
||||
Supported features with the Greaseweazle include:
|
||||
|
||||
- simple reading of disks, seeking etc
|
||||
- erasing disks
|
||||
- hard sectored disks
|
||||
- determining disk rotation speed
|
||||
- normal IBM buses
|
||||
|
||||
I don't know what happens if you try to use an Apple Superdrive or a Apple II
|
||||
disk with FluxEngine. If you've got one, [please get in
|
||||
touch](https://github.com/davidgiven/fluxengine/issues/new)!
|
||||
|
||||
What doesn't work
|
||||
-----------------
|
||||
|
||||
- voltage measurement
|
||||
- writing
|
||||
|
||||
Who to contact
|
||||
--------------
|
||||
|
||||
I want to make it clear that the FluxEngine code is _not_ supported by the
|
||||
Applesauce team. If you have any problems, please [contact
|
||||
me](https://github.com/davidgiven/fluxengine/issues/new) and not them.
|
||||
|
||||
In addition, the Applesauce release cycle is not synchronised to the
|
||||
FluxEngine release cycle, so it's possible you'll have a version of the
|
||||
Applesauce firmware which is not supported by FluxEngine. Hopefully, it'll
|
||||
detect this and complain. Again, [file an
|
||||
issue](https://github.com/davidgiven/fluxengine/issues/new) and I'll look into
|
||||
it.
|
||||
|
||||
@@ -50,9 +50,9 @@ file while changing the decoder options, to save disk wear. It's also much faste
|
||||
|
||||
### Connecting it up
|
||||
|
||||
To use, simply plug your FluxEngine (or [Greaseweazle](greaseweazle.md)) into
|
||||
your computer and run the client. If a single device is plugged in, it will be
|
||||
automatically detected and used.
|
||||
To use, simply plug your FluxEngine (or [Greaseweazle](greaseweazle.md) or
|
||||
[Applesauce](applesauce.md)) into your computer and run the client. If a single
|
||||
device is plugged in, it will be automatically detected and used.
|
||||
|
||||
If _more_ than one device is plugged in, you need to specify which one to use
|
||||
with the `--usb.serial` parameter, which takes the device serial number as a
|
||||
|
||||
@@ -48,7 +48,7 @@ bool FluxmapReader::findEvent(int event, unsigned& ticks)
|
||||
{
|
||||
ticks = 0;
|
||||
|
||||
for (;;)
|
||||
while (!eof())
|
||||
{
|
||||
unsigned thisTicks;
|
||||
int thisEvent;
|
||||
@@ -58,11 +58,11 @@ bool FluxmapReader::findEvent(int event, unsigned& ticks)
|
||||
if (thisEvent == F_EOF)
|
||||
return false;
|
||||
|
||||
if (eof())
|
||||
return false;
|
||||
if ((event == thisEvent) || (event & thisEvent))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned FluxmapReader::readInterval(nanoseconds_t clock)
|
||||
|
||||
@@ -157,7 +157,7 @@ std::vector<std::string> FlagGroup::parseFlagsWithFilenames(int argc,
|
||||
globalConfig().applyOption(path);
|
||||
}
|
||||
else
|
||||
error("unrecognised flag; try --help");
|
||||
error("unrecognised flag '-{}'; try --help", key);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@ message HardwareFluxSourceProto {}
|
||||
|
||||
message TestPatternFluxSourceProto {
|
||||
optional double interval_us = 1 [default = 4.0, (help) = "interval between pulses"];
|
||||
optional double sequence_length_us = 2 [default = 200.0, (help) = "length of test sequence"];
|
||||
optional double sequence_length_ms = 2 [default = 166.0, (help) = "length of test sequence"];
|
||||
}
|
||||
|
||||
message EraseFluxSourceProto {}
|
||||
|
||||
@@ -18,9 +18,9 @@ public:
|
||||
{
|
||||
auto fluxmap = std::make_unique<Fluxmap>();
|
||||
|
||||
while (fluxmap->duration() < (_config.sequence_length_us() * 1000000.0))
|
||||
while (fluxmap->duration() < (_config.sequence_length_ms() * 1e6))
|
||||
{
|
||||
fluxmap->appendInterval(_config.interval_us());
|
||||
fluxmap->appendInterval(_config.interval_us() * TICKS_PER_US);
|
||||
fluxmap->appendPulse();
|
||||
}
|
||||
|
||||
|
||||
@@ -14,5 +14,4 @@ double getCurrentTime(void)
|
||||
void warning(const std::string msg)
|
||||
{
|
||||
log(msg);
|
||||
fmt::print("Warning: {}\n", msg);
|
||||
}
|
||||
|
||||
64
lib/usb/applesauce.cc
Normal file
64
lib/usb/applesauce.cc
Normal file
@@ -0,0 +1,64 @@
|
||||
#include "lib/globals.h"
|
||||
#include "usb.h"
|
||||
#include "protocol.h"
|
||||
#include "lib/bytes.h"
|
||||
#include "greaseweazle.h"
|
||||
#include "lib/fluxmap.h"
|
||||
#include "lib/decoders/fluxmapreader.h"
|
||||
#include "lib/a2r.h"
|
||||
|
||||
static double a2r_to_ticks(double a2rticks)
|
||||
{
|
||||
return a2rticks * A2R_NS_PER_TICK / NS_PER_TICK;
|
||||
}
|
||||
|
||||
static double ticks_to_a2r(double flticks)
|
||||
{
|
||||
return flticks * NS_PER_TICK / A2R_NS_PER_TICK;
|
||||
}
|
||||
|
||||
Bytes fluxEngineToApplesauce(const Bytes& fldata)
|
||||
{
|
||||
Fluxmap fluxmap(fldata);
|
||||
FluxmapReader fmr(fluxmap);
|
||||
Bytes asdata;
|
||||
ByteWriter bw(asdata);
|
||||
|
||||
while (!fmr.eof())
|
||||
{
|
||||
unsigned ticks;
|
||||
if (!fmr.findEvent(F_BIT_PULSE, ticks))
|
||||
break;
|
||||
|
||||
uint32_t applesauceTicks = ticks_to_a2r(ticks);
|
||||
while (applesauceTicks >= 255)
|
||||
{
|
||||
bw.write_8(255);
|
||||
applesauceTicks -= 255;
|
||||
}
|
||||
bw.write_8(applesauceTicks);
|
||||
}
|
||||
|
||||
return asdata;
|
||||
}
|
||||
|
||||
Bytes applesauceToFluxEngine(const Bytes& asdata)
|
||||
{
|
||||
ByteReader br(asdata);
|
||||
Fluxmap fluxmap;
|
||||
|
||||
unsigned a2rTicksSinceLastPulse = 0;
|
||||
while (!br.eof())
|
||||
{
|
||||
uint8_t b = br.read_8();
|
||||
a2rTicksSinceLastPulse += b;
|
||||
if (b != 255)
|
||||
{
|
||||
fluxmap.appendInterval(a2r_to_ticks(a2rTicksSinceLastPulse));
|
||||
fluxmap.appendPulse();
|
||||
a2rTicksSinceLastPulse = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return fluxmap.rawBytes();
|
||||
}
|
||||
9
lib/usb/applesauce.h
Normal file
9
lib/usb/applesauce.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#define APPLESAUCE_VID 0x16c0
|
||||
#define APPLESAUCE_PID 0x0483
|
||||
|
||||
#define APPLESAUCE_ID ((APPLESAUCE_VID << 16) | APPLESAUCE_PID)
|
||||
|
||||
extern Bytes applesauceToFluxEngine(const Bytes& asdata);
|
||||
extern Bytes fluxEngineToApplesauce(const Bytes& fldata);
|
||||
345
lib/usb/applesauceusb.cc
Normal file
345
lib/usb/applesauceusb.cc
Normal file
@@ -0,0 +1,345 @@
|
||||
#include "lib/globals.h"
|
||||
#include "protocol.h"
|
||||
#include "lib/fluxmap.h"
|
||||
#include "lib/bytes.h"
|
||||
#include "lib/usb/usb.pb.h"
|
||||
#include "lib/utils.h"
|
||||
#include "applesauce.h"
|
||||
#include "serial.h"
|
||||
#include "usb.h"
|
||||
#include "lib/decoders/fluxmapreader.h"
|
||||
#include <unistd.h>
|
||||
|
||||
class ApplesauceUsb;
|
||||
static ApplesauceUsb* instance;
|
||||
|
||||
static uint32_t ss_rand_next(uint32_t x)
|
||||
{
|
||||
return (x & 1) ? (x >> 1) ^ 0x80000062 : x >> 1;
|
||||
}
|
||||
|
||||
static Bytes applesauceReadDataToFluxEngine(const Bytes& asdata,
|
||||
nanoseconds_t clock,
|
||||
const std::vector<unsigned>& indexMarks)
|
||||
{
|
||||
ByteReader br(asdata);
|
||||
Fluxmap fluxmap;
|
||||
auto indexIt = indexMarks.begin();
|
||||
fluxmap.appendIndex();
|
||||
|
||||
unsigned totalTicks = 0;
|
||||
while (!br.eof())
|
||||
{
|
||||
uint8_t b = br.read_8();
|
||||
fluxmap.appendInterval(b * clock / NS_PER_TICK);
|
||||
if (b != 255)
|
||||
fluxmap.appendPulse();
|
||||
|
||||
totalTicks += b;
|
||||
if ((indexIt != indexMarks.end()) && (totalTicks > *indexIt))
|
||||
{
|
||||
fluxmap.appendIndex();
|
||||
indexIt++;
|
||||
}
|
||||
}
|
||||
|
||||
return fluxmap.rawBytes();
|
||||
}
|
||||
|
||||
static Bytes fluxEngineToApplesauceWriteData(const Bytes& fldata)
|
||||
{
|
||||
Fluxmap fluxmap(fldata);
|
||||
FluxmapReader fmr(fluxmap);
|
||||
Bytes asdata;
|
||||
ByteWriter bw(asdata);
|
||||
|
||||
while (!fmr.eof())
|
||||
{
|
||||
unsigned ticks;
|
||||
if (!fmr.findEvent(F_BIT_PULSE, ticks))
|
||||
break;
|
||||
|
||||
uint32_t applesauceTicks = (double)ticks * NS_PER_TICK;
|
||||
while (applesauceTicks >= 0xffff)
|
||||
{
|
||||
bw.write_le16(0xffff);
|
||||
applesauceTicks -= 0xffff;
|
||||
}
|
||||
if (applesauceTicks == 0)
|
||||
error("bad data!");
|
||||
bw.write_le16(applesauceTicks);
|
||||
}
|
||||
|
||||
bw.write_le16(0);
|
||||
return asdata;
|
||||
}
|
||||
|
||||
class ApplesauceException : public ErrorException
|
||||
{
|
||||
public:
|
||||
ApplesauceException(const std::string& s): ErrorException(s) {}
|
||||
};
|
||||
|
||||
class ApplesauceUsb : public USB
|
||||
{
|
||||
public:
|
||||
ApplesauceUsb(const std::string& port, const ApplesauceProto& config):
|
||||
_serial(SerialPort::openSerialPort(port)),
|
||||
_config(config)
|
||||
{
|
||||
std::string s = sendrecv("?");
|
||||
if (s != "Applesauce")
|
||||
error(
|
||||
"Applesauce device not responding (expected 'Applesauce', got "
|
||||
"'{}')",
|
||||
s);
|
||||
|
||||
doCommand("client:v2");
|
||||
|
||||
atexit(
|
||||
[]()
|
||||
{
|
||||
delete instance;
|
||||
});
|
||||
}
|
||||
|
||||
~ApplesauceUsb()
|
||||
{
|
||||
sendrecv("disconnect");
|
||||
}
|
||||
|
||||
private:
|
||||
std::string sendrecv(const std::string& command)
|
||||
{
|
||||
if (_config.verbose())
|
||||
fmt::print(fmt::format("> {}\n", command));
|
||||
_serial->writeLine(command);
|
||||
auto r = _serial->readLine();
|
||||
if (_config.verbose())
|
||||
fmt::print(fmt::format("< {}\n", r));
|
||||
return r;
|
||||
}
|
||||
|
||||
void checkCommandResult(const std::string& result)
|
||||
{
|
||||
if (result != ".")
|
||||
throw ApplesauceException(
|
||||
fmt::format("low-level Applesauce error: '{}'", result));
|
||||
}
|
||||
|
||||
void doCommand(const std::string& command)
|
||||
{
|
||||
checkCommandResult(sendrecv(command));
|
||||
}
|
||||
|
||||
std::string doCommandX(const std::string& command)
|
||||
{
|
||||
doCommand(command);
|
||||
std::string r = _serial->readLine();
|
||||
if (_config.verbose())
|
||||
fmt::print(fmt::format("<< {}\n", r));
|
||||
return r;
|
||||
}
|
||||
|
||||
void connect()
|
||||
{
|
||||
if (!_connected)
|
||||
{
|
||||
try
|
||||
{
|
||||
doCommand("connect");
|
||||
doCommand("drive:enable");
|
||||
doCommand("motor:on");
|
||||
doCommand("head:zero");
|
||||
_connected = true;
|
||||
}
|
||||
catch (const ApplesauceException& e)
|
||||
{
|
||||
error("Applesauce could not connect to a drive");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void seek(int track) override
|
||||
{
|
||||
if (track == 0)
|
||||
doCommand("head:zero");
|
||||
else
|
||||
doCommand(fmt::format("head:track{}", track));
|
||||
}
|
||||
|
||||
nanoseconds_t getRotationalPeriod(int hardSectorCount) override
|
||||
{
|
||||
if (hardSectorCount != 0)
|
||||
error("hard sectors are currently unsupported on the Applesauce");
|
||||
|
||||
connect();
|
||||
try
|
||||
{
|
||||
double period_us = std::stod(doCommandX("sync:?speed"));
|
||||
_serial->writeByte('X');
|
||||
std::string r = _serial->readLine();
|
||||
if (_config.verbose())
|
||||
fmt::print("<< {}\n", r);
|
||||
return period_us * 1e3;
|
||||
}
|
||||
catch (const ApplesauceException& e)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void testBulkWrite() override
|
||||
{
|
||||
int max = std::stoi(sendrecv("data:?max"));
|
||||
fmt::print("Writing data: ");
|
||||
|
||||
doCommand(fmt::format("data:>{}", max));
|
||||
|
||||
Bytes junk(max);
|
||||
uint32_t seed = 0;
|
||||
for (int i = 0; i < max; i++)
|
||||
{
|
||||
junk[i] = seed;
|
||||
seed = ss_rand_next(seed);
|
||||
}
|
||||
double start_time = getCurrentTime();
|
||||
_serial->write(junk);
|
||||
_serial->readLine();
|
||||
double elapsed_time = getCurrentTime() - start_time;
|
||||
|
||||
std::cout << fmt::format(
|
||||
"transferred {} bytes from PC -> device in {} ms ({} kb/s)\n",
|
||||
max,
|
||||
int(elapsed_time * 1000.0),
|
||||
int((max / 1024.0) / elapsed_time));
|
||||
}
|
||||
|
||||
void testBulkRead() override
|
||||
{
|
||||
int max = std::stoi(sendrecv("data:?max"));
|
||||
fmt::print("Reading data: ");
|
||||
|
||||
doCommand(fmt::format("data:<{}", max));
|
||||
|
||||
double start_time = getCurrentTime();
|
||||
_serial->readBytes(max);
|
||||
double elapsed_time = getCurrentTime() - start_time;
|
||||
|
||||
std::cout << fmt::format(
|
||||
"transferred {} bytes from device -> PC in {} ms ({} kb/s)\n",
|
||||
max,
|
||||
int(elapsed_time * 1000.0),
|
||||
int((max / 1024.0) / elapsed_time));
|
||||
}
|
||||
|
||||
Bytes read(int side,
|
||||
bool synced,
|
||||
nanoseconds_t readTime,
|
||||
nanoseconds_t hardSectorThreshold) override
|
||||
{
|
||||
if (hardSectorThreshold != 0)
|
||||
error("hard sectors are currently unsupported on the Applesauce");
|
||||
bool shortRead = readTime < 400e6;
|
||||
warning(
|
||||
"applesauce: timed reads not supported; using read of {} "
|
||||
"revolutions",
|
||||
shortRead ? "1.25" : "2.25");
|
||||
|
||||
connect();
|
||||
doCommand(fmt::format("head:side{}", side));
|
||||
doCommand("sync:on");
|
||||
doCommand("data:clear");
|
||||
std::string r = doCommandX(shortRead ? "disk:read" : "disk:readx");
|
||||
auto rsplit = split(r, '|');
|
||||
if (rsplit.size() < 2)
|
||||
error("unrecognised Applesauce response to disk:read: '{}'", r);
|
||||
|
||||
int bufferSize = std::stoi(rsplit[0]);
|
||||
nanoseconds_t tickSize = std::stod(rsplit[1]) / 1e3;
|
||||
|
||||
std::vector<unsigned> indexMarks;
|
||||
for (auto i = rsplit.begin() + 2; i != rsplit.end(); i++)
|
||||
indexMarks.push_back(std::stoi(*i));
|
||||
|
||||
doCommand(fmt::format("data:<{}", bufferSize));
|
||||
|
||||
Bytes rawData = _serial->readBytes(bufferSize);
|
||||
Bytes b = applesauceReadDataToFluxEngine(rawData, tickSize, indexMarks);
|
||||
return b;
|
||||
}
|
||||
|
||||
private:
|
||||
void checkWritable()
|
||||
{
|
||||
if (sendrecv("disk:?write") == "-")
|
||||
error("cannot write --- disk is write protected");
|
||||
if (sendrecv("?safe") == "+")
|
||||
error("cannot write --- Applesauce 'safe' switch is on");
|
||||
if (sendrecv("?vers") < "0300")
|
||||
error("cannot write --- need Applesauce firmware 2.0 or above");
|
||||
}
|
||||
|
||||
public:
|
||||
void write(int side,
|
||||
const Bytes& fldata,
|
||||
nanoseconds_t hardSectorThreshold) override
|
||||
{
|
||||
if (hardSectorThreshold != 0)
|
||||
error("hard sectors are currently unsupported on the Applesauce");
|
||||
checkWritable();
|
||||
|
||||
connect();
|
||||
doCommand(fmt::format("head:side{}", side));
|
||||
doCommand("sync:on");
|
||||
doCommand("disk:wipe");
|
||||
doCommand("data:clear");
|
||||
doCommand("disk:wclear");
|
||||
|
||||
Bytes asdata = fluxEngineToApplesauceWriteData(fldata);
|
||||
doCommand(fmt::format("data:>{}", asdata.size()));
|
||||
_serial->write(asdata);
|
||||
checkCommandResult(_serial->readLine());
|
||||
doCommand("disk:wcmd0,0");
|
||||
doCommand("disk:write");
|
||||
}
|
||||
|
||||
void erase(int side, nanoseconds_t hardSectorThreshold) override
|
||||
{
|
||||
if (hardSectorThreshold != 0)
|
||||
error("hard sectors are currently unsupported on the Applesauce");
|
||||
checkWritable();
|
||||
|
||||
connect();
|
||||
doCommand(fmt::format("disk:side{}", side));
|
||||
doCommand("disk:wipe");
|
||||
}
|
||||
|
||||
void setDrive(int drive, bool high_density, int index_mode) override
|
||||
{
|
||||
if (drive != 0)
|
||||
error("the Applesauce only supports drive 0");
|
||||
|
||||
connect();
|
||||
doCommand(fmt::format("dpc:density{}", high_density ? '+' : '-'));
|
||||
}
|
||||
|
||||
void measureVoltages(struct voltages_frame* voltages) override
|
||||
{
|
||||
error("unsupported operation on the Greaseweazle");
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<SerialPort> _serial;
|
||||
const ApplesauceProto& _config;
|
||||
bool _connected = false;
|
||||
};
|
||||
|
||||
USB* createApplesauceUsb(const std::string& port, const ApplesauceProto& config)
|
||||
{
|
||||
instance = new ApplesauceUsb(port, config);
|
||||
return instance;
|
||||
}
|
||||
|
||||
// vim: sw=4 ts=4 et
|
||||
@@ -122,8 +122,7 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
int getVersion() override
|
||||
int getVersion()
|
||||
{
|
||||
struct any_frame f = {
|
||||
.f = {.type = F_FRAME_GET_VERSION_CMD, .size = sizeof(f)}
|
||||
@@ -133,6 +132,7 @@ public:
|
||||
return r->version;
|
||||
}
|
||||
|
||||
public:
|
||||
void seek(int track) override
|
||||
{
|
||||
struct seek_frame f = {
|
||||
|
||||
@@ -53,7 +53,7 @@ Bytes fluxEngineToGreaseweazle(const Bytes& fldata, nanoseconds_t clock)
|
||||
return gwdata;
|
||||
}
|
||||
|
||||
Bytes greaseWeazleToFluxEngine(const Bytes& gwdata, nanoseconds_t clock)
|
||||
Bytes greaseweazleToFluxEngine(const Bytes& gwdata, nanoseconds_t clock)
|
||||
{
|
||||
Bytes fldata;
|
||||
ByteReader br(gwdata);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#define EP_IN 0x83
|
||||
|
||||
extern Bytes fluxEngineToGreaseweazle(const Bytes& fldata, nanoseconds_t clock);
|
||||
extern Bytes greaseWeazleToFluxEngine(const Bytes& gwdata, nanoseconds_t clock);
|
||||
extern Bytes greaseweazleToFluxEngine(const Bytes& gwdata, nanoseconds_t clock);
|
||||
extern Bytes stripPartialRotation(const Bytes& fldata);
|
||||
|
||||
/* Copied from
|
||||
|
||||
@@ -109,7 +109,8 @@ public:
|
||||
do_command({CMD_SET_BUS_TYPE, 3, (uint8_t)config.bus_type()});
|
||||
}
|
||||
|
||||
int getVersion() override
|
||||
private:
|
||||
int getVersion()
|
||||
{
|
||||
do_command({CMD_GET_INFO, 3, GETINFO_FIRMWARE});
|
||||
|
||||
@@ -124,11 +125,7 @@ public:
|
||||
return br.read_be16();
|
||||
}
|
||||
|
||||
void recalibrate() override
|
||||
{
|
||||
seek(0);
|
||||
}
|
||||
|
||||
public:
|
||||
void seek(int track) override
|
||||
{
|
||||
do_command({CMD_SEEK, 3, (uint8_t)track});
|
||||
@@ -356,7 +353,7 @@ public:
|
||||
|
||||
do_command({CMD_GET_FLUX_STATUS, 2});
|
||||
|
||||
Bytes fldata = greaseWeazleToFluxEngine(buffer, _clock);
|
||||
Bytes fldata = greaseweazleToFluxEngine(buffer, _clock);
|
||||
if (synced)
|
||||
fldata = stripPartialRotation(fldata);
|
||||
return fldata;
|
||||
|
||||
@@ -254,6 +254,11 @@ uint8_t SerialPort::readByte()
|
||||
return b;
|
||||
}
|
||||
|
||||
void SerialPort::writeByte(uint8_t b)
|
||||
{
|
||||
this->write(&b, 1);
|
||||
}
|
||||
|
||||
void SerialPort::write(const Bytes& bytes)
|
||||
{
|
||||
int ptr = 0;
|
||||
@@ -264,6 +269,28 @@ void SerialPort::write(const Bytes& bytes)
|
||||
}
|
||||
}
|
||||
|
||||
void SerialPort::writeLine(const std::string& chars)
|
||||
{
|
||||
this->write((const uint8_t*)&chars[0], chars.size());
|
||||
writeByte('\n');
|
||||
}
|
||||
|
||||
std::string SerialPort::readLine()
|
||||
{
|
||||
std::string s;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
uint8_t b = readByte();
|
||||
if (b == '\r')
|
||||
continue;
|
||||
if (b == '\n')
|
||||
return s;
|
||||
|
||||
s += b;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<SerialPort> SerialPort::openSerialPort(const std::string& path)
|
||||
{
|
||||
return std::make_unique<SerialPortImpl>(path);
|
||||
|
||||
@@ -17,8 +17,12 @@ public:
|
||||
void read(Bytes& bytes);
|
||||
Bytes readBytes(size_t count);
|
||||
uint8_t readByte();
|
||||
void writeByte(uint8_t b);
|
||||
void write(const Bytes& bytes);
|
||||
|
||||
void writeLine(const std::string& chars);
|
||||
std::string readLine();
|
||||
|
||||
private:
|
||||
uint8_t _readbuffer[4096];
|
||||
size_t _readbuffer_ptr = 0;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "lib/proto.h"
|
||||
#include "usbfinder.h"
|
||||
#include "lib/logger.h"
|
||||
#include "applesauce.h"
|
||||
#include "greaseweazle.h"
|
||||
|
||||
static USB* usb = NULL;
|
||||
@@ -55,6 +56,11 @@ static std::shared_ptr<CandidateDevice> selectDevice()
|
||||
std::cerr << fmt::format(
|
||||
"Greaseweazle: {} on {}\n", c->serial, c->serialPort);
|
||||
break;
|
||||
|
||||
case APPLESAUCE_ID:
|
||||
std::cerr << fmt::format(
|
||||
"Applesauce: {} on {}\n", c->serial, c->serialPort);
|
||||
break;
|
||||
}
|
||||
}
|
||||
exit(1);
|
||||
@@ -72,6 +78,14 @@ USB* get_usb_impl()
|
||||
return createGreaseweazleUsb(conf.port(), conf);
|
||||
}
|
||||
|
||||
if (globalConfig()->usb().has_applesauce() &&
|
||||
globalConfig()->usb().applesauce().has_port())
|
||||
{
|
||||
const auto& conf = globalConfig()->usb().applesauce();
|
||||
log("Using Applesauce on serial port {}", conf.port());
|
||||
return createApplesauceUsb(conf.port(), conf);
|
||||
}
|
||||
|
||||
/* Otherwise, select a device by USB ID. */
|
||||
|
||||
auto candidate = selectDevice();
|
||||
@@ -88,6 +102,13 @@ USB* get_usb_impl()
|
||||
return createGreaseweazleUsb(
|
||||
candidate->serialPort, globalConfig()->usb().greaseweazle());
|
||||
|
||||
case APPLESAUCE_ID:
|
||||
log("Using Applesauce {} on {}",
|
||||
candidate->serial,
|
||||
candidate->serialPort);
|
||||
return createApplesauceUsb(
|
||||
candidate->serialPort, globalConfig()->usb().applesauce());
|
||||
|
||||
default:
|
||||
error("internal");
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
class Fluxmap;
|
||||
class GreaseweazleProto;
|
||||
class ApplesauceProto;
|
||||
namespace libusbp
|
||||
{
|
||||
class device;
|
||||
@@ -16,8 +17,10 @@ class USB
|
||||
public:
|
||||
virtual ~USB();
|
||||
|
||||
virtual int getVersion() = 0;
|
||||
virtual void recalibrate() = 0;
|
||||
virtual void recalibrate()
|
||||
{
|
||||
seek(0);
|
||||
};
|
||||
virtual void seek(int track) = 0;
|
||||
virtual nanoseconds_t getRotationalPeriod(int hardSectorCount) = 0;
|
||||
virtual void testBulkWrite() = 0;
|
||||
@@ -41,11 +44,9 @@ extern USB& getUsb();
|
||||
extern USB* createFluxengineUsb(libusbp::device& device);
|
||||
extern USB* createGreaseweazleUsb(
|
||||
const std::string& serialPort, const GreaseweazleProto& config);
|
||||
extern USB* createApplesauceUsb(
|
||||
const std::string& serialPort, const ApplesauceProto& config);
|
||||
|
||||
static inline int usbGetVersion()
|
||||
{
|
||||
return getUsb().getVersion();
|
||||
}
|
||||
static inline void usbRecalibrate()
|
||||
{
|
||||
getUsb().recalibrate();
|
||||
|
||||
@@ -16,9 +16,17 @@ message GreaseweazleProto {
|
||||
[(help) = "which FDD bus type is in use", default = IBMPC];
|
||||
}
|
||||
|
||||
message ApplesauceProto {
|
||||
optional string port = 1
|
||||
[(help) = "Applesauce serial port to use"];
|
||||
optional bool verbose = 2
|
||||
[(help) = "Enable verbose protocol logging", default = false];
|
||||
}
|
||||
|
||||
message UsbProto {
|
||||
optional string serial = 1
|
||||
[(help) = "serial number of FluxEngine or Greaseweazle device to use"];
|
||||
|
||||
optional GreaseweazleProto greaseweazle = 2 [(help) = "Greaseweazle-specific options"];
|
||||
optional ApplesauceProto applesauce = 3 [(help) = "Applesauce-specific options"];
|
||||
}
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
#include "usb.h"
|
||||
#include "lib/core/bytes.h"
|
||||
#include "usbfinder.h"
|
||||
#include "applesauce.h"
|
||||
#include "greaseweazle.h"
|
||||
#include "protocol.h"
|
||||
#include "libusbp.hpp"
|
||||
|
||||
static const std::set<uint32_t> VALID_DEVICES = {
|
||||
GREASEWEAZLE_ID, FLUXENGINE_ID};
|
||||
GREASEWEAZLE_ID, FLUXENGINE_ID, APPLESAUCE_ID};
|
||||
|
||||
static const std::string get_serial_number(const libusbp::device& device)
|
||||
{
|
||||
@@ -41,13 +42,17 @@ std::vector<std::shared_ptr<CandidateDevice>> findUsbDevices()
|
||||
candidate->serial = get_serial_number(it);
|
||||
|
||||
if (id == GREASEWEAZLE_ID)
|
||||
candidate->type = DEVICE_GREASEWEAZLE;
|
||||
else if (id == APPLESAUCE_ID)
|
||||
candidate->type = DEVICE_APPLESAUCE;
|
||||
else if (id == FLUXENGINE_ID)
|
||||
candidate->type = DEVICE_FLUXENGINE;
|
||||
|
||||
if ((id == GREASEWEAZLE_ID) || (id == APPLESAUCE_ID))
|
||||
{
|
||||
libusbp::serial_port port(candidate->device);
|
||||
candidate->serialPort = port.get_name();
|
||||
candidate->type = DEVICE_GREASEWEAZLE;
|
||||
}
|
||||
else if (id == FLUXENGINE_ID)
|
||||
candidate->type = DEVICE_FLUXENGINE;
|
||||
|
||||
candidates.push_back(std::move(candidate));
|
||||
}
|
||||
@@ -71,6 +76,9 @@ std::string getDeviceName(DeviceType type)
|
||||
case DEVICE_FLUXENGINE:
|
||||
return "FluxEngine";
|
||||
|
||||
case DEVICE_APPLESAUCE:
|
||||
return "Applesauce";
|
||||
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
enum DeviceType
|
||||
{
|
||||
DEVICE_FLUXENGINE,
|
||||
DEVICE_GREASEWEAZLE
|
||||
DEVICE_GREASEWEAZLE,
|
||||
DEVICE_APPLESAUCE,
|
||||
};
|
||||
|
||||
extern std::string getDeviceName(DeviceType type);
|
||||
|
||||
74
tests/applesauce.cc
Normal file
74
tests/applesauce.cc
Normal file
@@ -0,0 +1,74 @@
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include "lib/core/globals.h"
|
||||
#include "lib/fluxmap.h"
|
||||
#include "lib/usb/applesauce.h"
|
||||
|
||||
static void test_convert(const Bytes& asbytes, const Bytes& flbytes)
|
||||
{
|
||||
Bytes astoflbytes = applesauceToFluxEngine(asbytes);
|
||||
Bytes fltoasbytes = fluxEngineToApplesauce(flbytes);
|
||||
|
||||
if (astoflbytes != flbytes)
|
||||
{
|
||||
std::cout << "Applesauce to FluxEngine conversion failed.\n";
|
||||
std::cout << "Applesauce bytes:" << std::endl;
|
||||
hexdump(std::cout, asbytes);
|
||||
std::cout << std::endl << "Produced this:" << std::endl;
|
||||
hexdump(std::cout, astoflbytes);
|
||||
std::cout << std::endl << "Expected this:" << std::endl;
|
||||
hexdump(std::cout, flbytes);
|
||||
abort();
|
||||
}
|
||||
|
||||
if (fltoasbytes != asbytes)
|
||||
{
|
||||
std::cout << "FluxEngine to Applesauce conversion failed.\n";
|
||||
std::cout << "FluxEngine bytes:" << std::endl;
|
||||
hexdump(std::cout, flbytes);
|
||||
std::cout << std::endl << "Produced this:" << std::endl;
|
||||
hexdump(std::cout, fltoasbytes);
|
||||
std::cout << std::endl << "Expected this:" << std::endl;
|
||||
hexdump(std::cout, asbytes);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static void test_conversions()
|
||||
{
|
||||
/* Simple one-byte intervals. */
|
||||
|
||||
test_convert(Bytes{0x20, 0x20, 0x20, 0x20}, Bytes{0xb0, 0xb0, 0xb0, 0xb0});
|
||||
|
||||
/* Long, multibyte intervals. */
|
||||
|
||||
test_convert(Bytes{0xff, 0x1f, 0x20, 0xff, 0xff, 0x20},
|
||||
Bytes{0x3f,
|
||||
0x3f,
|
||||
0x3f,
|
||||
0x3f,
|
||||
0x3f,
|
||||
0x3f,
|
||||
0xb3,
|
||||
0xb0,
|
||||
0x3f,
|
||||
0x3f,
|
||||
0x3f,
|
||||
0x3f,
|
||||
0x3f,
|
||||
0x3f,
|
||||
0x3f,
|
||||
0x3f,
|
||||
0x3f,
|
||||
0x3f,
|
||||
0x3f,
|
||||
0x3f,
|
||||
0xb9});
|
||||
}
|
||||
|
||||
int main(int argc, const char* argv[])
|
||||
{
|
||||
test_conversions();
|
||||
return 0;
|
||||
}
|
||||
@@ -18,6 +18,7 @@ tests = [
|
||||
"agg",
|
||||
"amiga",
|
||||
"applesingle",
|
||||
"applesauce",
|
||||
"bitaccumulator",
|
||||
"bytes",
|
||||
"compression",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
static void test_convert(const Bytes& gwbytes, const Bytes& flbytes)
|
||||
{
|
||||
Bytes gwtoflbytes = greaseWeazleToFluxEngine(gwbytes, 2 * NS_PER_TICK);
|
||||
Bytes gwtoflbytes = greaseweazleToFluxEngine(gwbytes, 2 * NS_PER_TICK);
|
||||
Bytes fltogwbytes = fluxEngineToGreaseweazle(flbytes, 2 * NS_PER_TICK);
|
||||
|
||||
if (gwtoflbytes != flbytes)
|
||||
|
||||
Reference in New Issue
Block a user