1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-11-30 03:26:13 +01:00

Abstracted Hardware Interface

The hardware interface for all pins and timers is now in Hardware.cpp
This commit is contained in:
Asbelos 2020-05-26 12:44:02 +01:00
parent 28be07610a
commit 22406b44ed
7 changed files with 239 additions and 210 deletions

View File

@ -3,41 +3,40 @@
#include "JMRIParser.h" #include "JMRIParser.h"
/* this code is here to test the waveforwe generator and reveal the issues involved in programming track operations. /* this code is here to test the waveforwe generator and reveal the issues involved in programming track operations.
*
* It tests the Waveform genartor and demonstrates how a DCC API function can be simply written It tests the Waveform genartor and demonstrates how a DCC API function can be simply written
* to transmit and receive DCC data on the programming track. to transmit and receive DCC data on the programming track.
*
* Important... DCCWaveform.h contains hardware specific confioguration settings Once soem CVs have been listed, it then drops into JMRI input moce so you can play.
* that you will need to check.
* Important... Config.h contains hardware specific confioguration settings
* Notes: the waveform generator sends reset packets on progTrack when it has nothing better to do, this means that that you will need to check.
* the decoder does not step out of service mode. (The main track sends idles).
* It also means that the API functions dont have to mess with reset packets.
*
*/
const int cvnums[]={1,2,3,4,5,17,18,19,21,22,29}; */
const int cvnums[] = {1, 2, 3, 4, 5, 8, 17, 18, 19, 21, 22, 29};
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
DCC::begin(); DCC::begin();
DIAG(F("\n===== CVReader begin ==============================\n")); DIAG(F("\n===== CVReader begin ==============================\n"));
for (byte x=0;x<sizeof(cvnums)/sizeof(cvnums[0]);x++) { for (byte x = 0; x < sizeof(cvnums) / sizeof(cvnums[0]); x++) {
int value=DCC::readCV(cvnums[x]); int value = DCC::readCV(cvnums[x]);
DIAG(F("\nCV %d = %d 0x%x %s\n"),cvnums[x],value,value, value>=0?" VERIFIED OK":"FAILED VERIFICATION"); DIAG(F("\nCV %d = %d 0x%x %s\n"), cvnums[x], value, value, value >= 0 ? " VERIFIED OK" : "FAILED VERIFICATION");
} }
DIAG(F("\n===== CVReader done ==============================\n")); DIAG(F("\n===== CVReader done ==============================\n"));
DIAG(F("\nReady for JMRI commands\n")); DIAG(F("\nReady for JMRI commands\n"));
} }
void loop() { void loop() {
DCC::loop(); // required to keep locos running and check powwer DCC::loop(); // required to keep locos running and check powwer
// This line passes input on Serial to the JMRIparser // This line passes input on Serial to the JMRIparser
StringParser::loop(Serial, JMRIParser::parse); StringParser::loop(Serial, JMRIParser::parse);
} }

13
Config.h Normal file
View File

@ -0,0 +1,13 @@
// This hardware configuration would normally be setup using a bunch of #ifdefs.
const byte MAIN_POWER_PIN = 3;
const byte MAIN_SIGNAL_PIN = 12;
const byte MAIN_SIGNAL_PIN_ALT = 0; // for hardware that flipflops signal pins
const byte MAIN_SENSE_PIN = A0;
const byte MAIN_SENSE_FACTOR=1; // analgRead(MAIN_SENSE_PIN) * MAIN_SENSE_FACTOR = milliamps
const byte PROG_POWER_PIN = 11;
const byte PROG_SIGNAL_PIN = 13;
const byte PROG_SIGNAL_PIN_ALT = 0; // for hardware that flipflops signal pins
const byte PROG_SENSE_PIN = A1;
const float PROG_SENSE_FACTOR=1; // analgRead(PROG_SENSE_PIN) * PROG_SENSE_FACTOR = milliamps

View File

@ -182,7 +182,7 @@ byte DCC::cv2(int cv) {
bool DCC::verifyCV(int cv, byte value) { bool DCC::verifyCV(int cv, byte value) {
byte message[] = { cv1(0x74, cv), cv2(cv), value}; byte message[] = { cv1(0x74, cv), cv2(cv), value};
DIAG(F("\n\nVerifying cv %d = %d"),cv, value); DIAG(F("\n\nVerifying cv %d = %d"), cv, value);
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), 5); DCCWaveform::progTrack.schedulePacket(message, sizeof(message), 5);
return DCCWaveform::progTrack.getAck(); return DCCWaveform::progTrack.getAck();
} }
@ -206,4 +206,4 @@ void DCC::updateLocoReminder(int loco, byte tSpeed, bool forward) {
speedTable[reg].forward = forward; speedTable[reg].forward = forward;
} }
DCC::LOCO DCC::speedTable[MAX_LOCOS]; DCC::LOCO DCC::speedTable[MAX_LOCOS];
int DCC::nextLoco=0; int DCC::nextLoco = 0;

View File

@ -1,120 +1,103 @@
#include <Arduino.h> #include <Arduino.h>
#include <TimerThree.h> #include "Hardware.h"
#include "DCCWaveform.h" #include "DCCWaveform.h"
#include "DIAG.h" #include "DIAG.h"
DCCWaveform DCCWaveform::mainTrack(MAIN_POWER_PIN,MAIN_SIGNAL_PIN,MAIN_SENSE_PIN,PREAMBLE_BITS_MAIN,true); DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
DCCWaveform DCCWaveform::progTrack(PROG_POWER_PIN,PROG_SIGNAL_PIN,PROG_SENSE_PIN,PREAMBLE_BITS_PROG,false); DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
void DCCWaveform::begin() { void DCCWaveform::begin() {
Hardware::init();
Timer3.initialize(58); Hardware::setCallback(58, interruptHandler);
Timer3.disablePwm(MAIN_SIGNAL_PIN); mainTrack.beginTrack();
Timer3.disablePwm(PROG_SIGNAL_PIN); progTrack.beginTrack();
Timer3.attachInterrupt(interruptHandler);
mainTrack.beginTrack();
progTrack.beginTrack();
} }
void DCCWaveform::loop() { void DCCWaveform::loop() {
mainTrack.checkPowerOverload(); mainTrack.checkPowerOverload();
progTrack.checkPowerOverload(); progTrack.checkPowerOverload();
} }
// static // // static //
void DCCWaveform::interruptHandler() { void DCCWaveform::interruptHandler() {
// call the timer edge sensitive actions for progtrack and maintrack // call the timer edge sensitive actions for progtrack and maintrack
bool mainCall2=mainTrack.interrupt1(); bool mainCall2 = mainTrack.interrupt1();
bool progCall2=progTrack.interrupt1(); bool progCall2 = progTrack.interrupt1();
// call (if necessary) the procs to get the current bits // call (if necessary) the procs to get the current bits
// these must complete within 50microsecs of the interrupt // these must complete within 50microsecs of the interrupt
// but they are only called ONCE PER BIT TRANSMITTED // but they are only called ONCE PER BIT TRANSMITTED
// after the rising edge of the signal // after the rising edge of the signal
if (mainCall2) mainTrack.interrupt2(); if (mainCall2) mainTrack.interrupt2();
if (progCall2) progTrack.interrupt2(); if (progCall2) progTrack.interrupt2();
} }
// An instance of this class handles the DCC transmissions for one track. (main or prog) // An instance of this class handles the DCC transmissions for one track. (main or prog)
// Interrupts are marshalled via the statics. // Interrupts are marshalled via the statics.
// A track has a current transmit buffer, and a pending buffer. // A track has a current transmit buffer, and a pending buffer.
// When the current buffer is exhausted, either the pending buffer (if there is one waiting) or an idle buffer. // When the current buffer is exhausted, either the pending buffer (if there is one waiting) or an idle buffer.
// This bitmask has 9 entries as each byte is trasmitted as a zero + 8 bits. // This bitmask has 9 entries as each byte is trasmitted as a zero + 8 bits.
const byte bitMask[]={0x00,0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01}; const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
DCCWaveform::DCCWaveform(byte powerPinNo, byte directionPinNo, byte sensePinNo, byte preambleBits, bool isMain) { DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
// establish appropriate pins // establish appropriate pins
powerPin=Arduino_to_GPIO_pin(powerPinNo); isMainTrack = isMain;
directionPin=Arduino_to_GPIO_pin(directionPinNo); packetPending = false;
sensePin=sensePinNo; memcpy(transmitPacket, idlePacket, sizeof(idlePacket));
isMainTrack=isMain; state = 0;
packetPending=false; requiredPreambles = preambleBits;
memcpy(transmitPacket,idlePacket,sizeof(idlePacket)); bytes_sent = 0;
state=0; bits_sent = 0;
requiredPreambles=preambleBits; nextSampleDue = 0;
bytes_sent=0;
bits_sent=0; }
nextSampleDue=0; void DCCWaveform::beginTrack() {
setPowerMode(POWERMODE::ON);
} }
void DCCWaveform::beginTrack() {
pinMode2f(powerPin,OUTPUT);
pinMode2f(directionPin,OUTPUT);
pinMode(sensePin,INPUT);
if (isMainTrack && RAILCOM_CUTOUT) {
railcomBrakePin=Arduino_to_GPIO_pin(RAILCOM_BRAKE_PIN);
pinMode2f(railcomBrakePin, OUTPUT);
digitalWrite2f(railcomBrakePin, LOW);
}
setPowerMode(POWERMODE::ON);
DIAG(F("\nTrack started sensePin=%d\n"),sensePin);
}
POWERMODE DCCWaveform::getPowerMode() { POWERMODE DCCWaveform::getPowerMode() {
return powerMode; return powerMode;
} }
void DCCWaveform::setPowerMode(POWERMODE mode) {
powerMode = mode;
Hardware::setPower(isMainTrack, mode == POWERMODE::ON);
if (mode == POWERMODE::ON) delay(200);
}
void DCCWaveform::setPowerMode(POWERMODE mode) {
powerMode=mode;
digitalWrite2f(powerPin, mode==POWERMODE::ON ? HIGH:LOW);
if (mode==POWERMODE::ON) delay(200);
}
void DCCWaveform::checkPowerOverload() { void DCCWaveform::checkPowerOverload() {
if (millis()<nextSampleDue) return; if (millis() < nextSampleDue) return;
int current; int current;
int delay; int delay;
switch (powerMode) { switch (powerMode) {
case POWERMODE::OFF: case POWERMODE::OFF:
delay=POWER_SAMPLE_OFF_WAIT; delay = POWER_SAMPLE_OFF_WAIT;
break; break;
case POWERMODE::ON: case POWERMODE::ON:
// Check current // Check current
current=analogRead(sensePin); current = Hardware::getCurrentMilliamps(isMainTrack);
if (current < POWER_SAMPLE_MAX) delay=POWER_SAMPLE_ON_WAIT; if (current < POWER_SAMPLE_MAX) delay = POWER_SAMPLE_ON_WAIT;
else { else {
setPowerMode(POWERMODE::OVERLOAD); setPowerMode(POWERMODE::OVERLOAD);
DIAG(F("\n*** %s TRACK POWER OVERLOAD pin=%d current=%d max=%d ***\n"),isMainTrack?"MAIN":"PROG",sensePin,current,POWER_SAMPLE_MAX); DIAG(F("\n*** %s TRACK POWER OVERLOAD current=%d max=%d ***\n"), isMainTrack ? "MAIN" : "PROG", current, POWER_SAMPLE_MAX);
delay=POWER_SAMPLE_OVERLOAD_WAIT; delay = POWER_SAMPLE_OVERLOAD_WAIT;
} }
break; break;
case POWERMODE::OVERLOAD: case POWERMODE::OVERLOAD:
// Try setting it back on after the OVERLOAD_WAIT // Try setting it back on after the OVERLOAD_WAIT
setPowerMode(POWERMODE::ON); setPowerMode(POWERMODE::ON);
delay=POWER_SAMPLE_ON_WAIT; delay = POWER_SAMPLE_ON_WAIT;
break; break;
default: default:
delay=999; // cant get here..meaningless statement to avoid compiler warning. delay = 999; // cant get here..meaningless statement to avoid compiler warning.
} }
nextSampleDue=millis()+delay; nextSampleDue = millis() + delay;
} }
@ -122,141 +105,138 @@ void DCCWaveform::checkPowerOverload() {
// process time-edge sensitive part of interrupt // process time-edge sensitive part of interrupt
// return true if second level required // return true if second level required
bool DCCWaveform::interrupt1() { bool DCCWaveform::interrupt1() {
// NOTE: this must consume transmission buffers even if the power is off // NOTE: this must consume transmission buffers even if the power is off
// otherwise can cause hangs in main loop waiting for the pendingBuffer. // otherwise can cause hangs in main loop waiting for the pendingBuffer.
switch (state) { switch (state) {
case 0: // start of bit transmission case 0: // start of bit transmission
digitalWrite2f(directionPin, HIGH); Hardware::setSignal(isMainTrack, HIGH);
checkRailcom(); checkRailcom();
state = 1; state = 1;
return true; // must call interrupt2 to set currentBit return true; // must call interrupt2 to set currentBit
case 1: // 58us after case 0 case 1: // 58us after case 0
if (currentBit) { if (currentBit) {
digitalWrite2f(directionPin, LOW); Hardware::setSignal(isMainTrack, LOW);
state = 0; state = 0;
} }
else state = 2; else state = 2;
break; break;
case 2: // 116us after case 0 case 2: // 116us after case 0
digitalWrite2f(directionPin, LOW); Hardware::setSignal(isMainTrack, LOW);
state = 3; state = 3;
break; break;
case 3: // finished sending zero bit case 3: // finished sending zero bit
state = 0; state = 0;
break; break;
} }
return false; return false;
} }
void DCCWaveform::interrupt2() { void DCCWaveform::interrupt2() {
// set currentBit to be the next bit to be sent. // set currentBit to be the next bit to be sent.
if (remainingPreambles > 0 ) { if (remainingPreambles > 0 ) {
currentBit=true; currentBit = true;
remainingPreambles--; remainingPreambles--;
return; return;
} }
// beware OF 9-BIT MASK generating a zero to start each byte // beware OF 9-BIT MASK generating a zero to start each byte
currentBit=transmitPacket[bytes_sent] & bitMask[bits_sent]; currentBit = transmitPacket[bytes_sent] & bitMask[bits_sent];
bits_sent++; bits_sent++;
// If this is the last bit of a byte, prepare for the next byte // If this is the last bit of a byte, prepare for the next byte
if (bits_sent==9) { // zero followed by 8 bits of a byte
//end of Byte
bits_sent=0;
bytes_sent++;
// if this is the last byte, prepere for next packet
if (bytes_sent >= transmitLength) {
// end of transmission buffer... repeat or switch to next message
bytes_sent = 0;
remainingPreambles=requiredPreambles;
if (transmitRepeats > 0) { if (bits_sent == 9) { // zero followed by 8 bits of a byte
transmitRepeats--; //end of Byte
} bits_sent = 0;
else if (packetPending) { bytes_sent++;
// Copy pending packet to transmit packet // if this is the last byte, prepere for next packet
for (int b=0;b<pendingLength;b++) transmitPacket[b]= pendingPacket[b]; if (bytes_sent >= transmitLength) {
transmitLength=pendingLength; // end of transmission buffer... repeat or switch to next message
transmitRepeats=pendingRepeats; bytes_sent = 0;
packetPending=false; remainingPreambles = requiredPreambles;
}
else { if (transmitRepeats > 0) {
// Fortunately reset and idle packets are the same length transmitRepeats--;
memcpy( transmitPacket,isMainTrack?idlePacket:resetPacket, sizeof(idlePacket)); }
transmitLength=sizeof(idlePacket); else if (packetPending) {
transmitRepeats=0; // Copy pending packet to transmit packet
} for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b];
transmitLength = pendingLength;
transmitRepeats = pendingRepeats;
packetPending = false;
}
else {
// Fortunately reset and idle packets are the same length
memcpy( transmitPacket, isMainTrack ? idlePacket : resetPacket, sizeof(idlePacket));
transmitLength = sizeof(idlePacket);
transmitRepeats = 0;
} }
} }
} }
void DCCWaveform::checkRailcom() {
if (isMainTrack && RAILCOM_CUTOUT) {
byte preamble=PREAMBLE_BITS_MAIN - remainingPreambles;
if (preamble == RAILCOM_PREAMBLES_BEFORE_CUTOUT) {
digitalWrite2f(railcomBrakePin,HIGH);
}
else if (preamble== RAILCOM_PREAMBLES_BEFORE_CUTOUT+RAILCOM_PREAMBLES_SKIPPED_IN_CUTOUT) {
digitalWrite2f(railcomBrakePin,LOW);
}
}
} }
// Wait until there is no packet pending, then make this pending void DCCWaveform::checkRailcom() {
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) { if (isMainTrack && RAILCOM_CUTOUT) {
if (byteCount>=MAX_PACKET_SIZE) return; // allow for chksum byte preamble = PREAMBLE_BITS_MAIN - remainingPreambles;
while(packetPending); if (preamble == RAILCOM_PREAMBLES_BEFORE_CUTOUT) {
// TODO Railcom::startCutout();
byte checksum=0;
for (int b=0;b<byteCount; b++) {
checksum^=buffer[b];
pendingPacket[b]=buffer[b];
} }
pendingPacket[byteCount]=checksum;
pendingLength=byteCount+1;
pendingRepeats=repeats;
packetPending=true;
} }
}
// Wait until there is no packet pending, then make this pending
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
if (byteCount >= MAX_PACKET_SIZE) return; // allow for chksum
while (packetPending);
byte checksum = 0;
for (int b = 0; b < byteCount; b++) {
checksum ^= buffer[b];
pendingPacket[b] = buffer[b];
}
pendingPacket[byteCount] = checksum;
pendingLength = byteCount + 1;
pendingRepeats = repeats;
packetPending = true;
}
bool DCCWaveform::getAck() bool DCCWaveform::getAck()
{ {
if (isMainTrack) return false; // cant do this on main track
while(packetPending); // wait until transmitter has started transmitting the message
unsigned long timeout=millis()+ACK_TIMEOUT;
int maxCurrent=0;
bool result=false;
int upsamples=0;
int downsamples=0;
// Monitor looking for a reading high enough to be an ack if (isMainTrack) return false; // cant do this on main track
while(result==false && timeout>millis()) {
while (packetPending); // wait until transmitter has started transmitting the message
unsigned long timeout = millis() + ACK_TIMEOUT;
int maxCurrent = 0;
bool result = false;
int upsamples = 0;
int downsamples = 0;
// Monitor looking for a reading high enough to be an ack
while (result == false && timeout > millis()) {
upsamples++; upsamples++;
int current=analogRead(sensePin); int current = Hardware::getCurrentMilliamps(isMainTrack);
maxCurrent=max(maxCurrent,current); maxCurrent = max(maxCurrent, current);
result=current > ACK_MIN_PULSE; result = current > ACK_MIN_PULSE;
} }
// Monitor current until ack signal dies back // Monitor current until ack signal dies back
if (result) while( true) { if (result) while ( true) {
downsamples++; downsamples++;
int current=analogRead(sensePin); int current = Hardware::getCurrentMilliamps(isMainTrack);
maxCurrent=max(maxCurrent,current); maxCurrent = max(maxCurrent, current);
if (current<= ACK_MAX_NOT_PULSE) break; if (current <= ACK_MAX_NOT_PULSE) break;
} }
// The following DIAG is really useful as it can show how long and how far the // The following DIAG is really useful as it can show how long and how far the
// current changes during an ACK from the decoder. // current changes during an ACK from the decoder.
DIAG(F("\nack=%d max=%d, up=%d, down=%d "),result,maxCurrent, upsamples,downsamples); DIAG(F("\nack=%d max=%d, up=%d, down=%d "), result, maxCurrent, upsamples, downsamples);
return result; return result;
} }

View File

@ -1,12 +1,5 @@
#include <DIO2.h>
// This hardware configuration would normally be setup in a .h file elsewhere
const byte MAIN_POWER_PIN = 3;
const byte MAIN_SIGNAL_PIN = 12;
const byte MAIN_SENSE_PIN = A0;
const byte PROG_POWER_PIN = 11;
const byte PROG_SIGNAL_PIN = 13;
const byte PROG_SENSE_PIN = A1;
const int POWER_SAMPLE_MAX = 300; const int POWER_SAMPLE_MAX = 300;
const int POWER_SAMPLE_ON_WAIT = 100; const int POWER_SAMPLE_ON_WAIT = 100;
@ -36,14 +29,13 @@ const byte MAX_PACKET_SIZE = 12;
enum class POWERMODE { OFF, ON, OVERLOAD }; enum class POWERMODE { OFF, ON, OVERLOAD };
const byte idleMessage[] = {0xFF, 0x00};
const byte resetMessage[] = {0x00, 0x00};
const byte idlePacket[] = {0xFF, 0x00, 0xFF}; const byte idlePacket[] = {0xFF, 0x00, 0xFF};
const byte resetPacket[] = {0x00, 0x00, 0x00}; const byte resetPacket[] = {0x00, 0x00, 0x00};
class DCCWaveform { class DCCWaveform {
public: public:
DCCWaveform(byte powerPinNo, byte directionPinNo, byte sensePinNo, byte preambleBits, bool isMain); DCCWaveform( byte preambleBits, bool isMain);
static void begin(); static void begin();
static void loop(); static void loop();
static DCCWaveform mainTrack; static DCCWaveform mainTrack;
@ -83,15 +75,8 @@ class DCCWaveform {
byte pendingLength; byte pendingLength;
byte pendingRepeats; byte pendingRepeats;
// Hardware fast pins
GPIO_pin_t directionPin;
GPIO_pin_t powerPin;
GPIO_pin_t railcomBrakePin;
// current sampling // current sampling
POWERMODE powerMode; POWERMODE powerMode;
byte sensePin;
unsigned long nextSampleDue; unsigned long nextSampleDue;
}; };

42
Hardware.cpp Normal file
View File

@ -0,0 +1,42 @@
#include <Arduino.h>
#include <TimerThree.h>
#include "Hardware.h"
#include "Config.h"
void Hardware::init() {
pinMode(MAIN_POWER_PIN, OUTPUT);
pinMode(MAIN_SIGNAL_PIN, OUTPUT);
if (MAIN_SIGNAL_PIN_ALT) pinMode(MAIN_SIGNAL_PIN_ALT, OUTPUT);
pinMode(MAIN_SENSE_PIN, INPUT);
pinMode(PROG_POWER_PIN, OUTPUT);
pinMode(PROG_SIGNAL_PIN, OUTPUT);
if (PROG_SIGNAL_PIN_ALT) pinMode(PROG_SIGNAL_PIN_ALT, OUTPUT);
pinMode(PROG_SENSE_PIN, INPUT);
}
void Hardware::setPower(bool isMainTrack, bool on) {
digitalWrite(isMainTrack ? MAIN_POWER_PIN : PROG_POWER_PIN, on ? HIGH : LOW);
}
void Hardware::setSignal(bool isMainTrack, bool high) {
byte pin = isMainTrack ? MAIN_SIGNAL_PIN : PROG_SIGNAL_PIN;
byte pin2 = isMainTrack ? MAIN_SIGNAL_PIN_ALT : PROG_SIGNAL_PIN_ALT;
digitalWrite(pin, high ? HIGH : LOW);
if (pin2) digitalWrite(pin2, high ? LOW : HIGH);
}
int Hardware::getCurrentMilliamps(bool isMainTrack) {
int pin = isMainTrack ? MAIN_SENSE_PIN : PROG_SENSE_PIN;
float factor = isMainTrack ? MAIN_SENSE_FACTOR : PROG_SENSE_FACTOR;
int rawCurrent = analogRead(pin);
return (int)(rawCurrent * factor);
}
void Hardware::setCallback(int duration, void (*isr)()) {
Timer3.initialize(duration);
Timer3.disablePwm(TIMER3_A_PIN);
Timer3.disablePwm(TIMER3_B_PIN);
Timer3.attachInterrupt(isr);
}

10
Hardware.h Normal file
View File

@ -0,0 +1,10 @@
// Virtualised hardware Interface
class Hardware {
public:
static void init();
static void setPower(bool isMainTrack, bool on);
static void setSignal(bool isMainTrack, bool high);
static int getCurrentMilliamps(bool isMainTrack);
static void setCallback(int duration, void (*isr)());
};