package org.hihan.girinoscope.comm;
import gnu.io.CommPortIdentifier;
import java.io.IOException;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class Girino {
public enum Parameter {
BUFFER_SIZE(null, true), //
BAUD_RATE(null, true), //
PRESCALER("p", true), //
VOLTAGE_REFERENCE("r", false), //
TRIGGER_EVENT("e", true), //
WAIT_DURATION("w", true), //
THRESHOLD("t", true);
private String command;
private boolean readable;
Parameter(String command, boolean readable) {
this.command = command;
this.readable = readable;
}
public String getIdentifier() {
if (this == WAIT_DURATION) {
return "waitDuration";
} else {
return name().toLowerCase().replace('_', ' ');
}
}
public String getDescription() {
return name().charAt(0) + name().substring(1).toLowerCase().replace('_', ' ');
}
public static Parameter findByDescription(String description) {
for (Parameter parameter : values()) {
if (parameter.getDescription().equals(description)) {
return parameter;
}
}
return null;
}
private static Map.Entry<Parameter, Integer> read(Serial serial) throws IOException {
String data = serial.readLine();
if (READY_MESSAGE.equals(data)) {
data = serial.readLine();
}
String[] items = data.split(":");
String name = items[0].trim();
int value = Integer.parseInt(items[1].trim());
Parameter parameter = Parameter.findByDescription(name);
return new AbstractMap.SimpleEntry<Parameter, Integer>(parameter, value);
}
private int apply(Serial serial, int newValue) throws IOException {
if (command != null) {
serial.writeLine(command + newValue);
String data = serial.readLine();
String[] items = data.split(":");
if (items.length > 1) {
String message = items[0].trim();
String identifier = getIdentifier();
if (message.equals(String.format("Setting %s to", identifier))) {
return Integer.parseInt(items[1].trim());
} else {
throw new IOException("Not matching returned parameter " + identifier);
}
} else {
throw new IOException("Unknown parameter " + getDescription());
}
} else {
throw new IllegalArgumentException("Read only parameter " + getDescription());
}
}
}
public static class PrescalerInfo {
public final int value;
public final double frequency;
public final double timeframe;
public final String description;
public final boolean tooFast;
public final boolean reallyTooFast;
private PrescalerInfo(int n) {
value = (int) Math.pow(2, n);
double baseFrequency = 16 * 1000 * 1000;
double clockCycleCountPerConversion = 13;
frequency = baseFrequency / value / clockCycleCountPerConversion;
timeframe = 1280 / frequency;
tooFast = n < 5;
reallyTooFast = n < 3;
description = String.format("%.1f kHz / %.1f ms", frequency / 1000, timeframe * 1000);
}
public static List<PrescalerInfo> values() {
List<PrescalerInfo> infos = new LinkedList<PrescalerInfo>();
for (int i = 2; i < 8; ++i) {
infos.add(new PrescalerInfo(i));
}
return infos;
}
}
public enum TriggerEventMode {
TOGGLE(0, "Toggle"), //
FALLING_EDGE(2, "Falling edge"), //
RISING_EDGE(3, "Rising edge");
public int value;
public String description;
private TriggerEventMode(int value, String description) {
this.value = value;
this.description = description;
}
}
public enum VoltageReference {
AREF(0, "AREF, Internal Vref turned off"), //
AVCC(1, "AVCC with external capacitor at AREF pin"), //
/*
* The value is misleading since it sets [REFS1, REFS0] to 11 (10 is
* reserved). At least, the trigger event mode always uses the [ACI1,
* ACI0] value (hence the gap since 2 is also a reserved value).
*/
INTERNAL(2, "Internal 1.1V Vref with external capacitor at AREF pin");
public int value;
public String description;
private VoltageReference(int value, String description) {
this.value = value;
this.description = description;
}
}
/** Milliseconds to wait once a new connection has been etablished. */
private static final int SETUP_DELAY_ON_RESET = 5000;
private static final String READY_MESSAGE = "Girino ready";
private static final String START_ACQUIRING_COMMAND = "s";
private static final String STOP_ACQUIRING_COMMAND = "S";
private static final String DUMP_COMMAND = "d";
private Serial serial;
private CommPortIdentifier portId;
private Map<Parameter, Integer> parameters = new HashMap<Parameter, Integer>();
public static Map<Parameter, Integer> getDefaultParameters(Map<Parameter, Integer> parameters) {
parameters.put(Parameter.BUFFER_SIZE, 1280);
parameters.put(Parameter.PRESCALER, 32);
parameters.put(Parameter.VOLTAGE_REFERENCE, VoltageReference.AVCC.value);
parameters.put(Parameter.TRIGGER_EVENT, TriggerEventMode.TOGGLE.value);
parameters.put(Parameter.WAIT_DURATION, 1280 - 32);
parameters.put(Parameter.THRESHOLD, 150);
return parameters;
}
private void connect(CommPortIdentifier newPortId) throws Exception {
if (newPortId != null) {
if (serial == null || !same(portId, newPortId)) {
portId = newPortId;
if (serial != null) {
disconnect();
}
serial = new Serial(portId);
try {
/*
* Note that the USB to serial adapter is usually configured
* to reset the AVR each time a connection is etablish. The
* delay here is to give some time to the controller to set
* itself up.
*/
Thread.sleep(SETUP_DELAY_ON_RESET);
String data;
do {
data = serial.readLine();
} while (!data.endsWith(READY_MESSAGE));
readParameters();
} catch (InterruptedException e) {
disconnect();
}
}
} else {
throw new IllegalArgumentException("No serial port");
}
}
public void disconnect() throws IOException {
if (serial != null) {
serial.close();
serial = null;
}
}
private void readParameters() throws IOException {
int readableParameterCount = 0;
for (Parameter parameter : Parameter.values()) {
if (parameter.readable) {
++readableParameterCount;
}
}
serial.writeLine(DUMP_COMMAND);
for (int i = 0; i < readableParameterCount; ++i) {
Map.Entry<Parameter, Integer> entry = Parameter.read(serial);
if (entry.getKey() != null) {
parameters.put(entry.getKey(), entry.getValue());
}
}
}
/**
* @throws IOException
* Provided parameters shall be verified by the caller since
* some parameters could have been set back to a different value
* than asked.
*/
private void applyParameters(Map<Parameter, Integer> newParameters) throws IOException {
for (Map.Entry<Parameter, Integer> entry : newParameters.entrySet()) {
Parameter parameter = entry.getKey();
Integer newValue = entry.getValue();
// We only update modified parameters.
if (!same(newValue, parameters.get(parameter))) {
int returnedValue = parameter.apply(serial, newValue);
parameters.put(parameter, returnedValue);
if (!same(newValue, parameters.get(parameter))) {
throw new IOException("Change has been rejected for parameter " + parameter.getDescription() + ": "
+ newValue + " =/= " + returnedValue);
}
}
}
}
public void setConnection(final CommPortIdentifier newPortId, final Map<Parameter, Integer> newParameters)
throws Exception {
connect(newPortId);
if (serial != null) {
applyParameters(newParameters);
}
}
public byte[] acquireData() throws Exception {
serial.writeLine(START_ACQUIRING_COMMAND);
/*
* Note that the Girino reset its buffer (with zeros), meaning we won’t
* catch a lot of the signal before the trigger if it happens too fast.
*/
try {
byte[] buffer = new byte[1280];
int size = serial.readBytes(buffer);
return size == buffer.length ? buffer : null;
} finally {
/*
* We can only acquire a single buffer and need to stop / start to
* get the next one. That’s how the Girino code works, but it was
* probably a bit different during its development. In practise,
* this 'stop' is only required when we cancel a trigger waiting.
*/
serial.writeLine(STOP_ACQUIRING_COMMAND);
}
}
private static boolean same(Object o1, Object o2) {
if (o1 == o2) {
return true;
} else if (o1 == null || o2 == null) {
return false;
} else {
return o1.equals(o2);
}
}
}