1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-12-23 21:01:25 +01:00

First checkin

This commit is contained in:
Asbelos 2020-05-23 23:02:54 +01:00
parent e95a1a11a5
commit 2ebf1a6f66
12 changed files with 423 additions and 194 deletions

View File

@ -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

View File

@ -1,7 +0,0 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
]
}

67
CVReader.ino Normal file
View File

@ -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<sizeof(cvnums)/sizeof(cvnums[0]);x++) {
DIAG(F("\n\nCV VERIFICATION %d = %s\n"),cvnums[x],readCV(cvnums[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()<<i);
}
DIAG(F("\n*** READ CV %d = %d ******\n"),cv,value);
return verifyCV(cv,value)?value:-1;
}

235
DCCWaveform.cpp Normal file
View File

@ -0,0 +1,235 @@
#include <Arduino.h>
#include <TimerThree.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);
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()<nextSampleDue) return;
int current;
int delay;
switch (powerMode) {
case POWERMODE::OFF:
delay=POWER_SAMPLE_OFF_WAIT;
break;
case POWERMODE::ON:
// Check current
current=analogRead(sensePin);
if (current < POWER_SAMPLE_MAX) delay=POWER_SAMPLE_ON_WAIT;
else {
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);
delay=POWER_SAMPLE_OVERLOAD_WAIT;
}
break;
case POWERMODE::OVERLOAD:
// Try setting it back on after the OVERLOAD_WAIT
setPowerMode(POWERMODE::ON);
delay=POWER_SAMPLE_ON_WAIT;
break;
default:
delay=999; // cant get here..meaningless statement to avoid compiler warning.
}
nextSampleDue=millis()+delay;
}
// process time-edge sensitive part of interrupt
// return true if second level required
bool DCCWaveform::interrupt1() {
// NOTE: this must consume transmission buffers even if the power is off
// otherwise can cause hangs in main loop waiting for the pendingBuffer.
switch (state) {
case 0: // start of bit transmission
digitalWrite2f(directionPin, HIGH);
state = 1;
return true; // must call interrupt2
case 1: // 58Ms after case 0
if (currentBit) {
digitalWrite2f(directionPin, LOW);
state = 0;
}
else state = 2;
break;
case 2: digitalWrite2f(directionPin, LOW);
state = 3;
break;
case 3: state = 0;
break;
}
return false;
}
void DCCWaveform::interrupt2() {
// set currentBit to be the next bit to be sent.
if (remainingPreambles > 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<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;
}
}
}
}
// 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) delay(1);
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) delay(1); // wait until transmitter has gont into reset packets
delay(20); // time for enough resets
unsigned long timeout=millis()+ACK_TIMEOUT;
int maxCurrent=0;
bool result=false;
int upsamples=0;
int downsamples=0;
while(result==false && timeout>millis()) {
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;
}

89
DCCWaveform.h Normal file
View File

@ -0,0 +1,89 @@
#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_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;
};

27
DIAG.cpp Normal file
View File

@ -0,0 +1,27 @@
#include <arduino.h>
#include <stdarg.h>
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);
}

5
DIAG.h Normal file
View File

@ -0,0 +1,5 @@
void Serialprint(const __FlashStringHelper* input...);
#ifndef DIAG_ENABLED
#define DIAG_ENABLED true
#endif
#define DIAG if (DIAG_ENABLED) Serialprint

View File

@ -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

View File

@ -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 <Foo.h>
#include <Bar.h>
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

View File

@ -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

View File

@ -1,10 +0,0 @@
#include <Arduino.h>
void setup() {
Serial.begin(115200);
Serial.println(F("CVReader"));
}
void loop() {
// put your main code here, to run repeatedly:
}

View File

@ -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