diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7c486f1..0000000 --- a/.travis.yml +++ /dev/null @@ -1,67 +0,0 @@ -# Continuous Integration (CI) is the practice, in software -# engineering, of merging all developer working copies with a shared mainline -# several times a day < https://docs.platformio.org/page/ci/index.html > -# -# Documentation: -# -# * Travis CI Embedded Builds with PlatformIO -# < https://docs.travis-ci.com/user/integration/platformio/ > -# -# * PlatformIO integration with Travis CI -# < https://docs.platformio.org/page/ci/travis.html > -# -# * User Guide for `platformio ci` command -# < https://docs.platformio.org/page/userguide/cmd_ci.html > -# -# -# Please choose one of the following templates (proposed below) and uncomment -# it (remove "# " before each line) or use own configuration according to the -# Travis CI documentation (see above). -# - - -# -# Template #1: General project. Test it using existing `platformio.ini`. -# - -# language: python -# python: -# - "2.7" -# -# sudo: false -# cache: -# directories: -# - "~/.platformio" -# -# install: -# - pip install -U platformio -# - platformio update -# -# script: -# - platformio run - - -# -# Template #2: The project is intended to be used as a library with examples. -# - -# language: python -# python: -# - "2.7" -# -# sudo: false -# cache: -# directories: -# - "~/.platformio" -# -# env: -# - PLATFORMIO_CI_SRC=path/to/test/file.c -# - PLATFORMIO_CI_SRC=examples/file.ino -# - PLATFORMIO_CI_SRC=path/to/test/directory -# -# install: -# - pip install -U platformio -# - platformio update -# -# script: -# - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index e80666b..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "platformio.platformio-ide" - ] -} diff --git a/CVReader.ino b/CVReader.ino new file mode 100644 index 0000000..caa114e --- /dev/null +++ b/CVReader.ino @@ -0,0 +1,67 @@ +#include "DCCWaveform.h" +#include "DIAG.h" +bool verifyCV(int cv, byte bValue); +int readCv(int cv); +const int cvnums[]={1,2,3,4,5,17,18,19,21,22,29}; + +void setup() { + Serial.begin(115200); + DCCWaveform::begin(); + Serial.println(F("CVReader")); + + DIAG(F("\n===================================\n")); + + for (byte x=0;x=0?"OK":"FAIL"); + } + + DIAG(F("\nProgram complete, press reset to retry")); +} + +void loop() { + +} + +byte cv1(byte opcode, int cv) { + cv--; + return (highByte(cv) & (byte)0x03) | opcode; +} +byte cv2(int cv) { + cv--; + return lowByte(cv); +} + +//// The functions below are lifted from the DCCApi for easy testing and experimentation. +// Once reliable, tha DCCApi should be updated to match +bool verifyCV(int cv, byte value) { + + delay(2); // allow for decoder to quiesce latest pulse + + + byte message[] = { + cv1(0x74,cv) , // set-up to re-verify entire byte + cv2(cv), + value + }; + + DCCWaveform::progTrack.schedulePacket(message, sizeof(message), 5); + return DCCWaveform::progTrack.getAck(); +} + +int readCV(int cv) +{ + + byte message[]={ cv1(0x78,cv) , // any CV>1023 will become modulus(1024) due to bit-mask of 0x03 + cv2(cv), + 0}; // trailing zero will be updated in loop below + + byte value = 0; + + for (int i = 0; i<8; i++) { + message[2] = 0xE8 + i; + DCCWaveform::progTrack.schedulePacket(message,sizeof(message), 4); // NMRA recommends 5 rerad packets + value+= (DCCWaveform::progTrack.getAck()< +#include +#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); + +void DCCWaveform::begin() { + + Timer3.initialize(58); + Timer3.disablePwm(MAIN_SIGNAL_PIN); + Timer3.disablePwm(PROG_SIGNAL_PIN); + Timer3.attachInterrupt(interruptHandler); + mainTrack.beginTrack(); + progTrack.beginTrack(); +} + + 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(); +} + + +// 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. + + +// 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}; + + +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; +} + void DCCWaveform::beginTrack() { + pinMode2f(powerPin,OUTPUT); + pinMode2f(directionPin,OUTPUT); + pinMode(sensePin,INPUT); + setPowerMode(POWERMODE::ON); + DIAG(F("\nTrack started sensePin=%d\n"),sensePin); + } + + POWERMODE DCCWaveform::getPowerMode() { + return powerMode; + } + + void DCCWaveform::setPowerMode(POWERMODE mode) { + powerMode=mode; + digitalWrite2f(powerPin, mode==POWERMODE::ON ? HIGH:LOW); + if (mode==POWERMODE::ON) schedulePacket(resetMessage,2,20); + } + + +void DCCWaveform::checkPowerOverload() { + if (millis() 0 ) { + currentBit=true; + remainingPreambles--; + return; + } + + // 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 (transmitRepeats > 0) { + transmitRepeats--; + } + else if (packetPending) { + // Copy pending packet to transmit packet + for (int b=0;b=MAX_PACKET_SIZE) return; // allow for chksum + while(packetPending) delay(1); + + byte checksum=0; + for (int b=0;bmillis()) { + upsamples++; + delay(1); + int current=analogRead(sensePin); + maxCurrent=max(maxCurrent,current); + result=current > ACK_MIN_PULSE; + } + if (result) while( true) { + downsamples++; + delay(1); + int current=analogRead(sensePin); + maxCurrent=max(maxCurrent,current); + if (current<= ACK_MAX_NOT_PULSE)break; + } + DIAG(F("\nack=%d max=%d, up=%d, down=%d "),result,maxCurrent, upsamples,downsamples); + return result; +} diff --git a/DCCWaveform.h b/DCCWaveform.h new file mode 100644 index 0000000..e6d781b --- /dev/null +++ b/DCCWaveform.h @@ -0,0 +1,89 @@ +#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; +const int POWER_SAMPLE_OFF_WAIT=1000; +const int POWER_SAMPLE_OVERLOAD_WAIT=4000; + + +// ACK current analogRead values (vary depending on motor shield and cpu voltage) +const int ACK_TIMEOUT = 5 ; // millis getAck is prepared to wait for a signal +const int ACK_MAX_NOT_PULSE = 10 ; // current below which this is NOT a pulse any more +const int ACK_MIN_PULSE = 50 ; // current above which a pulse is recognised + +const int PREAMBLE_BITS_MAIN=16; +const int PREAMBLE_BITS_PROG=22; + +const byte MAX_PACKET_SIZE=12; +// NOTE: static functions are used for the overall controller, then +// one instance is created for each track. + + +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); + static void begin(); + static void loop(); + static DCCWaveform mainTrack; + static DCCWaveform progTrack; + + void beginTrack(); + void setPowerMode(POWERMODE); + POWERMODE getPowerMode(); + void checkPowerOverload(); + void schedulePacket(const byte buffer[], byte byteCount, byte repeats); + volatile bool packetPending; + bool startAckProcess(); + bool getAck(); + + + private: + + static void interruptHandler(); + bool interrupt1(); + void interrupt2(); + + POWERMODE powerMode; + +// Transmission controller + byte transmitPacket[MAX_PACKET_SIZE]; // packet being transmitted + byte transmitLength; + byte transmitRepeats; // remaining repeats of transmission + byte remainingPreambles; + byte requiredPreambles; + bool currentBit; // bit to be transmitted + + byte bits_sent; // 0-8 (yes 9 bits) sent for current byte + byte bytes_sent; // number of bytes sent from transmitPacket + + byte state; // wave generator state machine + + byte pendingPacket[MAX_PACKET_SIZE]; + byte pendingLength; + byte pendingRepeats; + + // Hardware pins + GPIO_pin_t directionPin; + GPIO_pin_t powerPin; + + // current sampling + bool isMainTrack; + byte sensePin; + unsigned long nextSampleDue; + int ackBaseCurrent; + +}; diff --git a/DIAG.cpp b/DIAG.cpp new file mode 100644 index 0000000..580010f --- /dev/null +++ b/DIAG.cpp @@ -0,0 +1,27 @@ +#include +#include + +void Serialprint(const __FlashStringHelper* input...) { + // thanks to Jan TuroĊˆ https://arduino.stackexchange.com/questions/56517/formatting-strings-in-arduino-for-output + + va_list args; + va_start(args, input); + char* flash=(char*)input; + for(int i=0; ; ++i) { + char c=pgm_read_byte_near(flash+i); + if (c=='\0') return; + if(c!='%') { Serial.print(c); continue; } + i++; + c=pgm_read_byte_near(flash+i); + switch(c) { + case '%': Serial.print('%'); break; + case 's': Serial.print(va_arg(args, char*)); break; + case 'd': Serial.print(va_arg(args, int), DEC); break; + case 'b': Serial.print(va_arg(args, int), BIN); break; + case 'o': Serial.print(va_arg(args, int), OCT); break; + case 'x': Serial.print(va_arg(args, int), HEX); break; + case 'f': Serial.print(va_arg(args, double), 2); break; + } + } + va_end(args); +} diff --git a/DIAG.h b/DIAG.h new file mode 100644 index 0000000..8ac3fdd --- /dev/null +++ b/DIAG.h @@ -0,0 +1,5 @@ +void Serialprint(const __FlashStringHelper* input...); +#ifndef DIAG_ENABLED + #define DIAG_ENABLED true +#endif +#define DIAG if (DIAG_ENABLED) Serialprint diff --git a/include/README b/include/README deleted file mode 100644 index 194dcd4..0000000 --- a/include/README +++ /dev/null @@ -1,39 +0,0 @@ - -This directory is intended for project header files. - -A header file is a file containing C declarations and macro definitions -to be shared between several project source files. You request the use of a -header file in your project source file (C, C++, etc) located in `src` folder -by including it, with the C preprocessing directive `#include'. - -```src/main.c - -#include "header.h" - -int main (void) -{ - ... -} -``` - -Including a header file produces the same results as copying the header file -into each source file that needs it. Such copying would be time-consuming -and error-prone. With a header file, the related declarations appear -in only one place. If they need to be changed, they can be changed in one -place, and programs that include the header file will automatically use the -new version when next recompiled. The header file eliminates the labor of -finding and changing all the copies as well as the risk that a failure to -find one copy will result in inconsistencies within a program. - -In C, the usual convention is to give header files names that end with `.h'. -It is most portable to use only letters, digits, dashes, and underscores in -header file names, and at most one dot. - -Read more about using header files in official GCC documentation: - -* Include Syntax -* Include Operation -* Once-Only Headers -* Computed Includes - -https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/README b/lib/README deleted file mode 100644 index 6debab1..0000000 --- a/lib/README +++ /dev/null @@ -1,46 +0,0 @@ - -This directory is intended for project specific (private) libraries. -PlatformIO will compile them to static libraries and link into executable file. - -The source code of each library should be placed in a an own separate directory -("lib/your_library_name/[here are source files]"). - -For example, see a structure of the following two libraries `Foo` and `Bar`: - -|--lib -| | -| |--Bar -| | |--docs -| | |--examples -| | |--src -| | |- Bar.c -| | |- Bar.h -| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html -| | -| |--Foo -| | |- Foo.c -| | |- Foo.h -| | -| |- README --> THIS FILE -| -|- platformio.ini -|--src - |- main.c - -and a contents of `src/main.c`: -``` -#include -#include - -int main (void) -{ - ... -} - -``` - -PlatformIO Library Dependency Finder will find automatically dependent -libraries scanning project source files. - -More information about PlatformIO Library Dependency Finder -- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/platformio.ini b/platformio.ini deleted file mode 100644 index d49d13f..0000000 --- a/platformio.ini +++ /dev/null @@ -1,14 +0,0 @@ -; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html - -[env:megaatmega1280] -platform = atmelavr -board = megaatmega1280 -framework = arduino diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index 2004045..0000000 --- a/src/main.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include - -void setup() { - Serial.begin(115200); - Serial.println(F("CVReader")); -} - -void loop() { - // put your main code here, to run repeatedly: -} \ No newline at end of file diff --git a/test/README b/test/README deleted file mode 100644 index df5066e..0000000 --- a/test/README +++ /dev/null @@ -1,11 +0,0 @@ - -This directory is intended for PIO Unit Testing and project tests. - -Unit Testing is a software testing method by which individual units of -source code, sets of one or more MCU program modules together with associated -control data, usage procedures, and operating procedures, are tested to -determine whether they are fit for use. Unit testing finds problems early -in the development cycle. - -More information about PIO Unit Testing: -- https://docs.platformio.org/page/plus/unit-testing.html