1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-12-27 22:31:24 +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"
/* 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<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");
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);
}

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) {
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;

View File

@ -1,120 +1,103 @@
#include <Arduino.h>
#include <TimerThree.h>
#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()<nextSampleDue) return;
if (millis() < nextSampleDue) return;
int current;
int delay;
switch (powerMode) {
case POWERMODE::OFF:
delay=POWER_SAMPLE_OFF_WAIT;
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;
case POWERMODE::ON:
// Check current
current = Hardware::getCurrentMilliamps(isMainTrack);
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;
DIAG(F("\n*** %s TRACK POWER OVERLOAD current=%d max=%d ***\n"), isMainTrack ? "MAIN" : "PROG", current, POWER_SAMPLE_MAX);
delay = POWER_SAMPLE_OVERLOAD_WAIT;
}
break;
case POWERMODE::OVERLOAD:
case POWERMODE::OVERLOAD:
// Try setting it back on after the OVERLOAD_WAIT
setPowerMode(POWERMODE::ON);
delay=POWER_SAMPLE_ON_WAIT;
delay = POWER_SAMPLE_ON_WAIT;
break;
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
// return true if second level required
// 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.
// 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);
Hardware::setSignal(isMainTrack, HIGH);
checkRailcom();
state = 1;
return true; // must call interrupt2 to set currentBit
case 1: // 58us after case 0
if (currentBit) {
digitalWrite2f(directionPin, LOW);
Hardware::setSignal(isMainTrack, LOW);
state = 0;
}
else state = 2;
break;
case 2: // 116us after case 0
digitalWrite2f(directionPin, LOW);
Hardware::setSignal(isMainTrack, LOW);
state = 3;
break;
case 3: // finished sending zero bit
case 3: // finished sending zero bit
state = 0;
break;
break;
}
return false;
}
void DCCWaveform::interrupt2() {
// set currentBit to be the next bit to be sent.
if (remainingPreambles > 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<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;
}
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;
}
}
}
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<byteCount; b++) {
checksum^=buffer[b];
pendingPacket[b]=buffer[b];
void DCCWaveform::checkRailcom() {
if (isMainTrack && RAILCOM_CUTOUT) {
byte preamble = PREAMBLE_BITS_MAIN - remainingPreambles;
if (preamble == RAILCOM_PREAMBLES_BEFORE_CUTOUT) {
// TODO Railcom::startCutout();
}
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()
{
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;
}

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_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;
};

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)());
};