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

Compare commits

...

7 Commits

10 changed files with 305 additions and 64 deletions

View File

@ -162,6 +162,7 @@ const int16_t HASH_KEYWORD_T='T';
const int16_t HASH_KEYWORD_X='X';
const int16_t HASH_KEYWORD_LCN = 15137;
const int16_t HASH_KEYWORD_HAL = 10853;
const int16_t HASH_KEYWORD_HBRIDGE=-20585;
const int16_t HASH_KEYWORD_SHOW = -21309;
const int16_t HASH_KEYWORD_ANIN = -10424;
const int16_t HASH_KEYWORD_ANOUT = -26399;
@ -916,7 +917,10 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
} else
if (params == 3 && p[1] == HASH_KEYWORD_VPIN) { // <T id VPIN n>
if (!VpinTurnout::create(p[0], p[2])) return false;
} else
} else
if (params == 5 && p[1] == HASH_KEYWORD_HBRIDGE) { // <T id HBRIDGE pin1 pin2 delay>
if (!HBridgeTurnout::create(p[0], p[2], p[3], p[4])) return false;
} else
if (params >= 3 && p[1] == HASH_KEYWORD_DCC) {
// <T id DCC addr subadd> 0<=addr<=511, 0<=subadd<=3 (like <a> command).<T>
if (params==4 && p[2]>=0 && p[2]<512 && p[3]>=0 && p[3]<4) { // <T id DCC n m>

View File

@ -238,7 +238,16 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
setTurnoutHiddenState(VpinTurnout::create(id,pin));
break;
}
case OPCODE_HBRIDGETURNOUT: {
VPIN id=operand;
VPIN pin1=getOperand(progCounter, 1);
VPIN pin2=getOperand(progCounter, 2);
uint16_t delay=getOperand(progCounter, 3);
setTurnoutHiddenState(HBridgeTurnout::create(id,pin1, pin2, delay));
break;
}
case OPCODE_AUTOSTART:
// automatically create a task from here at startup.
// Removed if (progCounter>0) check 4.2.31 because

View File

@ -51,7 +51,8 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_POM,
OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,OPCODE_FORGET,
OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,OPCODE_POWERON,
OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT,
OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT,
OPCODE_PINTURNOUT, OPCODE_HBRIDGETURNOUT,
OPCODE_PRINT,OPCODE_DCCACTIVATE,
OPCODE_ONACTIVATE,OPCODE_ONDEACTIVATE,
OPCODE_ROSTER,OPCODE_KILLALL,

View File

@ -62,6 +62,7 @@
#undef FWD
#undef GREEN
#undef HAL
#undef HBRIDGE_TURNOUT
#undef IF
#undef IFAMBER
#undef IFCLOSED
@ -187,6 +188,7 @@
#define FWD(speed)
#define GREEN(signal_id)
#define HAL(haltype,params...)
#define HBRIDGE_TURNOUT(id,pin1,pin2,dly,description...)
#define IF(sensor_id)
#define IFAMBER(signal_id)
#define IFCLOSED(turnout_id)

View File

@ -174,6 +174,8 @@ void RMFT2::printMessage(uint16_t id) {
#define TURNOUT(id,addr,subaddr,description...) O_DESC(id,description)
#undef TURNOUTL
#define TURNOUTL(id,addr,description...) O_DESC(id,description)
#undef HBRIDGE_TURNOUT
#define HBRIDGE_TURNOUT(id,pin1,pin2,delay_ms,description...) O_DESC(id,description)
#undef PIN_TURNOUT
#define PIN_TURNOUT(id,pin,description...) O_DESC(id,description)
#undef SERVO_TURNOUT
@ -293,6 +295,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#define FWD(speed) OPCODE_FWD,V(speed),
#define GREEN(signal_id) OPCODE_GREEN,V(signal_id),
#define HAL(haltype,params...)
#define HBRIDGE_TURNOUT(id,pin1,pin2,delay,description...) OPCODE_HBRIDGETURNOUT,V(id),OPCODE_PAD,V(pin1),OPCODE_PAD,V(pin2),OPCODE_PAD,V(delay),
#define IF(sensor_id) OPCODE_IF,V(sensor_id),
#define IFAMBER(signal_id) OPCODE_IFAMBER,V(signal_id),
#define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id),

115
IO_ScheduledPin.h Normal file
View File

@ -0,0 +1,115 @@
/*
* © 2023, Sergei Kotlyachkov. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef IO_SCHEDULED_PIN_H
#define IO_SCHEDULED_PIN_H
#include "IODevice.h"
#include <Arduino.h>
#include "defines.h"
/**
* Bounces back single Arduino Pin to specified state after set period of time.
*
* It will establish itself as owner of the pin over ArduinoPins class that typically responds to it and
* activates itself during loop() phase. It restores scheduled state and does not try again until
* another write()
*
* Example usage:
* Create: ScheduledPin::create(5, LOW, 20000);
*
* Then, when neeeded, just call:
* IODevice::write(5, HIGH); // this will call fastWriteDigital(5, HIGH)
*
* In 20 milliseconds, it will also call fastWriteDigital(5, LOW)
*
* In edge case where write() is called twice before responding in the loop,
* the schedule will restart and double the bounce back time.
*/
class ScheduledPin : public IODevice {
private:
int _scheduledValue;
uint32_t _durationMicros;
public:
// Static function to handle create calls.
static void create(VPIN pin, int scheduledValue, uint32_t durationMicros) {
new ScheduledPin(pin, scheduledValue, durationMicros);
}
protected:
// Constructor.
ScheduledPin(VPIN pin, int scheduledValue, uint32_t durationMicros) : IODevice(pin, 1) {
_scheduledValue = scheduledValue;
_durationMicros = durationMicros;
// Typically returned device will be ArduinoPins
IODevice* controlledDevice = IODevice::findDevice(pin);
if (controlledDevice != NULL) {
addDevice(this, controlledDevice);
}
else {
DIAG(F("ScheduledPin Controlled device not found for VPIN:%d"), pin);
_deviceState = DEVSTATE_FAILED;
}
}
// Device-specific initialisation
void _begin() override {
#ifdef DIAG_IO
_display();
#endif
pinMode(_firstVpin, OUTPUT);
ArduinoPins::fastWriteDigital(_firstVpin, _scheduledValue);
}
void _write(VPIN vpin, int value) override {
if (_deviceState == DEVSTATE_FAILED) return;
if (vpin != _firstVpin) {
#ifdef DIAG_IO
DIAG(F("ScheduledPin Error VPIN:%u not equal to %u"), vpin, _firstVpin);
#endif
return;
}
#ifdef DIAG_IO
DIAG(F("ScheduledPin Write VPIN:%u Value:%d Micros:%l"), vpin, value, micros());
#endif
unsigned long currentMicros = micros();
delayUntil(currentMicros + _durationMicros);
ArduinoPins::fastWriteDigital(_firstVpin, value);
}
void _loop(unsigned long currentMicros) {
if (_deviceState == DEVSTATE_FAILED) return;
#ifdef DIAG_IO
DIAG(F("ScheduledPin Bounce VPIN:%u Value:%d Micros:%l"), _firstVpin, _scheduledValue, micros());
#endif
ArduinoPins::fastWriteDigital(_firstVpin, _scheduledValue);
delayUntil(currentMicros + 0x7fffffff); // Largest time in the future! Effectively disable _loop calls.
}
// Display information about the device, and perhaps its current condition (e.g. active, disabled etc).
void _display() {
DIAG(F("ScheduledPin Configured:%u Value:%d Duration:%l"), (int)_firstVpin,
_scheduledValue, _durationMicros);
}
};
#endif // IO_SCHEDULED_PIN_H

View File

@ -1,77 +1,39 @@
# What is DCC++ EX?
DCC++ EX is the organization maintaining several codebases that together represent a fully open source DCC system. Currently, this includes the following:
# What is DCC-EX?
DCC-EX is a team of dedicated enthusiasts producing open source DCC & DC solutions for you to run your complete model railroad layout. Our easy to use, do-it-yourself, and free open source products run on off-the-shelf Arduino technology and are supported by numerous third party hardware and apps like JMRI, Engine Driver, wiThrottle, Rocrail and more.
* [CommandStation-EX](https://github.com/DCC-EX/CommandStation-EX/releases) - the latest take on the DCC++ command station for controlling your trains. Runs on an Arduino board, and includes advanced features such as a WiThrottle server implementation, turnout operation, general purpose inputs and outputs (I/O), and JMRI integration.
* [exWebThrottle](https://github.com/DCC-EX/exWebThrottle) - a simple web based controller for your DCC++ command station.
* [BaseStation-installer](https://github.com/DCC-EX/BaseStation-Installer) - an installer executable that takes care of downloading and installing DCC++ firmware onto your hardware setup.
* [BaseStation-Classic](https://github.com/DCC-EX/BaseStation-Classic) - the original DCC++ software, packaged in a stable release. No active development, bug fixes only.
Currently, our products include the following:
A basic DCC++ EX hardware setup can use easy to find, widely avalable Arduino boards that you can assemble yourself.
Both CommandStation-EX and BaseStation-Classic support much of the NMRA Digital Command Control (DCC) [standards](http://www.nmra.org/dcc-working-group "NMRA DCC Working Group"), including:
* simultaneous control of multiple locomotives
* 2-byte and 4-byte locomotive addressing
* 28 or 128-step speed throttling
* Activate/de-activate all accessory function addresses 0-2048
* Control of all cab functions F0-F28 and F29-F68
* Main Track: Write configuration variable bytes and set/clear specific configuration variable (CV) bits (aka Programming on Main or POM)
* Programming Track: Same as the main track with the addition of reading configuration variable bytes
* And many more custom features. see [What's new in CommandStation-EX?](#whats-new-in-commandstation-ex)
* [EX-CommandStation](https://github.com/DCC-EX/CommandStation-EX/releases)
* [EX-WebThrottle](https://github.com/DCC-EX/exWebThrottle)
* [EX-Installer](https://github.com/DCC-EX/EX-Installer)
* [EX-MotoShield8874](https://dcc-ex.com/reference/hardware/motorboards/ex-motor-shield-8874.html#gsc.tab=0)
* [EX-DCCInspector](https://github.com/DCC-EX/DCCInspector-EX)
* [EX-Toolbox](https://github.com/DCC-EX/EX-Toolbox)
* [EX-Turntable](https://github.com/DCC-EX/EX-Turntable)
* [EX-IOExpander](https://github.com/DCC-EX/EX-IOExpander)
* [EX-FastClock](https://github.com/DCC-EX/EX-FastClock)
* [DCCEXProtocol](https://github.com/DCC-EX/DCCEXProtocol)
Details of these projects can be found on [our web site](https://dcc-ex.com/).
# Whats in this Repository?
This repository, CommandStation-EX, contains a complete DCC++ EX Commmand Station sketch designed for compiling and uploading into an Arduino Uno, Mega, or Nano.
This repository, CommandStation-EX, contains a complete DCC-EX *EX-CommmandStation* sketch designed for compiling and uploading into an Arduino Uno, Mega, or Nano.
To utilize this sketch, you can use the following:
1. (beginner) our [automated installer](https://github.com/DCC-EX/BaseStation-Installer)
1. (recommended for all levels of user) our [automated installer](https://github.com/DCC-EX/EX-Installer)
2. (intermediate) download the latest version from the [releases page](https://github.com/DCC-EX/CommandStation-EX/releases)
3. (advanced) use git clone on this repository
Not using the installer? Open the file "CommandStation-EX.ino" in the
Arduino IDE. Please do not rename the folder containing the sketch
code, nor add any files in that folder. The Arduino IDE relies on the
structure and name of the folder to properly display and compile the
code. Rename or copy config.example.h to config.h. If you do not have
the standard setup, you must edit config.h according to the help texts
in config.h.
Refer to [our web site](https://https://dcc-ex.com/ex-commandstation/get-started/index.html#/) for the hardware required for this project.
## What's new in CommandStation-EX?
**We seriously recommend using the EX-Installer**, however if you choose not to use the installer...
* WiThrottle server built in. Connect Engine Driver or WiThrottle clients directly to your Command Station (or through JMRI as before)
* WiFi and Ethernet shield support
* No more jumpers or soldering!
* Direct support for all the most popular motor control boards including single pin (Arduino) or dual pin (IBT_2) type PWM inputs without the need for an adapter circuit
* I2C Display support (LCD and OLED)
* Improved short circuit detection and automatic reset from an overload
* Current reading, sensing and ACK detection settings in milliAmps instead of just pin readings
* Improved adherence to the NMRA DCC specification
* Complete support for all the old commands and front ends like JMRI
* Railcom cutout (beta)
* Simpler, modular, faster code with an API Library for developers for easy expansion
* New features and functions in JMRI
* Ability to join MAIN and PROG tracks into one MAIN track to run your locos
* "Drive-Away" feature - Throttles with support, like Engine Driver, can allow a loco to be programmed on a usable, electrically isolated programming track and then drive off onto the main track
* Diagnostic commands to test decoders that aren't reading or writing correctly
* Support for Uno, Nano, Mega, Nano Every and Teensy microcontrollers
* User Functions: Filter regular commands (like a turnout or output command) and pass it to your own function or accessory
* Support for LCN (layout control nodes)
* mySetup.h file that acts like an Autoexec.Bat command to send startup commands to the CS
* High Accuracty Waveform option for rock steady DCC signals
* New current response outputs current in mA, overlimit current, and maximum board capable current. Support for new current meter in JMRI
* USB Browser based EX-WebThrottle
* New, simpler, function control command
* Number of locos discovery command `<#>`
* Emergency stop command <!>
* Release cabs from memory command <-> all cabs, <- CAB> for just one loco address
* Automatic slot (register) management
* Automation (coming soon)
NOTE: DCC-EX is a major rewrite to the code. We started over and rebuilt it from the ground up! For what that means, you can read [HERE](https://dcc-ex.com/about/rewrite.html).
* Open the file ``CommandStation-EX.ino`` in the Arduino IDE or Visual Studio Code (VSC). Please do not rename the folder containing the sketch code, nor add any files in that folder. The Arduino IDE relies on the structure and name of the folder to properly display and compile the code.
* Rename or copy ``config.example.h`` to ``config.h``.
* You must edit ``config.h`` according to the help texts in ``config.h``.
# More information
You can learn more at the [DCC++ EX website](https://dcc-ex.com/)
You can learn more at the [DCC-EX website](https://dcc-ex.com/)
- November 14, 2020

View File

@ -36,6 +36,10 @@
#include "LCN.h"
#ifdef EESTOREDEBUG
#include "DIAG.h"
#endif
#ifndef IO_NO_HAL
#include "IO_ScheduledPin.h"
#endif
/*
@ -187,6 +191,10 @@
// VPIN turnout
tt = VpinTurnout::load(&turnoutData);
break;
case TURNOUT_HBRIDGE:
// HBRIDGE turnout
tt = HBridgeTurnout::load(&turnoutData);
break;
default:
// If we find anything else, then we don't know what it is or how long it is,
// so we can't go any further through the EEPROM!
@ -477,6 +485,102 @@
#endif
}
/*************************************************************************************
* HBridgeTurnout - Turnout controlled through a pair of HAL pins.
* Typically connected to Motor H-Bridge. Delay is used to quickly turn on/off power.
*************************************************************************************/
// Constructor
HBridgeTurnout::HBridgeTurnout(uint16_t id, VPIN pin1, VPIN pin2, uint16_t millisDelay, bool closed) :
Turnout(id, TURNOUT_HBRIDGE, closed)
{
_hbridgeTurnoutData.pin1 = pin1;
_hbridgeTurnoutData.pin2 = pin2;
_hbridgeTurnoutData.millisDelay = millisDelay;
#ifndef IO_NO_HAL
// HARD LIMIT to maximum 0.5 second to avoid burning the coil
// Also note 1000x multiplier because ScheduledPin works with microSeconds.
ScheduledPin::create(pin1, LOW, 1000*min(millisDelay, 500));
ScheduledPin::create(pin2, LOW, 1000*min(millisDelay, 500));
#else
DIAG(F("H-Brdige Turnout %d will be disabled because HAL is off"), id);
#endif
}
// Create function
/* static */ Turnout *HBridgeTurnout::create(uint16_t id, VPIN pin1, VPIN pin2, uint16_t millisDelay, bool closed) {
Turnout *tt = get(id);
if (tt) {
// Object already exists, check if it is usable
if (tt->isType(TURNOUT_HBRIDGE)) {
// Yes, so set parameters
HBridgeTurnout *hbt = (HBridgeTurnout *)tt;
hbt->_hbridgeTurnoutData.pin1 = pin1;
hbt->_hbridgeTurnoutData.pin2 = pin2;
hbt->_hbridgeTurnoutData.millisDelay = millisDelay;
// Don't touch the _closed parameter, retain the original value.
return tt;
} else {
// Incompatible object, delete and recreate
remove(id);
}
}
tt = (Turnout *)new HBridgeTurnout(id, pin1, pin2, millisDelay, closed);
return tt;
}
// Load a VPIN turnout definition from EEPROM. The common Turnout data has already been read at this point.
/* static */ Turnout *HBridgeTurnout::load(struct TurnoutData *turnoutData) {
#ifndef DISABLE_EEPROM
HBridgeTurnoutData hbridgeTurnoutData;
// Read class-specific data from EEPROM
EEPROM.get(EEStore::pointer(), hbridgeTurnoutData);
EEStore::advance(sizeof(hbridgeTurnoutData));
// Create new object
HBridgeTurnout *tt = new HBridgeTurnout(turnoutData->id, hbridgeTurnoutData.pin1,
hbridgeTurnoutData.pin2, hbridgeTurnoutData.millisDelay, turnoutData->closed);
return tt;
#else
(void)turnoutData;
return NULL;
#endif
}
// Report 1 for thrown, 0 for closed.
void HBridgeTurnout::print(Print *stream) {
StringFormatter::send(stream, F("<H %d HBRIDGE %d %d %d>\n"), _turnoutData.id, _hbridgeTurnoutData.pin1, _hbridgeTurnoutData.pin2,
!_turnoutData.closed);
}
void HBridgeTurnout::turnUpDown(VPIN pin) {
// HBridge turnouts require very small, prescribed time to keep pin1 or pin2 in HIGH state.
// Otherwise internal coil of the turnout will burn.
// If HAL is disabled (and therefore SchedulePin class), we can not turn this on,
// otherwise coil will burn and device will be lost.
#ifndef IO_NO_HAL
IODevice::write(pin, HIGH);
#endif
}
bool HBridgeTurnout::setClosedInternal(bool close) {
turnUpDown(close ? _hbridgeTurnoutData.pin2 : _hbridgeTurnoutData.pin1);
_turnoutData.closed = close;
return true;
}
void HBridgeTurnout::save() {
#ifndef DISABLE_EEPROM
// Write turnout definition and current position to EEPROM
// First write common servo data, then
// write the servo-specific data
EEPROM.put(EEStore::pointer(), _turnoutData);
EEStore::advance(sizeof(_turnoutData));
EEPROM.put(EEStore::pointer(), _hbridgeTurnoutData);
EEStore::advance(sizeof(_hbridgeTurnoutData));
#endif
}
/*************************************************************************************
* LCNTurnout - Turnout controlled by Loconet

View File

@ -37,6 +37,7 @@ enum {
TURNOUT_SERVO = 2,
TURNOUT_VPIN = 3,
TURNOUT_LCN = 4,
TURNOUT_HBRIDGE = 5,
};
/*************************************************************************************
@ -284,6 +285,41 @@ protected:
};
/*************************************************************************************
* HBridgeTurnout - Turnout controlled through a pair of HAL pins.
*
* Hard limited to maximum 0.5 second to avoid burning the coil
* Typical millisDelay should be within between 50 and 100
*************************************************************************************/
class HBridgeTurnout : public Turnout {
private:
// HBridgeTurnoutData contains data specific to this subclass that is
// written to EEPROM when the turnout is saved.
struct HBridgeTurnoutData {
VPIN pin1;
VPIN pin2;
uint16_t millisDelay;
} _hbridgeTurnoutData; // 6 bytes
// Constructor
HBridgeTurnout(uint16_t id, VPIN pin1, VPIN pin2, uint16_t millisDelay, bool closed);
public:
// Create function
static Turnout *create(uint16_t id, VPIN pin1, VPIN pin2, uint16_t millisDelay, bool closed=true);
// Load a HBRIDGE turnout definition from EEPROM. The common Turnout data has already been read at this point.
static Turnout *load(struct TurnoutData *turnoutData);
void print(Print *stream) override;
protected:
bool setClosedInternal(bool close) override;
void save() override;
private:
void turnUpDown(VPIN pin);
};
/*************************************************************************************
* LCNTurnout - Turnout controlled by Loconet

View File

@ -36,6 +36,11 @@ const unsigned long LOOP_TIMEOUT = 2000;
bool WifiInterface::connected = false;
Stream * WifiInterface::wifiStream;
#ifndef WIFI_AT_CHECK_TIMEOUT
// Some ESP32 AT firmware versions take time to initialize and do not respond to AT commands right away.
#define WIFI_AT_CHECK_TIMEOUT 2000
#endif
#ifndef WIFI_CONNECT_TIMEOUT
// Tested how long it takes to FAIL an unknown SSID on firmware 1.7.4.
// The ES should fail a connect in 15 seconds, we don't want to fail BEFORE that
@ -192,7 +197,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
}
StringFormatter::send(wifiStream, F("AT\r\n")); // Is something here that understands AT?
if(!checkForOK(200, true))
if(!checkForOK(WIFI_AT_CHECK_TIMEOUT, true))
return WIFI_NOAT; // No AT compatible WiFi module here
StringFormatter::send(wifiStream, F("ATE1\r\n")); // Turn on the echo, se we can see what's happening