593 lines
18 KiB
C++
593 lines
18 KiB
C++
// STRIX firmware for the Arduino MKR Vidor 4000: boots the STRIX FPGA image and serves
|
|
// entropy, health, live statistics and bias amplifier trials over USB serial.
|
|
//
|
|
// Protocol: one command per line; replies are single JSON lines, except `entropy`, which
|
|
// replies with hex text or a "BIN <n>" header followed by n raw bytes. See API.md.
|
|
|
|
#include <StrixFPGA.h>
|
|
|
|
using namespace strix;
|
|
|
|
const char FIRMWARE_VERSION[] = "0.1.0";
|
|
const unsigned long POLL_MS = 10;
|
|
const unsigned long ENTROPY_TIMEOUT_MS = 2000;
|
|
const size_t MAX_ENTROPY_BYTES = 65536;
|
|
const size_t CHUNK_BYTES = 256;
|
|
const uint8_t MAX_ROLLING = 50;
|
|
const char *CHANNEL_NAMES[STATS_CHANNELS] = {"g0", "g1", "g2", "g3", "combined"};
|
|
const char *PATH_NAMES[2] = {"bias", "autocorr"};
|
|
|
|
StrixFPGA fpga;
|
|
bool fpgaReady = false;
|
|
|
|
// Statistics windows, built from the FPGA's per-trial snapshots
|
|
|
|
struct Counts {
|
|
uint32_t bits, ones, agreements;
|
|
};
|
|
|
|
struct Totals {
|
|
uint64_t bits, ones, agreements;
|
|
};
|
|
|
|
struct StatsTrial {
|
|
uint32_t sequence;
|
|
uint8_t healthy;
|
|
bool vonNeumann;
|
|
uint16_t trialMs;
|
|
Counts channels[STATS_CHANNELS];
|
|
};
|
|
|
|
StatsTrial latestStats;
|
|
bool haveStats = false;
|
|
Counts history[MAX_ROLLING][STATS_CHANNELS];
|
|
uint8_t historyCount = 0, historyNext = 0, rolling = 10;
|
|
Totals totals[STATS_CHANNELS];
|
|
uint32_t trialsCounted = 0, missedTrials = 0;
|
|
// After a restart the snapshot still holds the last old trial until the first new one ends.
|
|
bool awaitingRestart = false;
|
|
uint32_t staleSequence = 0;
|
|
|
|
struct AmpPath {
|
|
uint32_t bits, ones;
|
|
uint16_t votes, voted;
|
|
bool result, valid;
|
|
};
|
|
|
|
struct AmpTrial {
|
|
uint32_t sequence;
|
|
uint8_t sources, subtrials, subMs;
|
|
bool vonNeumann, overflow;
|
|
AmpPath paths[2];
|
|
};
|
|
|
|
AmpTrial latestAmp;
|
|
bool haveAmp = false;
|
|
|
|
enum Watch { WATCH_OFF, WATCH_STATS, WATCH_AMP };
|
|
Watch watching = WATCH_OFF;
|
|
|
|
// Helpers
|
|
|
|
uint32_t word32(const uint8_t *bytes) {
|
|
return (uint32_t)bytes[0] << 24 | (uint32_t)bytes[1] << 16 | (uint32_t)bytes[2] << 8 | bytes[3];
|
|
}
|
|
|
|
void printU64(uint64_t value) {
|
|
char digits[21];
|
|
int i = 20;
|
|
digits[i] = '\0';
|
|
do {
|
|
digits[--i] = '0' + value % 10;
|
|
value /= 10;
|
|
} while (value);
|
|
Serial.print(&digits[i]);
|
|
}
|
|
|
|
void printI64(int64_t value) {
|
|
if (value < 0) {
|
|
Serial.print('-');
|
|
printU64((uint64_t)(-value));
|
|
} else {
|
|
printU64((uint64_t)value);
|
|
}
|
|
}
|
|
|
|
void printBool(bool value) { Serial.print(value ? "true" : "false"); }
|
|
|
|
// Bias z = (2 ones - n) / sqrt(n); lag-1 autocorrelation z = (2 agreements - n) / sqrt(n).
|
|
void printZ(double numerator, double bits) {
|
|
if (bits <= 0) {
|
|
Serial.print("null");
|
|
} else {
|
|
Serial.print(numerator / sqrt(bits), 3);
|
|
}
|
|
}
|
|
|
|
void printWindow(uint64_t bits, uint64_t ones, uint64_t agreements, bool withWalk) {
|
|
Serial.print("{\"bits\":");
|
|
printU64(bits);
|
|
Serial.print(",\"ones\":");
|
|
printU64(ones);
|
|
Serial.print(",\"z\":");
|
|
printZ(2.0 * (double)ones - (double)bits, (double)bits);
|
|
Serial.print(",\"autocorr_z\":");
|
|
printZ(2.0 * (double)agreements - (double)bits, (double)bits);
|
|
if (withWalk) {
|
|
Serial.print(",\"walk\":");
|
|
printI64(2 * (int64_t)ones - (int64_t)bits);
|
|
}
|
|
Serial.print('}');
|
|
}
|
|
|
|
void printError(const char *message) {
|
|
Serial.print("{\"error\":\"");
|
|
Serial.print(message);
|
|
Serial.println("\"}");
|
|
}
|
|
|
|
void printOk() { Serial.println("{\"ok\":true}"); }
|
|
|
|
// FPGA polling
|
|
|
|
void resetStatsWindows() {
|
|
memset(totals, 0, sizeof(totals));
|
|
historyCount = historyNext = 0;
|
|
trialsCounted = missedTrials = 0;
|
|
haveStats = false;
|
|
}
|
|
|
|
uint32_t snapshotSequence() {
|
|
uint8_t raw[4];
|
|
fpga.readBurst(REG_STATS, raw, 4);
|
|
return word32(raw);
|
|
}
|
|
|
|
void restartStats(uint8_t vonNeumann) {
|
|
staleSequence = snapshotSequence();
|
|
awaitingRestart = true;
|
|
fpga.write(REG_STATS_CTRL, vonNeumann | STATS_RESTART);
|
|
resetStatsWindows();
|
|
}
|
|
|
|
void restartStats() { restartStats(fpga.read(REG_STATS_CTRL) & STATS_VON_NEUMANN); }
|
|
|
|
void restartAmplifier() {
|
|
fpga.write(REG_AMP_CTRL, (fpga.read(REG_AMP_CTRL) & AMP_VON_NEUMANN) | AMP_RESTART);
|
|
haveAmp = false;
|
|
}
|
|
|
|
void printStats();
|
|
void printAmp();
|
|
|
|
void pollStats() {
|
|
uint8_t raw[STATS_SNAPSHOT_BYTES];
|
|
fpga.readBurst(REG_STATS, raw, STATS_SNAPSHOT_BYTES);
|
|
uint32_t sequence = word32(raw);
|
|
if (sequence != word32(raw + STATS_SNAPSHOT_BYTES - 4)) return; // torn, retry next poll
|
|
if (haveStats && sequence == latestStats.sequence) return;
|
|
if (awaitingRestart) {
|
|
if (sequence == staleSequence) return;
|
|
awaitingRestart = false;
|
|
}
|
|
if (haveStats && sequence > latestStats.sequence + 1) missedTrials += sequence - latestStats.sequence - 1;
|
|
|
|
latestStats.sequence = sequence;
|
|
latestStats.healthy = raw[4];
|
|
latestStats.vonNeumann = raw[5] & 1;
|
|
latestStats.trialMs = raw[6] << 8 | raw[7];
|
|
for (uint8_t c = 0; c < STATS_CHANNELS; c++) {
|
|
const uint8_t *p = raw + 8 + 12 * c;
|
|
Counts counts = {word32(p), word32(p + 4), word32(p + 8)};
|
|
latestStats.channels[c] = counts;
|
|
history[historyNext][c] = counts;
|
|
totals[c].bits += counts.bits;
|
|
totals[c].ones += counts.ones;
|
|
totals[c].agreements += counts.agreements;
|
|
}
|
|
historyNext = (historyNext + 1) % MAX_ROLLING;
|
|
if (historyCount < MAX_ROLLING) historyCount++;
|
|
trialsCounted++;
|
|
haveStats = true;
|
|
if (watching == WATCH_STATS) printStats();
|
|
}
|
|
|
|
void pollAmp() {
|
|
uint8_t raw[AMP_SNAPSHOT_BYTES];
|
|
fpga.readBurst(REG_AMP, raw, AMP_SNAPSHOT_BYTES);
|
|
uint32_t sequence = word32(raw);
|
|
if (sequence != word32(raw + AMP_SNAPSHOT_BYTES - 4)) return;
|
|
if (haveAmp && sequence == latestAmp.sequence) return;
|
|
|
|
latestAmp.sequence = sequence;
|
|
latestAmp.sources = raw[4] & 0x0F;
|
|
latestAmp.vonNeumann = raw[4] & 0x10;
|
|
latestAmp.overflow = raw[4] & 0x20;
|
|
latestAmp.subtrials = raw[5];
|
|
latestAmp.subMs = raw[6];
|
|
for (uint8_t p = 0; p < 2; p++) {
|
|
const uint8_t *b = raw + 8 + 16 * p;
|
|
AmpPath &path = latestAmp.paths[p];
|
|
path.bits = word32(b);
|
|
path.ones = word32(b + 4);
|
|
path.votes = b[8] << 8 | b[9];
|
|
path.voted = b[10] << 8 | b[11];
|
|
path.result = b[13] & 1;
|
|
path.valid = b[13] & 2;
|
|
}
|
|
haveAmp = true;
|
|
if (watching == WATCH_AMP) printAmp();
|
|
}
|
|
|
|
// Commands
|
|
|
|
void printId() {
|
|
Serial.print("{\"device\":\"STRIX\",\"firmware\":\"");
|
|
Serial.print(FIRMWARE_VERSION);
|
|
Serial.print("\",\"fpga_ready\":");
|
|
printBool(fpgaReady);
|
|
Serial.print(",\"fpga_version\":");
|
|
Serial.print(fpga.version());
|
|
Serial.print(",\"generators\":");
|
|
Serial.print(fpgaReady ? fpga.read(REG_GENERATORS) : 0);
|
|
Serial.print(",\"rings_per_generator\":");
|
|
Serial.print(fpgaReady ? fpga.read(REG_RINGS) : 0);
|
|
Serial.println('}');
|
|
}
|
|
|
|
void printHealth() {
|
|
uint8_t healthy = fpga.read(REG_HEALTHY);
|
|
Serial.print("{\"healthy\":");
|
|
Serial.print(healthy);
|
|
Serial.print(",\"generators\":[");
|
|
for (uint8_t g = 0; g < GENERATORS; g++) {
|
|
uint8_t flags = fpga.read(REG_GEN_HEALTH + 4 * g);
|
|
if (g) Serial.print(',');
|
|
Serial.print("{\"healthy\":");
|
|
printBool(flags & FLAG_HEALTHY);
|
|
Serial.print(",\"active\":");
|
|
printBool(flags & FLAG_ACTIVE);
|
|
Serial.print(",\"swept\":");
|
|
printBool(flags & FLAG_SWEPT);
|
|
Serial.print(",\"rct_failed\":");
|
|
printBool(flags & FLAG_RCT_FAILED);
|
|
Serial.print(",\"apt_failed\":");
|
|
printBool(flags & FLAG_APT_FAILED);
|
|
Serial.print(",\"rct_events\":");
|
|
Serial.print(fpga.read(REG_GEN_HEALTH + 4 * g + 1));
|
|
Serial.print(",\"apt_events\":");
|
|
Serial.print(fpga.read(REG_GEN_HEALTH + 4 * g + 2));
|
|
Serial.print(",\"dead_rings\":");
|
|
Serial.print(fpga.read(REG_GEN_HEALTH + 4 * g + 3));
|
|
Serial.print('}');
|
|
}
|
|
Serial.print("],\"stream\":{\"level\":");
|
|
Serial.print((uint16_t)fpga.read(REG_LEVEL_HIGH) << 8 | fpga.read(REG_LEVEL_LOW));
|
|
Serial.print(",\"dropped\":");
|
|
Serial.print((uint16_t)fpga.read(REG_DROP_HIGH) << 8 | fpga.read(REG_DROP_LOW));
|
|
Serial.println("}}");
|
|
}
|
|
|
|
void printStats() {
|
|
if (!haveStats) {
|
|
printError("no trial completed yet");
|
|
return;
|
|
}
|
|
uint8_t window = rolling < historyCount ? rolling : historyCount;
|
|
Serial.print("{\"trial\":");
|
|
Serial.print(latestStats.sequence);
|
|
Serial.print(",\"trial_ms\":");
|
|
Serial.print(latestStats.trialMs);
|
|
Serial.print(",\"von_neumann\":");
|
|
printBool(latestStats.vonNeumann);
|
|
Serial.print(",\"healthy\":");
|
|
Serial.print(latestStats.healthy);
|
|
Serial.print(",\"trials\":");
|
|
Serial.print(trialsCounted);
|
|
Serial.print(",\"missed\":");
|
|
Serial.print(missedTrials);
|
|
Serial.print(",\"rolling_trials\":");
|
|
Serial.print(window);
|
|
Serial.print(",\"channels\":[");
|
|
for (uint8_t c = 0; c < STATS_CHANNELS; c++) {
|
|
uint64_t bits = 0, ones = 0, agreements = 0;
|
|
for (uint8_t i = 0; i < window; i++) {
|
|
const Counts &counts = history[(historyNext + MAX_ROLLING - 1 - i) % MAX_ROLLING][c];
|
|
bits += counts.bits;
|
|
ones += counts.ones;
|
|
agreements += counts.agreements;
|
|
}
|
|
const Counts &trial = latestStats.channels[c];
|
|
if (c) Serial.print(',');
|
|
Serial.print("{\"name\":\"");
|
|
Serial.print(CHANNEL_NAMES[c]);
|
|
Serial.print("\",\"trial\":");
|
|
printWindow(trial.bits, trial.ones, trial.agreements, false);
|
|
Serial.print(",\"rolling\":");
|
|
printWindow(bits, ones, agreements, false);
|
|
Serial.print(",\"total\":");
|
|
printWindow(totals[c].bits, totals[c].ones, totals[c].agreements, true);
|
|
Serial.print('}');
|
|
}
|
|
Serial.println("]}");
|
|
}
|
|
|
|
void printAmp() {
|
|
if (!haveAmp) {
|
|
printError("no trial completed yet");
|
|
return;
|
|
}
|
|
Serial.print("{\"trial\":");
|
|
Serial.print(latestAmp.sequence);
|
|
Serial.print(",\"sources\":");
|
|
Serial.print(latestAmp.sources);
|
|
Serial.print(",\"von_neumann\":");
|
|
printBool(latestAmp.vonNeumann);
|
|
Serial.print(",\"subtrials\":");
|
|
Serial.print(latestAmp.subtrials);
|
|
Serial.print(",\"sub_ms\":");
|
|
Serial.print(latestAmp.subMs);
|
|
Serial.print(",\"overflow\":");
|
|
printBool(latestAmp.overflow);
|
|
Serial.print(",\"paths\":[");
|
|
for (uint8_t p = 0; p < 2; p++) {
|
|
const AmpPath &path = latestAmp.paths[p];
|
|
if (p) Serial.print(',');
|
|
Serial.print("{\"name\":\"");
|
|
Serial.print(PATH_NAMES[p]);
|
|
Serial.print("\",\"bits\":");
|
|
Serial.print(path.bits);
|
|
Serial.print(",\"ones\":");
|
|
Serial.print(path.ones);
|
|
Serial.print(",\"z\":");
|
|
printZ(2.0 * path.ones - (double)path.bits, (double)path.bits);
|
|
Serial.print(",\"votes\":\"");
|
|
for (uint8_t i = 0; i < latestAmp.subtrials; i++) {
|
|
Serial.print(path.voted >> i & 1 ? (path.votes >> i & 1 ? '1' : '0') : '.');
|
|
}
|
|
Serial.print("\",\"result\":");
|
|
if (path.valid) {
|
|
Serial.print(path.result ? 1 : 0);
|
|
} else {
|
|
Serial.print("null");
|
|
}
|
|
Serial.print('}');
|
|
}
|
|
Serial.println("]}");
|
|
}
|
|
|
|
// Streams `count` entropy bytes as hex or binary. Stops early (binary: short output, hex: an
|
|
// error line) if the selected source stops delivering.
|
|
void sendEntropy(size_t count, bool binary) {
|
|
uint8_t control = fpga.read(REG_STREAM_CTRL);
|
|
uint8_t source = control & 0x07;
|
|
uint8_t healthy = fpga.read(REG_HEALTHY);
|
|
bool sourceHealthy = source == STREAM_COMBINED ? healthy != 0 : (healthy >> source & 1);
|
|
if (!sourceHealthy) {
|
|
printError("stream source is not healthy");
|
|
return;
|
|
}
|
|
if (binary) {
|
|
Serial.print("BIN ");
|
|
Serial.println(count);
|
|
}
|
|
static uint8_t chunk[CHUNK_BYTES];
|
|
size_t sent = 0;
|
|
unsigned long lastData = millis();
|
|
while (sent < count) {
|
|
uint16_t level = (uint16_t)fpga.read(REG_LEVEL_HIGH) << 8 | fpga.read(REG_LEVEL_LOW);
|
|
if (level == 0) {
|
|
if (millis() - lastData > ENTROPY_TIMEOUT_MS) {
|
|
if (!binary) {
|
|
Serial.println();
|
|
printError("entropy timeout");
|
|
}
|
|
return;
|
|
}
|
|
continue;
|
|
}
|
|
size_t length = count - sent;
|
|
if (length > level) length = level;
|
|
if (length > CHUNK_BYTES) length = CHUNK_BYTES;
|
|
fpga.readBurst(REG_STREAM, chunk, length);
|
|
if (binary) {
|
|
Serial.write(chunk, length);
|
|
} else {
|
|
for (size_t i = 0; i < length; i++) {
|
|
if (chunk[i] < 0x10) Serial.print('0');
|
|
Serial.print(chunk[i], HEX);
|
|
}
|
|
}
|
|
sent += length;
|
|
lastData = millis();
|
|
}
|
|
if (!binary) Serial.println();
|
|
}
|
|
|
|
void printConfig() {
|
|
Serial.print("{\"temporal\":");
|
|
Serial.print(fpga.read(REG_TEMPORAL));
|
|
Serial.print(",\"generators\":");
|
|
Serial.print(fpga.read(REG_GEN_ENABLE));
|
|
Serial.print(",\"trial_ms\":");
|
|
Serial.print((uint16_t)fpga.read(REG_TRIAL_HIGH) << 8 | fpga.read(REG_TRIAL_LOW));
|
|
Serial.print(",\"stats_vn\":");
|
|
Serial.print(fpga.read(REG_STATS_CTRL) & STATS_VON_NEUMANN);
|
|
Serial.print(",\"rolling\":");
|
|
Serial.print(rolling);
|
|
uint8_t stream = fpga.read(REG_STREAM_CTRL);
|
|
Serial.print(",\"stream_source\":");
|
|
Serial.print(stream & 0x07);
|
|
Serial.print(",\"stream_vn\":");
|
|
Serial.print(stream & STREAM_VON_NEUMANN ? 1 : 0);
|
|
Serial.print(",\"amp_sources\":");
|
|
Serial.print(fpga.read(REG_AMP_SOURCES));
|
|
Serial.print(",\"amp_method\":\"");
|
|
Serial.print(fpga.read(REG_AMP_STAGE) & 1 ? "vote" : "walk");
|
|
Serial.print("\",\"amp_stages\":\"");
|
|
for (uint8_t s = 0; s < 3; s++) {
|
|
if (s) Serial.print(',');
|
|
Serial.print((uint16_t)fpga.read(REG_AMP_STAGE + 3 * s + 1) << 8 | fpga.read(REG_AMP_STAGE + 3 * s + 2));
|
|
}
|
|
Serial.print("\",\"amp_sub_ms\":");
|
|
Serial.print(fpga.read(REG_AMP_SUB_MS));
|
|
Serial.print(",\"amp_subtrials\":");
|
|
Serial.print(fpga.read(REG_AMP_SUBS));
|
|
Serial.print(",\"amp_vn\":");
|
|
Serial.print(fpga.read(REG_AMP_CTRL) & AMP_VON_NEUMANN);
|
|
Serial.println('}');
|
|
}
|
|
|
|
bool inRange(long value, long low, long high) { return value >= low && value <= high; }
|
|
|
|
void setConfig(const String &key, const String &value) {
|
|
long number = value.toInt();
|
|
if (key == "temporal" && inRange(number, 1, 8)) {
|
|
fpga.write(REG_TEMPORAL, number);
|
|
} else if (key == "generators" && inRange(number, 0, 15)) {
|
|
fpga.write(REG_GEN_ENABLE, number);
|
|
} else if (key == "trial_ms" && inRange(number, 1, 65535)) {
|
|
fpga.write(REG_TRIAL_HIGH, number >> 8);
|
|
fpga.write(REG_TRIAL_LOW, number & 0xFF);
|
|
restartStats();
|
|
} else if (key == "stats_vn" && inRange(number, 0, 1)) {
|
|
restartStats(number);
|
|
} else if (key == "rolling" && inRange(number, 1, MAX_ROLLING)) {
|
|
rolling = number;
|
|
} else if (key == "stream_source" && inRange(number, 0, 4)) {
|
|
fpga.write(REG_STREAM_CTRL, (fpga.read(REG_STREAM_CTRL) & STREAM_VON_NEUMANN) | number | STREAM_FLUSH);
|
|
} else if (key == "stream_vn" && inRange(number, 0, 1)) {
|
|
fpga.write(REG_STREAM_CTRL, (fpga.read(REG_STREAM_CTRL) & 0x07) | (number ? STREAM_VON_NEUMANN : 0) | STREAM_FLUSH);
|
|
} else if (key == "amp_sources" && inRange(number, 0, 15)) {
|
|
fpga.write(REG_AMP_SOURCES, number);
|
|
restartAmplifier();
|
|
} else if (key == "amp_method" && (value == "walk" || value == "vote")) {
|
|
for (uint8_t s = 0; s < 3; s++) fpga.write(REG_AMP_STAGE + 3 * s, value == "vote");
|
|
restartAmplifier();
|
|
} else if (key == "amp_stages") {
|
|
long stages[3];
|
|
int start = 0;
|
|
for (uint8_t s = 0; s < 3; s++) {
|
|
int comma = value.indexOf(',', start);
|
|
if ((s < 2) != (comma >= 0)) {
|
|
printError("amp_stages needs three comma-separated values");
|
|
return;
|
|
}
|
|
stages[s] = value.substring(start, comma < 0 ? value.length() : comma).toInt();
|
|
if (!inRange(stages[s], 1, 65535)) {
|
|
printError("amp_stages values must be 1-65535");
|
|
return;
|
|
}
|
|
start = comma + 1;
|
|
}
|
|
for (uint8_t s = 0; s < 3; s++) {
|
|
fpga.write(REG_AMP_STAGE + 3 * s + 1, stages[s] >> 8);
|
|
fpga.write(REG_AMP_STAGE + 3 * s + 2, stages[s] & 0xFF);
|
|
}
|
|
restartAmplifier();
|
|
} else if (key == "amp_sub_ms" && inRange(number, 1, 255)) {
|
|
fpga.write(REG_AMP_SUB_MS, number);
|
|
restartAmplifier();
|
|
} else if (key == "amp_subtrials" && inRange(number, 1, 15)) {
|
|
fpga.write(REG_AMP_SUBS, number);
|
|
restartAmplifier();
|
|
} else if (key == "amp_vn" && inRange(number, 0, 1)) {
|
|
fpga.write(REG_AMP_CTRL, number | AMP_RESTART);
|
|
haveAmp = false;
|
|
} else {
|
|
printError("unknown setting or value out of range");
|
|
return;
|
|
}
|
|
printOk();
|
|
}
|
|
|
|
void handleCommand(String line) {
|
|
line.trim();
|
|
int space = line.indexOf(' ');
|
|
String command = space < 0 ? line : line.substring(0, space);
|
|
String args = space < 0 ? "" : line.substring(space + 1);
|
|
args.trim();
|
|
int argSpace = args.indexOf(' ');
|
|
String first = argSpace < 0 ? args : args.substring(0, argSpace);
|
|
String rest = argSpace < 0 ? "" : args.substring(argSpace + 1);
|
|
rest.trim();
|
|
|
|
if (command.length() == 0) return;
|
|
if (command == "id") {
|
|
printId();
|
|
} else if (!fpgaReady) {
|
|
printError("FPGA not ready");
|
|
} else if (command == "entropy") {
|
|
long count = first.toInt();
|
|
if (!inRange(count, 1, MAX_ENTROPY_BYTES) || (rest.length() && rest != "hex" && rest != "bin")) {
|
|
printError("usage: entropy <1-65536> [hex|bin]");
|
|
} else {
|
|
sendEntropy(count, rest == "bin");
|
|
}
|
|
} else if (command == "health") {
|
|
if (first == "clear") {
|
|
fpga.write(REG_HEALTH_CTRL, HEALTH_CLEAR);
|
|
printOk();
|
|
} else {
|
|
printHealth();
|
|
}
|
|
} else if (command == "stats") {
|
|
if (first == "reset") {
|
|
restartStats();
|
|
printOk();
|
|
} else {
|
|
printStats();
|
|
}
|
|
} else if (command == "amp") {
|
|
printAmp();
|
|
} else if (command == "get") {
|
|
printConfig();
|
|
} else if (command == "set" && first.length() && rest.length()) {
|
|
setConfig(first, rest);
|
|
} else if (command == "watch" && (first == "stats" || first == "amp" || first == "off")) {
|
|
watching = first == "stats" ? WATCH_STATS : first == "amp" ? WATCH_AMP : WATCH_OFF;
|
|
printOk();
|
|
} else {
|
|
printError("unknown command");
|
|
}
|
|
}
|
|
|
|
// Main
|
|
|
|
char lineBuffer[128];
|
|
size_t lineLength = 0;
|
|
|
|
void readSerial() {
|
|
while (Serial.available()) {
|
|
char c = Serial.read();
|
|
if (c == '\n' || c == '\r') {
|
|
if (lineLength) {
|
|
lineBuffer[lineLength] = '\0';
|
|
handleCommand(String(lineBuffer));
|
|
lineLength = 0;
|
|
}
|
|
} else if (lineLength < sizeof(lineBuffer) - 1) {
|
|
lineBuffer[lineLength++] = c;
|
|
}
|
|
}
|
|
}
|
|
|
|
void setup() {
|
|
Serial.begin(115200); // no wait for a host: the device also runs on battery
|
|
pinMode(LED_BUILTIN, OUTPUT);
|
|
fpgaReady = fpga.begin();
|
|
if (fpgaReady) restartStats();
|
|
}
|
|
|
|
void loop() {
|
|
readSerial();
|
|
static unsigned long lastPoll = 0;
|
|
if (fpgaReady && millis() - lastPoll >= POLL_MS) {
|
|
lastPoll = millis();
|
|
pollStats();
|
|
pollAmp();
|
|
// LED on while every enabled generator is healthy
|
|
uint8_t enabled = fpga.read(REG_GEN_ENABLE);
|
|
digitalWrite(LED_BUILTIN, enabled && (fpga.read(REG_HEALTHY) & enabled) == enabled);
|
|
}
|
|
}
|