From 22406b44ed71fb42e50da12f7c1474b6da0e1afc Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 26 May 2020 12:44:02 +0100 Subject: [PATCH] Abstracted Hardware Interface The hardware interface for all pins and timers is now in Hardware.cpp --- CVReader.ino | 43 ++++--- Config.h | 13 ++ DCC.cpp | 4 +- DCCWaveform.cpp | 314 +++++++++++++++++++++++------------------------- DCCWaveform.h | 23 +--- Hardware.cpp | 42 +++++++ Hardware.h | 10 ++ 7 files changed, 239 insertions(+), 210 deletions(-) create mode 100644 Config.h create mode 100644 Hardware.cpp create mode 100644 Hardware.h diff --git a/CVReader.ino b/CVReader.ino index a95c2ca..49431c9 100644 --- a/CVReader.ino +++ b/CVReader.ino @@ -3,41 +3,40 @@ #include "JMRIParser.h" /* 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 - * to transmit and receive DCC data on the programming track. - * - * Important... DCCWaveform.h contains hardware specific confioguration settings - * that you will need to check. - * - * Notes: the waveform generator sends reset packets on progTrack when it has nothing better to do, this means that - * 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. - * - */ + + 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. + + Once soem CVs have been listed, it then drops into JMRI input moce so you can play. + + Important... Config.h contains hardware specific confioguration settings + that you will need to check. -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() { Serial.begin(115200); DCC::begin(); - + DIAG(F("\n===== CVReader begin ==============================\n")); - - for (byte x=0;x=0?" VERIFIED OK":"FAILED VERIFICATION"); + + for (byte x = 0; x < sizeof(cvnums) / sizeof(cvnums[0]); 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("\n===== CVReader done ==============================\n")); + DIAG(F("\n===== CVReader done ==============================\n")); DIAG(F("\nReady for JMRI commands\n")); } void loop() { 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); } - diff --git a/Config.h b/Config.h new file mode 100644 index 0000000..c472e18 --- /dev/null +++ b/Config.h @@ -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 diff --git a/DCC.cpp b/DCC.cpp index c7ec423..ed25e1f 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -182,7 +182,7 @@ byte DCC::cv2(int cv) { bool DCC::verifyCV(int cv, byte 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); return DCCWaveform::progTrack.getAck(); } @@ -206,4 +206,4 @@ void DCC::updateLocoReminder(int loco, byte tSpeed, bool forward) { speedTable[reg].forward = forward; } DCC::LOCO DCC::speedTable[MAX_LOCOS]; -int DCC::nextLoco=0; +int DCC::nextLoco = 0; diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 687ecb7..28b7838 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -1,120 +1,103 @@ #include -#include +#include "Hardware.h" #include "DCCWaveform.h" #include "DIAG.h" -DCCWaveform DCCWaveform::mainTrack(MAIN_POWER_PIN,MAIN_SIGNAL_PIN,MAIN_SENSE_PIN,PREAMBLE_BITS_MAIN,true); -DCCWaveform DCCWaveform::progTrack(PROG_POWER_PIN,PROG_SIGNAL_PIN,PROG_SENSE_PIN,PREAMBLE_BITS_PROG,false); +DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true); +DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false); void DCCWaveform::begin() { - - Timer3.initialize(58); - Timer3.disablePwm(MAIN_SIGNAL_PIN); - Timer3.disablePwm(PROG_SIGNAL_PIN); - Timer3.attachInterrupt(interruptHandler); - mainTrack.beginTrack(); - progTrack.beginTrack(); + Hardware::init(); + Hardware::setCallback(58, interruptHandler); + mainTrack.beginTrack(); + progTrack.beginTrack(); } - - void DCCWaveform::loop() { - mainTrack.checkPowerOverload(); - progTrack.checkPowerOverload(); - } - + +void DCCWaveform::loop() { + mainTrack.checkPowerOverload(); + progTrack.checkPowerOverload(); +} + // static // void DCCWaveform::interruptHandler() { - // call the timer edge sensitive actions for progtrack and maintrack - bool mainCall2=mainTrack.interrupt1(); - bool progCall2=progTrack.interrupt1(); - - // call (if necessary) the procs to get the current bits - // these must complete within 50microsecs of the interrupt - // but they are only called ONCE PER BIT TRANSMITTED - // after the rising edge of the signal - if (mainCall2) mainTrack.interrupt2(); - if (progCall2) progTrack.interrupt2(); + // call the timer edge sensitive actions for progtrack and maintrack + bool mainCall2 = mainTrack.interrupt1(); + bool progCall2 = progTrack.interrupt1(); + + // call (if necessary) the procs to get the current bits + // these must complete within 50microsecs of the interrupt + // but they are only called ONCE PER BIT TRANSMITTED + // after the rising edge of the signal + if (mainCall2) mainTrack.interrupt2(); + if (progCall2) progTrack.interrupt2(); } // An instance of this class handles the DCC transmissions for one track. (main or prog) // Interrupts are marshalled via the statics. // 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. -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) { - // establish appropriate pins - powerPin=Arduino_to_GPIO_pin(powerPinNo); - directionPin=Arduino_to_GPIO_pin(directionPinNo); - sensePin=sensePinNo; - isMainTrack=isMain; - packetPending=false; - memcpy(transmitPacket,idlePacket,sizeof(idlePacket)); - state=0; - requiredPreambles=preambleBits; - bytes_sent=0; - bits_sent=0; - nextSampleDue=0; - +DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) { + // establish appropriate pins + isMainTrack = isMain; + packetPending = false; + memcpy(transmitPacket, idlePacket, sizeof(idlePacket)); + state = 0; + requiredPreambles = preambleBits; + 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; - } +} + +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() { - if (millis() 0 ) { - currentBit=true; + currentBit = true; remainingPreambles--; return; } - - // beware OF 9-BIT MASK generating a zero to start each byte - currentBit=transmitPacket[bytes_sent] & bitMask[bits_sent]; + + // beware OF 9-BIT MASK generating a zero to start each byte + currentBit = transmitPacket[bytes_sent] & bitMask[bits_sent]; bits_sent++; - // 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 this is the last bit of a byte, prepare for the next byte - if (transmitRepeats > 0) { - transmitRepeats--; - } - else if (packetPending) { - // Copy pending packet to transmit packet - for (int b=0;b= transmitLength) { + // end of transmission buffer... repeat or switch to next message + bytes_sent = 0; + remainingPreambles = requiredPreambles; + + if (transmitRepeats > 0) { + transmitRepeats--; + } + else if (packetPending) { + // 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::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= 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() { - - 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 - while(result==false && timeout>millis()) { + 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 + while (result == false && timeout > millis()) { upsamples++; - int current=analogRead(sensePin); - maxCurrent=max(maxCurrent,current); - result=current > ACK_MIN_PULSE; + int current = Hardware::getCurrentMilliamps(isMainTrack); + maxCurrent = max(maxCurrent, current); + result = current > ACK_MIN_PULSE; } // Monitor current until ack signal dies back - if (result) while( true) { - downsamples++; - int current=analogRead(sensePin); - maxCurrent=max(maxCurrent,current); - if (current<= ACK_MAX_NOT_PULSE) break; - } - // The following DIAG is really useful as it can show how long and how far the + if (result) while ( true) { + downsamples++; + int current = Hardware::getCurrentMilliamps(isMainTrack); + maxCurrent = max(maxCurrent, current); + if (current <= ACK_MAX_NOT_PULSE) break; + } + // The following DIAG is really useful as it can show how long and how far the // 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; } diff --git a/DCCWaveform.h b/DCCWaveform.h index 3a1f890..d7a8a5e 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -1,12 +1,5 @@ -#include -// 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_ON_WAIT = 100; @@ -36,14 +29,13 @@ const byte MAX_PACKET_SIZE = 12; enum class POWERMODE { OFF, ON, OVERLOAD }; -const byte idleMessage[] = {0xFF, 0x00}; -const byte resetMessage[] = {0x00, 0x00}; + const byte idlePacket[] = {0xFF, 0x00, 0xFF}; const byte resetPacket[] = {0x00, 0x00, 0x00}; class DCCWaveform { public: - DCCWaveform(byte powerPinNo, byte directionPinNo, byte sensePinNo, byte preambleBits, bool isMain); + DCCWaveform( byte preambleBits, bool isMain); static void begin(); static void loop(); static DCCWaveform mainTrack; @@ -83,15 +75,8 @@ class DCCWaveform { byte pendingLength; byte pendingRepeats; - // Hardware fast pins - GPIO_pin_t directionPin; - GPIO_pin_t powerPin; - GPIO_pin_t railcomBrakePin; - + // current sampling POWERMODE powerMode; - byte sensePin; unsigned long nextSampleDue; - - }; diff --git a/Hardware.cpp b/Hardware.cpp new file mode 100644 index 0000000..0aa856a --- /dev/null +++ b/Hardware.cpp @@ -0,0 +1,42 @@ +#include +#include + +#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); +} diff --git a/Hardware.h b/Hardware.h new file mode 100644 index 0000000..efd5eec --- /dev/null +++ b/Hardware.h @@ -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)()); +};