mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-31 03:13:45 +02:00
Compare commits
112 Commits
ESP32-moto
...
v3.2.0rc12
Author | SHA1 | Date | |
---|---|---|---|
|
73ea7a1479 | ||
|
f807339eec | ||
|
1416f83f8c | ||
|
d5955a36bf | ||
|
35f7ac3d77 | ||
|
41cda58bef | ||
|
0b8c455594 | ||
|
e0a7c4d155 | ||
|
5f878b5911 | ||
|
61390cb0e2 | ||
|
d45585ce3d | ||
|
434c292fd9 | ||
|
b0915e8332 | ||
|
1934fdd0e1 | ||
|
ff102dbf88 | ||
|
85f0712d31 | ||
|
14ede75643 | ||
|
503d4c56cf | ||
|
e78c3001cf | ||
|
b0e81eec46 | ||
|
823420615e | ||
|
9cd3e4b7c1 | ||
|
024313deac | ||
|
bbd569cc88 | ||
|
e3bca1592c | ||
|
7017c6bbf5 | ||
|
230a119cd0 | ||
|
1ad4e57332 | ||
|
a806af6f2f | ||
|
582d30916e | ||
|
06a07a49cd | ||
|
a003d54fdd | ||
|
7fc2d32ad3 | ||
|
a45a43f6d4 | ||
|
b7077565b9 | ||
|
00e3c80b44 | ||
|
7a7ca6a436 | ||
|
cc1cdc35ec | ||
|
7b8fa200f2 | ||
|
52cc1ecd7b | ||
|
a4fcff902c | ||
|
0912ad484a | ||
|
b05cbc1fdf | ||
|
52e7929b08 | ||
|
c15d536e9b | ||
|
e24e1669f7 | ||
|
cbf9f39ea6 | ||
|
65ce238bfb | ||
|
10828bc6b8 | ||
|
aa40231ac7 | ||
|
89cf6016e8 | ||
|
988510112d | ||
|
2c47c309dc | ||
|
f755c291d5 | ||
|
94a2839bca | ||
|
0eacda0cf9 | ||
|
6bfe18bb21 | ||
|
82092075bf | ||
|
1b07d0a5c6 | ||
|
e5c66a2755 | ||
|
0947467bfa | ||
|
4d809b85b3 | ||
|
2ddf583fbc | ||
|
bb2c85d973 | ||
|
b0c9806f3b | ||
|
96933ed516 | ||
|
985f0e777c | ||
|
2049cc89b3 | ||
|
18695888dd | ||
|
b8293d07f2 | ||
|
a4fc10d466 | ||
|
0a40ef5ceb | ||
|
0f36ccdc57 | ||
|
92591c8a2e | ||
|
0f728c1c15 | ||
|
b5af39dfc9 | ||
|
4924cc7779 | ||
|
7d665fe577 | ||
|
5c18f4a19d | ||
|
58afea135c | ||
|
9018ec9757 | ||
|
aa734b25e4 | ||
|
67e48d34f4 | ||
|
0237c9721f | ||
|
419822ef06 | ||
|
d4ee215ae6 | ||
|
259696a117 | ||
|
4a8065d33b | ||
|
43538d3b32 | ||
|
c363ea4714 | ||
|
fd43a9b88b | ||
|
8a17965cd2 | ||
|
a4e94610e6 | ||
|
92d6a15ee5 | ||
|
3bddeeda3e | ||
|
f05b3d1730 | ||
|
a2f8a8ec91 | ||
|
746350b846 | ||
|
97f3450621 | ||
|
2be3e276f9 | ||
|
88fa5ad37c | ||
|
ef1719f6fc | ||
|
39c7bf3983 | ||
|
a4f746c00c | ||
|
106fb612dc | ||
|
53113e981d | ||
|
0018ba676b | ||
|
5cb427f774 | ||
|
4ea458b140 | ||
|
1807189183 | ||
|
683f9d33fe | ||
|
33b5f4fdf0 |
@@ -1,6 +1,9 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020,Gregor Baues, Chris Harlow. All rights reserved.
|
* © 2022 Harald Barth
|
||||||
*
|
* © 2020-2021 Chris Harlow
|
||||||
|
* © 2020 Gregor Baues
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
* This is free software: you can redistribute it and/or modify
|
* This is free software: you can redistribute it and/or modify
|
||||||
@@ -16,16 +19,119 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "CommandDistributor.h"
|
#include "CommandDistributor.h"
|
||||||
|
#include "SerialManager.h"
|
||||||
#include "WiThrottle.h"
|
#include "WiThrottle.h"
|
||||||
|
#include "DIAG.h"
|
||||||
|
#include "defines.h"
|
||||||
|
#include "DCCWaveform.h"
|
||||||
|
#include "DCC.h"
|
||||||
|
|
||||||
DCCEXParser * CommandDistributor::parser=0;
|
#if defined(BIG_MEMORY) | defined(WIFI_ON) | defined(ETHERNET_ON)
|
||||||
|
// This section of CommandDistributor is simply not relevant on a uno or similar
|
||||||
|
const byte NO_CLIENT=255;
|
||||||
|
|
||||||
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * streamer) {
|
RingStream * CommandDistributor::ring=0;
|
||||||
if (buffer[0] == '<') {
|
byte CommandDistributor::ringClient=NO_CLIENT;
|
||||||
if (!parser) parser = new DCCEXParser();
|
CommandDistributor::clientType CommandDistributor::clients[8]={
|
||||||
parser->parse(streamer, buffer, streamer);
|
NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE};
|
||||||
|
RingStream * CommandDistributor::broadcastBufferWriter=new RingStream(100);
|
||||||
|
|
||||||
|
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream) {
|
||||||
|
ring=stream;
|
||||||
|
ringClient=stream->peekTargetMark();
|
||||||
|
if (buffer[0] == '<') {
|
||||||
|
clients[clientId]=COMMAND_TYPE;
|
||||||
|
DCCEXParser::parse(stream, buffer, ring);
|
||||||
|
} else {
|
||||||
|
clients[clientId]=WITHROTTLE_TYPE;
|
||||||
|
WiThrottle::getThrottle(clientId)->parse(ring, buffer);
|
||||||
}
|
}
|
||||||
else WiThrottle::getThrottle(clientId)->parse(streamer, buffer);
|
ringClient=NO_CLIENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandDistributor::forget(byte clientId) {
|
||||||
|
clients[clientId]=NONE_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CommandDistributor::broadcast(bool includeWithrottleClients) {
|
||||||
|
broadcastBufferWriter->write((byte)'\0');
|
||||||
|
|
||||||
|
/* Boadcast to Serials */
|
||||||
|
SerialManager::broadcast(broadcastBufferWriter);
|
||||||
|
|
||||||
|
#if defined(WIFI_ON) | defined(ETHERNET_ON)
|
||||||
|
// If we are broadcasting from a wifi/eth process we need to complete its output
|
||||||
|
// before merging broadcasts in the ring, then reinstate it in case
|
||||||
|
// the process continues to output to its client.
|
||||||
|
if (ringClient!=NO_CLIENT) ring->commit();
|
||||||
|
|
||||||
|
/* loop through ring clients */
|
||||||
|
for (byte clientId=0; clientId<sizeof(clients); clientId++) {
|
||||||
|
if (clients[clientId]==NONE_TYPE) continue;
|
||||||
|
if ( clients[clientId]==WITHROTTLE_TYPE && !includeWithrottleClients) continue;
|
||||||
|
ring->mark(clientId);
|
||||||
|
broadcastBufferWriter->printBuffer(ring);
|
||||||
|
ring->commit();
|
||||||
|
}
|
||||||
|
if (ringClient!=NO_CLIENT) ring->mark(ringClient);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
broadcastBufferWriter->flush();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// For a UNO/NANO we can broadcast direct to just one Serial instead of the ring
|
||||||
|
// Redirect ring output ditrect to Serial
|
||||||
|
#define broadcastBufferWriter &Serial
|
||||||
|
// and ignore the internal broadcast call.
|
||||||
|
void CommandDistributor::broadcast(bool includeWithrottleClients) {
|
||||||
|
(void)includeWithrottleClients;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void CommandDistributor::broadcastSensor(int16_t id, bool on ) {
|
||||||
|
StringFormatter::send(broadcastBufferWriter,F("<%c %d>\n"), on?'Q':'q', id);
|
||||||
|
broadcast(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) {
|
||||||
|
// For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed;
|
||||||
|
// The string below contains serial and Withrottle protocols which should
|
||||||
|
// be safe for both types.
|
||||||
|
StringFormatter::send(broadcastBufferWriter,F("<H %d %d>\n"),id, !isClosed);
|
||||||
|
#if defined(WIFI_ON) | defined(ETHERNET_ON)
|
||||||
|
StringFormatter::send(broadcastBufferWriter,F("PTA%c%d\n"), isClosed?'2':'4', id);
|
||||||
|
#endif
|
||||||
|
broadcast(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandDistributor::broadcastLoco(byte slot) {
|
||||||
|
DCC::LOCO * sp=&DCC::speedTable[slot];
|
||||||
|
StringFormatter::send(broadcastBufferWriter,F("<l %d %d %d %l>\n"),
|
||||||
|
sp->loco,slot,sp->speedCode,sp->functions);
|
||||||
|
broadcast(false);
|
||||||
|
#if defined(WIFI_ON) | defined(ETHERNET_ON)
|
||||||
|
WiThrottle::markForBroadcast(sp->loco);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandDistributor::broadcastPower() {
|
||||||
|
bool main=DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON;
|
||||||
|
bool prog=DCCWaveform::progTrack.getPowerMode()==POWERMODE::ON;
|
||||||
|
bool join=DCCWaveform::progTrackSyncMain;
|
||||||
|
const FSH * reason=F("");
|
||||||
|
char state='1';
|
||||||
|
if (main && prog && join) reason=F(" JOIN");
|
||||||
|
else if (main && prog);
|
||||||
|
else if (main) reason=F(" MAIN");
|
||||||
|
else if (prog) reason=F(" PROG");
|
||||||
|
else state='0';
|
||||||
|
|
||||||
|
StringFormatter::send(broadcastBufferWriter,
|
||||||
|
F("<p%c%S>\nPPA%c\n"),state,reason, main?'1':'0');
|
||||||
|
LCD(2,F("Power %S%S"),state=='1'?F("On"):F("Off"),reason);
|
||||||
|
broadcast(true);
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020,Gregor Baues, Chris Harlow. All rights reserved.
|
* © 2022 Harald Barth
|
||||||
*
|
* © 2020-2021 Chris Harlow
|
||||||
|
* © 2020 Gregor Baues
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
* This is free software: you can redistribute it and/or modify
|
* This is free software: you can redistribute it and/or modify
|
||||||
@@ -24,9 +27,21 @@
|
|||||||
class CommandDistributor {
|
class CommandDistributor {
|
||||||
|
|
||||||
public :
|
public :
|
||||||
static void parse(byte clientId,byte* buffer, RingStream * streamer);
|
static void parse(byte clientId,byte* buffer, RingStream * ring);
|
||||||
|
static void broadcastLoco(byte slot);
|
||||||
|
static void broadcastSensor(int16_t id, bool value);
|
||||||
|
static void broadcastTurnout(int16_t id, bool isClosed);
|
||||||
|
static void broadcastPower();
|
||||||
|
static void forget(byte clientId);
|
||||||
private:
|
private:
|
||||||
static DCCEXParser * parser;
|
static void broadcast(bool includeWithrottleClients);
|
||||||
|
static RingStream * ring;
|
||||||
|
static RingStream * broadcastBufferWriter;
|
||||||
|
static byte ringClient;
|
||||||
|
|
||||||
|
// each bit in broadcastlist = 1<<clientid
|
||||||
|
enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE};
|
||||||
|
static clientType clients[8];
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -1,33 +1,35 @@
|
|||||||
////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////
|
||||||
// DCC-EX CommandStation-EX Please see https://DCC-EX.com
|
// DCC-EX CommandStation-EX Please see https://DCC-EX.com
|
||||||
//
|
//
|
||||||
// This file is the main sketch for the Command Station.
|
// This file is the main sketch for the Command Station.
|
||||||
//
|
//
|
||||||
// CONFIGURATION:
|
// CONFIGURATION:
|
||||||
// Configuration is normally performed by editing a file called config.h.
|
// Configuration is normally performed by editing a file called config.h.
|
||||||
// This file is NOT shipped with the code so that if you pull a later version
|
// This file is NOT shipped with the code so that if you pull a later version
|
||||||
// of the code, your configuration will not be overwritten.
|
// of the code, your configuration will not be overwritten.
|
||||||
//
|
//
|
||||||
// If you used the automatic installer program, config.h will have been created automatically.
|
// If you used the automatic installer program, config.h will have been created automatically.
|
||||||
//
|
//
|
||||||
// To obtain a starting copy of config.h please copy the file config.example.h which is
|
// To obtain a starting copy of config.h please copy the file config.example.h which is
|
||||||
// shipped with the code and may be updated as new features are added.
|
// shipped with the code and may be updated as new features are added.
|
||||||
//
|
//
|
||||||
// If config.h is not found, config.example.h will be used with all defaults.
|
// If config.h is not found, config.example.h will be used with all defaults.
|
||||||
////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#if __has_include ( "config.h")
|
#if __has_include ( "config.h")
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#else
|
#else
|
||||||
#warning config.h not found. Using defaults from config.example.h
|
#warning config.h not found. Using defaults from config.example.h
|
||||||
#include "config.example.h"
|
#include "config.example.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* © 2020,2021 Chris Harlow, Harald Barth, David Cutting,
|
* © 2021 Neil McKechnie
|
||||||
* Fred Decker, Gregor Baues, Anthony W - Dayton All rights reserved.
|
* © 2020-2021 Chris Harlow, Harald Barth, David Cutting,
|
||||||
*
|
* Fred Decker, Gregor Baues, Anthony W - Dayton
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
* This is free software: you can redistribute it and/or modify
|
* This is free software: you can redistribute it and/or modify
|
||||||
@@ -45,11 +47,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "DCCEX.h"
|
#include "DCCEX.h"
|
||||||
|
#ifdef WIFI_WARNING
|
||||||
// Create a serial command parser for the USB connection,
|
#warning You have defined that you want WiFi but your hardware has not enough memory to do that, so WiFi DISABLED
|
||||||
// This supports JMRI or manual diagnostics and commands
|
#endif
|
||||||
// to be issued from the USB serial console.
|
#ifdef ETHERNET_WARNING
|
||||||
DCCEXParser serialParser;
|
#warning You have defined that you want Ethernet but your hardware has not enough memory to do that, so Ethernet DISABLED
|
||||||
|
#endif
|
||||||
|
#ifdef EXRAIL_WARNING
|
||||||
|
#warning You have myAutomation.h but your hardware has not enough memory to do that, so EX-RAIL DISABLED
|
||||||
|
#endif
|
||||||
|
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
@@ -57,54 +63,53 @@ void setup()
|
|||||||
|
|
||||||
// Responsibility 1: Start the usb connection for diagnostics
|
// Responsibility 1: Start the usb connection for diagnostics
|
||||||
// This is normally Serial but uses SerialUSB on a SAMD processor
|
// This is normally Serial but uses SerialUSB on a SAMD processor
|
||||||
Serial.begin(115200);
|
SerialManager::init();
|
||||||
#ifdef ESP_DEBUG
|
|
||||||
Serial.setDebugOutput(true);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com"));
|
DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com"));
|
||||||
|
|
||||||
CONDITIONAL_LCD_START {
|
CONDITIONAL_LCD_START {
|
||||||
// This block is still executed for DIAGS if LCD not in use
|
// This block is still executed for DIAGS if LCD not in use
|
||||||
LCD(0,F("DCC++ EX v%S"),F(VERSION));
|
LCD(0,F("DCC++ EX v%S"),F(VERSION));
|
||||||
LCD(1,F("Lic GPLv3"));
|
LCD(1,F("Lic GPLv3"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Responsibility 2: Start all the communications before the DCC engine
|
// Responsibility 2: Start all the communications before the DCC engine
|
||||||
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi
|
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi
|
||||||
// Start Ethernet if it exists
|
// Start Ethernet if it exists
|
||||||
#if WIFI_ON
|
#if WIFI_ON
|
||||||
#ifndef ESP_FAMILY
|
|
||||||
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL);
|
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL);
|
||||||
#else
|
|
||||||
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL);
|
|
||||||
#endif
|
|
||||||
#endif // WIFI_ON
|
#endif // WIFI_ON
|
||||||
|
|
||||||
#if ETHERNET_ON
|
#if ETHERNET_ON
|
||||||
EthernetInterface::setup();
|
EthernetInterface::setup();
|
||||||
#endif // ETHERNET_ON
|
#endif // ETHERNET_ON
|
||||||
|
|
||||||
// Responsibility 3: Start the DCC engine.
|
// Responsibility 3: Start the DCC engine.
|
||||||
DCC::begin();
|
// Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s)
|
||||||
|
// Standard supported devices have pre-configured macros but custome hardware installations require
|
||||||
|
// detailed pin mappings and may also require modified subclasses of the MotorDriver to implement specialist logic.
|
||||||
|
// STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h
|
||||||
|
DCC::begin(MOTOR_SHIELD_TYPE);
|
||||||
|
|
||||||
// Start RMFT (ignored if no automnation)
|
// Start RMFT (ignored if no automnation)
|
||||||
RMFT::begin();
|
RMFT::begin();
|
||||||
|
|
||||||
|
|
||||||
// Invoke any DCC++EX commands in the form "SETUP("xxxx");"" found in optional file mySetup.h.
|
|
||||||
|
// Invoke any DCC++EX commands in the form "SETUP("xxxx");"" found in optional file mySetup.h.
|
||||||
// This can be used to create turnouts, outputs, sensors etc. through the normal text commands.
|
// This can be used to create turnouts, outputs, sensors etc. through the normal text commands.
|
||||||
#if __has_include ( "mySetup.h")
|
#if __has_include ( "mySetup.h")
|
||||||
#define SETUP(cmd) serialParser.parse(F(cmd))
|
#define SETUP(cmd) DCCEXParser::parse(F(cmd))
|
||||||
#include "mySetup.h"
|
#include "mySetup.h"
|
||||||
#undef SETUP
|
#undef SETUP
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(LCN_SERIAL)
|
|
||||||
LCN_SERIAL.begin(115200);
|
|
||||||
LCN::init(LCN_SERIAL);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
LCD(3,F("Ready"));
|
#if defined(LCN_SERIAL)
|
||||||
|
LCN_SERIAL.begin(115200);
|
||||||
|
LCN::init(LCN_SERIAL);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LCD(3,F("Ready"));
|
||||||
|
CommandDistributor::broadcastPower();
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop()
|
void loop()
|
||||||
@@ -114,41 +119,36 @@ void loop()
|
|||||||
// Responsibility 1: Handle DCC background processes
|
// Responsibility 1: Handle DCC background processes
|
||||||
// (loco reminders and power checks)
|
// (loco reminders and power checks)
|
||||||
DCC::loop();
|
DCC::loop();
|
||||||
// Responsibility 2: handle any incoming commands on USB connection
|
|
||||||
serialParser.loop(Serial);
|
|
||||||
|
|
||||||
// Responsibility 3: Optionally handle any incoming WiFi traffic
|
// Responsibility 2: handle any incoming commands on USB connection
|
||||||
|
SerialManager::loop();
|
||||||
|
|
||||||
|
// Responsibility 3: Optionally handle any incoming WiFi traffic
|
||||||
#if WIFI_ON
|
#if WIFI_ON
|
||||||
#ifndef ESP_FAMILY
|
|
||||||
WifiInterface::loop();
|
WifiInterface::loop();
|
||||||
#endif
|
#endif
|
||||||
#if defined(ARDUINO_ARCH_ESP8266) // on ESP32 own task
|
|
||||||
WifiESP::loop();
|
|
||||||
#endif
|
|
||||||
#endif //WIFI_ON
|
|
||||||
#if ETHERNET_ON
|
#if ETHERNET_ON
|
||||||
EthernetInterface::loop();
|
EthernetInterface::loop();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
RMFT::loop(); // ignored if no automation
|
RMFT::loop(); // ignored if no automation
|
||||||
|
|
||||||
#if defined(LCN_SERIAL)
|
#if defined(LCN_SERIAL)
|
||||||
LCN::loop();
|
LCN::loop();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
LCDDisplay::loop(); // ignored if LCD not in use
|
LCDDisplay::loop(); // ignored if LCD not in use
|
||||||
|
|
||||||
// Handle/update IO devices.
|
// Handle/update IO devices.
|
||||||
IODevice::loop();
|
IODevice::loop();
|
||||||
|
|
||||||
|
Sensor::checkAll(); // Update and print changes
|
||||||
|
|
||||||
// Report any decrease in memory (will automatically trigger on first call)
|
// Report any decrease in memory (will automatically trigger on first call)
|
||||||
static int ramLowWatermark = __INT_MAX__; // replaced on first loop
|
static int ramLowWatermark = __INT_MAX__; // replaced on first loop
|
||||||
#ifdef ESP_FAMILY
|
|
||||||
updateMinimumFreeMemory(128);
|
|
||||||
#endif
|
|
||||||
int freeNow = minimumFreeMemory();
|
int freeNow = minimumFreeMemory();
|
||||||
if (freeNow < ramLowWatermark)
|
if (freeNow < ramLowWatermark) {
|
||||||
{
|
|
||||||
ramLowWatermark = freeNow;
|
ramLowWatermark = freeNow;
|
||||||
LCD(3,F("Free RAM=%5db"), ramLowWatermark);
|
LCD(3,F("Free RAM=%5db"), ramLowWatermark);
|
||||||
}
|
}
|
||||||
|
447
DCC.cpp
447
DCC.cpp
@@ -1,7 +1,13 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2021 Neil McKechnie
|
||||||
* © 2020, Harald Barth
|
* © 2021 Mike S
|
||||||
*
|
* © 2021 Fred Decker
|
||||||
|
* © 2021 Herb Morton
|
||||||
|
* © 2020-2022 Harald Barth
|
||||||
|
* © 2020-2021 M Steve Todd
|
||||||
|
* © 2020-2021 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
* This file is part of Asbelos DCC API
|
* This file is part of Asbelos DCC API
|
||||||
*
|
*
|
||||||
* This is free software: you can redistribute it and/or modify
|
* This is free software: you can redistribute it and/or modify
|
||||||
@@ -20,14 +26,15 @@
|
|||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
#include "DCC.h"
|
#include "DCC.h"
|
||||||
#include "DCCWaveform.h"
|
#include "DCCWaveform.h"
|
||||||
#include "DCCTrack.h"
|
#ifndef DISABLE_EEPROM
|
||||||
#include "EEStore.h"
|
#include "EEStore.h"
|
||||||
|
#endif
|
||||||
#include "GITHUB_SHA.h"
|
#include "GITHUB_SHA.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "FSH.h"
|
#include "FSH.h"
|
||||||
#include "IODevice.h"
|
#include "IODevice.h"
|
||||||
|
#include "RMFT2.h"
|
||||||
#include "MotorDriver.h"
|
#include "CommandDistributor.h"
|
||||||
|
|
||||||
// This module is responsible for converting API calls into
|
// This module is responsible for converting API calls into
|
||||||
// messages to be sent to the waveform generator.
|
// messages to be sent to the waveform generator.
|
||||||
@@ -42,39 +49,30 @@
|
|||||||
// Obtaining ACKs from the prog track using a function
|
// Obtaining ACKs from the prog track using a function
|
||||||
// There are no volatiles here.
|
// There are no volatiles here.
|
||||||
|
|
||||||
const byte FN_GROUP_1=0x01;
|
const byte FN_GROUP_1=0x01;
|
||||||
const byte FN_GROUP_2=0x02;
|
const byte FN_GROUP_2=0x02;
|
||||||
const byte FN_GROUP_3=0x04;
|
const byte FN_GROUP_3=0x04;
|
||||||
const byte FN_GROUP_4=0x08;
|
const byte FN_GROUP_4=0x08;
|
||||||
const byte FN_GROUP_5=0x10;
|
const byte FN_GROUP_5=0x10;
|
||||||
|
|
||||||
FSH* DCC::shieldName=NULL;
|
FSH* DCC::shieldName=NULL;
|
||||||
byte DCC::joinRelay=UNUSED_PIN;
|
byte DCC::joinRelay=UNUSED_PIN;
|
||||||
byte DCC::globalSpeedsteps=128;
|
byte DCC::globalSpeedsteps=128;
|
||||||
|
|
||||||
void DCC::begin() {
|
void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver) {
|
||||||
StringFormatter::send(Serial,F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE),
|
shieldName=(FSH *)motorShieldName;
|
||||||
MotorDriverContainer::mDC.getMotorShieldName(), F(GITHUB_SHA));
|
StringFormatter::send(Serial,F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
|
||||||
|
|
||||||
// BE AWARE, USES I2C PINS, MAY LEAD TO PIN CONFLICTS
|
|
||||||
// Initialise HAL layer before reading EEprom.
|
// Initialise HAL layer before reading EEprom.
|
||||||
IODevice::begin();
|
IODevice::begin();
|
||||||
|
|
||||||
//example how to use add:
|
#ifndef DISABLE_EEPROM
|
||||||
//MotorDriverContainer::mDC.add(new MotorDriver(16, 21, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 2.00, 2000, UNUSED_PIN, RMT_MAIN));
|
|
||||||
// Load stuff from EEprom
|
// Load stuff from EEprom
|
||||||
(void)EEPROM; // tell compiler not to warn this is unused
|
(void)EEPROM; // tell compiler not to warn this is unused
|
||||||
EEStore::init();
|
EEStore::init();
|
||||||
|
#endif
|
||||||
|
|
||||||
MotorDriverContainer::mDC.diag();
|
DCCWaveform::begin(mainDriver,progDriver);
|
||||||
DCCWaveform::begin(MotorDriverContainer::mDC.mainTrack(),MotorDriverContainer::mDC.progTrack());
|
|
||||||
|
|
||||||
// Add main and prog drivers to the main and prog packet sources (dcc-tracks).
|
|
||||||
std::vector<MotorDriver*> v;
|
|
||||||
v = MotorDriverContainer::mDC.getDriverType(RMT_MAIN|TIMER_MAIN);
|
|
||||||
for (const auto& d: v) DCCTrack::mainTrack.addDriver(d);
|
|
||||||
v = MotorDriverContainer::mDC.getDriverType(RMT_PROG|TIMER_PROG);
|
|
||||||
for (const auto& d: v) DCCTrack::progTrack.addDriver(d);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::setJoinRelayPin(byte joinRelayPin) {
|
void DCC::setJoinRelayPin(byte joinRelayPin) {
|
||||||
@@ -86,7 +84,7 @@ void DCC::setJoinRelayPin(byte joinRelayPin) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) {
|
void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) {
|
||||||
byte speedCode = (tSpeed & 0x7F) + tDirection * 128;
|
byte speedCode = (tSpeed & 0x7F) + tDirection * 128;
|
||||||
setThrottle2(cab, speedCode);
|
setThrottle2(cab, speedCode);
|
||||||
// retain speed for loco reminders
|
// retain speed for loco reminders
|
||||||
updateLocoReminder(cab, speedCode );
|
updateLocoReminder(cab, speedCode );
|
||||||
@@ -97,8 +95,8 @@ void DCC::setThrottle2( uint16_t cab, byte speedCode) {
|
|||||||
uint8_t b[4];
|
uint8_t b[4];
|
||||||
uint8_t nB = 0;
|
uint8_t nB = 0;
|
||||||
// DIAG(F("setSpeedInternal %d %x"),cab,speedCode);
|
// DIAG(F("setSpeedInternal %d %x"),cab,speedCode);
|
||||||
|
|
||||||
if (cab > 127)
|
if (cab > HIGHEST_SHORT_ADDR)
|
||||||
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
|
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
|
||||||
b[nB++] = lowByte(cab);
|
b[nB++] = lowByte(cab);
|
||||||
|
|
||||||
@@ -130,7 +128,7 @@ void DCC::setThrottle2( uint16_t cab, byte speedCode) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DCCTrack::mainTrack.schedulePacket(b, nB, 0);
|
DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
|
void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
|
||||||
@@ -138,13 +136,13 @@ void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
|
|||||||
byte b[4];
|
byte b[4];
|
||||||
byte nB = 0;
|
byte nB = 0;
|
||||||
|
|
||||||
if (cab > 127)
|
if (cab > HIGHEST_SHORT_ADDR)
|
||||||
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
|
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
|
||||||
b[nB++] = lowByte(cab);
|
b[nB++] = lowByte(cab);
|
||||||
if (byte1!=0) b[nB++] = byte1;
|
if (byte1!=0) b[nB++] = byte1;
|
||||||
b[nB++] = byte2;
|
b[nB++] = byte2;
|
||||||
|
|
||||||
DCCTrack::mainTrack.schedulePacket(b, nB, 0);
|
DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t DCC::getThrottleSpeed(int cab) {
|
uint8_t DCC::getThrottleSpeed(int cab) {
|
||||||
@@ -162,86 +160,67 @@ bool DCC::getThrottleDirection(int cab) {
|
|||||||
// Set function to value on or off
|
// Set function to value on or off
|
||||||
void DCC::setFn( int cab, int16_t functionNumber, bool on) {
|
void DCC::setFn( int cab, int16_t functionNumber, bool on) {
|
||||||
if (cab<=0 ) return;
|
if (cab<=0 ) return;
|
||||||
|
|
||||||
if (functionNumber>28) {
|
if (functionNumber>28) {
|
||||||
//non reminding advanced binary bit set
|
//non reminding advanced binary bit set
|
||||||
byte b[5];
|
byte b[5];
|
||||||
byte nB = 0;
|
byte nB = 0;
|
||||||
if (cab > 127)
|
if (cab > HIGHEST_SHORT_ADDR)
|
||||||
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
|
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
|
||||||
b[nB++] = lowByte(cab);
|
b[nB++] = lowByte(cab);
|
||||||
if (functionNumber <= 127) {
|
if (functionNumber <= 127) {
|
||||||
b[nB++] = 0b11011101; // Binary State Control Instruction short form
|
b[nB++] = 0b11011101; // Binary State Control Instruction short form
|
||||||
b[nB++] = functionNumber | (on ? 0x80 : 0);
|
b[nB++] = functionNumber | (on ? 0x80 : 0);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
b[nB++] = 0b11000000; // Binary State Control Instruction long form
|
b[nB++] = 0b11000000; // Binary State Control Instruction long form
|
||||||
b[nB++] = (functionNumber & 0x7F) | (on ? 0x80 : 0); // low order bits and state flag
|
b[nB++] = (functionNumber & 0x7F) | (on ? 0x80 : 0); // low order bits and state flag
|
||||||
b[nB++] = functionNumber >>7 ; // high order bits
|
b[nB++] = functionNumber >>7 ; // high order bits
|
||||||
}
|
}
|
||||||
DCCTrack::mainTrack.schedulePacket(b, nB, 3);
|
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int reg = lookupSpeedTable(cab);
|
int reg = lookupSpeedTable(cab);
|
||||||
if (reg<0) return;
|
if (reg<0) return;
|
||||||
|
|
||||||
// Take care of functions:
|
// Take care of functions:
|
||||||
// Set state of function
|
// Set state of function
|
||||||
|
unsigned long previous=speedTable[reg].functions;
|
||||||
unsigned long funcmask = (1UL<<functionNumber);
|
unsigned long funcmask = (1UL<<functionNumber);
|
||||||
if (on) {
|
if (on) {
|
||||||
speedTable[reg].functions |= funcmask;
|
speedTable[reg].functions |= funcmask;
|
||||||
} else {
|
} else {
|
||||||
speedTable[reg].functions &= ~funcmask;
|
speedTable[reg].functions &= ~funcmask;
|
||||||
}
|
}
|
||||||
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
if (speedTable[reg].functions != previous) {
|
||||||
return;
|
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
||||||
|
CommandDistributor::broadcastLoco(reg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change function according to how button was pressed,
|
// Flip function state
|
||||||
// typically in WiThrottle.
|
void DCC::changeFn( int cab, int16_t functionNumber) {
|
||||||
// Returns new state or -1 if nothing was changed.
|
if (cab<=0 || functionNumber>28) return;
|
||||||
int DCC::changeFn( int cab, int16_t functionNumber, bool pressed) {
|
|
||||||
int funcstate = -1;
|
|
||||||
if (cab<=0 || functionNumber>28) return funcstate;
|
|
||||||
int reg = lookupSpeedTable(cab);
|
int reg = lookupSpeedTable(cab);
|
||||||
if (reg<0) return funcstate;
|
if (reg<0) return;
|
||||||
|
|
||||||
// Take care of functions:
|
|
||||||
// Imitate how many command stations do it: Button press is
|
|
||||||
// toggle but for F2 where it is momentary
|
|
||||||
unsigned long funcmask = (1UL<<functionNumber);
|
unsigned long funcmask = (1UL<<functionNumber);
|
||||||
if (functionNumber == 2) {
|
speedTable[reg].functions ^= funcmask;
|
||||||
// turn on F2 on press and off again at release of button
|
|
||||||
if (pressed) {
|
|
||||||
speedTable[reg].functions |= funcmask;
|
|
||||||
funcstate = 1;
|
|
||||||
} else {
|
|
||||||
speedTable[reg].functions &= ~funcmask;
|
|
||||||
funcstate = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// toggle function on press, ignore release
|
|
||||||
if (pressed) {
|
|
||||||
speedTable[reg].functions ^= funcmask;
|
|
||||||
}
|
|
||||||
funcstate = (speedTable[reg].functions & funcmask)? 1 : 0;
|
|
||||||
}
|
|
||||||
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
||||||
return funcstate;
|
CommandDistributor::broadcastLoco(reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
int DCC::getFn( int cab, int16_t functionNumber) {
|
int DCC::getFn( int cab, int16_t functionNumber) {
|
||||||
if (cab<=0 || functionNumber>28) return -1; // unknown
|
if (cab<=0 || functionNumber>28) return -1; // unknown
|
||||||
int reg = lookupSpeedTable(cab);
|
int reg = lookupSpeedTable(cab);
|
||||||
if (reg<0) return -1;
|
if (reg<0) return -1;
|
||||||
|
|
||||||
unsigned long funcmask = (1UL<<functionNumber);
|
unsigned long funcmask = (1UL<<functionNumber);
|
||||||
return (speedTable[reg].functions & funcmask)? 1 : 0;
|
return (speedTable[reg].functions & funcmask)? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the group flag to say we have touched the particular group.
|
// Set the group flag to say we have touched the particular group.
|
||||||
// A group will be reminded only if it has been touched.
|
// A group will be reminded only if it has been touched.
|
||||||
void DCC::updateGroupflags(byte & flags, int16_t functionNumber) {
|
void DCC::updateGroupflags(byte & flags, int16_t functionNumber) {
|
||||||
byte groupMask;
|
byte groupMask;
|
||||||
if (functionNumber<=4) groupMask=FN_GROUP_1;
|
if (functionNumber<=4) groupMask=FN_GROUP_1;
|
||||||
@@ -249,7 +228,13 @@ void DCC::updateGroupflags(byte & flags, int16_t functionNumber) {
|
|||||||
else if (functionNumber<=12) groupMask=FN_GROUP_3;
|
else if (functionNumber<=12) groupMask=FN_GROUP_3;
|
||||||
else if (functionNumber<=20) groupMask=FN_GROUP_4;
|
else if (functionNumber<=20) groupMask=FN_GROUP_4;
|
||||||
else groupMask=FN_GROUP_5;
|
else groupMask=FN_GROUP_5;
|
||||||
flags |= groupMask;
|
flags |= groupMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t DCC::getFunctionMap(int cab) {
|
||||||
|
if (cab<=0) return 0; // unknown pretend all functions off
|
||||||
|
int reg = lookupSpeedTable(cab);
|
||||||
|
return (reg<0)?0:speedTable[reg].functions;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::setAccessory(int address, byte number, bool activate) {
|
void DCC::setAccessory(int address, byte number, bool activate) {
|
||||||
@@ -266,7 +251,10 @@ void DCC::setAccessory(int address, byte number, bool activate) {
|
|||||||
b[0] = address % 64 + 128; // first byte is of the form 10AAAAAA, where AAAAAA represent 6 least signifcant bits of accessory address
|
b[0] = address % 64 + 128; // first byte is of the form 10AAAAAA, where AAAAAA represent 6 least signifcant bits of accessory address
|
||||||
b[1] = ((((address / 64) % 8) << 4) + (number % 4 << 1) + activate % 2) ^ 0xF8; // second byte is of the form 1AAACDDD, where C should be 1, and the least significant D represent activate/deactivate
|
b[1] = ((((address / 64) % 8) << 4) + (number % 4 << 1) + activate % 2) ^ 0xF8; // second byte is of the form 1AAACDDD, where C should be 1, and the least significant D represent activate/deactivate
|
||||||
|
|
||||||
DCCTrack::mainTrack.schedulePacket(b, 2, 3); // Repeat the packet four times (3 2 1 0)
|
DCCWaveform::mainTrack.schedulePacket(b, 2, 4); // Repeat the packet four times
|
||||||
|
#if defined(RMFT_ACTIVE)
|
||||||
|
RMFT2::activateEvent(address<<2|number,activate);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -276,7 +264,7 @@ void DCC::setAccessory(int address, byte number, bool activate) {
|
|||||||
void DCC::writeCVByteMain(int cab, int cv, byte bValue) {
|
void DCC::writeCVByteMain(int cab, int cv, byte bValue) {
|
||||||
byte b[5];
|
byte b[5];
|
||||||
byte nB = 0;
|
byte nB = 0;
|
||||||
if (cab > 127)
|
if (cab > HIGHEST_SHORT_ADDR)
|
||||||
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
|
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
|
||||||
|
|
||||||
b[nB++] = lowByte(cab);
|
b[nB++] = lowByte(cab);
|
||||||
@@ -284,7 +272,7 @@ void DCC::writeCVByteMain(int cab, int cv, byte bValue) {
|
|||||||
b[nB++] = cv2(cv);
|
b[nB++] = cv2(cv);
|
||||||
b[nB++] = bValue;
|
b[nB++] = bValue;
|
||||||
|
|
||||||
DCCTrack::mainTrack.schedulePacket(b, nB, 3);
|
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -297,7 +285,7 @@ void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) {
|
|||||||
bValue = bValue % 2;
|
bValue = bValue % 2;
|
||||||
bNum = bNum % 8;
|
bNum = bNum % 8;
|
||||||
|
|
||||||
if (cab > 127)
|
if (cab > HIGHEST_SHORT_ADDR)
|
||||||
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
|
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
|
||||||
|
|
||||||
b[nB++] = lowByte(cab);
|
b[nB++] = lowByte(cab);
|
||||||
@@ -305,7 +293,7 @@ void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) {
|
|||||||
b[nB++] = cv2(cv);
|
b[nB++] = cv2(cv);
|
||||||
b[nB++] = WRITE_BIT | (bValue ? BIT_ON : BIT_OFF) | bNum;
|
b[nB++] = WRITE_BIT | (bValue ? BIT_ON : BIT_OFF) | bNum;
|
||||||
|
|
||||||
DCCTrack::mainTrack.schedulePacket(b, nB, 3);
|
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::setProgTrackSyncMain(bool on) {
|
void DCC::setProgTrackSyncMain(bool on) {
|
||||||
@@ -319,64 +307,64 @@ void DCC::setProgTrackBoost(bool on) {
|
|||||||
FSH* DCC::getMotorShieldName() {
|
FSH* DCC::getMotorShieldName() {
|
||||||
return shieldName;
|
return shieldName;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ackOp FLASH WRITE_BIT0_PROG[] = {
|
const ackOp FLASH WRITE_BIT0_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
W0,WACK,
|
W0,WACK,
|
||||||
V0, WACK, // validate bit is 0
|
V0, WACK, // validate bit is 0
|
||||||
ITC1, // if acked, callback(1)
|
ITC1, // if acked, callback(1)
|
||||||
CALLFAIL // callback (-1)
|
FAIL // callback (-1)
|
||||||
};
|
};
|
||||||
const ackOp FLASH WRITE_BIT1_PROG[] = {
|
const ackOp FLASH WRITE_BIT1_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
W1,WACK,
|
W1,WACK,
|
||||||
V1, WACK, // validate bit is 1
|
V1, WACK, // validate bit is 1
|
||||||
ITC1, // if acked, callback(1)
|
ITC1, // if acked, callback(1)
|
||||||
CALLFAIL // callback (-1)
|
FAIL // callback (-1)
|
||||||
};
|
};
|
||||||
|
|
||||||
const ackOp FLASH VERIFY_BIT0_PROG[] = {
|
const ackOp FLASH VERIFY_BIT0_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
V0, WACK, // validate bit is 0
|
V0, WACK, // validate bit is 0
|
||||||
ITC0, // if acked, callback(0)
|
ITC0, // if acked, callback(0)
|
||||||
V1, WACK, // validate bit is 1
|
V1, WACK, // validate bit is 1
|
||||||
ITC1,
|
ITC1,
|
||||||
CALLFAIL // callback (-1)
|
FAIL // callback (-1)
|
||||||
};
|
};
|
||||||
const ackOp FLASH VERIFY_BIT1_PROG[] = {
|
const ackOp FLASH VERIFY_BIT1_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
V1, WACK, // validate bit is 1
|
V1, WACK, // validate bit is 1
|
||||||
ITC1, // if acked, callback(1)
|
ITC1, // if acked, callback(1)
|
||||||
V0, WACK,
|
V0, WACK,
|
||||||
ITC0,
|
ITC0,
|
||||||
CALLFAIL // callback (-1)
|
FAIL // callback (-1)
|
||||||
};
|
};
|
||||||
|
|
||||||
const ackOp FLASH READ_BIT_PROG[] = {
|
const ackOp FLASH READ_BIT_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
V1, WACK, // validate bit is 1
|
V1, WACK, // validate bit is 1
|
||||||
ITC1, // if acked, callback(1)
|
ITC1, // if acked, callback(1)
|
||||||
V0, WACK, // validate bit is zero
|
V0, WACK, // validate bit is zero
|
||||||
ITC0, // if acked callback 0
|
ITC0, // if acked callback 0
|
||||||
CALLFAIL // bit not readable
|
FAIL // bit not readable
|
||||||
};
|
};
|
||||||
|
|
||||||
const ackOp FLASH WRITE_BYTE_PROG[] = {
|
const ackOp FLASH WRITE_BYTE_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
WB,WACK,ITC1, // Write and callback(1) if ACK
|
WB,WACK,ITC1, // Write and callback(1) if ACK
|
||||||
// handle decoders that dont ack a write
|
// handle decoders that dont ack a write
|
||||||
VB,WACK,ITC1, // validate byte and callback(1) if correct
|
VB,WACK,ITC1, // validate byte and callback(1) if correct
|
||||||
CALLFAIL // callback (-1)
|
FAIL // callback (-1)
|
||||||
};
|
};
|
||||||
|
|
||||||
const ackOp FLASH VERIFY_BYTE_PROG[] = {
|
const ackOp FLASH VERIFY_BYTE_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
BIV, // ackManagerByte initial value
|
BIV, // ackManagerByte initial value
|
||||||
VB,WACK, // validate byte
|
VB,WACK, // validate byte
|
||||||
ITCB, // if ok callback value
|
ITCB, // if ok callback value
|
||||||
STARTMERGE, //clear bit and byte values ready for merge pass
|
STARTMERGE, //clear bit and byte values ready for merge pass
|
||||||
// each bit is validated against 0 and the result inverted in MERGE
|
// each bit is validated against 0 and the result inverted in MERGE
|
||||||
// this is because there tend to be more zeros in cv values than ones.
|
// this is because there tend to be more zeros in cv values than ones.
|
||||||
// There is no need for one validation as entire byte is validated at the end
|
// There is no need for one validation as entire byte is validated at the end
|
||||||
V0, WACK, MERGE, // read and merge first tested bit (7)
|
V0, WACK, MERGE, // read and merge first tested bit (7)
|
||||||
ITSKIP, // do small excursion if there was no ack
|
ITSKIP, // do small excursion if there was no ack
|
||||||
@@ -392,14 +380,14 @@ const ackOp FLASH VERIFY_BYTE_PROG[] = {
|
|||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
VB, WACK, ITCBV, // verify merged byte and return it if acked ok - with retry report
|
VB, WACK, ITCBV, // verify merged byte and return it if acked ok - with retry report
|
||||||
CALLFAIL };
|
FAIL };
|
||||||
|
|
||||||
|
|
||||||
const ackOp FLASH READ_CV_PROG[] = {
|
const ackOp FLASH READ_CV_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
STARTMERGE, //clear bit and byte values ready for merge pass
|
STARTMERGE, //clear bit and byte values ready for merge pass
|
||||||
// each bit is validated against 0 and the result inverted in MERGE
|
// each bit is validated against 0 and the result inverted in MERGE
|
||||||
// this is because there tend to be more zeros in cv values than ones.
|
// this is because there tend to be more zeros in cv values than ones.
|
||||||
// There is no need for one validation as entire byte is validated at the end
|
// There is no need for one validation as entire byte is validated at the end
|
||||||
V0, WACK, MERGE, // read and merge first tested bit (7)
|
V0, WACK, MERGE, // read and merge first tested bit (7)
|
||||||
ITSKIP, // do small excursion if there was no ack
|
ITSKIP, // do small excursion if there was no ack
|
||||||
@@ -414,20 +402,20 @@ const ackOp FLASH READ_CV_PROG[] = {
|
|||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
VB, WACK, ITCB, // verify merged byte and return it if acked ok
|
VB, WACK, ITCB, // verify merged byte and return it if acked ok
|
||||||
CALLFAIL }; // verification failed
|
FAIL }; // verification failed
|
||||||
|
|
||||||
|
|
||||||
const ackOp FLASH LOCO_ID_PROG[] = {
|
const ackOp FLASH LOCO_ID_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
SETCV, (ackOp)19, // CV 19 is consist setting
|
SETCV, (ackOp)19, // CV 19 is consist setting
|
||||||
SETBYTE, (ackOp)0,
|
SETBYTE, (ackOp)0,
|
||||||
VB, WACK, ITSKIP, // ignore consist if cv19 is zero (no consist)
|
VB, WACK, ITSKIP, // ignore consist if cv19 is zero (no consist)
|
||||||
SETBYTE, (ackOp)128,
|
SETBYTE, (ackOp)128,
|
||||||
VB, WACK, ITSKIP, // ignore consist if cv19 is 128 (no consist, direction bit set)
|
VB, WACK, ITSKIP, // ignore consist if cv19 is 128 (no consist, direction bit set)
|
||||||
STARTMERGE, // Setup to read cv 19
|
STARTMERGE, // Setup to read cv 19
|
||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
@@ -435,13 +423,13 @@ const ackOp FLASH LOCO_ID_PROG[] = {
|
|||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
VB, WACK, ITCB7, // return 7 bits only, No_ACK means CV19 not supported so ignore it
|
VB, WACK, ITCB7, // return 7 bits only, No_ACK means CV19 not supported so ignore it
|
||||||
|
|
||||||
SKIPTARGET, // continue here if CV 19 is zero or fails all validation
|
SKIPTARGET, // continue here if CV 19 is zero or fails all validation
|
||||||
SETCV,(ackOp)29,
|
SETCV,(ackOp)29,
|
||||||
SETBIT,(ackOp)5,
|
SETBIT,(ackOp)5,
|
||||||
V0, WACK, ITSKIP, // Skip to SKIPTARGET if bit 5 of CV29 is zero
|
V0, WACK, ITSKIP, // Skip to SKIPTARGET if bit 5 of CV29 is zero
|
||||||
|
|
||||||
// Long locoid
|
// Long locoid
|
||||||
SETCV, (ackOp)17, // CV 17 is part of locoid
|
SETCV, (ackOp)17, // CV 17 is part of locoid
|
||||||
STARTMERGE,
|
STARTMERGE,
|
||||||
V0, WACK, MERGE, // read and merge bit 1 etc
|
V0, WACK, MERGE, // read and merge bit 1 etc
|
||||||
@@ -453,8 +441,8 @@ const ackOp FLASH LOCO_ID_PROG[] = {
|
|||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
VB, WACK, NAKFAIL, // verify merged byte and return -1 it if not acked ok
|
VB, WACK, NAKFAIL, // verify merged byte and return -1 it if not acked ok
|
||||||
STASHLOCOID, // keep stashed cv 17 for later
|
STASHLOCOID, // keep stashed cv 17 for later
|
||||||
// Read 2nd part from CV 18
|
// Read 2nd part from CV 18
|
||||||
SETCV, (ackOp)18,
|
SETCV, (ackOp)18,
|
||||||
STARTMERGE,
|
STARTMERGE,
|
||||||
V0, WACK, MERGE, // read and merge bit 1 etc
|
V0, WACK, MERGE, // read and merge bit 1 etc
|
||||||
@@ -467,8 +455,8 @@ const ackOp FLASH LOCO_ID_PROG[] = {
|
|||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
VB, WACK, NAKFAIL, // verify merged byte and return -1 it if not acked ok
|
VB, WACK, NAKFAIL, // verify merged byte and return -1 it if not acked ok
|
||||||
COMBINELOCOID, // Combile byte with stash to make long locoid and callback
|
COMBINELOCOID, // Combile byte with stash to make long locoid and callback
|
||||||
|
|
||||||
// ITSKIP Skips to here if CV 29 bit 5 was zero. so read CV 1 and return that
|
// ITSKIP Skips to here if CV 29 bit 5 was zero. so read CV 1 and return that
|
||||||
SKIPTARGET,
|
SKIPTARGET,
|
||||||
SETCV, (ackOp)1,
|
SETCV, (ackOp)1,
|
||||||
STARTMERGE,
|
STARTMERGE,
|
||||||
@@ -481,8 +469,8 @@ const ackOp FLASH LOCO_ID_PROG[] = {
|
|||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
VB, WACK, ITCB, // verify merged byte and callback
|
VB, WACK, ITCB, // verify merged byte and callback
|
||||||
CALLFAIL
|
FAIL
|
||||||
};
|
};
|
||||||
|
|
||||||
const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
|
const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
@@ -494,12 +482,12 @@ const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
|
|||||||
SETBIT,(ackOp)5,
|
SETBIT,(ackOp)5,
|
||||||
W0,WACK,
|
W0,WACK,
|
||||||
V0,WACK,NAKFAIL,
|
V0,WACK,NAKFAIL,
|
||||||
SETCV, (ackOp)1,
|
SETCV, (ackOp)1,
|
||||||
SETBYTEL, // low byte of word
|
SETBYTEL, // low byte of word
|
||||||
WB,WACK, // some decoders don't ACK writes
|
WB,WACK, // some decoders don't ACK writes
|
||||||
VB,WACK,ITCB,
|
VB,WACK,ITCB,
|
||||||
CALLFAIL
|
FAIL
|
||||||
};
|
};
|
||||||
|
|
||||||
const ackOp FLASH LONG_LOCO_ID_PROG[] = {
|
const ackOp FLASH LONG_LOCO_ID_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
@@ -514,16 +502,16 @@ const ackOp FLASH LONG_LOCO_ID_PROG[] = {
|
|||||||
V1,WACK,NAKFAIL,
|
V1,WACK,NAKFAIL,
|
||||||
// Store high byte of address in cv 17
|
// Store high byte of address in cv 17
|
||||||
SETCV, (ackOp)17,
|
SETCV, (ackOp)17,
|
||||||
SETBYTEH, // high byte of word
|
SETBYTEH, // high byte of word
|
||||||
WB,WACK,
|
WB,WACK,
|
||||||
VB,WACK,NAKFAIL,
|
VB,WACK,NAKFAIL,
|
||||||
// store
|
// store
|
||||||
SETCV, (ackOp)18,
|
SETCV, (ackOp)18,
|
||||||
SETBYTEL, // low byte of word
|
SETBYTEL, // low byte of word
|
||||||
WB,WACK,
|
WB,WACK,
|
||||||
VB,WACK,ITC1, // callback(1) means Ok
|
VB,WACK,ITC1, // callback(1) means Ok
|
||||||
CALLFAIL
|
FAIL
|
||||||
};
|
};
|
||||||
|
|
||||||
void DCC::writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) {
|
void DCC::writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) {
|
||||||
ackManagerSetup(cv, byteValue, WRITE_BYTE_PROG, callback);
|
ackManagerSetup(cv, byteValue, WRITE_BYTE_PROG, callback);
|
||||||
@@ -562,28 +550,27 @@ void DCC::setLocoId(int id,ACK_CALLBACK callback) {
|
|||||||
callback(-1);
|
callback(-1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (id<=127)
|
if (id<=HIGHEST_SHORT_ADDR)
|
||||||
ackManagerSetup(id, SHORT_LOCO_ID_PROG, callback);
|
ackManagerSetup(id, SHORT_LOCO_ID_PROG, callback);
|
||||||
else
|
else
|
||||||
ackManagerSetup(id | 0xc000,LONG_LOCO_ID_PROG, callback);
|
ackManagerSetup(id | 0xc000,LONG_LOCO_ID_PROG, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
|
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
|
||||||
setThrottle2(cab,1); // ESTOP this loco if still on track
|
setThrottle2(cab,1); // ESTOP this loco if still on track
|
||||||
int reg=lookupSpeedTable(cab);
|
int reg=lookupSpeedTable(cab);
|
||||||
if (reg>=0) speedTable[reg].loco=0;
|
if (reg>=0) speedTable[reg].loco=0;
|
||||||
setThrottle2(cab,1); // ESTOP if this loco still on track
|
setThrottle2(cab,1); // ESTOP if this loco still on track
|
||||||
}
|
}
|
||||||
void DCC::forgetAllLocos() { // removes all speed reminders
|
void DCC::forgetAllLocos() { // removes all speed reminders
|
||||||
setThrottle2(0,1); // ESTOP all locos still on track
|
setThrottle2(0,1); // ESTOP all locos still on track
|
||||||
for (int i=0;i<MAX_LOCOS;i++) speedTable[i].loco=0;
|
for (int i=0;i<MAX_LOCOS;i++) speedTable[i].loco=0;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte DCC::loopStatus=0;
|
byte DCC::loopStatus=0;
|
||||||
|
|
||||||
void DCC::loop() {
|
void DCC::loop() {
|
||||||
DCCWaveform::loop(ackManagerProg!=NULL); // power overload checks
|
DCCWaveform::loop(ackManagerProg!=NULL); // power overload checks
|
||||||
MotorDriverContainer::mDC.loop();
|
|
||||||
ackManagerLoop(); // maintain prog track ack manager
|
ackManagerLoop(); // maintain prog track ack manager
|
||||||
issueReminders();
|
issueReminders();
|
||||||
}
|
}
|
||||||
@@ -595,58 +582,58 @@ void DCC::issueReminders() {
|
|||||||
// This loop searches for a loco in the speed table starting at nextLoco and cycling back around
|
// This loop searches for a loco in the speed table starting at nextLoco and cycling back around
|
||||||
for (int reg=0;reg<MAX_LOCOS;reg++) {
|
for (int reg=0;reg<MAX_LOCOS;reg++) {
|
||||||
int slot=reg+nextLoco;
|
int slot=reg+nextLoco;
|
||||||
if (slot>=MAX_LOCOS) slot-=MAX_LOCOS;
|
if (slot>=MAX_LOCOS) slot-=MAX_LOCOS;
|
||||||
if (speedTable[slot].loco > 0) {
|
if (speedTable[slot].loco > 0) {
|
||||||
// have found the next loco to remind
|
// have found the next loco to remind
|
||||||
// issueReminder will return true if this loco is completed (ie speed and functions)
|
// issueReminder will return true if this loco is completed (ie speed and functions)
|
||||||
if (issueReminder(slot)) nextLoco=slot+1;
|
if (issueReminder(slot)) nextLoco=slot+1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DCC::issueReminder(int reg) {
|
bool DCC::issueReminder(int reg) {
|
||||||
unsigned long functions=speedTable[reg].functions;
|
unsigned long functions=speedTable[reg].functions;
|
||||||
int loco=speedTable[reg].loco;
|
int loco=speedTable[reg].loco;
|
||||||
byte flags=speedTable[reg].groupFlags;
|
byte flags=speedTable[reg].groupFlags;
|
||||||
|
|
||||||
switch (loopStatus) {
|
switch (loopStatus) {
|
||||||
case 0:
|
case 0:
|
||||||
// DIAG(F("Reminder %d speed %d"),loco,speedTable[reg].speedCode);
|
// DIAG(F("Reminder %d speed %d"),loco,speedTable[reg].speedCode);
|
||||||
setThrottle2(loco, speedTable[reg].speedCode);
|
setThrottle2(loco, speedTable[reg].speedCode);
|
||||||
break;
|
break;
|
||||||
case 1: // remind function group 1 (F0-F4)
|
case 1: // remind function group 1 (F0-F4)
|
||||||
if (flags & FN_GROUP_1)
|
if (flags & FN_GROUP_1)
|
||||||
setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4)); // 100D DDDD
|
setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4)); // 100D DDDD
|
||||||
break;
|
break;
|
||||||
case 2: // remind function group 2 F5-F8
|
case 2: // remind function group 2 F5-F8
|
||||||
if (flags & FN_GROUP_2)
|
if (flags & FN_GROUP_2)
|
||||||
setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F)); // 1011 DDDD
|
setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F)); // 1011 DDDD
|
||||||
break;
|
break;
|
||||||
case 3: // remind function group 3 F9-F12
|
case 3: // remind function group 3 F9-F12
|
||||||
if (flags & FN_GROUP_3)
|
if (flags & FN_GROUP_3)
|
||||||
setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F)); // 1010 DDDD
|
setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F)); // 1010 DDDD
|
||||||
break;
|
break;
|
||||||
case 4: // remind function group 4 F13-F20
|
case 4: // remind function group 4 F13-F20
|
||||||
if (flags & FN_GROUP_4)
|
if (flags & FN_GROUP_4)
|
||||||
setFunctionInternal(loco,222, ((functions>>13)& 0xFF));
|
setFunctionInternal(loco,222, ((functions>>13)& 0xFF));
|
||||||
flags&= ~FN_GROUP_4; // dont send them again
|
flags&= ~FN_GROUP_4; // dont send them again
|
||||||
break;
|
break;
|
||||||
case 5: // remind function group 5 F21-F28
|
case 5: // remind function group 5 F21-F28
|
||||||
if (flags & FN_GROUP_5)
|
if (flags & FN_GROUP_5)
|
||||||
setFunctionInternal(loco,223, ((functions>>21)& 0xFF));
|
setFunctionInternal(loco,223, ((functions>>21)& 0xFF));
|
||||||
flags&= ~FN_GROUP_5; // dont send them again
|
flags&= ~FN_GROUP_5; // dont send them again
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
loopStatus++;
|
loopStatus++;
|
||||||
// if we reach status 6 then this loco is done so
|
// if we reach status 6 then this loco is done so
|
||||||
// reset status to 0 for next loco and return true so caller
|
// reset status to 0 for next loco and return true so caller
|
||||||
// moves on to next loco.
|
// moves on to next loco.
|
||||||
if (loopStatus>5) loopStatus=0;
|
if (loopStatus>5) loopStatus=0;
|
||||||
return loopStatus==0;
|
return loopStatus==0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
///// Private helper functions below here /////////////////////
|
///// Private helper functions below here /////////////////////
|
||||||
@@ -681,20 +668,28 @@ int DCC::lookupSpeedTable(int locoId) {
|
|||||||
}
|
}
|
||||||
return reg;
|
return reg;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::updateLocoReminder(int loco, byte speedCode) {
|
void DCC::updateLocoReminder(int loco, byte speedCode) {
|
||||||
|
|
||||||
if (loco==0) {
|
if (loco==0) {
|
||||||
// broadcast stop/estop but dont change direction
|
// broadcast stop/estop but dont change direction
|
||||||
for (int reg = 0; reg < MAX_LOCOS; reg++) {
|
for (int reg = 0; reg < MAX_LOCOS; reg++) {
|
||||||
speedTable[reg].speedCode = (speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f);
|
if (speedTable[reg].loco==0) continue;
|
||||||
|
byte newspeed=(speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f);
|
||||||
|
if (speedTable[reg].speedCode != newspeed) {
|
||||||
|
speedTable[reg].speedCode = newspeed;
|
||||||
|
CommandDistributor::broadcastLoco(reg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// determine speed reg for this loco
|
// determine speed reg for this loco
|
||||||
int reg=lookupSpeedTable(loco);
|
int reg=lookupSpeedTable(loco);
|
||||||
if (reg>=0) speedTable[reg].speedCode = speedCode;
|
if (reg>=0 && speedTable[reg].speedCode!=speedCode) {
|
||||||
|
speedTable[reg].speedCode = speedCode;
|
||||||
|
CommandDistributor::broadcastLoco(reg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DCC::LOCO DCC::speedTable[MAX_LOCOS];
|
DCC::LOCO DCC::speedTable[MAX_LOCOS];
|
||||||
@@ -726,19 +721,21 @@ void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[]
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ackManagerRejoin=DCCWaveform::progTrackSyncMain;
|
ackManagerRejoin=DCCWaveform::progTrackSyncMain;
|
||||||
if (ackManagerRejoin ) {
|
if (ackManagerRejoin ) {
|
||||||
// Change from JOIN must zero resets packet.
|
// Change from JOIN must zero resets packet.
|
||||||
setProgTrackSyncMain(false);
|
setProgTrackSyncMain(false);
|
||||||
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
DCCWaveform::progTrack.autoPowerOff=false;
|
DCCWaveform::progTrack.autoPowerOff=false;
|
||||||
if (DCCWaveform::progTrack.getPowerMode() == POWERMODE::OFF) {
|
if (DCCWaveform::progTrack.getPowerMode() == POWERMODE::OFF) {
|
||||||
DCCWaveform::progTrack.autoPowerOff=true; // power off afterwards
|
DCCWaveform::progTrack.autoPowerOff=true; // power off afterwards
|
||||||
if (Diag::ACK) DIAG(F("Auto Prog power on"));
|
if (Diag::ACK) DIAG(F("Auto Prog power on"));
|
||||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||||
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
if (MotorDriver::commonFaultPin)
|
||||||
|
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||||
|
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ackManagerCv = cv;
|
ackManagerCv = cv;
|
||||||
@@ -766,7 +763,7 @@ bool DCC::checkResets(uint8_t numResets) {
|
|||||||
void DCC::ackManagerLoop() {
|
void DCC::ackManagerLoop() {
|
||||||
while (ackManagerProg) {
|
while (ackManagerProg) {
|
||||||
byte opcode=GETFLASH(ackManagerProg);
|
byte opcode=GETFLASH(ackManagerProg);
|
||||||
|
|
||||||
// breaks from this switch will step to next prog entry
|
// breaks from this switch will step to next prog entry
|
||||||
// returns from this switch will stay on same entry
|
// returns from this switch will stay on same entry
|
||||||
// (typically waiting for a reset counter or ACK waiting, or when all finished.)
|
// (typically waiting for a reset counter or ACK waiting, or when all finished.)
|
||||||
@@ -776,57 +773,57 @@ void DCC::ackManagerLoop() {
|
|||||||
if (checkResets(DCCWaveform::progTrack.autoPowerOff || ackManagerRejoin ? 20 : 3)) return;
|
if (checkResets(DCCWaveform::progTrack.autoPowerOff || ackManagerRejoin ? 20 : 3)) return;
|
||||||
DCCWaveform::progTrack.setAckBaseline();
|
DCCWaveform::progTrack.setAckBaseline();
|
||||||
callbackState=READY;
|
callbackState=READY;
|
||||||
break;
|
break;
|
||||||
case W0: // write 0 bit
|
case W0: // write 0 bit
|
||||||
case W1: // write 1 bit
|
case W1: // write 1 bit
|
||||||
{
|
{
|
||||||
if (checkResets(RESET_MIN)) return;
|
if (checkResets(RESET_MIN)) return;
|
||||||
if (Diag::ACK) DIAG(F("W%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum);
|
if (Diag::ACK) DIAG(F("W%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum);
|
||||||
byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum;
|
byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum;
|
||||||
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
|
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
|
||||||
DCCTrack::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||||
DCCWaveform::progTrack.setAckPending();
|
DCCWaveform::progTrack.setAckPending();
|
||||||
callbackState=AFTER_WRITE;
|
callbackState=AFTER_WRITE;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WB: // write byte
|
case WB: // write byte
|
||||||
{
|
{
|
||||||
if (checkResets( RESET_MIN)) return;
|
if (checkResets( RESET_MIN)) return;
|
||||||
if (Diag::ACK) DIAG(F("WB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
if (Diag::ACK) DIAG(F("WB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
||||||
byte message[] = {cv1(WRITE_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
|
byte message[] = {cv1(WRITE_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
|
||||||
DCCTrack::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||||
DCCWaveform::progTrack.setAckPending();
|
DCCWaveform::progTrack.setAckPending();
|
||||||
callbackState=AFTER_WRITE;
|
callbackState=AFTER_WRITE;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case VB: // Issue validate Byte packet
|
case VB: // Issue validate Byte packet
|
||||||
{
|
{
|
||||||
if (checkResets( RESET_MIN)) return;
|
if (checkResets( RESET_MIN)) return;
|
||||||
if (Diag::ACK) DIAG(F("VB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
if (Diag::ACK) DIAG(F("VB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
||||||
byte message[] = { cv1(VERIFY_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
|
byte message[] = { cv1(VERIFY_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
|
||||||
DCCTrack::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||||
DCCWaveform::progTrack.setAckPending();
|
DCCWaveform::progTrack.setAckPending();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case V0:
|
case V0:
|
||||||
case V1: // Issue validate bit=0 or bit=1 packet
|
case V1: // Issue validate bit=0 or bit=1 packet
|
||||||
{
|
{
|
||||||
if (checkResets(RESET_MIN)) return;
|
if (checkResets(RESET_MIN)) return;
|
||||||
if (Diag::ACK) DIAG(F("V%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum);
|
if (Diag::ACK) DIAG(F("V%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum);
|
||||||
byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum;
|
byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum;
|
||||||
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
|
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
|
||||||
DCCTrack::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||||
DCCWaveform::progTrack.setAckPending();
|
DCCWaveform::progTrack.setAckPending();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WACK: // wait for ack (or absence of ack)
|
case WACK: // wait for ack (or absence of ack)
|
||||||
{
|
{
|
||||||
byte ackState=2; // keep polling
|
byte ackState=2; // keep polling
|
||||||
|
|
||||||
ackState=DCCWaveform::progTrack.getAck();
|
ackState=DCCWaveform::progTrack.getAck();
|
||||||
if (ackState==2) return; // keep polling
|
if (ackState==2) return; // keep polling
|
||||||
ackReceived=ackState==1;
|
ackReceived=ackState==1;
|
||||||
@@ -839,14 +836,14 @@ void DCC::ackManagerLoop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ITCB: // If True callback(byte)
|
case ITCB: // If True callback(byte)
|
||||||
if (ackReceived) {
|
if (ackReceived) {
|
||||||
callback(ackManagerByte);
|
callback(ackManagerByte);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ITCBV: // If True callback(byte) - Verify
|
case ITCBV: // If True callback(byte) - Verify
|
||||||
if (ackReceived) {
|
if (ackReceived) {
|
||||||
if (ackManagerByte == ackManagerByteVerify) {
|
if (ackManagerByte == ackManagerByteVerify) {
|
||||||
@@ -857,22 +854,22 @@ void DCC::ackManagerLoop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ITCB7: // If True callback(byte & 0x7F)
|
case ITCB7: // If True callback(byte & 0x7F)
|
||||||
if (ackReceived) {
|
if (ackReceived) {
|
||||||
callback(ackManagerByte & 0x7F);
|
callback(ackManagerByte & 0x7F);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case NAKFAIL: // If nack callback(-1)
|
case NAKFAIL: // If nack callback(-1)
|
||||||
if (!ackReceived) {
|
if (!ackReceived) {
|
||||||
callback(-1);
|
callback(-1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CALLFAIL: // callback(-1)
|
case FAIL: // callback(-1)
|
||||||
callback(-1);
|
callback(-1);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -882,63 +879,63 @@ void DCC::ackManagerLoop() {
|
|||||||
|
|
||||||
case STARTMERGE:
|
case STARTMERGE:
|
||||||
ackManagerBitNum=7;
|
ackManagerBitNum=7;
|
||||||
ackManagerByte=0;
|
ackManagerByte=0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MERGE: // Merge previous Validate zero wack response with byte value and update bit number (use for reading CV bytes)
|
case MERGE: // Merge previous Validate zero wack response with byte value and update bit number (use for reading CV bytes)
|
||||||
ackManagerByte <<= 1;
|
ackManagerByte <<= 1;
|
||||||
// ackReceived means bit is zero.
|
// ackReceived means bit is zero.
|
||||||
if (!ackReceived) ackManagerByte |= 1;
|
if (!ackReceived) ackManagerByte |= 1;
|
||||||
ackManagerBitNum--;
|
ackManagerBitNum--;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SETBIT:
|
case SETBIT:
|
||||||
ackManagerProg++;
|
ackManagerProg++;
|
||||||
ackManagerBitNum=GETFLASH(ackManagerProg);
|
ackManagerBitNum=GETFLASH(ackManagerProg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SETCV:
|
case SETCV:
|
||||||
ackManagerProg++;
|
ackManagerProg++;
|
||||||
ackManagerCv=GETFLASH(ackManagerProg);
|
ackManagerCv=GETFLASH(ackManagerProg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SETBYTE:
|
case SETBYTE:
|
||||||
ackManagerProg++;
|
ackManagerProg++;
|
||||||
ackManagerByte=GETFLASH(ackManagerProg);
|
ackManagerByte=GETFLASH(ackManagerProg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SETBYTEH:
|
case SETBYTEH:
|
||||||
ackManagerByte=highByte(ackManagerWord);
|
ackManagerByte=highByte(ackManagerWord);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SETBYTEL:
|
case SETBYTEL:
|
||||||
ackManagerByte=lowByte(ackManagerWord);
|
ackManagerByte=lowByte(ackManagerWord);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case STASHLOCOID:
|
case STASHLOCOID:
|
||||||
ackManagerStash=ackManagerByte; // stash value from CV17
|
ackManagerStash=ackManagerByte; // stash value from CV17
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case COMBINELOCOID:
|
case COMBINELOCOID:
|
||||||
// ackManagerStash is cv17, ackManagerByte is CV 18
|
// ackManagerStash is cv17, ackManagerByte is CV 18
|
||||||
callback( ackManagerByte + ((ackManagerStash - 192) << 8));
|
callback( LONG_ADDR_MARKER | ( ackManagerByte + ((ackManagerStash - 192) << 8)));
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case ITSKIP:
|
case ITSKIP:
|
||||||
if (!ackReceived) break;
|
if (!ackReceived) break;
|
||||||
// SKIP opcodes until SKIPTARGET found
|
// SKIP opcodes until SKIPTARGET found
|
||||||
while (opcode!=SKIPTARGET) {
|
while (opcode!=SKIPTARGET) {
|
||||||
ackManagerProg++;
|
ackManagerProg++;
|
||||||
opcode=GETFLASH(ackManagerProg);
|
opcode=GETFLASH(ackManagerProg);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SKIPTARGET:
|
case SKIPTARGET:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
DIAG(F("!! ackOp %d FAULT!!"),opcode);
|
DIAG(F("!! ackOp %d FAULT!!"),opcode);
|
||||||
callback( -1);
|
callback( -1);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
} // end of switch
|
} // end of switch
|
||||||
ackManagerProg++;
|
ackManagerProg++;
|
||||||
}
|
}
|
||||||
@@ -959,7 +956,7 @@ void DCC::callback(int value) {
|
|||||||
// Rule 1: If we have written to a decoder we must maintain power for 100mS
|
// Rule 1: If we have written to a decoder we must maintain power for 100mS
|
||||||
// Rule 2: If we are re-joining the main track we must power off for 30mS
|
// Rule 2: If we are re-joining the main track we must power off for 30mS
|
||||||
|
|
||||||
switch (callbackState) {
|
switch (callbackState) {
|
||||||
case AFTER_WRITE: // first attempt to callback after a write operation
|
case AFTER_WRITE: // first attempt to callback after a write operation
|
||||||
if (!ackManagerRejoin && !DCCWaveform::progTrack.autoPowerOff) {
|
if (!ackManagerRejoin && !DCCWaveform::progTrack.autoPowerOff) {
|
||||||
callbackState=READY;
|
callbackState=READY;
|
||||||
@@ -969,7 +966,7 @@ void DCC::callback(int value) {
|
|||||||
callbackState=WAITING_100;
|
callbackState=WAITING_100;
|
||||||
if (Diag::ACK) DIAG(F("Stable 100mS"));
|
if (Diag::ACK) DIAG(F("Stable 100mS"));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WAITING_100: // waiting for 100mS
|
case WAITING_100: // waiting for 100mS
|
||||||
if (millis()-callbackStart < 100) break;
|
if (millis()-callbackStart < 100) break;
|
||||||
// stable after power maintained for 100mS
|
// stable after power maintained for 100mS
|
||||||
@@ -978,32 +975,34 @@ void DCC::callback(int value) {
|
|||||||
// but if we will keep the power on, we must off it for 30mS
|
// but if we will keep the power on, we must off it for 30mS
|
||||||
if (DCCWaveform::progTrack.autoPowerOff) callbackState=READY;
|
if (DCCWaveform::progTrack.autoPowerOff) callbackState=READY;
|
||||||
else { // Need to cycle power off and on
|
else { // Need to cycle power off and on
|
||||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
||||||
callbackStart=millis();
|
callbackStart=millis();
|
||||||
callbackState=WAITING_30;
|
callbackState=WAITING_30;
|
||||||
if (Diag::ACK) DIAG(F("OFF 30mS"));
|
if (Diag::ACK) DIAG(F("OFF 30mS"));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WAITING_30: // waiting for 30mS with power off
|
case WAITING_30: // waiting for 30mS with power off
|
||||||
if (millis()-callbackStart < 30) break;
|
if (millis()-callbackStart < 30) break;
|
||||||
//power has been off for 30mS
|
//power has been off for 30mS
|
||||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||||
callbackState=READY;
|
callbackState=READY;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case READY: // ready after read, or write after power delay and off period.
|
case READY: // ready after read, or write after power delay and off period.
|
||||||
// power off if we powered it on
|
// power off if we powered it on
|
||||||
if (DCCWaveform::progTrack.autoPowerOff) {
|
if (DCCWaveform::progTrack.autoPowerOff) {
|
||||||
if (Diag::ACK) DIAG(F("Auto Prog power off"));
|
if (Diag::ACK) DIAG(F("Auto Prog power off"));
|
||||||
DCCWaveform::progTrack.doAutoPowerOff();
|
DCCWaveform::progTrack.doAutoPowerOff();
|
||||||
|
if (MotorDriver::commonFaultPin)
|
||||||
|
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||||
}
|
}
|
||||||
// Restore <1 JOIN> to state before BASELINE
|
// Restore <1 JOIN> to state before BASELINE
|
||||||
if (ackManagerRejoin) {
|
if (ackManagerRejoin) {
|
||||||
setProgTrackSyncMain(true);
|
setProgTrackSyncMain(true);
|
||||||
if (Diag::ACK) DIAG(F("Auto JOIN"));
|
if (Diag::ACK) DIAG(F("Auto JOIN"));
|
||||||
}
|
}
|
||||||
|
|
||||||
ackManagerProg=NULL; // no more steps to execute
|
ackManagerProg=NULL; // no more steps to execute
|
||||||
if (Diag::ACK) DIAG(F("Callback(%d)"),value);
|
if (Diag::ACK) DIAG(F("Callback(%d)"),value);
|
||||||
(ackManagerCallback)( value);
|
(ackManagerCallback)( value);
|
||||||
@@ -1016,10 +1015,10 @@ void DCC::displayCabList(Print * stream) {
|
|||||||
for (int reg = 0; reg < MAX_LOCOS; reg++) {
|
for (int reg = 0; reg < MAX_LOCOS; reg++) {
|
||||||
if (speedTable[reg].loco>0) {
|
if (speedTable[reg].loco>0) {
|
||||||
used ++;
|
used ++;
|
||||||
StringFormatter::send(stream,F("cab=%d, speed=%d, dir=%c \n"),
|
StringFormatter::send(stream,F("cab=%d, speed=%d, dir=%c \n"),
|
||||||
speedTable[reg].loco, speedTable[reg].speedCode & 0x7f,(speedTable[reg].speedCode & 0x80) ? 'F':'R');
|
speedTable[reg].loco, speedTable[reg].speedCode & 0x7f,(speedTable[reg].speedCode & 0x80) ? 'F':'R');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StringFormatter::send(stream,F("Used=%d, max=%d\n"),used,MAX_LOCOS);
|
StringFormatter::send(stream,F("Used=%d, max=%d\n"),used,MAX_LOCOS);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
33
DCC.h
33
DCC.h
@@ -1,5 +1,10 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2021 Mike S
|
||||||
|
* © 2021 Fred Decker
|
||||||
|
* © 2021 Herb Morton
|
||||||
|
* © 2020-2021 Harald Barth
|
||||||
|
* © 2020-2021 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC API
|
* This file is part of Asbelos DCC API
|
||||||
*
|
*
|
||||||
@@ -23,6 +28,16 @@
|
|||||||
#include "MotorDrivers.h"
|
#include "MotorDrivers.h"
|
||||||
#include "FSH.h"
|
#include "FSH.h"
|
||||||
|
|
||||||
|
#include "defines.h"
|
||||||
|
#ifndef HIGHEST_SHORT_ADDR
|
||||||
|
#define HIGHEST_SHORT_ADDR 127
|
||||||
|
#else
|
||||||
|
#if HIGHEST_SHORT_ADDR > 127
|
||||||
|
#error short addr greater than 127 does not make sense
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
const uint16_t LONG_ADDR_MARKER = 0x4000;
|
||||||
|
|
||||||
typedef void (*ACK_CALLBACK)(int16_t result);
|
typedef void (*ACK_CALLBACK)(int16_t result);
|
||||||
|
|
||||||
enum ackOp : byte
|
enum ackOp : byte
|
||||||
@@ -41,7 +56,7 @@ enum ackOp : byte
|
|||||||
ITCBV, // If True callback(byte) - end of Verify Byte
|
ITCBV, // If True callback(byte) - end of Verify Byte
|
||||||
ITCB7, // If True callback(byte &0x7F)
|
ITCB7, // If True callback(byte &0x7F)
|
||||||
NAKFAIL, // if false callback(-1)
|
NAKFAIL, // if false callback(-1)
|
||||||
CALLFAIL, // callback(-1)
|
FAIL, // callback(-1)
|
||||||
BIV, // Set ackManagerByte to initial value for Verify retry
|
BIV, // Set ackManagerByte to initial value for Verify retry
|
||||||
STARTMERGE, // Clear bit and byte settings ready for merge pass
|
STARTMERGE, // Clear bit and byte settings ready for merge pass
|
||||||
MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes)
|
MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes)
|
||||||
@@ -77,7 +92,7 @@ const byte MAX_LOCOS = 50;
|
|||||||
class DCC
|
class DCC
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static void begin();
|
static void begin(const FSH * motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver);
|
||||||
static void setJoinRelayPin(byte joinRelayPin);
|
static void setJoinRelayPin(byte joinRelayPin);
|
||||||
static void loop();
|
static void loop();
|
||||||
|
|
||||||
@@ -89,8 +104,9 @@ public:
|
|||||||
static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue);
|
static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue);
|
||||||
static void setFunction(int cab, byte fByte, byte eByte);
|
static void setFunction(int cab, byte fByte, byte eByte);
|
||||||
static void setFn(int cab, int16_t functionNumber, bool on);
|
static void setFn(int cab, int16_t functionNumber, bool on);
|
||||||
static int changeFn(int cab, int16_t functionNumber, bool pressed);
|
static void changeFn(int cab, int16_t functionNumber);
|
||||||
static int getFn(int cab, int16_t functionNumber);
|
static int getFn(int cab, int16_t functionNumber);
|
||||||
|
static uint32_t getFunctionMap(int cab);
|
||||||
static void updateGroupflags(byte &flags, int16_t functionNumber);
|
static void updateGroupflags(byte &flags, int16_t functionNumber);
|
||||||
static void setAccessory(int aAdd, byte aNum, bool activate);
|
static void setAccessory(int aAdd, byte aNum, bool activate);
|
||||||
static bool writeTextPacket(byte *b, int nBytes);
|
static bool writeTextPacket(byte *b, int nBytes);
|
||||||
@@ -124,7 +140,6 @@ public:
|
|||||||
return ackRetryPSum;
|
return ackRetryPSum;
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
|
||||||
struct LOCO
|
struct LOCO
|
||||||
{
|
{
|
||||||
int loco;
|
int loco;
|
||||||
@@ -132,6 +147,9 @@ private:
|
|||||||
byte groupFlags;
|
byte groupFlags;
|
||||||
unsigned long functions;
|
unsigned long functions;
|
||||||
};
|
};
|
||||||
|
static LOCO speedTable[MAX_LOCOS];
|
||||||
|
|
||||||
|
private:
|
||||||
static byte joinRelay;
|
static byte joinRelay;
|
||||||
static byte loopStatus;
|
static byte loopStatus;
|
||||||
static void setThrottle2(uint16_t cab, uint8_t speedCode);
|
static void setThrottle2(uint16_t cab, uint8_t speedCode);
|
||||||
@@ -142,7 +160,6 @@ private:
|
|||||||
static FSH *shieldName;
|
static FSH *shieldName;
|
||||||
static byte globalSpeedsteps;
|
static byte globalSpeedsteps;
|
||||||
|
|
||||||
static LOCO speedTable[MAX_LOCOS];
|
|
||||||
static byte cv1(byte opcode, int cv);
|
static byte cv1(byte opcode, int cv);
|
||||||
static byte cv2(int cv);
|
static byte cv2(int cv);
|
||||||
static int lookupSpeedTable(int locoId);
|
static int lookupSpeedTable(int locoId);
|
||||||
@@ -207,10 +224,6 @@ private:
|
|||||||
#define ARDUINO_TYPE "TEENSY40"
|
#define ARDUINO_TYPE "TEENSY40"
|
||||||
#elif defined(ARDUINO_TEENSY41)
|
#elif defined(ARDUINO_TEENSY41)
|
||||||
#define ARDUINO_TYPE "TEENSY41"
|
#define ARDUINO_TYPE "TEENSY41"
|
||||||
#elif defined(ARDUINO_ARCH_ESP8266)
|
|
||||||
#define ARDUINO_TYPE "ESP8266"
|
|
||||||
#elif defined(ARDUINO_ARCH_ESP32)
|
|
||||||
#define ARDUINO_TYPE "ESP32"
|
|
||||||
#else
|
#else
|
||||||
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560
|
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560
|
||||||
#endif
|
#endif
|
||||||
|
21
DCCEX.h
21
DCCEX.h
@@ -1,7 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* (c) 2020 Chris Harlow. All rights reserved.
|
* © 2021 Fred Decker
|
||||||
* (c) 2021 Fred Decker. All rights reserved.
|
* © 2020-2021 Harald Barth
|
||||||
* (c) 2020 Harald Barth. All rights reserved.
|
* © 2020-2021 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
@@ -29,14 +30,9 @@
|
|||||||
#include "DCC.h"
|
#include "DCC.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
#include "DCCEXParser.h"
|
#include "DCCEXParser.h"
|
||||||
|
#include "SerialManager.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#if defined(ARDUINO_ARCH_ESP8266)
|
|
||||||
#include "WifiESP8266.h"
|
|
||||||
#elif defined(ARDUINO_ARCH_ESP32)
|
|
||||||
#include "WifiESP32.h"
|
|
||||||
#else
|
|
||||||
#include "WifiInterface.h"
|
#include "WifiInterface.h"
|
||||||
#endif
|
|
||||||
#if ETHERNET_ON == true
|
#if ETHERNET_ON == true
|
||||||
#include "EthernetInterface.h"
|
#include "EthernetInterface.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -48,11 +44,6 @@
|
|||||||
#include "Sensors.h"
|
#include "Sensors.h"
|
||||||
#include "Outputs.h"
|
#include "Outputs.h"
|
||||||
#include "RMFT.h"
|
#include "RMFT.h"
|
||||||
|
#include "CommandDistributor.h"
|
||||||
// not yet in this branch
|
|
||||||
//#if __has_include ( "myAutomation.h")
|
|
||||||
// #include "RMFT.h"
|
|
||||||
// #define RMFT_ACTIVE
|
|
||||||
//#endif
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
302
DCCEXParser.cpp
302
DCCEXParser.cpp
@@ -1,6 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2021 Neil McKechnie
|
||||||
* © 2020, Harald Barth.
|
* © 2021 Mike S
|
||||||
|
* © 2021 Herb Morton
|
||||||
|
* © 2020-2022 Harald Barth
|
||||||
|
* © 2020-2021 M Steve Todd
|
||||||
|
* © 2020-2021 Fred Decker
|
||||||
|
* © 2020-2021 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
@@ -17,12 +23,10 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
#include "DCC.h" // includes "Motordriver.h" and <Arduino.h>
|
|
||||||
#include "defines.h"
|
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
#include "DCCEXParser.h"
|
#include "DCCEXParser.h"
|
||||||
|
#include "DCC.h"
|
||||||
#include "DCCWaveform.h"
|
#include "DCCWaveform.h"
|
||||||
#include "DCCTrack.h"
|
|
||||||
#include "Turnouts.h"
|
#include "Turnouts.h"
|
||||||
#include "Outputs.h"
|
#include "Outputs.h"
|
||||||
#include "Sensors.h"
|
#include "Sensors.h"
|
||||||
@@ -30,12 +34,10 @@
|
|||||||
#include "GITHUB_SHA.h"
|
#include "GITHUB_SHA.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "defines.h"
|
#include "defines.h"
|
||||||
|
#include "CommandDistributor.h"
|
||||||
#include "EEStore.h"
|
#include "EEStore.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
#ifndef ESP_FAMILY
|
|
||||||
#include <avr/wdt.h>
|
#include <avr/wdt.h>
|
||||||
#endif
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
@@ -60,7 +62,9 @@ const int16_t HASH_KEYWORD_ON = 2657;
|
|||||||
const int16_t HASH_KEYWORD_DCC = 6436;
|
const int16_t HASH_KEYWORD_DCC = 6436;
|
||||||
const int16_t HASH_KEYWORD_SLOW = -17209;
|
const int16_t HASH_KEYWORD_SLOW = -17209;
|
||||||
const int16_t HASH_KEYWORD_PROGBOOST = -6353;
|
const int16_t HASH_KEYWORD_PROGBOOST = -6353;
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
const int16_t HASH_KEYWORD_EEPROM = -7168;
|
const int16_t HASH_KEYWORD_EEPROM = -7168;
|
||||||
|
#endif
|
||||||
const int16_t HASH_KEYWORD_LIMIT = 27413;
|
const int16_t HASH_KEYWORD_LIMIT = 27413;
|
||||||
const int16_t HASH_KEYWORD_MAX = 16244;
|
const int16_t HASH_KEYWORD_MAX = 16244;
|
||||||
const int16_t HASH_KEYWORD_MIN = 15978;
|
const int16_t HASH_KEYWORD_MIN = 15978;
|
||||||
@@ -77,15 +81,12 @@ const int16_t HASH_KEYWORD_HAL = 10853;
|
|||||||
const int16_t HASH_KEYWORD_SHOW = -21309;
|
const int16_t HASH_KEYWORD_SHOW = -21309;
|
||||||
const int16_t HASH_KEYWORD_ANIN = -10424;
|
const int16_t HASH_KEYWORD_ANIN = -10424;
|
||||||
const int16_t HASH_KEYWORD_ANOUT = -26399;
|
const int16_t HASH_KEYWORD_ANOUT = -26399;
|
||||||
#ifdef HAS_ENOUGH_MEMORY
|
|
||||||
const int16_t HASH_KEYWORD_WIFI = -5583;
|
const int16_t HASH_KEYWORD_WIFI = -5583;
|
||||||
const int16_t HASH_KEYWORD_ETHERNET = -30767;
|
const int16_t HASH_KEYWORD_ETHERNET = -30767;
|
||||||
const int16_t HASH_KEYWORD_WIT = 31594;
|
const int16_t HASH_KEYWORD_WIT = 31594;
|
||||||
#endif
|
|
||||||
|
|
||||||
int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS];
|
int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS];
|
||||||
bool DCCEXParser::stashBusy;
|
bool DCCEXParser::stashBusy;
|
||||||
|
|
||||||
Print *DCCEXParser::stashStream = NULL;
|
Print *DCCEXParser::stashStream = NULL;
|
||||||
RingStream *DCCEXParser::stashRingStream = NULL;
|
RingStream *DCCEXParser::stashRingStream = NULL;
|
||||||
byte DCCEXParser::stashTarget=0;
|
byte DCCEXParser::stashTarget=0;
|
||||||
@@ -96,46 +97,8 @@ byte DCCEXParser::stashTarget=0;
|
|||||||
// calls the corresponding DCC api.
|
// calls the corresponding DCC api.
|
||||||
// Non-DCC things like turnouts, pins and sensors are handled in additional JMRI interface classes.
|
// Non-DCC things like turnouts, pins and sensors are handled in additional JMRI interface classes.
|
||||||
|
|
||||||
DCCEXParser::DCCEXParser() {}
|
|
||||||
void DCCEXParser::flush()
|
|
||||||
{
|
|
||||||
if (Diag::CMD)
|
|
||||||
DIAG(F("Buffer flush"));
|
|
||||||
bufferLength = 0;
|
|
||||||
inCommandPayload = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DCCEXParser::loop(Stream &stream)
|
int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte *cmd, bool usehex)
|
||||||
{
|
|
||||||
while (stream.available())
|
|
||||||
{
|
|
||||||
if (bufferLength == MAX_BUFFER)
|
|
||||||
{
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
char ch = stream.read();
|
|
||||||
if (ch == '<')
|
|
||||||
{
|
|
||||||
inCommandPayload = true;
|
|
||||||
bufferLength = 0;
|
|
||||||
buffer[0] = '\0';
|
|
||||||
}
|
|
||||||
else if (ch == '>')
|
|
||||||
{
|
|
||||||
buffer[bufferLength] = '\0';
|
|
||||||
parse(&stream, buffer, NULL); // Parse this (No ringStream for serial)
|
|
||||||
inCommandPayload = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (inCommandPayload)
|
|
||||||
{
|
|
||||||
buffer[bufferLength++] = ch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Sensor::checkAll(&stream); // Update and print changes
|
|
||||||
}
|
|
||||||
|
|
||||||
int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte *cmd)
|
|
||||||
{
|
{
|
||||||
byte state = 1;
|
byte state = 1;
|
||||||
byte parameterCount = 0;
|
byte parameterCount = 0;
|
||||||
@@ -173,10 +136,15 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte
|
|||||||
case 3: // building a parameter
|
case 3: // building a parameter
|
||||||
if (hot >= '0' && hot <= '9')
|
if (hot >= '0' && hot <= '9')
|
||||||
{
|
{
|
||||||
runningValue = 10 * runningValue + (hot - '0');
|
runningValue = (usehex?16:10) * runningValue + (hot - '0');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (hot >= 'a' && hot <= 'z') hot=hot-'a'+'A'; // uppercase a..z
|
if (hot >= 'a' && hot <= 'z') hot=hot-'a'+'A'; // uppercase a..z
|
||||||
|
if (usehex && hot>='A' && hot<='F') {
|
||||||
|
// treat A..F as hex not keyword
|
||||||
|
runningValue = 16 * runningValue + (hot - 'A' + 10);
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (hot >= 'A' && hot <= 'Z')
|
if (hot >= 'A' && hot <= 'Z')
|
||||||
{
|
{
|
||||||
// Since JMRI got modified to send keywords in some rare cases, we need this
|
// Since JMRI got modified to send keywords in some rare cases, we need this
|
||||||
@@ -194,66 +162,6 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte
|
|||||||
return parameterCount;
|
return parameterCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
int16_t DCCEXParser::splitHexValues(int16_t result[MAX_COMMAND_PARAMS], const byte *cmd)
|
|
||||||
{
|
|
||||||
byte state = 1;
|
|
||||||
byte parameterCount = 0;
|
|
||||||
int16_t runningValue = 0;
|
|
||||||
const byte *remainingCmd = cmd + 1; // skips the opcode
|
|
||||||
|
|
||||||
// clear all parameters in case not enough found
|
|
||||||
for (int16_t i = 0; i < MAX_COMMAND_PARAMS; i++)
|
|
||||||
result[i] = 0;
|
|
||||||
|
|
||||||
while (parameterCount < MAX_COMMAND_PARAMS)
|
|
||||||
{
|
|
||||||
byte hot = *remainingCmd;
|
|
||||||
|
|
||||||
switch (state)
|
|
||||||
{
|
|
||||||
|
|
||||||
case 1: // skipping spaces before a param
|
|
||||||
if (hot == ' ')
|
|
||||||
break;
|
|
||||||
if (hot == '\0' || hot == '>')
|
|
||||||
return parameterCount;
|
|
||||||
state = 2;
|
|
||||||
continue;
|
|
||||||
|
|
||||||
case 2: // checking first hex digit
|
|
||||||
runningValue = 0;
|
|
||||||
state = 3;
|
|
||||||
continue;
|
|
||||||
|
|
||||||
case 3: // building a parameter
|
|
||||||
if (hot >= '0' && hot <= '9')
|
|
||||||
{
|
|
||||||
runningValue = 16 * runningValue + (hot - '0');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (hot >= 'A' && hot <= 'F')
|
|
||||||
{
|
|
||||||
runningValue = 16 * runningValue + 10 + (hot - 'A');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (hot >= 'a' && hot <= 'f')
|
|
||||||
{
|
|
||||||
runningValue = 16 * runningValue + 10 + (hot - 'a');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (hot==' ' || hot=='>' || hot=='\0') {
|
|
||||||
result[parameterCount] = runningValue;
|
|
||||||
parameterCount++;
|
|
||||||
state = 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return -1; // invalid hex digit
|
|
||||||
}
|
|
||||||
remainingCmd++;
|
|
||||||
}
|
|
||||||
return parameterCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
FILTER_CALLBACK DCCEXParser::filterCallback = 0;
|
FILTER_CALLBACK DCCEXParser::filterCallback = 0;
|
||||||
FILTER_CALLBACK DCCEXParser::filterRMFTCallback = 0;
|
FILTER_CALLBACK DCCEXParser::filterRMFTCallback = 0;
|
||||||
AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0;
|
AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0;
|
||||||
@@ -272,6 +180,7 @@ void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback)
|
|||||||
|
|
||||||
// Parse an F() string
|
// Parse an F() string
|
||||||
void DCCEXParser::parse(const FSH * cmd) {
|
void DCCEXParser::parse(const FSH * cmd) {
|
||||||
|
DIAG(F("SETUP(\"%S\")"),cmd);
|
||||||
int size=strlen_P((char *)cmd)+1;
|
int size=strlen_P((char *)cmd)+1;
|
||||||
char buffer[size];
|
char buffer[size];
|
||||||
strcpy_P(buffer,(char *)cmd);
|
strcpy_P(buffer,(char *)cmd);
|
||||||
@@ -282,15 +191,17 @@ void DCCEXParser::parse(const FSH * cmd) {
|
|||||||
|
|
||||||
void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||||
{
|
{
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
(void)EEPROM; // tell compiler not to warn this is unused
|
(void)EEPROM; // tell compiler not to warn this is unused
|
||||||
|
#endif
|
||||||
if (Diag::CMD)
|
if (Diag::CMD)
|
||||||
DIAG(F("PARSING:%s"), com);
|
DIAG(F("PARSING:%s"), com);
|
||||||
int16_t p[MAX_COMMAND_PARAMS];
|
int16_t p[MAX_COMMAND_PARAMS];
|
||||||
while (com[0] == '<' || com[0] == ' ')
|
while (com[0] == '<' || com[0] == ' ')
|
||||||
com++; // strip off any number of < or spaces
|
com++; // strip off any number of < or spaces
|
||||||
byte params = splitValues(p, com);
|
|
||||||
byte opcode = com[0];
|
byte opcode = com[0];
|
||||||
|
byte params = splitValues(p, com, opcode=='M' || opcode=='P');
|
||||||
|
|
||||||
if (filterCallback)
|
if (filterCallback)
|
||||||
filterCallback(stream, opcode, params, p);
|
filterCallback(stream, opcode, params, p);
|
||||||
if (filterRMFTCallback && opcode!='\0')
|
if (filterRMFTCallback && opcode!='\0')
|
||||||
@@ -338,10 +249,9 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
|||||||
break; // invalid direction code
|
break; // invalid direction code
|
||||||
|
|
||||||
DCC::setThrottle(cab, tspeed, direction);
|
DCC::setThrottle(cab, tspeed, direction);
|
||||||
if (params == 4)
|
if (params == 4) // send obsolete format T response
|
||||||
StringFormatter::send(stream, F("<T %d %d %d>\n"), p[0], p[2], p[3]);
|
StringFormatter::send(stream, F("<T %d %d %d>\n"), p[0], p[2], p[3]);
|
||||||
else
|
// speed change will be broadcast anyway in new <l > format
|
||||||
StringFormatter::send(stream, F("<O>\n"));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'f': // FUNCTION <f CAB BYTE1 [BYTE2]>
|
case 'f': // FUNCTION <f CAB BYTE1 [BYTE2]>
|
||||||
@@ -372,7 +282,7 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
|||||||
|| ((p[activep] & 0x01) != p[activep]) // invalid activate 0|1
|
|| ((p[activep] & 0x01) != p[activep]) // invalid activate 0|1
|
||||||
) break;
|
) break;
|
||||||
// Honour the configuration option (config.h) which allows the <a> command to be reversed
|
// Honour the configuration option (config.h) which allows the <a> command to be reversed
|
||||||
#ifdef DCC_ACCESSORY_RCN_213
|
#ifdef DCC_ACCESSORY_COMMAND_REVERSE
|
||||||
DCC::setAccessory(address, subaddress,p[activep]==0);
|
DCC::setAccessory(address, subaddress,p[activep]==0);
|
||||||
#else
|
#else
|
||||||
DCC::setAccessory(address, subaddress,p[activep]==1);
|
DCC::setAccessory(address, subaddress,p[activep]==1);
|
||||||
@@ -405,8 +315,8 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
|||||||
|
|
||||||
case 'M': // WRITE TRANSPARENT DCC PACKET MAIN <M REG X1 ... X9>
|
case 'M': // WRITE TRANSPARENT DCC PACKET MAIN <M REG X1 ... X9>
|
||||||
case 'P': // WRITE TRANSPARENT DCC PACKET PROG <P REG X1 ... X9>
|
case 'P': // WRITE TRANSPARENT DCC PACKET PROG <P REG X1 ... X9>
|
||||||
// Re-parse the command using a hex-only splitter
|
// NOTE: this command was parsed in HEX instead of decimal
|
||||||
params=splitHexValues(p,com)-1; // drop REG
|
params--; // drop REG
|
||||||
if (params<1) break;
|
if (params<1) break;
|
||||||
{
|
{
|
||||||
byte packet[params];
|
byte packet[params];
|
||||||
@@ -414,7 +324,7 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
|||||||
packet[i]=(byte)p[i+1];
|
packet[i]=(byte)p[i+1];
|
||||||
if (Diag::CMD) DIAG(F("packet[%d]=%d (0x%x)"), i, packet[i], packet[i]);
|
if (Diag::CMD) DIAG(F("packet[%d]=%d (0x%x)"), i, packet[i], packet[i]);
|
||||||
}
|
}
|
||||||
(opcode=='M'?DCCTrack::mainTrack:DCCTrack::progTrack).schedulePacket(packet,params,3);
|
(opcode=='M'?DCCWaveform::mainTrack:DCCWaveform::progTrack).schedulePacket(packet,params,3);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -467,61 +377,70 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '1': // POWERON <1 [MAIN|PROG]>
|
case '1': // POWERON <1 [MAIN|PROG|JOIN]>
|
||||||
case '0': // POWEROFF <0 [MAIN | PROG] >
|
|
||||||
if (params > 1)
|
|
||||||
break;
|
|
||||||
{
|
{
|
||||||
POWERMODE mode = opcode == '1' ? POWERMODE::ON : POWERMODE::OFF;
|
bool main=false;
|
||||||
DCC::setProgTrackSyncMain(false); // Only <1 JOIN> will set this on, all others set it off
|
bool prog=false;
|
||||||
if (params == 0 ||
|
bool join=false;
|
||||||
(MotorDriver::commonFaultPin && p[0] != HASH_KEYWORD_JOIN)) // commonFaultPin prevents individual track handling
|
if (params > 1) break;
|
||||||
{
|
if (params==0 || MotorDriver::commonFaultPin) { // <1> or tracks can not be handled individually
|
||||||
DCCWaveform::mainTrack.setPowerMode(mode);
|
main=true;
|
||||||
DCCWaveform::progTrack.setPowerMode(mode);
|
prog=true;
|
||||||
if (mode == POWERMODE::OFF)
|
|
||||||
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
|
|
||||||
StringFormatter::send(stream, F("<p%c>\n"), opcode);
|
|
||||||
LCD(2, F("p%c"), opcode);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (p[0])
|
|
||||||
{
|
|
||||||
case HASH_KEYWORD_MAIN:
|
|
||||||
DCCWaveform::mainTrack.setPowerMode(mode);
|
|
||||||
StringFormatter::send(stream, F("<p%c MAIN>\n"), opcode);
|
|
||||||
LCD(2, F("p%c MAIN"), opcode);
|
|
||||||
return;
|
|
||||||
|
|
||||||
case HASH_KEYWORD_PROG:
|
|
||||||
DCCWaveform::progTrack.setPowerMode(mode);
|
|
||||||
if (mode == POWERMODE::OFF)
|
|
||||||
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
|
|
||||||
StringFormatter::send(stream, F("<p%c PROG>\n"), opcode);
|
|
||||||
LCD(2, F("p%c PROG"), opcode);
|
|
||||||
return;
|
|
||||||
case HASH_KEYWORD_JOIN:
|
|
||||||
DCCWaveform::mainTrack.setPowerMode(mode);
|
|
||||||
DCCWaveform::progTrack.setPowerMode(mode);
|
|
||||||
if (mode == POWERMODE::ON)
|
|
||||||
{
|
|
||||||
DCC::setProgTrackSyncMain(true);
|
|
||||||
StringFormatter::send(stream, F("<p1 JOIN>\n"), opcode);
|
|
||||||
LCD(2, F("p1 JOIN"));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
StringFormatter::send(stream, F("<p0>\n"));
|
|
||||||
LCD(2, F("p0"));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
if (params==1) {
|
||||||
|
if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
|
||||||
|
main=true;
|
||||||
|
prog=true;
|
||||||
|
join=true;
|
||||||
|
}
|
||||||
|
else if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
|
||||||
|
main=true;
|
||||||
|
}
|
||||||
|
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
|
||||||
|
prog=true;
|
||||||
|
}
|
||||||
|
else break; // will reply <X>
|
||||||
|
}
|
||||||
|
if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||||
|
if (prog) DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||||
|
DCC::setProgTrackSyncMain(join);
|
||||||
|
|
||||||
|
CommandDistributor::broadcastPower();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '0': // POWEROFF <0 [MAIN | PROG] >
|
||||||
|
{
|
||||||
|
bool main=false;
|
||||||
|
bool prog=false;
|
||||||
|
if (params > 1) break;
|
||||||
|
if (params==0 || MotorDriver::commonFaultPin) { // <0> or tracks can not be handled individually
|
||||||
|
main=true;
|
||||||
|
prog=true;
|
||||||
|
}
|
||||||
|
if (params==1) {
|
||||||
|
if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
|
||||||
|
main=true;
|
||||||
|
}
|
||||||
|
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
|
||||||
|
prog=true;
|
||||||
|
}
|
||||||
|
else break; // will reply <X>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||||
|
if (prog) {
|
||||||
|
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
|
||||||
|
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
||||||
|
}
|
||||||
|
DCC::setProgTrackSyncMain(false);
|
||||||
|
|
||||||
|
CommandDistributor::broadcastPower();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
case '!': // ESTOP ALL <!>
|
case '!': // ESTOP ALL <!>
|
||||||
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
|
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'c': // SEND METER RESPONSES <c>
|
case 'c': // SEND METER RESPONSES <c>
|
||||||
@@ -544,6 +463,7 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
|||||||
// TODO Send stats of speed reminders table
|
// TODO Send stats of speed reminders table
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
case 'E': // STORE EPROM <E>
|
case 'E': // STORE EPROM <E>
|
||||||
EEStore::store();
|
EEStore::store();
|
||||||
StringFormatter::send(stream, F("<e %d %d %d>\n"), EEStore::eeStore->data.nTurnouts, EEStore::eeStore->data.nSensors, EEStore::eeStore->data.nOutputs);
|
StringFormatter::send(stream, F("<e %d %d %d>\n"), EEStore::eeStore->data.nTurnouts, EEStore::eeStore->data.nSensors, EEStore::eeStore->data.nOutputs);
|
||||||
@@ -553,7 +473,7 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
|||||||
EEStore::clear();
|
EEStore::clear();
|
||||||
StringFormatter::send(stream, F("<O>\n"));
|
StringFormatter::send(stream, F("<O>\n"));
|
||||||
return;
|
return;
|
||||||
|
#endif
|
||||||
case ' ': // < >
|
case ' ': // < >
|
||||||
StringFormatter::send(stream, F("\n"));
|
StringFormatter::send(stream, F("\n"));
|
||||||
return;
|
return;
|
||||||
@@ -574,16 +494,17 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
case 'F': // New command to call the new Loco Function API <F cab func 1|0>
|
case 'F': // New command to call the new Loco Function API <F cab func 1|0>
|
||||||
|
if(params!=3) break;
|
||||||
if (Diag::CMD)
|
if (Diag::CMD)
|
||||||
DIAG(F("Setting loco %d F%d %S"), p[0], p[1], p[2] ? F("ON") : F("OFF"));
|
DIAG(F("Setting loco %d F%d %S"), p[0], p[1], p[2] ? F("ON") : F("OFF"));
|
||||||
DCC::setFn(p[0], p[1], p[2] == 1);
|
DCC::setFn(p[0], p[1], p[2] == 1);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case '+': // Complex Wifi interface command (not usual parse)
|
case '+': // Complex Wifi interface command (not usual parse)
|
||||||
if (atCommandCallback) {
|
if (atCommandCallback && !ringStream) {
|
||||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
||||||
atCommandCallback(com);
|
atCommandCallback((HardwareSerial *)stream,com);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -727,9 +648,7 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
|
|||||||
}
|
}
|
||||||
if (!Turnout::setClosed(p[0], state)) return false;
|
if (!Turnout::setClosed(p[0], state)) return false;
|
||||||
|
|
||||||
// Send acknowledgement to caller if the command was not received over Serial
|
|
||||||
// (acknowledgement messages on Serial are sent by the Turnout class).
|
|
||||||
if (stream != &Serial) Turnout::printState(p[0], stream);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -864,19 +783,17 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
|||||||
|
|
||||||
case HASH_KEYWORD_RESET:
|
case HASH_KEYWORD_RESET:
|
||||||
{
|
{
|
||||||
#ifndef ESP_FAMILY
|
wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms
|
||||||
wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms
|
delay(50); // wait for the prescaller time to expire
|
||||||
delay(50); // wait for the prescaler time to expire
|
|
||||||
#else
|
|
||||||
/* XXX do right thing to reboot */
|
|
||||||
#endif
|
|
||||||
break; // and <X> if we didnt restart
|
break; // and <X> if we didnt restart
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries>
|
case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries>
|
||||||
if (params >= 2)
|
if (params >= 2)
|
||||||
EEStore::dump(p[1]);
|
EEStore::dump(p[1]);
|
||||||
return true;
|
return true;
|
||||||
|
#endif
|
||||||
|
|
||||||
case HASH_KEYWORD_SPEED28:
|
case HASH_KEYWORD_SPEED28:
|
||||||
DCC::setGlobalSpeedsteps(28);
|
DCC::setGlobalSpeedsteps(28);
|
||||||
@@ -966,10 +883,21 @@ void DCCEXParser::callback_R(int16_t result)
|
|||||||
commitAsyncReplyStream();
|
commitAsyncReplyStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCCEXParser::callback_Rloco(int16_t result)
|
void DCCEXParser::callback_Rloco(int16_t result) {
|
||||||
{
|
const FSH * detail;
|
||||||
StringFormatter::send(getAsyncReplyStream(), F("<r %d>\n"), result);
|
if (result<=0) {
|
||||||
commitAsyncReplyStream();
|
detail=F("<r ERROR %d>\n");
|
||||||
|
} else {
|
||||||
|
bool longAddr=result & LONG_ADDR_MARKER; //long addr
|
||||||
|
if (longAddr)
|
||||||
|
result = result^LONG_ADDR_MARKER;
|
||||||
|
if (longAddr && result <= HIGHEST_SHORT_ADDR)
|
||||||
|
detail=F("<r LONG %d UNSUPPORTED>\n");
|
||||||
|
else
|
||||||
|
detail=F("<r %d>\n");
|
||||||
|
}
|
||||||
|
StringFormatter::send(getAsyncReplyStream(), detail, result);
|
||||||
|
commitAsyncReplyStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCCEXParser::callback_Wloco(int16_t result)
|
void DCCEXParser::callback_Wloco(int16_t result)
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2021 Mike S
|
||||||
|
* © 2021 Fred Decker
|
||||||
|
* © 2020-2021 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC API
|
* This file is part of Asbelos DCC API
|
||||||
*
|
*
|
||||||
@@ -23,15 +26,13 @@
|
|||||||
#include "RingStream.h"
|
#include "RingStream.h"
|
||||||
|
|
||||||
typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
||||||
typedef void (*AT_COMMAND_CALLBACK)(const byte * command);
|
typedef void (*AT_COMMAND_CALLBACK)(HardwareSerial * stream,const byte * command);
|
||||||
|
|
||||||
struct DCCEXParser
|
struct DCCEXParser
|
||||||
{
|
{
|
||||||
DCCEXParser();
|
|
||||||
void loop(Stream & stream);
|
static void parse(Print * stream, byte * command, RingStream * ringStream);
|
||||||
void parse(Print * stream, byte * command, RingStream * ringStream);
|
static void parse(const FSH * cmd);
|
||||||
void parse(const FSH * cmd);
|
|
||||||
void flush();
|
|
||||||
static void setFilter(FILTER_CALLBACK filter);
|
static void setFilter(FILTER_CALLBACK filter);
|
||||||
static void setRMFTFilter(FILTER_CALLBACK filter);
|
static void setRMFTFilter(FILTER_CALLBACK filter);
|
||||||
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
|
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
|
||||||
@@ -40,17 +41,13 @@ struct DCCEXParser
|
|||||||
private:
|
private:
|
||||||
|
|
||||||
static const int16_t MAX_BUFFER=50; // longest command sent in
|
static const int16_t MAX_BUFFER=50; // longest command sent in
|
||||||
byte bufferLength=0;
|
static int16_t splitValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command, bool usehex);
|
||||||
bool inCommandPayload=false;
|
|
||||||
byte buffer[MAX_BUFFER+2];
|
|
||||||
int16_t splitValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
|
|
||||||
int16_t splitHexValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
|
|
||||||
|
|
||||||
bool parseT(Print * stream, int16_t params, int16_t p[]);
|
static bool parseT(Print * stream, int16_t params, int16_t p[]);
|
||||||
bool parseZ(Print * stream, int16_t params, int16_t p[]);
|
static bool parseZ(Print * stream, int16_t params, int16_t p[]);
|
||||||
bool parseS(Print * stream, int16_t params, int16_t p[]);
|
static bool parseS(Print * stream, int16_t params, int16_t p[]);
|
||||||
bool parsef(Print * stream, int16_t params, int16_t p[]);
|
static bool parsef(Print * stream, int16_t params, int16_t p[]);
|
||||||
bool parseD(Print * stream, int16_t params, int16_t p[]);
|
static bool parseD(Print * stream, int16_t params, int16_t p[]);
|
||||||
|
|
||||||
static Print * getAsyncReplyStream();
|
static Print * getAsyncReplyStream();
|
||||||
static void commitAsyncReplyStream();
|
static void commitAsyncReplyStream();
|
||||||
@@ -61,7 +58,7 @@ struct DCCEXParser
|
|||||||
static RingStream * stashRingStream;
|
static RingStream * stashRingStream;
|
||||||
|
|
||||||
static int16_t stashP[MAX_COMMAND_PARAMS];
|
static int16_t stashP[MAX_COMMAND_PARAMS];
|
||||||
bool stashCallback(Print * stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream);
|
static bool stashCallback(Print * stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream);
|
||||||
static void callback_W(int16_t result);
|
static void callback_W(int16_t result);
|
||||||
static void callback_B(int16_t result);
|
static void callback_B(int16_t result);
|
||||||
static void callback_R(int16_t result);
|
static void callback_R(int16_t result);
|
||||||
|
12
DCCPacket.h
12
DCCPacket.h
@@ -1,12 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum.
|
|
||||||
|
|
||||||
class dccPacket {
|
|
||||||
public:
|
|
||||||
byte data[MAX_PACKET_SIZE+1]; // space for checksum if needed
|
|
||||||
byte length : 4; // future proof up to 15
|
|
||||||
byte repeat : 4; // hopefully 15 enough for ever
|
|
||||||
//byte priority : 2; // 0 repeats; 1 mobile function ; 2 accessory ; 3 mobile speed
|
|
||||||
};
|
|
||||||
|
|
189
DCCRMT.cpp
189
DCCRMT.cpp
@@ -1,189 +0,0 @@
|
|||||||
/*
|
|
||||||
* © 2021, Harald Barth.
|
|
||||||
*
|
|
||||||
* This file is part of DCC-EX
|
|
||||||
*
|
|
||||||
* 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "defines.h"
|
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
|
||||||
#include "DIAG.h"
|
|
||||||
#include "DCCRMT.h"
|
|
||||||
#include "DCCWaveform.h" // for MAX_PACKET_SIZE
|
|
||||||
#include "soc/gpio_sig_map.h"
|
|
||||||
|
|
||||||
#define DATA_LEN(X) ((X)*9+1) // Each byte has one bit extra and we have one EOF marker
|
|
||||||
|
|
||||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,2,0)
|
|
||||||
#error wrong IDF version
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void setDCCBit1(rmt_item32_t* item) {
|
|
||||||
item->level0 = 1;
|
|
||||||
item->duration0 = DCC_1_HALFPERIOD;
|
|
||||||
item->level1 = 0;
|
|
||||||
item->duration1 = DCC_1_HALFPERIOD;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setDCCBit0(rmt_item32_t* item) {
|
|
||||||
item->level0 = 1;
|
|
||||||
item->duration0 = DCC_0_HALFPERIOD;
|
|
||||||
item->level1 = 0;
|
|
||||||
item->duration1 = DCC_0_HALFPERIOD;
|
|
||||||
}
|
|
||||||
|
|
||||||
// special long zero to trigger scope
|
|
||||||
void setDCCBit0Long(rmt_item32_t* item) {
|
|
||||||
item->level0 = 1;
|
|
||||||
item->duration0 = DCC_0_HALFPERIOD + DCC_0_HALFPERIOD/10;
|
|
||||||
item->level1 = 0;
|
|
||||||
item->duration1 = DCC_0_HALFPERIOD + DCC_0_HALFPERIOD/10;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setEOT(rmt_item32_t* item) {
|
|
||||||
item->val = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRAM_ATTR interrupt(rmt_channel_t channel, void *t) {
|
|
||||||
RMTChannel *tt = (RMTChannel *)t;
|
|
||||||
tt->RMTinterrupt();
|
|
||||||
}
|
|
||||||
|
|
||||||
RMTChannel::RMTChannel(byte pin, byte ch, byte plen) {
|
|
||||||
|
|
||||||
// preamble
|
|
||||||
preambleLen = plen+2; // plen 1 bits, one 0 bit and one EOF marker
|
|
||||||
preamble = (rmt_item32_t*)malloc(preambleLen*sizeof(rmt_item32_t));
|
|
||||||
for (byte n=0; n<plen; n++)
|
|
||||||
setDCCBit1(preamble + n); // preamble bits
|
|
||||||
#ifdef SCOPE
|
|
||||||
setDCCBit0Long(preamble + plen); // start of packet 0 bit long version
|
|
||||||
#else
|
|
||||||
setDCCBit0(preamble + plen); // start of packet 0 bit normal version
|
|
||||||
#endif
|
|
||||||
setEOT(preamble + plen + 1); // EOT marker
|
|
||||||
|
|
||||||
// idle
|
|
||||||
idleLen = 28;
|
|
||||||
idle = (rmt_item32_t*)malloc(idleLen*sizeof(rmt_item32_t));
|
|
||||||
for (byte n=0; n<8; n++) // 0 to 7
|
|
||||||
setDCCBit1(idle + n);
|
|
||||||
for (byte n=8; n<18; n++) // 8, 9 to 16, 17
|
|
||||||
setDCCBit0(idle + n);
|
|
||||||
for (byte n=18; n<26; n++) // 18 to 25
|
|
||||||
setDCCBit1(idle + n);
|
|
||||||
setDCCBit1(idle + 26); // end bit
|
|
||||||
setEOT(idle + 27); // EOT marker
|
|
||||||
|
|
||||||
// data: max packet size today is 5 + checksum
|
|
||||||
maxDataLen = DATA_LEN(MAX_PACKET_SIZE);
|
|
||||||
data = (rmt_item32_t*)malloc(maxDataLen*sizeof(rmt_item32_t));
|
|
||||||
|
|
||||||
rmt_config_t config;
|
|
||||||
// Configure the RMT channel for TX
|
|
||||||
bzero(&config, sizeof(rmt_config_t));
|
|
||||||
config.rmt_mode = RMT_MODE_TX;
|
|
||||||
config.channel = channel = (rmt_channel_t)ch;
|
|
||||||
config.clk_div = RMT_CLOCK_DIVIDER;
|
|
||||||
config.gpio_num = (gpio_num_t)pin;
|
|
||||||
config.mem_block_num = 2; // With longest DCC packet 11 inc checksum (future expansion)
|
|
||||||
// number of bits needed is 22preamble + start +
|
|
||||||
// 11*9 + extrazero + EOT = 124
|
|
||||||
// 2 mem block of 64 RMT items should be enough
|
|
||||||
|
|
||||||
ESP_ERROR_CHECK(rmt_config(&config));
|
|
||||||
/*
|
|
||||||
// test: config another gpio pin
|
|
||||||
gpio_num_t gpioNum = (gpio_num_t)(pin-1);
|
|
||||||
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpioNum], PIN_FUNC_GPIO);
|
|
||||||
gpio_set_direction(gpioNum, GPIO_MODE_OUTPUT);
|
|
||||||
gpio_matrix_out(gpioNum, RMT_SIG_OUT0_IDX, 0, 0);
|
|
||||||
*/
|
|
||||||
|
|
||||||
// NOTE: ESP_INTR_FLAG_IRAM is *NOT* included in this bitmask
|
|
||||||
ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, ESP_INTR_FLAG_LOWMED|ESP_INTR_FLAG_SHARED));
|
|
||||||
|
|
||||||
DIAG(F("Register interrupt on core %d"), xPortGetCoreID());
|
|
||||||
|
|
||||||
ESP_ERROR_CHECK(rmt_set_tx_loop_mode(channel, true));
|
|
||||||
rmt_register_tx_end_callback(interrupt, this);
|
|
||||||
rmt_set_tx_intr_en(channel, true);
|
|
||||||
|
|
||||||
DIAG(F("Starting channel %d signal generator"), config.channel);
|
|
||||||
|
|
||||||
// send one bit to kickstart the signal, remaining data will come from the
|
|
||||||
// packet queue. We intentionally do not wait for the RMT TX complete here.
|
|
||||||
//rmt_write_items(channel, preamble, preambleLen, false);
|
|
||||||
RMTprefill();
|
|
||||||
preambleNext = true;
|
|
||||||
dataReady = false;
|
|
||||||
RMTinterrupt();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RMTChannel::RMTprefill() {
|
|
||||||
rmt_fill_tx_items(channel, preamble, preambleLen, 0);
|
|
||||||
rmt_fill_tx_items(channel, idle, idleLen, preambleLen-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const byte transmitMask[] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
|
|
||||||
|
|
||||||
//bool RMTChannel::RMTfillData(const byte buffer[], byte byteCount, byte repeatCount=0) {
|
|
||||||
bool RMTChannel::RMTfillData(dccPacket packet) {
|
|
||||||
// dataReady: Signals to then interrupt routine. It is set when
|
|
||||||
// we have data in the channel buffer which can be copied out
|
|
||||||
// to the HW. dataRepeat on the other hand signals back to
|
|
||||||
// the caller of this function if the data has been sent enough
|
|
||||||
// times (0 to 3 means 1 to 4 times in total).
|
|
||||||
if (dataReady == true || dataRepeat > 0) // we have still old work to do
|
|
||||||
return false;
|
|
||||||
if (DATA_LEN(packet.length) > maxDataLen) { // this would overun our allocated memory for data
|
|
||||||
DIAG(F("Can not convert DCC bytes # %d to DCC bits %d, buffer too small"), packet.length, maxDataLen);
|
|
||||||
return false; // something very broken, can not convert packet
|
|
||||||
}
|
|
||||||
|
|
||||||
byte *buffer = packet.data;
|
|
||||||
|
|
||||||
// convert bytes to RMT stream of "bits"
|
|
||||||
byte bitcounter = 0;
|
|
||||||
for(byte n=0; n<packet.length; n++) {
|
|
||||||
for(byte bit=0; bit<8; bit++) {
|
|
||||||
if (buffer[n] & transmitMask[bit])
|
|
||||||
setDCCBit1(data + bitcounter++);
|
|
||||||
else
|
|
||||||
setDCCBit0(data + bitcounter++);
|
|
||||||
}
|
|
||||||
setDCCBit0(data + bitcounter++); // zero at end of each byte
|
|
||||||
}
|
|
||||||
setDCCBit1(data + bitcounter-1); // overwrite previous zero bit with one bit
|
|
||||||
setEOT(data + bitcounter++); // EOT marker
|
|
||||||
dataLen = bitcounter;
|
|
||||||
dataReady = true;
|
|
||||||
dataRepeat = packet.repeat+1; // repeatCount of 0 means send once
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRAM_ATTR RMTChannel::RMTinterrupt() {
|
|
||||||
//no rmt_tx_start(channel,true) as we run in loop mode
|
|
||||||
//preamble is always loaded at beginning of buffer
|
|
||||||
if (dataReady) { // if we have new data, fill while preamble is running
|
|
||||||
rmt_fill_tx_items(channel, data, dataLen, preambleLen-1);
|
|
||||||
dataReady = false;
|
|
||||||
}
|
|
||||||
if (dataRepeat > 0) // if a repeat count was specified, work on that
|
|
||||||
dataRepeat--;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif //ESP32
|
|
61
DCCRMT.h
61
DCCRMT.h
@@ -1,61 +0,0 @@
|
|||||||
/*
|
|
||||||
* © 2021, Harald Barth.
|
|
||||||
*
|
|
||||||
* This file is part of DCC-EX
|
|
||||||
*
|
|
||||||
* 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <Arduino.h>
|
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
|
||||||
#include "DCCPacket.h"
|
|
||||||
#include "driver/rmt.h"
|
|
||||||
#include "soc/rmt_reg.h"
|
|
||||||
#include "soc/rmt_struct.h"
|
|
||||||
|
|
||||||
// make calculations easy and set up for microseconds
|
|
||||||
#define RMT_CLOCK_DIVIDER 80
|
|
||||||
#define DCC_1_HALFPERIOD 58 //4640 // 1 / 80000000 * 4640 = 58us
|
|
||||||
#define DCC_0_HALFPERIOD 100 //8000
|
|
||||||
|
|
||||||
class RMTChannel {
|
|
||||||
public:
|
|
||||||
RMTChannel(byte pin, byte ch, byte plen);
|
|
||||||
void IRAM_ATTR RMTinterrupt();
|
|
||||||
void RMTprefill();
|
|
||||||
bool RMTfillData(dccPacket packet);
|
|
||||||
//bool RMTfillData(const byte buffer[], byte byteCount, byte repeatCount);
|
|
||||||
|
|
||||||
static RMTChannel mainRMTChannel;
|
|
||||||
static RMTChannel progRMTChannel;
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
rmt_channel_t channel;
|
|
||||||
// 3 types of data to send, preamble and then idle or data
|
|
||||||
// if this is prog track, idle will contain reset instead
|
|
||||||
rmt_item32_t *idle;
|
|
||||||
byte idleLen;
|
|
||||||
rmt_item32_t *preamble;
|
|
||||||
byte preambleLen;
|
|
||||||
rmt_item32_t *data;
|
|
||||||
byte dataLen;
|
|
||||||
byte maxDataLen;
|
|
||||||
// flags
|
|
||||||
volatile bool preambleNext = true; // alternate between preamble and content
|
|
||||||
volatile bool dataReady = false; // do we have real data available or send idle
|
|
||||||
volatile byte dataRepeat = 0;
|
|
||||||
};
|
|
||||||
#endif //ESP32
|
|
61
DCCTimer.cpp
61
DCCTimer.cpp
@@ -1,5 +1,10 @@
|
|||||||
/*
|
/*
|
||||||
* © 2021, Chris Harlow & David Cutting. All rights reserved.
|
* © 2021 Mike S
|
||||||
|
* © 2021 Harald Barth
|
||||||
|
* © 2021 Fred Decker
|
||||||
|
* © 2021 Chris Harlow
|
||||||
|
* © 2021 David Cutting
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC API
|
* This file is part of Asbelos DCC API
|
||||||
*
|
*
|
||||||
@@ -44,7 +49,7 @@
|
|||||||
|
|
||||||
#include "DCCTimer.h"
|
#include "DCCTimer.h"
|
||||||
const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle
|
const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle
|
||||||
const long CLOCK_CYCLES=(F_CPU / 1000000 * DCC_SIGNAL_TIME);
|
const long CLOCK_CYCLES=(F_CPU / 1000000 * DCC_SIGNAL_TIME) >>1;
|
||||||
|
|
||||||
INTERRUPT_CALLBACK interruptHandler=0;
|
INTERRUPT_CALLBACK interruptHandler=0;
|
||||||
|
|
||||||
@@ -53,11 +58,11 @@ INTERRUPT_CALLBACK interruptHandler=0;
|
|||||||
|
|
||||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||||
interruptHandler=callback;
|
interruptHandler=callback;
|
||||||
noInterrupts();
|
noInterrupts();
|
||||||
ADC0.CTRLC = (ADC0.CTRLC & 0b00110000) | 0b01000011; // speed up analogRead sample time
|
ADC0.CTRLC = (ADC0.CTRLC & 0b00110000) | 0b01000011; // speed up analogRead sample time
|
||||||
TCB0.CTRLB = TCB_CNTMODE_INT_gc & ~TCB_CCMPEN_bm; // timer compare mode with output disabled
|
TCB0.CTRLB = TCB_CNTMODE_INT_gc & ~TCB_CCMPEN_bm; // timer compare mode with output disabled
|
||||||
TCB0.CTRLA = TCB_CLKSEL_CLKDIV2_gc; // 8 MHz ~ 0.125 us
|
TCB0.CTRLA = TCB_CLKSEL_CLKDIV2_gc; // 8 MHz ~ 0.125 us
|
||||||
TCB0.CCMP = (CLOCK_CYCLES>>1) -1; // 1 tick less for timer reset
|
TCB0.CCMP = CLOCK_CYCLES -1; // 1 tick less for timer reset
|
||||||
TCB0.INTFLAGS = TCB_CAPT_bm; // clear interrupt request flag
|
TCB0.INTFLAGS = TCB_CAPT_bm; // clear interrupt request flag
|
||||||
TCB0.INTCTRL = TCB_CAPT_bm; // Enable the interrupt
|
TCB0.INTCTRL = TCB_CAPT_bm; // Enable the interrupt
|
||||||
TCB0.CNT = 0;
|
TCB0.CNT = 0;
|
||||||
@@ -146,50 +151,6 @@ void DCCTimer::read(uint8_t word, uint8_t *mac, uint8_t offset) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#elif defined(ARDUINO_ARCH_ESP8266)
|
|
||||||
|
|
||||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
|
||||||
interruptHandler=callback;
|
|
||||||
timer1_disable();
|
|
||||||
|
|
||||||
// There seem to be differnt ways to attach interrupt handler
|
|
||||||
// ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
|
|
||||||
// ETS_FRC_TIMER1_NMI_INTR_ATTACH(interruptHandler);
|
|
||||||
// Let us choose the one from the API
|
|
||||||
timer1_attachInterrupt(interruptHandler);
|
|
||||||
|
|
||||||
// not exactly sure of order:
|
|
||||||
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_LOOP);
|
|
||||||
timer1_write(CLOCK_CYCLES);
|
|
||||||
}
|
|
||||||
// We do not support to use PWM to make the Waveform on ESP
|
|
||||||
bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) {
|
|
||||||
}
|
|
||||||
|
|
||||||
#elif defined(ARDUINO_ARCH_ESP32)
|
|
||||||
// https://www.visualmicro.com/page/Timer-Interrupts-Explained.aspx
|
|
||||||
|
|
||||||
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
|
|
||||||
|
|
||||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
|
||||||
interruptHandler = callback;
|
|
||||||
hw_timer_t *timer = NULL;
|
|
||||||
timer = timerBegin(0, 2, true); // prescaler can be 2 to 65536 so choose 2
|
|
||||||
timerAttachInterrupt(timer, interruptHandler, true);
|
|
||||||
timerAlarmWrite(timer, CLOCK_CYCLES / 6, true); // divide by prescaler*3 (Clockbase is 80Mhz and not F_CPU 240Mhz)
|
|
||||||
timerAlarmEnable(timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We do not support to use PWM to make the Waveform on ESP
|
|
||||||
bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) {
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
#else
|
||||||
// Arduino nano, uno, mega etc
|
// Arduino nano, uno, mega etc
|
||||||
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
|
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
|
||||||
@@ -203,10 +164,10 @@ void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) {
|
|||||||
|
|
||||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||||
interruptHandler=callback;
|
interruptHandler=callback;
|
||||||
noInterrupts();
|
noInterrupts();
|
||||||
ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time
|
ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time
|
||||||
TCCR1A = 0;
|
TCCR1A = 0;
|
||||||
ICR1 = CLOCK_CYCLES>>1;
|
ICR1 = CLOCK_CYCLES;
|
||||||
TCNT1 = 0;
|
TCNT1 = 0;
|
||||||
TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1
|
TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1
|
||||||
TIMSK1 = _BV(TOIE1); // Enable Software interrupt
|
TIMSK1 = _BV(TOIE1); // Enable Software interrupt
|
||||||
|
16
DCCTimer.h
16
DCCTimer.h
@@ -1,6 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* (c) 2021 Mike S. All rights reserved.
|
* © 2021 Mike S
|
||||||
* (c) 2021 Fred Decker. All rights reserved.
|
* © 2021 Harald Barth
|
||||||
|
* © 2021 Fred Decker
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
@@ -37,14 +39,4 @@ class DCCTimer {
|
|||||||
private:
|
private:
|
||||||
};
|
};
|
||||||
|
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
|
||||||
extern portMUX_TYPE timerMux;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if !(defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266))
|
|
||||||
#ifndef IRAM_ATTR
|
|
||||||
#define IRAM_ATTR
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif //DCCTimer.h
|
|
||||||
|
38
DCCTrack.cpp
38
DCCTrack.cpp
@@ -1,38 +0,0 @@
|
|||||||
|
|
||||||
#include "defines.h"
|
|
||||||
#include "DCCTrack.h"
|
|
||||||
#include "DIAG.h"
|
|
||||||
|
|
||||||
DCCTrack::DCCTrack(DCCWaveform *w) {
|
|
||||||
waveform = w;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DCCTrack::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
|
|
||||||
dccPacket packet;
|
|
||||||
|
|
||||||
// add checksum now, makes stuff easier later
|
|
||||||
byte checksum = 0;
|
|
||||||
for (byte b = 0; b < byteCount; b++) {
|
|
||||||
checksum ^= buffer[b];
|
|
||||||
packet.data[b] = buffer[b];
|
|
||||||
}
|
|
||||||
packet.data[byteCount] = checksum;
|
|
||||||
packet.length = byteCount + 1;
|
|
||||||
packet.repeat = repeats;
|
|
||||||
schedulePacket(packet);
|
|
||||||
};
|
|
||||||
|
|
||||||
void DCCTrack::schedulePacket(dccPacket packet) {
|
|
||||||
bool once=true;
|
|
||||||
for (const auto& driver: mD) {
|
|
||||||
if (driver->type() == RMT_MAIN || driver->type() == RMT_PROG) {
|
|
||||||
//DIAG(F("DCCTrack::schedulePacket RMT l=%d d=%x"),packet.length, packet.data[0]);
|
|
||||||
driver->schedulePacket(packet);
|
|
||||||
}
|
|
||||||
if (driver->type() & (TIMER_MAIN | TIMER_PROG) && waveform && once) {
|
|
||||||
//DIAG(F("DCCTrack::schedulePacket WAVE l=%d d=%x"),packet.length, packet.data[0]);
|
|
||||||
waveform->schedulePacket(packet);
|
|
||||||
once=false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
23
DCCTrack.h
23
DCCTrack.h
@@ -1,23 +0,0 @@
|
|||||||
|
|
||||||
#pragma once
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include "DCCPacket.h"
|
|
||||||
#include "DCCWaveform.h"
|
|
||||||
#include "DIAG.h"
|
|
||||||
|
|
||||||
class DCCTrack {
|
|
||||||
public:
|
|
||||||
DCCTrack(DCCWaveform *w);
|
|
||||||
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
|
|
||||||
void schedulePacket(dccPacket packet);
|
|
||||||
inline void addDriver(MotorDriver *m) {
|
|
||||||
mD.push_back(m);
|
|
||||||
};
|
|
||||||
static DCCTrack mainTrack;
|
|
||||||
static DCCTrack progTrack;
|
|
||||||
private:
|
|
||||||
DCCWaveform *waveform;
|
|
||||||
std::vector<MotorDriver *>mD;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
149
DCCWaveform.cpp
149
DCCWaveform.cpp
@@ -1,8 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2021 Neil McKechnie
|
||||||
* © 2020, Harald Barth.
|
* © 2021 Mike S
|
||||||
|
* © 2021 Fred Decker
|
||||||
|
* © 2020-2021 Harald Barth
|
||||||
|
* © 2020-2021 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC API
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
* This is free software: you can redistribute it and/or modify
|
* This is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,26 +24,14 @@
|
|||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
#include "defines.h"
|
|
||||||
#include "DCCWaveform.h"
|
#include "DCCWaveform.h"
|
||||||
#include "DCCTrack.h"
|
|
||||||
#include "DCCTimer.h"
|
#include "DCCTimer.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
#include "freeMemory.h"
|
#include "freeMemory.h"
|
||||||
|
|
||||||
// The two Waveforms which defines what happens when the
|
|
||||||
// interrupt driven DCC signal is generated. This is tied
|
|
||||||
// to the timer interrupts of the hardware.
|
|
||||||
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
|
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
|
||||||
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
||||||
|
|
||||||
|
|
||||||
// The two different DCC _kinds_ of signals we want to be able
|
|
||||||
// to genrate at the same time. When timer interupts are used,
|
|
||||||
// these need the respective waveform
|
|
||||||
DCCTrack DCCTrack::mainTrack(&DCCWaveform::mainTrack);
|
|
||||||
DCCTrack DCCTrack::progTrack(&DCCWaveform::progTrack);
|
|
||||||
|
|
||||||
bool DCCWaveform::progTrackSyncMain=false;
|
bool DCCWaveform::progTrackSyncMain=false;
|
||||||
bool DCCWaveform::progTrackBoosted=false;
|
bool DCCWaveform::progTrackBoosted=false;
|
||||||
int DCCWaveform::progTripValue=0;
|
int DCCWaveform::progTripValue=0;
|
||||||
@@ -48,89 +40,48 @@ volatile uint8_t DCCWaveform::numAckSamples=0;
|
|||||||
uint8_t DCCWaveform::trailingEdgeCounter=0;
|
uint8_t DCCWaveform::trailingEdgeCounter=0;
|
||||||
|
|
||||||
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) {
|
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) {
|
||||||
|
mainTrack.motorDriver=mainDriver;
|
||||||
if(mainDriver) {
|
progTrack.motorDriver=progDriver;
|
||||||
mainTrack.motorDriver=mainDriver;
|
progTripValue = progDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once hence static
|
||||||
mainTrack.setPowerMode(POWERMODE::OFF);
|
mainTrack.setPowerMode(POWERMODE::OFF);
|
||||||
}
|
progTrack.setPowerMode(POWERMODE::OFF);
|
||||||
if(progDriver) {
|
// Fault pin config for odd motor boards (example pololu)
|
||||||
progTrack.motorDriver=progDriver;
|
MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin())
|
||||||
progTripValue = progDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once hence static
|
&& (mainDriver->getFaultPin() != UNUSED_PIN));
|
||||||
progTrack.setPowerMode(POWERMODE::OFF);
|
// Only use PWM if both pins are PWM capable. Otherwise JOIN does not work
|
||||||
}
|
MotorDriver::usePWM= mainDriver->isPWMCapable() && progDriver->isPWMCapable();
|
||||||
if(mainDriver && progDriver) {
|
DIAG(F("Signal pin config: %S accuracy waveform"),
|
||||||
// Fault pin config for odd motor boards (example pololu)
|
|
||||||
MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin())
|
|
||||||
&& (mainDriver->getFaultPin() != UNUSED_PIN));
|
|
||||||
// Only use PWM if both pins are PWM capable. Otherwise JOIN does not work
|
|
||||||
MotorDriver::usePWM= mainDriver->isPWMCapable() && progDriver->isPWMCapable();
|
|
||||||
}
|
|
||||||
if(mainDriver || progDriver) {
|
|
||||||
DIAG(F("Signal pin config: %S accuracy waveform"),
|
|
||||||
MotorDriver::usePWM ? F("high") : F("normal") );
|
MotorDriver::usePWM ? F("high") : F("normal") );
|
||||||
}
|
DCCTimer::begin(DCCWaveform::interruptHandler);
|
||||||
DCCTimer::begin(DCCWaveform::interruptHandler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef SLOW_ANALOG_READ
|
void DCCWaveform::loop(bool ackManagerActive) {
|
||||||
// Flag to hold if we need to run ack checking in loop
|
|
||||||
volatile bool ackflag = 0;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void IRAM_ATTR DCCWaveform::loop(bool ackManagerActive) {
|
|
||||||
|
|
||||||
//if (mainTrack.packetPendingRMT) {
|
|
||||||
// mainTrack.rmtPin->RMTfillData(mainTrack.pendingPacket, mainTrack.pendingLength, mainTrack.pendingRepeats);
|
|
||||||
// mainTrack.packetPendingRMT=false;
|
|
||||||
// sentResetsSincePacket = 0 // later when progtrack
|
|
||||||
//}
|
|
||||||
|
|
||||||
#ifdef SLOW_ANALOG_READ
|
|
||||||
if (ackflag) {
|
|
||||||
progTrack.checkAck();
|
|
||||||
// reset flag AFTER check is done
|
|
||||||
portENTER_CRITICAL(&timerMux);
|
|
||||||
ackflag = 0;
|
|
||||||
portEXIT_CRITICAL(&timerMux);
|
|
||||||
} else {
|
|
||||||
progTrack.checkPowerOverload(ackManagerActive);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
progTrack.checkPowerOverload(ackManagerActive);
|
|
||||||
#endif
|
|
||||||
mainTrack.checkPowerOverload(false);
|
mainTrack.checkPowerOverload(false);
|
||||||
|
progTrack.checkPowerOverload(ackManagerActive);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma GCC push_options
|
#pragma GCC push_options
|
||||||
#pragma GCC optimize ("-O3")
|
#pragma GCC optimize ("-O3")
|
||||||
void IRAM_ATTR DCCWaveform::interruptHandler() {
|
void DCCWaveform::interruptHandler() {
|
||||||
// call the timer edge sensitive actions for progtrack and maintrack
|
// call the timer edge sensitive actions for progtrack and maintrack
|
||||||
// member functions would be cleaner but have more overhead
|
// member functions would be cleaner but have more overhead
|
||||||
byte sigMain=signalTransform[mainTrack.state];
|
byte sigMain=signalTransform[mainTrack.state];
|
||||||
byte sigProg=progTrackSyncMain? sigMain : signalTransform[progTrack.state];
|
byte sigProg=progTrackSyncMain? sigMain : signalTransform[progTrack.state];
|
||||||
|
|
||||||
// Set the signal state for both tracks
|
// Set the signal state for both tracks
|
||||||
if (mainTrack.motorDriver)
|
mainTrack.motorDriver->setSignal(sigMain);
|
||||||
mainTrack.motorDriver->setSignal(sigMain);
|
progTrack.motorDriver->setSignal(sigProg);
|
||||||
if (progTrack.motorDriver)
|
|
||||||
progTrack.motorDriver->setSignal(sigProg);
|
|
||||||
// Move on in the state engine
|
// Move on in the state engine
|
||||||
mainTrack.state=stateTransform[mainTrack.state];
|
mainTrack.state=stateTransform[mainTrack.state];
|
||||||
progTrack.state=stateTransform[progTrack.state];
|
progTrack.state=stateTransform[progTrack.state];
|
||||||
|
|
||||||
|
|
||||||
// WAVE_PENDING means we dont yet know what the next bit is
|
// WAVE_PENDING means we dont yet know what the next bit is
|
||||||
if (mainTrack.state==WAVE_PENDING)
|
if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2();
|
||||||
mainTrack.interrupt2();
|
if (progTrack.state==WAVE_PENDING) progTrack.interrupt2();
|
||||||
if (progTrack.state==WAVE_PENDING)
|
else if (progTrack.ackPending) progTrack.checkAck();
|
||||||
progTrack.interrupt2();
|
|
||||||
#ifdef SLOW_ANALOG_READ
|
|
||||||
else if (progTrack.ackPending && ackflag == 0) { // We need AND we are not already checking
|
|
||||||
portENTER_CRITICAL(&timerMux);
|
|
||||||
ackflag = 1;
|
|
||||||
portEXIT_CRITICAL(&timerMux);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
else if (progTrack.ackPending)
|
|
||||||
progTrack.checkAck();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
#pragma GCC push_options
|
#pragma GCC push_options
|
||||||
|
|
||||||
@@ -147,7 +98,6 @@ const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
|
|||||||
DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
||||||
isMainTrack = isMain;
|
isMainTrack = isMain;
|
||||||
packetPending = false;
|
packetPending = false;
|
||||||
packetPendingRMT = false;
|
|
||||||
memcpy(transmitPacket, idlePacket, sizeof(idlePacket));
|
memcpy(transmitPacket, idlePacket, sizeof(idlePacket));
|
||||||
state = WAVE_START;
|
state = WAVE_START;
|
||||||
// The +1 below is to allow the preamble generator to create the stop bit
|
// The +1 below is to allow the preamble generator to create the stop bit
|
||||||
@@ -167,14 +117,12 @@ POWERMODE DCCWaveform::getPowerMode() {
|
|||||||
void DCCWaveform::setPowerMode(POWERMODE mode) {
|
void DCCWaveform::setPowerMode(POWERMODE mode) {
|
||||||
powerMode = mode;
|
powerMode = mode;
|
||||||
bool ison = (mode == POWERMODE::ON);
|
bool ison = (mode == POWERMODE::ON);
|
||||||
if (motorDriver)
|
motorDriver->setPower( ison);
|
||||||
motorDriver->setPower( ison);
|
|
||||||
sentResetsSincePacket=0;
|
sentResetsSincePacket=0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DCCWaveform::checkPowerOverload(bool ackManagerActive) {
|
void DCCWaveform::checkPowerOverload(bool ackManagerActive) {
|
||||||
if (!motorDriver) return;
|
|
||||||
if (millis() - lastSampleTaken < sampleDelay) return;
|
if (millis() - lastSampleTaken < sampleDelay) return;
|
||||||
lastSampleTaken = millis();
|
lastSampleTaken = millis();
|
||||||
int tripValue= motorDriver->getRawCurrentTripValue();
|
int tripValue= motorDriver->getRawCurrentTripValue();
|
||||||
@@ -258,7 +206,7 @@ const bool DCCWaveform::signalTransform[]={
|
|||||||
|
|
||||||
#pragma GCC push_options
|
#pragma GCC push_options
|
||||||
#pragma GCC optimize ("-O3")
|
#pragma GCC optimize ("-O3")
|
||||||
void IRAM_ATTR DCCWaveform::interrupt2() {
|
void DCCWaveform::interrupt2() {
|
||||||
// calculate the next bit to be sent:
|
// calculate the next bit to be sent:
|
||||||
// set state WAVE_MID_1 for a 1=bit
|
// set state WAVE_MID_1 for a 1=bit
|
||||||
// or WAVE_HIGH_0 for a 0 bit.
|
// or WAVE_HIGH_0 for a 0 bit.
|
||||||
@@ -268,9 +216,7 @@ void IRAM_ATTR DCCWaveform::interrupt2() {
|
|||||||
remainingPreambles--;
|
remainingPreambles--;
|
||||||
// Update free memory diagnostic as we don't have anything else to do this time.
|
// Update free memory diagnostic as we don't have anything else to do this time.
|
||||||
// Allow for checkAck and its called functions using 22 bytes more.
|
// Allow for checkAck and its called functions using 22 bytes more.
|
||||||
#ifndef ESP_FAMILY
|
updateMinimumFreeMemory(22);
|
||||||
updateMinimumFreeMemory(22);
|
|
||||||
#endif
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,8 +241,7 @@ void IRAM_ATTR DCCWaveform::interrupt2() {
|
|||||||
transmitRepeats--;
|
transmitRepeats--;
|
||||||
}
|
}
|
||||||
else if (packetPending) {
|
else if (packetPending) {
|
||||||
portENTER_CRITICAL(&timerMux);
|
// Copy pending packet to transmit packet
|
||||||
// Copy pending packet to transmit packet
|
|
||||||
// a fixed length memcpy is faster than a variable length loop for these small lengths
|
// a fixed length memcpy is faster than a variable length loop for these small lengths
|
||||||
// for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b];
|
// for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b];
|
||||||
memcpy( transmitPacket, pendingPacket, sizeof(pendingPacket));
|
memcpy( transmitPacket, pendingPacket, sizeof(pendingPacket));
|
||||||
@@ -305,7 +250,6 @@ void IRAM_ATTR DCCWaveform::interrupt2() {
|
|||||||
transmitRepeats = pendingRepeats;
|
transmitRepeats = pendingRepeats;
|
||||||
packetPending = false;
|
packetPending = false;
|
||||||
sentResetsSincePacket=0;
|
sentResetsSincePacket=0;
|
||||||
portEXIT_CRITICAL(&timerMux);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Fortunately reset and idle packets are the same length
|
// Fortunately reset and idle packets are the same length
|
||||||
@@ -322,29 +266,26 @@ void IRAM_ATTR DCCWaveform::interrupt2() {
|
|||||||
|
|
||||||
// Wait until there is no packet pending, then make this pending
|
// Wait until there is no packet pending, then make this pending
|
||||||
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
|
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
|
||||||
if (byteCount > MAX_PACKET_SIZE+1) return; // has chksum
|
if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum
|
||||||
while (packetPending);
|
while (packetPending);
|
||||||
portENTER_CRITICAL(&timerMux);
|
|
||||||
//byte checksum = 0;
|
byte checksum = 0;
|
||||||
for (byte b = 0; b < byteCount; b++) {
|
for (byte b = 0; b < byteCount; b++) {
|
||||||
//checksum ^= buffer[b];
|
checksum ^= buffer[b];
|
||||||
pendingPacket[b] = buffer[b];
|
pendingPacket[b] = buffer[b];
|
||||||
}
|
}
|
||||||
// buffer is MAX_PACKET_SIZE but pendingPacket is one bigger
|
// buffer is MAX_PACKET_SIZE but pendingPacket is one bigger
|
||||||
//pendingPacket[byteCount] = checksum;
|
pendingPacket[byteCount] = checksum;
|
||||||
pendingLength = byteCount /*+ 1*/;
|
pendingLength = byteCount + 1;
|
||||||
pendingRepeats = repeats;
|
pendingRepeats = repeats;
|
||||||
packetPending = true;
|
packetPending = true;
|
||||||
packetPendingRMT = true;
|
|
||||||
sentResetsSincePacket=0;
|
sentResetsSincePacket=0;
|
||||||
portEXIT_CRITICAL(&timerMux);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Operations applicable to PROG track ONLY.
|
// Operations applicable to PROG track ONLY.
|
||||||
// (yes I know I could have subclassed the main track but...)
|
// (yes I know I could have subclassed the main track but...)
|
||||||
|
|
||||||
void DCCWaveform::setAckBaseline() {
|
void DCCWaveform::setAckBaseline() {
|
||||||
if (!motorDriver) return;
|
|
||||||
if (isMainTrack) return;
|
if (isMainTrack) return;
|
||||||
int baseline=motorDriver->getCurrentRaw();
|
int baseline=motorDriver->getCurrentRaw();
|
||||||
ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA);
|
ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA);
|
||||||
@@ -367,7 +308,6 @@ void DCCWaveform::setAckPending() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
byte DCCWaveform::getAck() {
|
byte DCCWaveform::getAck() {
|
||||||
if (!motorDriver) return 0;
|
|
||||||
if (ackPending) return (2); // still waiting
|
if (ackPending) return (2); // still waiting
|
||||||
if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%duS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
|
if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%duS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
|
||||||
ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps);
|
ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps);
|
||||||
@@ -377,15 +317,14 @@ byte DCCWaveform::getAck() {
|
|||||||
|
|
||||||
#pragma GCC push_options
|
#pragma GCC push_options
|
||||||
#pragma GCC optimize ("-O3")
|
#pragma GCC optimize ("-O3")
|
||||||
void IRAM_ATTR DCCWaveform::checkAck() {
|
void DCCWaveform::checkAck() {
|
||||||
if (!motorDriver) return;
|
|
||||||
// This function operates in interrupt() time so must be fast and can't DIAG
|
// This function operates in interrupt() time so must be fast and can't DIAG
|
||||||
if (sentResetsSincePacket > 6) { //ACK timeout
|
if (sentResetsSincePacket > 6) { //ACK timeout
|
||||||
ackCheckDuration=millis()-ackCheckStart;
|
ackCheckDuration=millis()-ackCheckStart;
|
||||||
ackPending = false;
|
ackPending = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int current=motorDriver->getCurrentRaw();
|
int current=motorDriver->getCurrentRaw();
|
||||||
numAckSamples++;
|
numAckSamples++;
|
||||||
if (current > ackMaxCurrent) ackMaxCurrent=current;
|
if (current > ackMaxCurrent) ackMaxCurrent=current;
|
||||||
|
@@ -1,8 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2021 M Steve Todd
|
||||||
* © 2020, Harald Barth.
|
* © 2021 Mike S
|
||||||
|
* © 2021 Fred Decker
|
||||||
|
* © 2020-2021 Harald Barth
|
||||||
|
* © 2020-2021 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC API
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
* This is free software: you can redistribute it and/or modify
|
* This is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,7 +24,6 @@
|
|||||||
#ifndef DCCWaveform_h
|
#ifndef DCCWaveform_h
|
||||||
#define DCCWaveform_h
|
#define DCCWaveform_h
|
||||||
|
|
||||||
#include "DCCRMT.h"
|
|
||||||
#include "MotorDriver.h"
|
#include "MotorDriver.h"
|
||||||
|
|
||||||
// Wait times for power management. Unit: milliseconds
|
// Wait times for power management. Unit: milliseconds
|
||||||
@@ -28,8 +31,10 @@ const int POWER_SAMPLE_ON_WAIT = 100;
|
|||||||
const int POWER_SAMPLE_OFF_WAIT = 1000;
|
const int POWER_SAMPLE_OFF_WAIT = 1000;
|
||||||
const int POWER_SAMPLE_OVERLOAD_WAIT = 20;
|
const int POWER_SAMPLE_OVERLOAD_WAIT = 20;
|
||||||
|
|
||||||
//const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum.
|
// Number of preamble bits.
|
||||||
#include "DCCPacket.h"
|
const int PREAMBLE_BITS_MAIN = 16;
|
||||||
|
const int PREAMBLE_BITS_PROG = 22;
|
||||||
|
const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum.
|
||||||
|
|
||||||
// The WAVE_STATE enum is deliberately numbered because a change of order would be catastrophic
|
// The WAVE_STATE enum is deliberately numbered because a change of order would be catastrophic
|
||||||
// to the transform array.
|
// to the transform array.
|
||||||
@@ -79,12 +84,8 @@ class DCCWaveform {
|
|||||||
}
|
}
|
||||||
return tripmA;
|
return tripmA;
|
||||||
}
|
}
|
||||||
inline void schedulePacket(dccPacket packet) {
|
|
||||||
schedulePacket(packet.data, packet.length, packet.repeat);
|
|
||||||
};
|
|
||||||
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
|
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
|
||||||
volatile bool packetPending;
|
volatile bool packetPending;
|
||||||
volatile bool packetPendingRMT;
|
|
||||||
volatile byte sentResetsSincePacket;
|
volatile byte sentResetsSincePacket;
|
||||||
volatile bool autoPowerOff=false;
|
volatile bool autoPowerOff=false;
|
||||||
void setAckBaseline(); //prog track only
|
void setAckBaseline(); //prog track only
|
||||||
|
6
DIAG.h
6
DIAG.h
@@ -1,7 +1,9 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2021 Fred Decker
|
||||||
|
* © 2020 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC API
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
* This is free software: you can redistribute it and/or modify
|
* This is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
|
* © 2021 Neil McKechnie
|
||||||
|
* © 2021 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
@@ -19,4 +21,4 @@
|
|||||||
|
|
||||||
#include "DisplayInterface.h"
|
#include "DisplayInterface.h"
|
||||||
|
|
||||||
DisplayInterface *DisplayInterface::lcdDisplay = 0;
|
DisplayInterface *DisplayInterface::lcdDisplay = 0;
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
|
* © 2021 Neil McKechnie
|
||||||
|
* © 2021 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
@@ -32,4 +34,4 @@ public:
|
|||||||
static DisplayInterface *lcdDisplay;
|
static DisplayInterface *lcdDisplay;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
13
EEStore.cpp
13
EEStore.cpp
@@ -1,9 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
|
* © 2021 Neil McKechnie
|
||||||
|
* © 2021 Fred Decker
|
||||||
|
* © 2020-2021 Harald Barth
|
||||||
|
* © 2020-2021 Chris Harlow
|
||||||
* © 2013-2016 Gregg E. Berman
|
* © 2013-2016 Gregg E. Berman
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* All rights reserved.
|
||||||
* © 2020, Harald Barth.
|
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC API
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
* This is free software: you can redistribute it and/or modify
|
* This is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -18,6 +21,9 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "defines.h"
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
#include "EEStore.h"
|
#include "EEStore.h"
|
||||||
|
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
@@ -103,3 +109,4 @@ void EEStore::dump(int num) {
|
|||||||
|
|
||||||
EEStore *EEStore::eeStore = NULL;
|
EEStore *EEStore::eeStore = NULL;
|
||||||
int EEStore::eeAddress = 0;
|
int EEStore::eeAddress = 0;
|
||||||
|
#endif
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
/*
|
/*
|
||||||
* (c) 2020 Chris Harlow. All rights reserved.
|
* © 2021 Neil McKechnie
|
||||||
* (c) 2020 Harald Barth. All rights reserved.
|
* © 2021 Fred Decker
|
||||||
|
* © 2020-2021 Harald Barth
|
||||||
|
* © 2020 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
@@ -17,6 +20,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
#ifndef EEStore_h
|
#ifndef EEStore_h
|
||||||
#define EEStore_h
|
#define EEStore_h
|
||||||
|
|
||||||
@@ -52,3 +56,4 @@ struct EEStore{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
#endif // DISABLE_EEPROM
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020,Gregor Baues, Chris Harlow. All rights reserved.
|
* © 2021 Fred Decker
|
||||||
|
* © 2020-2021 Harald Barth
|
||||||
|
* © 2020-2021 Chris Harlow
|
||||||
|
* © 2020 Gregor Baues
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of DCC-EX/CommandStation-EX
|
* This file is part of DCC-EX/CommandStation-EX
|
||||||
*
|
*
|
||||||
@@ -170,6 +174,7 @@ void EthernetInterface::loop()
|
|||||||
for (int socket = 0; socket<MAX_SOCK_NUM; socket++) {
|
for (int socket = 0; socket<MAX_SOCK_NUM; socket++) {
|
||||||
if (clients[socket] && !clients[socket].connected()) {
|
if (clients[socket] && !clients[socket].connected()) {
|
||||||
clients[socket].stop();
|
clients[socket].stop();
|
||||||
|
CommandDistributor::forget(socket);
|
||||||
if (Diag::ETHERNET) DIAG(F("Ethernet: disconnect %d "), socket);
|
if (Diag::ETHERNET) DIAG(F("Ethernet: disconnect %d "), socket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,10 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020,Gregor Baues, Chris Harlow. All rights reserved.
|
* © 2021 Neil McKechnie
|
||||||
|
* © 2021 Mike S
|
||||||
|
* © 2021 Fred Decker
|
||||||
|
* © 2020-2021 Chris Harlow
|
||||||
|
* © 2020 Gregor Baues
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of DCC-EX/CommandStation-EX
|
* This file is part of DCC-EX/CommandStation-EX
|
||||||
*
|
*
|
||||||
|
7
FSH.h
7
FSH.h
@@ -1,5 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* (c) 2021 Fred Decker. All rights reserved.
|
* © 2021 Neil McKechnie
|
||||||
|
* © 2021 Harald Barth
|
||||||
|
* © 2021 Fred Decker
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
@@ -41,7 +44,6 @@
|
|||||||
typedef char FSH;
|
typedef char FSH;
|
||||||
#define GETFLASH(addr) (*(const unsigned char *)(addr))
|
#define GETFLASH(addr) (*(const unsigned char *)(addr))
|
||||||
#define GETFLASHW(addr) (*(const unsigned short *)(addr))
|
#define GETFLASHW(addr) (*(const unsigned short *)(addr))
|
||||||
#define GETFLASHP(addr) (*(void * const *)(addr))
|
|
||||||
#define FLASH
|
#define FLASH
|
||||||
#define strlen_P strlen
|
#define strlen_P strlen
|
||||||
#define strcpy_P strcpy
|
#define strcpy_P strcpy
|
||||||
@@ -49,7 +51,6 @@ typedef char FSH;
|
|||||||
typedef __FlashStringHelper FSH;
|
typedef __FlashStringHelper FSH;
|
||||||
#define GETFLASH(addr) pgm_read_byte_near(addr)
|
#define GETFLASH(addr) pgm_read_byte_near(addr)
|
||||||
#define GETFLASHW(addr) pgm_read_word_near(addr)
|
#define GETFLASHW(addr) pgm_read_word_near(addr)
|
||||||
#define GETFLASHP(addr) pgm_read_ptr_near(addr)
|
|
||||||
#define FLASH PROGMEM
|
#define FLASH PROGMEM
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
@@ -1 +1 @@
|
|||||||
#define GITHUB_SHA "ESP32-motordriver-2021129-00:12"
|
#define GITHUB_SHA "35f7ac3"
|
||||||
|
@@ -72,7 +72,7 @@ void I2CManagerClass::I2C_sendStart() {
|
|||||||
bytesToReceive = currentRequest->readLen;
|
bytesToReceive = currentRequest->readLen;
|
||||||
|
|
||||||
// If anything to send, initiate write. Otherwise initiate read.
|
// If anything to send, initiate write. Otherwise initiate read.
|
||||||
if (operation == OPERATION_READ || (operation == OPERATION_REQUEST & !bytesToSend))
|
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) & !bytesToSend))
|
||||||
TWI0.MADDR = (currentRequest->i2cAddress << 1) | 1;
|
TWI0.MADDR = (currentRequest->i2cAddress << 1) | 1;
|
||||||
else
|
else
|
||||||
TWI0.MADDR = (currentRequest->i2cAddress << 1) | 0;
|
TWI0.MADDR = (currentRequest->i2cAddress << 1) | 0;
|
||||||
@@ -157,4 +157,4 @@ ISR(TWI0_TWIM_vect) {
|
|||||||
I2CManagerClass::handleInterrupt();
|
I2CManagerClass::handleInterrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -98,22 +98,20 @@ uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t rea
|
|||||||
* returned in the I2CRB as for the asynchronous version.
|
* returned in the I2CRB as for the asynchronous version.
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
void I2CManagerClass::queueRequest(I2CRB *req) {
|
void I2CManagerClass::queueRequest(I2CRB *req) {
|
||||||
uint8_t status;
|
|
||||||
switch (req->operation) {
|
switch (req->operation) {
|
||||||
case OPERATION_READ:
|
case OPERATION_READ:
|
||||||
status = read(req->i2cAddress, req->readBuffer, req->readLen, NULL, 0, req);
|
req->status = read(req->i2cAddress, req->readBuffer, req->readLen, NULL, 0, req);
|
||||||
break;
|
break;
|
||||||
case OPERATION_SEND:
|
case OPERATION_SEND:
|
||||||
status = write(req->i2cAddress, req->writeBuffer, req->writeLen, req);
|
req->status = write(req->i2cAddress, req->writeBuffer, req->writeLen, req);
|
||||||
break;
|
break;
|
||||||
case OPERATION_SEND_P:
|
case OPERATION_SEND_P:
|
||||||
status = write_P(req->i2cAddress, req->writeBuffer, req->writeLen, req);
|
req->status = write_P(req->i2cAddress, req->writeBuffer, req->writeLen, req);
|
||||||
break;
|
break;
|
||||||
case OPERATION_REQUEST:
|
case OPERATION_REQUEST:
|
||||||
status = read(req->i2cAddress, req->readBuffer, req->readLen, req->writeBuffer, req->writeLen, req);
|
req->status = read(req->i2cAddress, req->readBuffer, req->readLen, req->writeBuffer, req->writeLen, req);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
req->status = status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* © 2021, Neil McKechnie. All rights reserved.
|
* © 2021 Neil McKechnie
|
||||||
|
* © 2021 Harald Barth
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of DCC++EX API
|
* This file is part of DCC++EX API
|
||||||
*
|
*
|
||||||
|
@@ -47,11 +47,15 @@ void DCCAccessoryDecoder::_begin() {
|
|||||||
// Device-specific write function. State 1=closed, 0=thrown. Adjust for RCN-213 compliance
|
// Device-specific write function. State 1=closed, 0=thrown. Adjust for RCN-213 compliance
|
||||||
void DCCAccessoryDecoder::_write(VPIN id, int state) {
|
void DCCAccessoryDecoder::_write(VPIN id, int state) {
|
||||||
int packedAddress = _packedAddress + id - _firstVpin;
|
int packedAddress = _packedAddress + id - _firstVpin;
|
||||||
#ifdef DIAG_IO
|
#if defined(HAL_ACCESSORY_COMMAND_REVERSE)
|
||||||
DIAG(F("DCC Write Linear Address:%d State:%d"), packedAddress, state);
|
|
||||||
#endif
|
|
||||||
#if !defined(DCC_ACCESSORY_RCN_213)
|
|
||||||
state = !state;
|
state = !state;
|
||||||
|
#ifdef DIAG_IO
|
||||||
|
DIAG(F("DCC Write Linear Address:%d State:%d (inverted)"), packedAddress, state);
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#ifdef DIAG_IO
|
||||||
|
DIAG(F("DCC Write Linear Address:%d State:%d"), packedAddress, state);
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
DCC::setAccessory(ADDRESS(packedAddress), SUBADDRESS(packedAddress), state);
|
DCC::setAccessory(ADDRESS(packedAddress), SUBADDRESS(packedAddress), state);
|
||||||
}
|
}
|
||||||
|
@@ -69,10 +69,6 @@ protected:
|
|||||||
|
|
||||||
I2CRB requestBlock;
|
I2CRB requestBlock;
|
||||||
FSH *_deviceName;
|
FSH *_deviceName;
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
|
||||||
// workaround: Has somehow no min function for all types
|
|
||||||
static inline T min(T a, int b) { return a < b ? a : b; };
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Because class GPIOBase is a template, the implementation (below) must be contained within the same
|
// Because class GPIOBase is a template, the implementation (below) must be contained within the same
|
||||||
@@ -250,4 +246,4 @@ int GPIOBase<T>::_read(VPIN vpin) {
|
|||||||
return (_portInputState & mask) ? 0 : 1; // Invert state (5v=0, 0v=1)
|
return (_portInputState & mask) ? 0 : 1; // Invert state (5v=0, 0v=1)
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
@@ -97,4 +97,4 @@ private:
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
@@ -99,4 +99,4 @@ private:
|
|||||||
uint8_t inputBuffer[1];
|
uint8_t inputBuffer[1];
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
4
LCN.h
4
LCN.h
@@ -1,5 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* (c) 2021 Fred Decker. All rights reserved.
|
* © 2021 Harald Barth
|
||||||
|
* © 2021 Fred Decker
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
|
137
MotorDriver.cpp
137
MotorDriver.cpp
@@ -1,7 +1,11 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2021 Mike S
|
||||||
|
* © 2021 Fred Decker
|
||||||
|
* © 2020-2022 Harald Barth
|
||||||
|
* © 2020-2021 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC API
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
* This is free software: you can redistribute it and/or modify
|
* This is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -17,47 +21,35 @@
|
|||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "config.h"
|
|
||||||
#include "defines.h"
|
|
||||||
#include "MotorDriver.h"
|
#include "MotorDriver.h"
|
||||||
#include "DCCTimer.h"
|
#include "DCCTimer.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
|
||||||
#include <driver/adc.h>
|
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
|
||||||
#define pinToADC1Channel(X) (adc1_channel_t)(((X) > 35) ? (X)-36 : (X)-28)
|
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
|
||||||
#endif
|
#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH)
|
||||||
|
#define isLOW(fastpin) (!isHIGH(fastpin))
|
||||||
|
|
||||||
bool MotorDriver::usePWM=false;
|
bool MotorDriver::usePWM=false;
|
||||||
bool MotorDriver::commonFaultPin=false;
|
bool MotorDriver::commonFaultPin=false;
|
||||||
|
|
||||||
MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||||
byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin,
|
byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin) {
|
||||||
driverType dt) {
|
|
||||||
dtype = dt;
|
|
||||||
powerPin=power_pin;
|
powerPin=power_pin;
|
||||||
getFastPin(F("POWER"),powerPin,fastPowerPin);
|
getFastPin(F("POWER"),powerPin,fastPowerPin);
|
||||||
pinMode(powerPin, OUTPUT);
|
pinMode(powerPin, OUTPUT);
|
||||||
|
|
||||||
if (dtype == RMT_MAIN) {
|
signalPin=signal_pin;
|
||||||
signalPin=signal_pin;
|
getFastPin(F("SIG"),signalPin,fastSignalPin);
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
pinMode(signalPin, OUTPUT);
|
||||||
rmtChannel = new RMTChannel(signalPin, 0, PREAMBLE_BITS_MAIN);
|
|
||||||
#endif
|
signalPin2=signal_pin2;
|
||||||
dualSignal=false;
|
if (signalPin2!=UNUSED_PIN) {
|
||||||
} else if (dtype & (TIMER_MAIN | TIMER_PROG)) {
|
dualSignal=true;
|
||||||
signalPin=signal_pin;
|
getFastPin(F("SIG2"),signalPin2,fastSignalPin2);
|
||||||
getFastPin(F("SIG"),signalPin,fastSignalPin);
|
pinMode(signalPin2, OUTPUT);
|
||||||
pinMode(signalPin, OUTPUT);
|
|
||||||
|
|
||||||
signalPin2=signal_pin2;
|
|
||||||
if (signalPin2!=UNUSED_PIN) {
|
|
||||||
dualSignal=true;
|
|
||||||
getFastPin(F("SIG2"),signalPin2,fastSignalPin2);
|
|
||||||
pinMode(signalPin2, OUTPUT);
|
|
||||||
} else {
|
|
||||||
dualSignal=false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else dualSignal=false;
|
||||||
|
|
||||||
brakePin=brake_pin;
|
brakePin=brake_pin;
|
||||||
if (brake_pin!=UNUSED_PIN){
|
if (brake_pin!=UNUSED_PIN){
|
||||||
@@ -71,15 +63,8 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8
|
|||||||
|
|
||||||
currentPin=current_pin;
|
currentPin=current_pin;
|
||||||
if (currentPin!=UNUSED_PIN) {
|
if (currentPin!=UNUSED_PIN) {
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
|
||||||
pinMode(currentPin, ANALOG);
|
|
||||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
|
||||||
adc1_config_channel_atten(pinToADC1Channel(currentPin),ADC_ATTEN_DB_11);
|
|
||||||
senseOffset = adc1_get_raw(pinToADC1Channel(currentPin));
|
|
||||||
#else
|
|
||||||
pinMode(currentPin, INPUT);
|
pinMode(currentPin, INPUT);
|
||||||
senseOffset=analogRead(currentPin); // value of sensor at zero current
|
senseOffset=analogRead(currentPin); // value of sensor at zero current
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
faultPin=fault_pin;
|
faultPin=fault_pin;
|
||||||
@@ -95,7 +80,7 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8
|
|||||||
if (currentPin==UNUSED_PIN)
|
if (currentPin==UNUSED_PIN)
|
||||||
DIAG(F("MotorDriver ** WARNING ** No current or short detection"));
|
DIAG(F("MotorDriver ** WARNING ** No current or short detection"));
|
||||||
else
|
else
|
||||||
DIAG(F("MotorDriver currentPin=A%d, senseOffset=%d, rawCurentTripValue(relative to offset)=%d"),
|
DIAG(F("MotorDriver currentPin=A%d, senseOffset=%d, rawCurrentTripValue(relative to offset)=%d"),
|
||||||
currentPin-A0, senseOffset,rawCurrentTripValue);
|
currentPin-A0, senseOffset,rawCurrentTripValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +114,7 @@ void MotorDriver::setBrake(bool on) {
|
|||||||
else setLOW(fastBrakePin);
|
else setLOW(fastBrakePin);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IRAM_ATTR MotorDriver::setSignal( bool high) {
|
void MotorDriver::setSignal( bool high) {
|
||||||
if (usePWM) {
|
if (usePWM) {
|
||||||
DCCTimer::setPWM(signalPin,high);
|
DCCTimer::setPWM(signalPin,high);
|
||||||
}
|
}
|
||||||
@@ -174,8 +159,6 @@ int MotorDriver::getCurrentRaw() {
|
|||||||
current = analogRead(currentPin)-senseOffset;
|
current = analogRead(currentPin)-senseOffset;
|
||||||
overflow_count = 0;
|
overflow_count = 0;
|
||||||
SREG = sreg_backup; /* restore interrupt state */
|
SREG = sreg_backup; /* restore interrupt state */
|
||||||
#elif defined(ARDUINO_ARCH_ESP32)
|
|
||||||
current = adc1_get_raw(pinToADC1Channel(currentPin))-senseOffset;
|
|
||||||
#else
|
#else
|
||||||
current = analogRead(currentPin)-senseOffset;
|
current = analogRead(currentPin)-senseOffset;
|
||||||
#endif
|
#endif
|
||||||
@@ -197,8 +180,8 @@ int MotorDriver::mA2raw( unsigned int mA) {
|
|||||||
|
|
||||||
void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & result) {
|
void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & result) {
|
||||||
// DIAG(F("MotorDriver %S Pin=%d,"),type,pin);
|
// DIAG(F("MotorDriver %S Pin=%d,"),type,pin);
|
||||||
(void) type; // avoid compiler warning if diag not used above.
|
(void) type; // avoid compiler warning if diag not used above.
|
||||||
PORTTYPE port = digitalPinToPort(pin);
|
uint8_t port = digitalPinToPort(pin);
|
||||||
if (input)
|
if (input)
|
||||||
result.inout = portInputRegister(port);
|
result.inout = portInputRegister(port);
|
||||||
else
|
else
|
||||||
@@ -207,69 +190,3 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res
|
|||||||
result.maskLOW = ~result.maskHIGH;
|
result.maskLOW = ~result.maskHIGH;
|
||||||
// DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH);
|
// DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MotorDriver::schedulePacket(dccPacket packet) {
|
|
||||||
if(!rmtChannel) return true; // fake success if functionality is not there
|
|
||||||
|
|
||||||
outQueue.push(packet);
|
|
||||||
uint16_t size = outQueue.size();
|
|
||||||
if (size > 10) {
|
|
||||||
DIAG(F("Warning: outQueue %d > 10"),size);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MotorDriver::loop() {
|
|
||||||
if (rmtChannel && !outQueue.empty() && rmtChannel->RMTfillData(outQueue.front()))
|
|
||||||
outQueue.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
MotorDriverContainer::MotorDriverContainer(const FSH * motorShieldName,
|
|
||||||
MotorDriver *m0,
|
|
||||||
MotorDriver *m1,
|
|
||||||
MotorDriver *m2,
|
|
||||||
MotorDriver *m3,
|
|
||||||
MotorDriver *m4,
|
|
||||||
MotorDriver *m5,
|
|
||||||
MotorDriver *m6,
|
|
||||||
MotorDriver *m7) {
|
|
||||||
// THIS AUTOMATIC DOES NOT WORK YET. TIMER_MAIN AND TIMER_PROG required in CONSTRUCTOR
|
|
||||||
// AND CAN NOT BE ADDED LATER
|
|
||||||
if (m0) {
|
|
||||||
if (m0->type() == TYPE_UNKNOWN)
|
|
||||||
m0->setType(TIMER_MAIN);
|
|
||||||
mD.push_back(m0);
|
|
||||||
}
|
|
||||||
if (m1) {
|
|
||||||
if (m1->type() == TYPE_UNKNOWN)
|
|
||||||
m1->setType(TIMER_PROG);
|
|
||||||
mD.push_back(m1);
|
|
||||||
}
|
|
||||||
if (m2) mD.push_back(m2);
|
|
||||||
if (m3) mD.push_back(m3);
|
|
||||||
if (m4) mD.push_back(m4);
|
|
||||||
if (m5) mD.push_back(m5);
|
|
||||||
if (m6) mD.push_back(m6);
|
|
||||||
if (m7) mD.push_back(m7);
|
|
||||||
shieldName = (FSH *)motorShieldName;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MotorDriverContainer::loop() {
|
|
||||||
// loops over MotorDrivers which have loop tasks
|
|
||||||
if (mD.empty())
|
|
||||||
return;
|
|
||||||
for(const auto& d: mD)
|
|
||||||
if (d->type() & (RMT_MAIN | RMT_PROG))
|
|
||||||
d->loop();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<MotorDriver*> MotorDriverContainer::getDriverType(driverType t) {
|
|
||||||
std::vector<MotorDriver*> v;
|
|
||||||
for(const auto& d: mD){
|
|
||||||
if (d->type() & t)
|
|
||||||
v.push_back(d);
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
MotorDriverContainer MotorDriverContainer::mDC(MOTOR_SHIELD_TYPE);
|
|
||||||
|
106
MotorDriver.h
106
MotorDriver.h
@@ -1,5 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2021 Mike S
|
||||||
|
* © 2021 Fred Decker
|
||||||
|
* © 2020 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC API
|
* This file is part of Asbelos DCC API
|
||||||
*
|
*
|
||||||
@@ -16,36 +19,23 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef MotorDriver_h
|
#ifndef MotorDriver_h
|
||||||
#define MotorDriver_h
|
#define MotorDriver_h
|
||||||
#include <vector>
|
|
||||||
#include "defines.h"
|
|
||||||
#include "FSH.h"
|
#include "FSH.h"
|
||||||
#include "DIAG.h"
|
|
||||||
|
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
// Virtualised Motor shield 1-track hardware Interface
|
||||||
#include <queue>
|
|
||||||
#include "DCCRMT.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Number of preamble bits (moved here so MotorDriver and Waveform know)
|
|
||||||
const int PREAMBLE_BITS_MAIN = 16;
|
|
||||||
const int PREAMBLE_BITS_PROG = 22;
|
|
||||||
|
|
||||||
#ifndef UNUSED_PIN // sync define with the one in MotorDrivers.h
|
#ifndef UNUSED_PIN // sync define with the one in MotorDrivers.h
|
||||||
#define UNUSED_PIN 127 // inside int8_t
|
#define UNUSED_PIN 127 // inside int8_t
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(__IMXRT1062__) || defined(ESP_FAMILY)
|
#if defined(__IMXRT1062__)
|
||||||
typedef uint32_t PORTTYPE;
|
|
||||||
struct FASTPIN {
|
struct FASTPIN {
|
||||||
volatile uint32_t *inout;
|
volatile uint32_t *inout;
|
||||||
uint32_t maskHIGH;
|
uint32_t maskHIGH;
|
||||||
uint32_t maskLOW;
|
uint32_t maskLOW;
|
||||||
};
|
};
|
||||||
#else
|
#else
|
||||||
typedef uint8_t PORTTYPE;
|
|
||||||
struct FASTPIN {
|
struct FASTPIN {
|
||||||
volatile uint8_t *inout;
|
volatile uint8_t *inout;
|
||||||
uint8_t maskHIGH;
|
uint8_t maskHIGH;
|
||||||
@@ -53,31 +43,16 @@ struct FASTPIN {
|
|||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
|
|
||||||
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
|
|
||||||
#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH)
|
|
||||||
#define isLOW(fastpin) (!isHIGH(fastpin))
|
|
||||||
|
|
||||||
typedef byte driverType;
|
|
||||||
const driverType TYPE_UNKNOWN=0;
|
|
||||||
const driverType TIMER_MAIN=1;
|
|
||||||
const driverType TIMER_PROG=2;
|
|
||||||
const driverType RMT_MAIN=4;
|
|
||||||
const driverType RMT_PROG=16;
|
|
||||||
const driverType DC_ENA=32;
|
|
||||||
const driverType DC_BRAKE=64;
|
|
||||||
|
|
||||||
class MotorDriver {
|
class MotorDriver {
|
||||||
public:
|
public:
|
||||||
MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||||
byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin,
|
byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
|
||||||
driverType t=TYPE_UNKNOWN);
|
virtual void setPower( bool on);
|
||||||
void setPower( bool on);
|
virtual void setSignal( bool high);
|
||||||
void setSignal( bool high);
|
virtual void setBrake( bool on);
|
||||||
void setBrake( bool on);
|
virtual int getCurrentRaw();
|
||||||
int getCurrentRaw();
|
virtual unsigned int raw2mA( int raw);
|
||||||
unsigned int raw2mA( int raw);
|
virtual int mA2raw( unsigned int mA);
|
||||||
int mA2raw( unsigned int mA);
|
|
||||||
inline int getRawCurrentTripValue() {
|
inline int getRawCurrentTripValue() {
|
||||||
return rawCurrentTripValue;
|
return rawCurrentTripValue;
|
||||||
}
|
}
|
||||||
@@ -88,13 +63,6 @@ class MotorDriver {
|
|||||||
inline byte getFaultPin() {
|
inline byte getFaultPin() {
|
||||||
return faultPin;
|
return faultPin;
|
||||||
}
|
}
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
|
||||||
void loop();
|
|
||||||
inline driverType type() { return dtype; };
|
|
||||||
inline void setType(driverType t) { dtype = t; };
|
|
||||||
bool schedulePacket(dccPacket packet);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
|
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
|
||||||
void getFastPin(const FSH* type,int pin, FASTPIN & result) {
|
void getFastPin(const FSH* type,int pin, FASTPIN & result) {
|
||||||
@@ -119,53 +87,5 @@ class MotorDriver {
|
|||||||
if (doit) __enable_irq();
|
if (doit) __enable_irq();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
|
||||||
RMTChannel* rmtChannel;
|
|
||||||
std::queue<dccPacket> outQueue;
|
|
||||||
driverType dtype;
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
class MotorDriverContainer {
|
|
||||||
public:
|
|
||||||
MotorDriverContainer(const FSH * motorShieldName,
|
|
||||||
MotorDriver *m0=NULL,
|
|
||||||
MotorDriver *m1=NULL,
|
|
||||||
MotorDriver *m2=NULL,
|
|
||||||
MotorDriver *m3=NULL,
|
|
||||||
MotorDriver *m4=NULL,
|
|
||||||
MotorDriver *m5=NULL,
|
|
||||||
MotorDriver *m6=NULL,
|
|
||||||
MotorDriver *m7=NULL);
|
|
||||||
static MotorDriverContainer mDC;
|
|
||||||
inline void add(MotorDriver *m) {
|
|
||||||
mD.push_back(m);
|
|
||||||
};
|
|
||||||
// void SetCapability(byte n, byte cap, char [] name);
|
|
||||||
inline FSH *getMotorShieldName() { return shieldName; };
|
|
||||||
inline void diag() {
|
|
||||||
if (mD.empty()) {
|
|
||||||
DIAG(F("Container empty"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for(const auto& d: mD)
|
|
||||||
DIAG(F("Container: mDType=%d"),d->type());
|
|
||||||
};
|
|
||||||
inline MotorDriver *mainTrack() {
|
|
||||||
std::vector<MotorDriver *> v = getDriverType(TIMER_MAIN);
|
|
||||||
if(v.empty()) return NULL;
|
|
||||||
return v.front();
|
|
||||||
};
|
|
||||||
inline MotorDriver *progTrack() {
|
|
||||||
std::vector<MotorDriver *> v = getDriverType(TIMER_PROG);
|
|
||||||
if(v.empty()) return NULL;
|
|
||||||
return v.front();
|
|
||||||
};
|
|
||||||
void loop();
|
|
||||||
std::vector<MotorDriver*> getDriverType(driverType t);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<MotorDriver *>mD;
|
|
||||||
FSH *shieldName;
|
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
|
* © 2021 Fred Decker
|
||||||
|
* © 2020-2022 Harald Barth
|
||||||
* (c) 2020 Chris Harlow. All rights reserved.
|
* (c) 2020 Chris Harlow. All rights reserved.
|
||||||
* (c) 2021 Fred Decker. All rights reserved.
|
* (c) 2021 Fred Decker. All rights reserved.
|
||||||
* (c) 2020 Harald Barth. All rights reserved.
|
* (c) 2020 Harald Barth. All rights reserved.
|
||||||
@@ -87,4 +89,22 @@
|
|||||||
new MotorDriver(5, 4, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \
|
new MotorDriver(5, 4, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \
|
||||||
new MotorDriver(6, 7, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
new MotorDriver(6, 7, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||||
|
|
||||||
|
// Makeblock ORION UNO like sized board with integrated motor driver
|
||||||
|
// This is like an Uno with H-bridge and RJ12 contacts instead of pin rows.
|
||||||
|
// No current sense. Barrel connector max 12V, Vmotor max 15V. 1.1A polyfuse as output protection.
|
||||||
|
// Main is marked M1 and near RJ12 #5
|
||||||
|
// Prog is marked M2 and near RJ12 #4
|
||||||
|
// For details see
|
||||||
|
// http://docs.makeblock.com/diy-platform/en/electronic-modules/main-control-boards/makeblock-orion.html
|
||||||
|
#define ORION_UNO_INTEGRATED_SHIELD F("ORION_UNO_INTEGRATED_SHIELD"), \
|
||||||
|
new MotorDriver(6, 7, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 1.0, 1100, UNUSED_PIN), \
|
||||||
|
new MotorDriver(5, 4, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 1.0, 1100, UNUSED_PIN)
|
||||||
|
|
||||||
|
// This is an example how to setup a motor shield definition for a motor shield connected
|
||||||
|
// to an NANO EVERY board. You have to make the connectons from the shield to the board
|
||||||
|
// as in this example or adjust the values yourself.
|
||||||
|
#define NANOEVERY_EXAMPLE F("NANOEVERY_EXAMPLE"), \
|
||||||
|
new MotorDriver(5, 6, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN),\
|
||||||
|
new MotorDriver(9, 10, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
14
Outputs.cpp
14
Outputs.cpp
@@ -1,5 +1,9 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2021 Neil McKechnie
|
||||||
|
* © 2021 Harald Barth
|
||||||
|
* © 2020-2021 Fred Decker
|
||||||
|
* © 2020-2021 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC API
|
* This file is part of Asbelos DCC API
|
||||||
*
|
*
|
||||||
@@ -82,7 +86,9 @@ the state of any outputs being monitored or controlled by a separate interface o
|
|||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
|
|
||||||
#include "Outputs.h"
|
#include "Outputs.h"
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
#include "EEStore.h"
|
#include "EEStore.h"
|
||||||
|
#endif
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
#include "IODevice.h"
|
#include "IODevice.h"
|
||||||
|
|
||||||
@@ -102,10 +108,11 @@ void Output::activate(uint16_t s){
|
|||||||
data.active = s; // if s>0, set status to active, else inactive
|
data.active = s; // if s>0, set status to active, else inactive
|
||||||
// set state of output pin to HIGH or LOW depending on whether bit zero of iFlag is set to 0 (ACTIVE=HIGH) or 1 (ACTIVE=LOW)
|
// set state of output pin to HIGH or LOW depending on whether bit zero of iFlag is set to 0 (ACTIVE=HIGH) or 1 (ACTIVE=LOW)
|
||||||
IODevice::write(data.pin, s ^ data.invert);
|
IODevice::write(data.pin, s ^ data.invert);
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
// Update EEPROM if output has been stored.
|
// Update EEPROM if output has been stored.
|
||||||
if(EEStore::eeStore->data.nOutputs > 0 && num > 0)
|
if(EEStore::eeStore->data.nOutputs > 0 && num > 0)
|
||||||
EEPROM.put(num, data.oStatus);
|
EEPROM.put(num, data.oStatus);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -141,7 +148,7 @@ bool Output::remove(uint16_t n){
|
|||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// Static function to load configuration and state of all Outputs from EEPROM
|
// Static function to load configuration and state of all Outputs from EEPROM
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
void Output::load(){
|
void Output::load(){
|
||||||
struct OutputData data;
|
struct OutputData data;
|
||||||
Output *tt;
|
Output *tt;
|
||||||
@@ -176,6 +183,7 @@ void Output::store(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// Static function to create an Output object
|
// Static function to create an Output object
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2021 Harald Barth
|
||||||
|
* © 2021 Fred Decker
|
||||||
|
* © 2020 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC API
|
* This file is part of Asbelos DCC API
|
||||||
*
|
*
|
||||||
@@ -48,8 +51,10 @@ public:
|
|||||||
bool isActive();
|
bool isActive();
|
||||||
static Output* get(uint16_t);
|
static Output* get(uint16_t);
|
||||||
static bool remove(uint16_t);
|
static bool remove(uint16_t);
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
static void load();
|
static void load();
|
||||||
static void store();
|
static void store();
|
||||||
|
#endif
|
||||||
static Output *create(uint16_t, VPIN, int, int=0);
|
static Output *create(uint16_t, VPIN, int, int=0);
|
||||||
static Output *firstOutput;
|
static Output *firstOutput;
|
||||||
struct OutputData data;
|
struct OutputData data;
|
||||||
|
@@ -17,7 +17,7 @@ Both CommandStation-EX and BaseStation-Classic support much of the NMRA Digital
|
|||||||
* Control of all cab functions F0-F28 and F29-F68
|
* 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)
|
* 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
|
* Programming Track: Same as the main track with the addition of reading configuration variable bytes
|
||||||
* And manu more custom features. see [What's new in CommandStation-EX?](#whats-new-in-commandstation-ex)
|
* And many more custom features. see [What's new in CommandStation-EX?](#whats-new-in-commandstation-ex)
|
||||||
|
|
||||||
|
|
||||||
# What’s in this Repository?
|
# What’s in this Repository?
|
||||||
|
63
RMFT2.h
63
RMFT2.h
@@ -1,5 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2021 Neil McKechnie
|
||||||
|
* © 2020-2022 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
@@ -22,7 +24,7 @@
|
|||||||
#include "IODevice.h"
|
#include "IODevice.h"
|
||||||
|
|
||||||
// The following are the operation codes (or instructions) for a kind of virtual machine.
|
// The following are the operation codes (or instructions) for a kind of virtual machine.
|
||||||
// Each instruction is normally 2 bytes long with an operation code followed by a parameter.
|
// Each instruction is normally 3 bytes long with an operation code followed by a parameter.
|
||||||
// In cases where more than one parameter is required, the first parameter is followed by one
|
// In cases where more than one parameter is required, the first parameter is followed by one
|
||||||
// or more OPCODE_PAD instructions with the subsequent parameters. This wastes a byte but makes
|
// or more OPCODE_PAD instructions with the subsequent parameters. This wastes a byte but makes
|
||||||
// searching easier as a parameter can never be confused with an opcode.
|
// searching easier as a parameter can never be confused with an opcode.
|
||||||
@@ -30,34 +32,52 @@
|
|||||||
enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||||
OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION,
|
OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION,
|
||||||
OPCODE_RESERVE,OPCODE_FREE,
|
OPCODE_RESERVE,OPCODE_FREE,
|
||||||
OPCODE_AT,OPCODE_AFTER,
|
OPCODE_AT,OPCODE_AFTER,OPCODE_AUTOSTART,
|
||||||
|
OPCODE_ATTIMEOUT1,OPCODE_ATTIMEOUT2,OPCODE_IFTIMEOUT,
|
||||||
OPCODE_LATCH,OPCODE_UNLATCH,OPCODE_SET,OPCODE_RESET,
|
OPCODE_LATCH,OPCODE_UNLATCH,OPCODE_SET,OPCODE_RESET,
|
||||||
OPCODE_IF,OPCODE_IFNOT,OPCODE_ENDIF,OPCODE_IFRANDOM,OPCODE_IFRESERVE,
|
OPCODE_IF,OPCODE_IFNOT,OPCODE_ENDIF,OPCODE_IFRANDOM,OPCODE_IFRESERVE,
|
||||||
OPCODE_DELAY,OPCODE_DELAYMINS,OPCODE_RANDWAIT,
|
OPCODE_IFCLOSED, OPCODE_IFTHROWN,OPCODE_ELSE,
|
||||||
|
OPCODE_DELAY,OPCODE_DELAYMINS,OPCODE_DELAYMS,OPCODE_RANDWAIT,
|
||||||
OPCODE_FON,OPCODE_FOFF,OPCODE_XFON,OPCODE_XFOFF,
|
OPCODE_FON,OPCODE_FOFF,OPCODE_XFON,OPCODE_XFOFF,
|
||||||
OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,
|
OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE,
|
||||||
OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR,
|
OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR,
|
||||||
OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN,
|
OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN,
|
||||||
OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,OPCODE_POM,
|
OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,OPCODE_POM,
|
||||||
OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,
|
OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,
|
||||||
OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,
|
OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,
|
||||||
OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT,
|
OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT,
|
||||||
OPCODE_PRINT,
|
OPCODE_PRINT,OPCODE_DCCACTIVATE,
|
||||||
|
OPCODE_ONACTIVATE,OPCODE_ONDEACTIVATE,OPCODE_IFGTE,OPCODE_IFLT,
|
||||||
|
OPCODE_ROSTER,
|
||||||
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,OPCODE_ENDTASK,OPCODE_ENDEXRAIL
|
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,OPCODE_ENDTASK,OPCODE_ENDEXRAIL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Flag bits for status of hardware and TPL
|
// Flag bits for status of hardware and TPL
|
||||||
static const byte SECTION_FLAG = 0x01;
|
static const byte SECTION_FLAG = 0x80;
|
||||||
static const byte LATCH_FLAG = 0x02;
|
static const byte LATCH_FLAG = 0x40;
|
||||||
static const byte TASK_FLAG = 0x04;
|
static const byte TASK_FLAG = 0x20;
|
||||||
|
static const byte SPARE_FLAG = 0x10;
|
||||||
|
static const byte COUNTER_MASK= 0x0F;
|
||||||
|
|
||||||
static const byte MAX_STACK_DEPTH=4;
|
static const byte MAX_STACK_DEPTH=4;
|
||||||
|
|
||||||
static const short MAX_FLAGS=256;
|
static const short MAX_FLAGS=256;
|
||||||
#define FLAGOVERFLOW(x) x>=MAX_FLAGS
|
#define FLAGOVERFLOW(x) x>=MAX_FLAGS
|
||||||
|
|
||||||
|
class LookList {
|
||||||
|
public:
|
||||||
|
LookList(int16_t size);
|
||||||
|
void add(int16_t lookup, int16_t result);
|
||||||
|
int16_t find(int16_t value);
|
||||||
|
private:
|
||||||
|
int16_t m_size;
|
||||||
|
int16_t m_loaded;
|
||||||
|
int16_t * m_lookupArray;
|
||||||
|
int16_t * m_resultArray;
|
||||||
|
};
|
||||||
|
|
||||||
class RMFT2 {
|
class RMFT2 {
|
||||||
public:
|
public:
|
||||||
static void begin();
|
static void begin();
|
||||||
@@ -69,13 +89,17 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
|||||||
static void emitWithrottleRouteList(Print* stream);
|
static void emitWithrottleRouteList(Print* stream);
|
||||||
static void createNewTask(int route, uint16_t cab);
|
static void createNewTask(int route, uint16_t cab);
|
||||||
static void turnoutEvent(int16_t id, bool closed);
|
static void turnoutEvent(int16_t id, bool closed);
|
||||||
|
static void activateEvent(int16_t addr, bool active);
|
||||||
|
static void emitTurnoutDescription(Print* stream,int16_t id);
|
||||||
|
static const byte rosterNameCount;
|
||||||
|
static void emitWithrottleRoster(Print * stream);
|
||||||
|
static const FSH * getRosterFunctions(int16_t cabid);
|
||||||
private:
|
private:
|
||||||
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
||||||
static bool parseSlash(Print * stream, byte & paramCount, int16_t p[]) ;
|
static bool parseSlash(Print * stream, byte & paramCount, int16_t p[]) ;
|
||||||
static void streamFlags(Print* stream);
|
static void streamFlags(Print* stream);
|
||||||
static void setFlag(VPIN id,byte onMask, byte OffMask=0);
|
static void setFlag(VPIN id,byte onMask, byte OffMask=0);
|
||||||
static bool getFlag(VPIN id,byte mask);
|
static bool getFlag(VPIN id,byte mask);
|
||||||
static int locateRouteStart(int16_t _route);
|
|
||||||
static int16_t progtrackLocoId;
|
static int16_t progtrackLocoId;
|
||||||
static void doSignal(VPIN id,bool red, bool amber, bool green);
|
static void doSignal(VPIN id,bool red, bool amber, bool green);
|
||||||
static void emitRouteDescription(Print * stream, char type, int id, const FSH * description);
|
static void emitRouteDescription(Print * stream, char type, int id, const FSH * description);
|
||||||
@@ -92,18 +116,28 @@ private:
|
|||||||
void kill(const FSH * reason=NULL,int operand=0);
|
void kill(const FSH * reason=NULL,int operand=0);
|
||||||
void printMessage(uint16_t id); // Built by RMFTMacros.h
|
void printMessage(uint16_t id); // Built by RMFTMacros.h
|
||||||
void printMessage2(const FSH * msg);
|
void printMessage2(const FSH * msg);
|
||||||
|
|
||||||
|
|
||||||
static bool diag;
|
static bool diag;
|
||||||
static const FLASH byte RouteCode[];
|
static const FLASH byte RouteCode[];
|
||||||
|
static const FLASH int16_t SignalDefinitions[];
|
||||||
static byte flags[MAX_FLAGS];
|
static byte flags[MAX_FLAGS];
|
||||||
|
static LookList * sequenceLookup;
|
||||||
|
static LookList * onThrowLookup;
|
||||||
|
static LookList * onCloseLookup;
|
||||||
|
static LookList * onActivateLookup;
|
||||||
|
static LookList * onDeactivateLookup;
|
||||||
|
|
||||||
// Local variables - exist for each instance/task
|
// Local variables - exist for each instance/task
|
||||||
RMFT2 *next; // loop chain
|
RMFT2 *next; // loop chain
|
||||||
int progCounter; // Byte offset of next route opcode in ROUTES table
|
int progCounter; // Byte offset of next route opcode in ROUTES table
|
||||||
unsigned long delayStart; // Used by opcodes that must be recalled before completing
|
unsigned long delayStart; // Used by opcodes that must be recalled before completing
|
||||||
unsigned long waitAfter; // Used by OPCODE_AFTER
|
|
||||||
unsigned long delayTime;
|
unsigned long delayTime;
|
||||||
|
union {
|
||||||
|
unsigned long waitAfter; // Used by OPCODE_AFTER
|
||||||
|
unsigned long timeoutStart; // Used by OPCODE_ATTIMEOUT
|
||||||
|
};
|
||||||
|
bool timeoutFlag;
|
||||||
byte taskId;
|
byte taskId;
|
||||||
|
|
||||||
uint16_t loco;
|
uint16_t loco;
|
||||||
@@ -111,6 +145,7 @@ private:
|
|||||||
bool invert;
|
bool invert;
|
||||||
byte speedo;
|
byte speedo;
|
||||||
int16_t onTurnoutId;
|
int16_t onTurnoutId;
|
||||||
|
int16_t onActivateAddr;
|
||||||
byte stackDepth;
|
byte stackDepth;
|
||||||
int callStack[MAX_STACK_DEPTH];
|
int callStack[MAX_STACK_DEPTH];
|
||||||
};
|
};
|
||||||
|
202
RMFT2MacroReset.h
Normal file
202
RMFT2MacroReset.h
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
/*
|
||||||
|
* © 2021-2022 Chris Harlow
|
||||||
|
* © 2020,2021 Chris Harlow. All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is part of CommandStation-EX
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This file cleans and resets the RMFT2 Macros.
|
||||||
|
// It is used between passes to reduce complexity in RMFT2Macros.h
|
||||||
|
// DO NOT add an include guard to this file.
|
||||||
|
|
||||||
|
// Undefine all RMFT macros
|
||||||
|
#undef ACTIVATE
|
||||||
|
#undef ACTIVATEL
|
||||||
|
#undef AFTER
|
||||||
|
#undef ALIAS
|
||||||
|
#undef AMBER
|
||||||
|
#undef AT
|
||||||
|
#undef ATTIMEOUT
|
||||||
|
#undef AUTOMATION
|
||||||
|
#undef AUTOSTART
|
||||||
|
#undef CALL
|
||||||
|
#undef CLOSE
|
||||||
|
#undef DEACTIVATE
|
||||||
|
#undef DEACTIVATEL
|
||||||
|
#undef DELAY
|
||||||
|
#undef DELAYMINS
|
||||||
|
#undef DELAYRANDOM
|
||||||
|
#undef DONE
|
||||||
|
#undef DRIVE
|
||||||
|
#undef ELSE
|
||||||
|
#undef ENDEXRAIL
|
||||||
|
#undef ENDIF
|
||||||
|
#undef ENDTASK
|
||||||
|
#undef ESTOP
|
||||||
|
#undef EXRAIL
|
||||||
|
#undef FADE
|
||||||
|
#undef FOFF
|
||||||
|
#undef FOLLOW
|
||||||
|
#undef FON
|
||||||
|
#undef FREE
|
||||||
|
#undef FWD
|
||||||
|
#undef GREEN
|
||||||
|
#undef IF
|
||||||
|
#undef IFCLOSED
|
||||||
|
#undef IFGTE
|
||||||
|
#undef IFLT
|
||||||
|
#undef IFNOT
|
||||||
|
#undef IFRANDOM
|
||||||
|
#undef IFRESERVE
|
||||||
|
#undef IFTHROWN
|
||||||
|
#undef IFTIMEOUT
|
||||||
|
#undef INVERT_DIRECTION
|
||||||
|
#undef JOIN
|
||||||
|
#undef LATCH
|
||||||
|
#undef LCD
|
||||||
|
#undef LCN
|
||||||
|
#undef ONACTIVATE
|
||||||
|
#undef ONACTIVATEL
|
||||||
|
#undef ONDEACTIVATE
|
||||||
|
#undef ONDEACTIVATEL
|
||||||
|
#undef ONCLOSE
|
||||||
|
#undef ONTHROW
|
||||||
|
#undef PAUSE
|
||||||
|
#undef PIN_TURNOUT
|
||||||
|
#undef PRINT
|
||||||
|
#undef POM
|
||||||
|
#undef POWEROFF
|
||||||
|
#undef READ_LOCO
|
||||||
|
#undef RED
|
||||||
|
#undef RESERVE
|
||||||
|
#undef RESET
|
||||||
|
#undef RESUME
|
||||||
|
#undef RETURN
|
||||||
|
#undef REV
|
||||||
|
#undef ROSTER
|
||||||
|
#undef ROUTE
|
||||||
|
#undef SENDLOCO
|
||||||
|
#undef SEQUENCE
|
||||||
|
#undef SERIAL
|
||||||
|
#undef SERIAL1
|
||||||
|
#undef SERIAL2
|
||||||
|
#undef SERIAL3
|
||||||
|
#undef SERVO
|
||||||
|
#undef SERVO2
|
||||||
|
#undef SERVO_TURNOUT
|
||||||
|
#undef SET
|
||||||
|
#undef SETLOCO
|
||||||
|
#undef SIGNAL
|
||||||
|
#undef SPEED
|
||||||
|
#undef START
|
||||||
|
#undef STOP
|
||||||
|
#undef THROW
|
||||||
|
#undef TURNOUT
|
||||||
|
#undef UNJOIN
|
||||||
|
#undef UNLATCH
|
||||||
|
#undef WAITFOR
|
||||||
|
#undef XFOFF
|
||||||
|
#undef XFON
|
||||||
|
|
||||||
|
#ifndef RMFT2_UNDEF_ONLY
|
||||||
|
#define ACTIVATE(addr,subaddr)
|
||||||
|
#define ACTIVATEL(addr)
|
||||||
|
#define AFTER(sensor_id)
|
||||||
|
#define ALIAS(name,value)
|
||||||
|
#define AMBER(signal_id)
|
||||||
|
#define AT(sensor_id)
|
||||||
|
#define ATTIMEOUT(sensor_id,timeout_ms)
|
||||||
|
#define AUTOMATION(id, description)
|
||||||
|
#define AUTOSTART
|
||||||
|
#define CALL(route)
|
||||||
|
#define CLOSE(id)
|
||||||
|
#define DEACTIVATE(addr,subaddr)
|
||||||
|
#define DEACTIVATEL(addr)
|
||||||
|
#define DELAY(mindelay)
|
||||||
|
#define DELAYMINS(mindelay)
|
||||||
|
#define DELAYRANDOM(mindelay,maxdelay)
|
||||||
|
#define DONE
|
||||||
|
#define DRIVE(analogpin)
|
||||||
|
#define ELSE
|
||||||
|
#define ENDEXRAIL
|
||||||
|
#define ENDIF
|
||||||
|
#define ENDTASK
|
||||||
|
#define ESTOP
|
||||||
|
#define EXRAIL
|
||||||
|
#define FADE(pin,value,ms)
|
||||||
|
#define FOFF(func)
|
||||||
|
#define FOLLOW(route)
|
||||||
|
#define FON(func)
|
||||||
|
#define FREE(blockid)
|
||||||
|
#define FWD(speed)
|
||||||
|
#define GREEN(signal_id)
|
||||||
|
#define IF(sensor_id)
|
||||||
|
#define IFCLOSED(turnout_id)
|
||||||
|
#define IFGTE(sensor_id,value)
|
||||||
|
#define IFLT(sensor_id,value)
|
||||||
|
#define IFNOT(sensor_id)
|
||||||
|
#define IFRANDOM(percent)
|
||||||
|
#define IFTHROWN(turnout_id)
|
||||||
|
#define IFRESERVE(block)
|
||||||
|
#define IFTIMEOUT
|
||||||
|
#define INVERT_DIRECTION
|
||||||
|
#define JOIN
|
||||||
|
#define LATCH(sensor_id)
|
||||||
|
#define LCD(row,msg)
|
||||||
|
#define LCN(msg)
|
||||||
|
#define ONACTIVATE(addr,subaddr)
|
||||||
|
#define ONACTIVATEL(linear)
|
||||||
|
#define ONDEACTIVATE(addr,subaddr)
|
||||||
|
#define ONDEACTIVATEL(linear)
|
||||||
|
#define ONCLOSE(turnout_id)
|
||||||
|
#define ONTHROW(turnout_id)
|
||||||
|
#define PAUSE
|
||||||
|
#define PIN_TURNOUT(id,pin,description...)
|
||||||
|
#define PRINT(msg)
|
||||||
|
#define POM(cv,value)
|
||||||
|
#define POWEROFF
|
||||||
|
#define READ_LOCO
|
||||||
|
#define RED(signal_id)
|
||||||
|
#define RESERVE(blockid)
|
||||||
|
#define RESET(pin)
|
||||||
|
#define RESUME
|
||||||
|
#define RETURN
|
||||||
|
#define REV(speed)
|
||||||
|
#define ROUTE(id, description)
|
||||||
|
#define ROSTER(cab,name,funcmap...)
|
||||||
|
#define SENDLOCO(cab,route)
|
||||||
|
#define SEQUENCE(id)
|
||||||
|
#define SERIAL(msg)
|
||||||
|
#define SERIAL1(msg)
|
||||||
|
#define SERIAL2(msg)
|
||||||
|
#define SERIAL3(msg)
|
||||||
|
#define SERVO(id,position,profile)
|
||||||
|
#define SERVO2(id,position,duration)
|
||||||
|
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...)
|
||||||
|
#define SET(pin)
|
||||||
|
#define SETLOCO(loco)
|
||||||
|
#define SIGNAL(redpin,amberpin,greenpin)
|
||||||
|
#define SPEED(speed)
|
||||||
|
#define START(route)
|
||||||
|
#define STOP
|
||||||
|
#define THROW(id)
|
||||||
|
#define TURNOUT(id,addr,subaddr,description...)
|
||||||
|
#define UNJOIN
|
||||||
|
#define UNLATCH(sensor_id)
|
||||||
|
#define WAITFOR(pin)
|
||||||
|
#define XFOFF(cab,func)
|
||||||
|
#define XFON(cab,func)
|
||||||
|
#endif
|
352
RMFTMacros.h
352
RMFTMacros.h
@@ -1,5 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020,2021 Chris Harlow. All rights reserved.
|
* © 2021 Neil McKechnie
|
||||||
|
* © 2020-2022 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
@@ -16,6 +18,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef RMFTMacros_H
|
#ifndef RMFTMacros_H
|
||||||
#define RMFTMacros_H
|
#define RMFTMacros_H
|
||||||
|
|
||||||
@@ -25,7 +28,7 @@
|
|||||||
|
|
||||||
|
|
||||||
// This file will include and build the EXRAIL script and associated helper tricks.
|
// This file will include and build the EXRAIL script and associated helper tricks.
|
||||||
// It does this by incliding myAutomation.h several times, each with a set of macros to
|
// It does this by including myAutomation.h several times, each with a set of macros to
|
||||||
// extract the relevant parts.
|
// extract the relevant parts.
|
||||||
|
|
||||||
// The entire automation script is contained within a byte array RMFT2::RouteCode[]
|
// The entire automation script is contained within a byte array RMFT2::RouteCode[]
|
||||||
@@ -46,207 +49,142 @@
|
|||||||
|
|
||||||
// CAUTION: The macros below are multiple passed over myAutomation.h
|
// CAUTION: The macros below are multiple passed over myAutomation.h
|
||||||
|
|
||||||
// Pass 1 Implements aliases and
|
// Pass 1 Implements aliases
|
||||||
// converts descriptions to withrottle format emitter function
|
#include "RMFT2MacroReset.h"
|
||||||
// Most macros are simply ignored in this pass.
|
|
||||||
|
|
||||||
|
|
||||||
#define ALIAS(name,value) const int name=value;
|
|
||||||
#define EXRAIL void RMFT2::emitWithrottleDescriptions(Print * stream) {(void)stream;
|
|
||||||
#define ROUTE(id, description) emitRouteDescription(stream,'R',id,F(description));
|
|
||||||
#define AUTOMATION(id, description) emitRouteDescription(stream,'A',id,F(description));
|
|
||||||
#define ENDEXRAIL }
|
|
||||||
|
|
||||||
#define AFTER(sensor_id)
|
|
||||||
#define AMBER(signal_id)
|
|
||||||
#define AT(sensor_id)
|
|
||||||
#define CALL(route)
|
|
||||||
#define CLOSE(id)
|
|
||||||
#define DELAY(mindelay)
|
|
||||||
#define DELAYMINS(mindelay)
|
|
||||||
#define DELAYRANDOM(mindelay,maxdelay)
|
|
||||||
#define DONE
|
|
||||||
#define ENDIF
|
|
||||||
#define ENDTASK
|
|
||||||
#define ESTOP
|
|
||||||
#define FADE(pin,value,ms)
|
|
||||||
#define FOFF(func)
|
|
||||||
#define FOLLOW(route)
|
|
||||||
#define FON(func)
|
|
||||||
#define FREE(blockid)
|
|
||||||
#define FWD(speed)
|
|
||||||
#define GREEN(signal_id)
|
|
||||||
#define IF(sensor_id)
|
|
||||||
#define IFNOT(sensor_id)
|
|
||||||
#define IFRANDOM(percent)
|
|
||||||
#define IFRESERVE(block)
|
|
||||||
#define INVERT_DIRECTION
|
|
||||||
#define JOIN
|
|
||||||
#define LATCH(sensor_id)
|
|
||||||
#define LCD(row,msg)
|
|
||||||
#define LCN(msg)
|
|
||||||
#define ONCLOSE(turnout_id)
|
|
||||||
#define ONTHROW(turnout_id)
|
|
||||||
#define PAUSE
|
|
||||||
#define PRINT(msg)
|
|
||||||
#define POM(cv,value)
|
|
||||||
#define POWEROFF
|
|
||||||
#define READ_LOCO
|
|
||||||
#define RED(signal_id)
|
|
||||||
#define RESERVE(blockid)
|
|
||||||
#define RESET(pin)
|
|
||||||
#define RESUME
|
|
||||||
#define RETURN
|
|
||||||
#define REV(speed)
|
|
||||||
#define START(route)
|
|
||||||
#define SENDLOCO(cab,route)
|
|
||||||
#define SERIAL(msg)
|
|
||||||
#define SERIAL1(msg)
|
|
||||||
#define SERIAL2(msg)
|
|
||||||
#define SERIAL3(msg)
|
|
||||||
#define SERVO(id,position,profile)
|
|
||||||
#define SERVO2(id,position,duration)
|
|
||||||
#define SETLOCO(loco)
|
|
||||||
#define SET(pin)
|
|
||||||
#define SEQUENCE(id)
|
|
||||||
#define SPEED(speed)
|
|
||||||
#define STOP
|
|
||||||
#undef SIGNAL
|
|
||||||
#define SIGNAL(redpin,amberpin,greenpin)
|
|
||||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile)
|
|
||||||
#define PIN_TURNOUT(id,pin)
|
|
||||||
#define THROW(id)
|
|
||||||
#define TURNOUT(id,addr,subaddr)
|
|
||||||
#define UNJOIN
|
|
||||||
#define UNLATCH(sensor_id)
|
|
||||||
#define WAITFOR(pin)
|
|
||||||
#define XFOFF(cab,func)
|
|
||||||
#define XFON(cab,func)
|
|
||||||
|
|
||||||
#include "myAutomation.h"
|
|
||||||
|
|
||||||
// setup for pass 2... Create getMessageText function
|
|
||||||
#undef ALIAS
|
#undef ALIAS
|
||||||
#undef ROUTE
|
#define ALIAS(name,value) const int name=value;
|
||||||
#undef AUTOMATION
|
|
||||||
#define ROUTE(id, description)
|
|
||||||
#define AUTOMATION(id, description)
|
|
||||||
|
|
||||||
#undef EXRAIL
|
|
||||||
#undef PRINT
|
|
||||||
#undef LCN
|
|
||||||
#undef SERIAL
|
|
||||||
#undef SERIAL1
|
|
||||||
#undef SERIAL2
|
|
||||||
#undef SERIAL3
|
|
||||||
#undef ENDEXRAIL
|
|
||||||
#undef LCD
|
|
||||||
const int StringMacroTracker1=__COUNTER__;
|
|
||||||
#define ALIAS(name,value)
|
|
||||||
#define EXRAIL void RMFT2::printMessage(uint16_t id) { switch(id) {
|
|
||||||
#define ENDEXRAIL default: DIAG(F("printMessage error %d %d"),id,StringMacroTracker1); return ; }}
|
|
||||||
#define PRINT(msg) case (__COUNTER__ - StringMacroTracker1) : printMessage2(F(msg));break;
|
|
||||||
#define LCN(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&LCN_SERIAL,F(msg));break;
|
|
||||||
#define SERIAL(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial,F(msg));break;
|
|
||||||
#define SERIAL1(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial1,F(msg));break;
|
|
||||||
#define SERIAL2(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial2,F(msg));break;
|
|
||||||
#define SERIAL3(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial3,F(msg));break;
|
|
||||||
#define LCD(id,msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::lcd(id,F(msg));break;
|
|
||||||
#include "myAutomation.h"
|
#include "myAutomation.h"
|
||||||
|
|
||||||
// Setup for Pass 3: create main routes table
|
// Pass 2 convert descriptions to withrottle format emitter function
|
||||||
#undef AFTER
|
#include "RMFT2MacroReset.h"
|
||||||
#undef AMBER
|
#undef ROUTE
|
||||||
#undef AT
|
#define ROUTE(id, description) emitRouteDescription(stream,'R',id,F(description));
|
||||||
#undef AUTOMATION
|
#undef AUTOMATION
|
||||||
#undef CALL
|
#define AUTOMATION(id, description) emitRouteDescription(stream,'A',id,F(description));
|
||||||
#undef CLOSE
|
void RMFT2::emitWithrottleDescriptions(Print * stream) {
|
||||||
#undef DELAY
|
(void)stream;
|
||||||
#undef DELAYMINS
|
#include "myAutomation.h"
|
||||||
#undef DELAYRANDOM
|
}
|
||||||
#undef DONE
|
|
||||||
#undef ENDIF
|
// Pass 3... Create Text sending functions
|
||||||
#undef ENDEXRAIL
|
#include "RMFT2MacroReset.h"
|
||||||
#undef ENDTASK
|
const int StringMacroTracker1=__COUNTER__;
|
||||||
#undef ESTOP
|
|
||||||
#undef EXRAIL
|
|
||||||
#undef FOFF
|
|
||||||
#undef FOLLOW
|
|
||||||
#undef FON
|
|
||||||
#undef FREE
|
|
||||||
#undef FWD
|
|
||||||
#undef GREEN
|
|
||||||
#undef IF
|
|
||||||
#undef IFNOT
|
|
||||||
#undef IFRANDOM
|
|
||||||
#undef IFRESERVE
|
|
||||||
#undef INVERT_DIRECTION
|
|
||||||
#undef JOIN
|
|
||||||
#undef LATCH
|
|
||||||
#undef LCD
|
|
||||||
#undef LCN
|
|
||||||
#undef ONCLOSE
|
|
||||||
#undef ONTHROW
|
|
||||||
#undef PAUSE
|
|
||||||
#undef POM
|
|
||||||
#undef POWEROFF
|
|
||||||
#undef PRINT
|
#undef PRINT
|
||||||
#undef READ_LOCO
|
#define PRINT(msg) case (__COUNTER__ - StringMacroTracker1) : printMessage2(F(msg));break;
|
||||||
#undef RED
|
#undef LCN
|
||||||
#undef RESERVE
|
#define LCN(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&LCN_SERIAL,F(msg));break;
|
||||||
#undef RESET
|
|
||||||
#undef RESUME
|
|
||||||
#undef RETURN
|
|
||||||
#undef REV
|
|
||||||
#undef ROUTE
|
|
||||||
#undef START
|
|
||||||
#undef SEQUENCE
|
|
||||||
#undef SERVO
|
|
||||||
#undef SERVO2
|
|
||||||
#undef FADE
|
|
||||||
#undef SENDLOCO
|
|
||||||
#undef SERIAL
|
#undef SERIAL
|
||||||
|
#define SERIAL(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial,F(msg));break;
|
||||||
#undef SERIAL1
|
#undef SERIAL1
|
||||||
|
#define SERIAL1(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial1,F(msg));break;
|
||||||
#undef SERIAL2
|
#undef SERIAL2
|
||||||
|
#define SERIAL2(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial2,F(msg));break;
|
||||||
#undef SERIAL3
|
#undef SERIAL3
|
||||||
#undef SETLOCO
|
#define SERIAL3(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial3,F(msg));break;
|
||||||
#undef SET
|
#undef LCD
|
||||||
#undef SPEED
|
#define LCD(id,msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::lcd(id,F(msg));break;
|
||||||
#undef STOP
|
|
||||||
#undef SIGNAL
|
void RMFT2::printMessage(uint16_t id) {
|
||||||
#undef SERVO_TURNOUT
|
switch(id) {
|
||||||
#undef PIN_TURNOUT
|
#include "myAutomation.h"
|
||||||
#undef THROW
|
default: break ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Pass 4: Turnout descriptions (optional)
|
||||||
|
#include "RMFT2MacroReset.h"
|
||||||
#undef TURNOUT
|
#undef TURNOUT
|
||||||
#undef UNJOIN
|
#define TURNOUT(id,addr,subaddr,description...) case id: desc=F("" description); break;
|
||||||
#undef UNLATCH
|
#undef PIN_TURNOUT
|
||||||
#undef WAITFOR
|
#define PIN_TURNOUT(id,pin,description...) case id: desc=F("" description); break;
|
||||||
#undef XFOFF
|
#undef SERVO_TURNOUT
|
||||||
#undef XFON
|
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) case id: desc=F("" description); break;
|
||||||
|
|
||||||
|
void RMFT2::emitTurnoutDescription(Print* stream,int16_t turnoutid) {
|
||||||
|
const FSH * desc=F("");
|
||||||
|
switch (turnoutid) {
|
||||||
|
#include "myAutomation.h"
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
if (GETFLASH(desc)=='\0') desc=F("%d");
|
||||||
|
StringFormatter::send(stream,desc,turnoutid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass 5: Roster names (count)
|
||||||
|
#include "RMFT2MacroReset.h"
|
||||||
|
#undef ROSTER
|
||||||
|
#define ROSTER(cabid,name,funcmap...) +1
|
||||||
|
|
||||||
|
const byte RMFT2::rosterNameCount=0
|
||||||
|
#include "myAutomation.h"
|
||||||
|
;
|
||||||
|
|
||||||
|
// Pass 6: Roster names emitter
|
||||||
|
#include "RMFT2MacroReset.h"
|
||||||
|
#undef ROSTER
|
||||||
|
#define ROSTER(cabid,name,funcmap...) StringFormatter::send(stream,(FSH *)format,F(name),cabid,cabid<128?'S':'L');
|
||||||
|
void RMFT2::emitWithrottleRoster(Print * stream) {
|
||||||
|
static const char format[] FLASH ="]\\[%S}|{%d}|{%c";
|
||||||
|
(void)format;
|
||||||
|
StringFormatter::send(stream,F("RL%d"), rosterNameCount);
|
||||||
|
#include "myAutomation.h"
|
||||||
|
stream->write('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass 7: functions getter
|
||||||
|
#include "RMFT2MacroReset.h"
|
||||||
|
#undef ROSTER
|
||||||
|
#define ROSTER(cabid,name,funcmap...) case cabid: return F("" funcmap);
|
||||||
|
const FSH * RMFT2::getRosterFunctions(int16_t cabid) {
|
||||||
|
switch(cabid) {
|
||||||
|
#include "myAutomation.h"
|
||||||
|
default: return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass 8 Signal definitions
|
||||||
|
#include "RMFT2MacroReset.h"
|
||||||
|
#undef SIGNAL
|
||||||
|
#define SIGNAL(redpin,amberpin,greenpin) redpin,amberpin,greenpin,
|
||||||
|
const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||||
|
#include "myAutomation.h"
|
||||||
|
0,0,0 };
|
||||||
|
|
||||||
|
// Last Pass : create main routes table
|
||||||
|
// Only undef the macros, not dummy them.
|
||||||
|
#define RMFT2_UNDEF_ONLY
|
||||||
|
#include "RMFT2MacroReset.h"
|
||||||
|
// Define internal helper macros.
|
||||||
|
// Everything we generate here has to be compile-time evaluated to
|
||||||
|
// a constant.
|
||||||
|
#define V(val) (byte)(((int16_t)(val))&0x00FF),(byte)(((int16_t)(val)>>8)&0x00FF)
|
||||||
// Define macros for route code creation
|
// Define macros for route code creation
|
||||||
#define V(val) ((int16_t)(val))&0x00FF,((int16_t)(val)>>8)&0x00FF
|
|
||||||
#define NOOPERAND 0,0
|
|
||||||
|
|
||||||
#define ALIAS(name,value)
|
#define ACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1 | 1),
|
||||||
#define EXRAIL const FLASH byte RMFT2::RouteCode[] = {
|
#define ACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1 | 1),
|
||||||
#define AUTOMATION(id, description) OPCODE_AUTOMATION, V(id),
|
|
||||||
#define ROUTE(id, description) OPCODE_ROUTE, V(id),
|
|
||||||
#define SEQUENCE(id) OPCODE_SEQUENCE, V(id),
|
|
||||||
#define ENDTASK OPCODE_ENDTASK,NOOPERAND,
|
|
||||||
#define DONE OPCODE_ENDTASK,NOOPERAND,
|
|
||||||
#define ENDEXRAIL OPCODE_ENDTASK,NOOPERAND,OPCODE_ENDEXRAIL,NOOPERAND };
|
|
||||||
|
|
||||||
#define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id),
|
#define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id),
|
||||||
|
#define ALIAS(name,value)
|
||||||
#define AMBER(signal_id) OPCODE_AMBER,V(signal_id),
|
#define AMBER(signal_id) OPCODE_AMBER,V(signal_id),
|
||||||
#define AT(sensor_id) OPCODE_AT,V(sensor_id),
|
#define AT(sensor_id) OPCODE_AT,V(sensor_id),
|
||||||
|
#define ATTIMEOUT(sensor_id,timeout) OPCODE_ATTIMEOUT1,0,0,OPCODE_ATTIMEOUT2,V(sensor_id),OPCODE_PAD,V(timeout/100L),
|
||||||
|
#define AUTOMATION(id, description) OPCODE_AUTOMATION, V(id),
|
||||||
|
#define AUTOSTART OPCODE_AUTOSTART,0,0,
|
||||||
#define CALL(route) OPCODE_CALL,V(route),
|
#define CALL(route) OPCODE_CALL,V(route),
|
||||||
#define CLOSE(id) OPCODE_CLOSE,V(id),
|
#define CLOSE(id) OPCODE_CLOSE,V(id),
|
||||||
#define DELAY(ms) OPCODE_DELAY,V(ms/100L),
|
#define DEACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1),
|
||||||
|
#define DEACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1),
|
||||||
|
#define DELAY(ms) ms<30000?OPCODE_DELAYMS:OPCODE_DELAY,V(ms/(ms<30000?1L:100L)),
|
||||||
#define DELAYMINS(mindelay) OPCODE_DELAYMINS,V(mindelay),
|
#define DELAYMINS(mindelay) OPCODE_DELAYMINS,V(mindelay),
|
||||||
#define DELAYRANDOM(mindelay,maxdelay) OPCODE_DELAY,V(mindelay/100L),OPCODE_RANDWAIT,V((maxdelay-mindelay)/100L),
|
#define DELAYRANDOM(mindelay,maxdelay) DELAY(mindelay) OPCODE_RANDWAIT,V((maxdelay-mindelay)/100L),
|
||||||
#define ENDIF OPCODE_ENDIF,NOOPERAND,
|
#define DONE OPCODE_ENDTASK,0,0,
|
||||||
|
#define DRIVE(analogpin) OPCODE_DRIVE,V(analogpin),
|
||||||
|
#define ELSE OPCODE_ELSE,0,0,
|
||||||
|
#define ENDEXRAIL
|
||||||
|
#define ENDIF OPCODE_ENDIF,0,0,
|
||||||
|
#define ENDTASK OPCODE_ENDTASK,0,0,
|
||||||
#define ESTOP OPCODE_SPEED,V(1),
|
#define ESTOP OPCODE_SPEED,V(1),
|
||||||
|
#define EXRAIL
|
||||||
#define FADE(pin,value,ms) OPCODE_SERVO,V(pin),OPCODE_PAD,V(value),OPCODE_PAD,V(PCA9685::ProfileType::UseDuration|PCA9685::NoPowerOff),OPCODE_PAD,V(ms/100L),
|
#define FADE(pin,value,ms) OPCODE_SERVO,V(pin),OPCODE_PAD,V(value),OPCODE_PAD,V(PCA9685::ProfileType::UseDuration|PCA9685::NoPowerOff),OPCODE_PAD,V(ms/100L),
|
||||||
#define FOFF(func) OPCODE_FOFF,V(func),
|
#define FOFF(func) OPCODE_FOFF,V(func),
|
||||||
#define FOLLOW(route) OPCODE_FOLLOW,V(route),
|
#define FOLLOW(route) OPCODE_FOLLOW,V(route),
|
||||||
@@ -255,53 +193,67 @@ const int StringMacroTracker1=__COUNTER__;
|
|||||||
#define FWD(speed) OPCODE_FWD,V(speed),
|
#define FWD(speed) OPCODE_FWD,V(speed),
|
||||||
#define GREEN(signal_id) OPCODE_GREEN,V(signal_id),
|
#define GREEN(signal_id) OPCODE_GREEN,V(signal_id),
|
||||||
#define IF(sensor_id) OPCODE_IF,V(sensor_id),
|
#define IF(sensor_id) OPCODE_IF,V(sensor_id),
|
||||||
|
#define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id),
|
||||||
|
#define IFGTE(sensor_id,value) OPCODE_IFGTE,V(sensor_id),OPCODE_PAD,V(value),
|
||||||
|
#define IFLT(sensor_id,value) OPCODE_IFLT,V(sensor_id),OPCODE_PAD,V(value),
|
||||||
#define IFNOT(sensor_id) OPCODE_IFNOT,V(sensor_id),
|
#define IFNOT(sensor_id) OPCODE_IFNOT,V(sensor_id),
|
||||||
#define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent),
|
#define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent),
|
||||||
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
|
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
|
||||||
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,NOOPERAND,
|
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),
|
||||||
#define JOIN OPCODE_JOIN,NOOPERAND,
|
#define IFTIMEOUT OPCODE_IFTIMEOUT,0,0,
|
||||||
|
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0,
|
||||||
|
#define JOIN OPCODE_JOIN,0,0,
|
||||||
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
|
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
|
||||||
#define LCD(id,msg) PRINT(msg)
|
#define LCD(id,msg) PRINT(msg)
|
||||||
#define LCN(msg) PRINT(msg)
|
#define LCN(msg) PRINT(msg)
|
||||||
|
#define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr),
|
||||||
|
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
|
||||||
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
|
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
|
||||||
|
#define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr),
|
||||||
|
#define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3),
|
||||||
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
|
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
|
||||||
#define PAUSE OPCODE_PAUSE,NOOPERAND,
|
#define PAUSE OPCODE_PAUSE,0,0,
|
||||||
|
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
|
||||||
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
|
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
|
||||||
#define POWEROFF OPCODE_POWEROFF,NOOPERAND,
|
#define POWEROFF OPCODE_POWEROFF,0,0,
|
||||||
#define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2),
|
#define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2),
|
||||||
#define READ_LOCO OPCODE_READ_LOCO1,NOOPERAND,OPCODE_READ_LOCO2,NOOPERAND,
|
#define READ_LOCO OPCODE_READ_LOCO1,0,0,OPCODE_READ_LOCO2,0,0,
|
||||||
#define RED(signal_id) OPCODE_RED,V(signal_id),
|
#define RED(signal_id) OPCODE_RED,V(signal_id),
|
||||||
#define RESERVE(blockid) OPCODE_RESERVE,V(blockid),
|
#define RESERVE(blockid) OPCODE_RESERVE,V(blockid),
|
||||||
#define RESET(pin) OPCODE_RESET,V(pin),
|
#define RESET(pin) OPCODE_RESET,V(pin),
|
||||||
#define RESUME OPCODE_RESUME,NOOPERAND,
|
#define RESUME OPCODE_RESUME,0,0,
|
||||||
#define RETURN OPCODE_RETURN,NOOPERAND,
|
#define RETURN OPCODE_RETURN,0,0,
|
||||||
#define REV(speed) OPCODE_REV,V(speed),
|
#define REV(speed) OPCODE_REV,V(speed),
|
||||||
|
#define ROSTER(cabid,name,funcmap...)
|
||||||
|
#define ROUTE(id, description) OPCODE_ROUTE, V(id),
|
||||||
#define SENDLOCO(cab,route) OPCODE_SENDLOCO,V(cab),OPCODE_PAD,V(route),
|
#define SENDLOCO(cab,route) OPCODE_SENDLOCO,V(cab),OPCODE_PAD,V(route),
|
||||||
|
#define SEQUENCE(id) OPCODE_SEQUENCE, V(id),
|
||||||
#define SERIAL(msg) PRINT(msg)
|
#define SERIAL(msg) PRINT(msg)
|
||||||
#define SERIAL1(msg) PRINT(msg)
|
#define SERIAL1(msg) PRINT(msg)
|
||||||
#define SERIAL2(msg) PRINT(msg)
|
#define SERIAL2(msg) PRINT(msg)
|
||||||
#define SERIAL3(msg) PRINT(msg)
|
#define SERIAL3(msg) PRINT(msg)
|
||||||
#define START(route) OPCODE_START,V(route),
|
|
||||||
#define SERVO(id,position,profile) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::profile),OPCODE_PAD,V(0),
|
#define SERVO(id,position,profile) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::profile),OPCODE_PAD,V(0),
|
||||||
#define SERVO2(id,position,ms) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::Instant),OPCODE_PAD,V(ms/100L),
|
#define SERVO2(id,position,ms) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::Instant),OPCODE_PAD,V(ms/100L),
|
||||||
#define SETLOCO(loco) OPCODE_SETLOCO,V(loco),
|
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) OPCODE_SERVOTURNOUT,V(id),OPCODE_PAD,V(pin),OPCODE_PAD,V(activeAngle),OPCODE_PAD,V(inactiveAngle),OPCODE_PAD,V(PCA9685::ProfileType::profile),
|
||||||
#define SET(pin) OPCODE_SET,V(pin),
|
#define SET(pin) OPCODE_SET,V(pin),
|
||||||
|
#define SETLOCO(loco) OPCODE_SETLOCO,V(loco),
|
||||||
|
#define SIGNAL(redpin,amberpin,greenpin)
|
||||||
#define SPEED(speed) OPCODE_SPEED,V(speed),
|
#define SPEED(speed) OPCODE_SPEED,V(speed),
|
||||||
|
#define START(route) OPCODE_START,V(route),
|
||||||
#define STOP OPCODE_SPEED,V(0),
|
#define STOP OPCODE_SPEED,V(0),
|
||||||
#define SIGNAL(redpin,amberpin,greenpin) OPCODE_SIGNAL,V(redpin),OPCODE_PAD,V(amberpin),OPCODE_PAD,V(greenpin),
|
|
||||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile) OPCODE_SERVOTURNOUT,V(id),OPCODE_PAD,V(pin),OPCODE_PAD,V(activeAngle),OPCODE_PAD,V(inactiveAngle),OPCODE_PAD,V(PCA9685::ProfileType::profile),
|
|
||||||
#define PIN_TURNOUT(id,pin) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
|
|
||||||
#define THROW(id) OPCODE_THROW,V(id),
|
#define THROW(id) OPCODE_THROW,V(id),
|
||||||
#define TURNOUT(id,addr,subaddr) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr),
|
#define TURNOUT(id,addr,subaddr,description...) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr),
|
||||||
#define UNJOIN OPCODE_UNJOIN,NOOPERAND,
|
#define UNJOIN OPCODE_UNJOIN,0,0,
|
||||||
#define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id),
|
#define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id),
|
||||||
#define WAITFOR(pin) OPCODE_WAITFOR,V(pin),
|
#define WAITFOR(pin) OPCODE_WAITFOR,V(pin),
|
||||||
#define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func),
|
#define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func),
|
||||||
#define XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func),
|
#define XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func),
|
||||||
|
|
||||||
// PASS2 Build RouteCode
|
// Build RouteCode
|
||||||
const int StringMacroTracker2=__COUNTER__;
|
const int StringMacroTracker2=__COUNTER__;
|
||||||
#include "myAutomation.h"
|
const FLASH byte RMFT2::RouteCode[] = {
|
||||||
|
#include "myAutomation.h"
|
||||||
|
OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 };
|
||||||
|
|
||||||
// Restore normal code LCD & SERIAL macro
|
// Restore normal code LCD & SERIAL macro
|
||||||
#undef LCD
|
#undef LCD
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2020-2021 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of DCC-EX CommandStation-EX
|
* This file is part of DCC-EX CommandStation-EX
|
||||||
*
|
*
|
||||||
@@ -18,7 +19,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "RingStream.h"
|
#include "RingStream.h"
|
||||||
#include "defines.h"
|
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
|
|
||||||
RingStream::RingStream( const uint16_t len)
|
RingStream::RingStream( const uint16_t len)
|
||||||
@@ -31,36 +31,27 @@ RingStream::RingStream( const uint16_t len)
|
|||||||
_overflow=false;
|
_overflow=false;
|
||||||
_mark=0;
|
_mark=0;
|
||||||
_count=0;
|
_count=0;
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
|
||||||
_bufMux = portMUX_INITIALIZER_UNLOCKED;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t RingStream::write(uint8_t b) {
|
size_t RingStream::write(uint8_t b) {
|
||||||
if (_overflow) return 0;
|
if (_overflow) return 0;
|
||||||
portENTER_CRITICAL(&_bufMux);
|
|
||||||
_buffer[_pos_write] = b;
|
_buffer[_pos_write] = b;
|
||||||
++_pos_write;
|
++_pos_write;
|
||||||
if (_pos_write==_len) _pos_write=0;
|
if (_pos_write==_len) _pos_write=0;
|
||||||
if (_pos_write==_pos_read) {
|
if (_pos_write==_pos_read) {
|
||||||
_overflow=true;
|
_overflow=true;
|
||||||
portEXIT_CRITICAL(&_bufMux);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
_count++;
|
_count++;
|
||||||
portEXIT_CRITICAL(&_bufMux);
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int RingStream::read(byte advance) {
|
int RingStream::read() {
|
||||||
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
|
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
|
||||||
if (_pos_read == _mark) return -1;
|
|
||||||
portENTER_CRITICAL(&_bufMux);
|
|
||||||
byte b=_buffer[_pos_read];
|
byte b=_buffer[_pos_read];
|
||||||
_pos_read += advance;
|
_pos_read++;
|
||||||
if (_pos_read==_len) _pos_read=0;
|
if (_pos_read==_len) _pos_read=0;
|
||||||
_overflow=false;
|
_overflow=false;
|
||||||
portEXIT_CRITICAL(&_bufMux);
|
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,14 +69,11 @@ int RingStream::freeSpace() {
|
|||||||
|
|
||||||
// mark start of message with client id (0...9)
|
// mark start of message with client id (0...9)
|
||||||
void RingStream::mark(uint8_t b) {
|
void RingStream::mark(uint8_t b) {
|
||||||
//DIAG(F("Mark1 len=%d count=%d pr=%d pw=%d m=%d"),_len, _count,_pos_read,_pos_write,_mark);
|
|
||||||
portENTER_CRITICAL(&_bufMux);
|
|
||||||
_mark=_pos_write;
|
_mark=_pos_write;
|
||||||
write(b); // client id
|
write(b); // client id
|
||||||
write((uint8_t)0); // count MSB placemarker
|
write((uint8_t)0); // count MSB placemarker
|
||||||
write((uint8_t)0); // count LSB placemarker
|
write((uint8_t)0); // count LSB placemarker
|
||||||
_count=0;
|
_count=0;
|
||||||
portEXIT_CRITICAL(&_bufMux);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// peekTargetMark is used by the parser stash routines to know which client
|
// peekTargetMark is used by the parser stash routines to know which client
|
||||||
@@ -94,25 +82,17 @@ uint8_t RingStream::peekTargetMark() {
|
|||||||
return _buffer[_mark];
|
return _buffer[_mark];
|
||||||
}
|
}
|
||||||
|
|
||||||
void RingStream::info() {
|
|
||||||
DIAG(F("Info len=%d count=%d pr=%d pw=%d m=%d"),_len, _count,_pos_read,_pos_write,_mark);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RingStream::commit() {
|
bool RingStream::commit() {
|
||||||
//DIAG(F("Commit1 len=%d count=%d pr=%d pw=%d m=%d"),_len, _count,_pos_read,_pos_write,_mark);
|
|
||||||
portENTER_CRITICAL(&_bufMux);
|
|
||||||
if (_overflow) {
|
if (_overflow) {
|
||||||
DIAG(F("RingStream(%d) commit(%d) OVERFLOW"),_len, _count);
|
DIAG(F("RingStream(%d) commit(%d) OVERFLOW"),_len, _count);
|
||||||
// just throw it away
|
// just throw it away
|
||||||
_pos_write=_mark;
|
_pos_write=_mark;
|
||||||
_overflow=false;
|
_overflow=false;
|
||||||
portEXIT_CRITICAL(&_bufMux);
|
return false; // commit failed
|
||||||
return false; // commit failed
|
|
||||||
}
|
}
|
||||||
if (_count==0) {
|
if (_count==0) {
|
||||||
// ignore empty response
|
// ignore empty response
|
||||||
_pos_write=_mark;
|
_pos_write=_mark;
|
||||||
portEXIT_CRITICAL(&_bufMux);
|
|
||||||
return true; // true=commit ok
|
return true; // true=commit ok
|
||||||
}
|
}
|
||||||
// Go back to the _mark and inject the count 1 byte later
|
// Go back to the _mark and inject the count 1 byte later
|
||||||
@@ -122,8 +102,14 @@ bool RingStream::commit() {
|
|||||||
_mark++;
|
_mark++;
|
||||||
if (_mark==_len) _mark=0;
|
if (_mark==_len) _mark=0;
|
||||||
_buffer[_mark]=lowByte(_count);
|
_buffer[_mark]=lowByte(_count);
|
||||||
_mark=_len+1;
|
|
||||||
//DIAG(F("Commit2 len=%d count=%d pr=%d pw=%d m=%d"),_len, _count,_pos_read,_pos_write,_mark);
|
|
||||||
portEXIT_CRITICAL(&_bufMux);
|
|
||||||
return true; // commit worked
|
return true; // commit worked
|
||||||
}
|
}
|
||||||
|
void RingStream::flush() {
|
||||||
|
_pos_write=0;
|
||||||
|
_pos_read=0;
|
||||||
|
_buffer[0]=0;
|
||||||
|
}
|
||||||
|
void RingStream::printBuffer(Print * stream) {
|
||||||
|
_buffer[_pos_write]='\0';
|
||||||
|
stream->print((char *)_buffer);
|
||||||
|
}
|
||||||
|
14
RingStream.h
14
RingStream.h
@@ -1,7 +1,8 @@
|
|||||||
#ifndef RingStream_h
|
#ifndef RingStream_h
|
||||||
#define RingStream_h
|
#define RingStream_h
|
||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2020-2021 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of DCC-EX CommandStation-EX
|
* This file is part of DCC-EX CommandStation-EX
|
||||||
*
|
*
|
||||||
@@ -28,17 +29,15 @@ class RingStream : public Print {
|
|||||||
|
|
||||||
virtual size_t write(uint8_t b);
|
virtual size_t write(uint8_t b);
|
||||||
using Print::write;
|
using Print::write;
|
||||||
inline int read() { return read(1); };
|
int read();
|
||||||
inline int peek() { return read(0); };
|
|
||||||
int count();
|
int count();
|
||||||
int freeSpace();
|
int freeSpace();
|
||||||
void mark(uint8_t b);
|
void mark(uint8_t b);
|
||||||
bool commit();
|
bool commit();
|
||||||
uint8_t peekTargetMark();
|
uint8_t peekTargetMark();
|
||||||
void info();
|
void printBuffer(Print * streamer);
|
||||||
|
void flush();
|
||||||
private:
|
private:
|
||||||
int read(byte advance);
|
|
||||||
int _len;
|
int _len;
|
||||||
int _pos_write;
|
int _pos_write;
|
||||||
int _pos_read;
|
int _pos_read;
|
||||||
@@ -46,9 +45,6 @@ class RingStream : public Print {
|
|||||||
int _mark;
|
int _mark;
|
||||||
int _count;
|
int _count;
|
||||||
byte * _buffer;
|
byte * _buffer;
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
|
||||||
portMUX_TYPE _bufMux;
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
170
SSD1306Ascii.cpp
170
SSD1306Ascii.cpp
@@ -89,28 +89,93 @@ const uint8_t FLASH SSD1306AsciiWire::blankPixels[30] =
|
|||||||
{0x40, // First byte specifies data mode
|
{0x40, // First byte specifies data mode
|
||||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// this section is based on https://github.com/adafruit/Adafruit_SSD1306
|
||||||
|
|
||||||
|
/** Initialization commands for a 128x32 or 128x64 SSD1306 oled display. */
|
||||||
|
const uint8_t FLASH SSD1306AsciiWire::Adafruit128xXXinit[] = {
|
||||||
|
// Init sequence for Adafruit 128x32/64 OLED module
|
||||||
|
0x00, // Set to command mode
|
||||||
|
SSD1306_DISPLAYOFF,
|
||||||
|
SSD1306_SETDISPLAYCLOCKDIV, 0x80, // the suggested ratio 0x80
|
||||||
|
SSD1306_SETMULTIPLEX, 0x3F, // ratio 64 (initially)
|
||||||
|
SSD1306_SETDISPLAYOFFSET, 0x0, // no offset
|
||||||
|
SSD1306_SETSTARTLINE | 0x0, // line #0
|
||||||
|
SSD1306_CHARGEPUMP, 0x14, // internal vcc
|
||||||
|
SSD1306_MEMORYMODE, 0x02, // page mode
|
||||||
|
SSD1306_SEGREMAP | 0x1, // column 127 mapped to SEG0
|
||||||
|
SSD1306_COMSCANDEC, // column scan direction reversed
|
||||||
|
SSD1306_SETCOMPINS, 0X12, // set COM pins
|
||||||
|
SSD1306_SETCONTRAST, 0x7F, // contrast level 127
|
||||||
|
SSD1306_SETPRECHARGE, 0xF1, // pre-charge period (1, 15)
|
||||||
|
SSD1306_SETVCOMDETECT, 0x40, // vcomh regulator level
|
||||||
|
SSD1306_DISPLAYALLON_RESUME,
|
||||||
|
SSD1306_NORMALDISPLAY,
|
||||||
|
SSD1306_DISPLAYON
|
||||||
|
};
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// This section is based on https://github.com/stanleyhuangyc/MultiLCD
|
||||||
|
|
||||||
|
/** Initialization commands for a 128x64 SH1106 oled display. */
|
||||||
|
const uint8_t FLASH SSD1306AsciiWire::SH1106_132x64init[] = {
|
||||||
|
0x00, // Set to command mode
|
||||||
|
SSD1306_DISPLAYOFF,
|
||||||
|
SSD1306_SETDISPLAYCLOCKDIV, 0X80, // set osc division
|
||||||
|
SSD1306_SETMULTIPLEX, 0x3F, // ratio 64
|
||||||
|
SSD1306_SETDISPLAYOFFSET, 0X00, // set display offset
|
||||||
|
SSD1306_SETSTARTPAGE | 0X0, // set page address
|
||||||
|
SSD1306_SETSTARTLINE | 0x0, // set start line
|
||||||
|
SH1106_SET_PUMP_MODE, SH1106_PUMP_ON, // set charge pump enable
|
||||||
|
SSD1306_SEGREMAP | 0X1, // set segment remap
|
||||||
|
SSD1306_COMSCANDEC, // Com scan direction
|
||||||
|
SSD1306_SETCOMPINS, 0X12, // set COM pins
|
||||||
|
SSD1306_SETCONTRAST, 0x80, // 128
|
||||||
|
SSD1306_SETPRECHARGE, 0X1F, // set pre-charge period
|
||||||
|
SSD1306_SETVCOMDETECT, 0x40, // set vcomh
|
||||||
|
SH1106_SET_PUMP_VOLTAGE | 0X2, // 8.0 volts
|
||||||
|
SSD1306_NORMALDISPLAY, // normal / reverse
|
||||||
|
SSD1306_DISPLAYON
|
||||||
|
};
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
// SSD1306AsciiWire Method Definitions
|
// SSD1306AsciiWire Method Definitions
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) {
|
SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) {
|
||||||
|
m_displayWidth = width;
|
||||||
|
m_displayHeight = height;
|
||||||
// Set size in characters in base class
|
// Set size in characters in base class
|
||||||
lcdRows = height / 8;
|
lcdRows = height / 8;
|
||||||
lcdCols = width / 6;
|
lcdCols = width / 6;
|
||||||
|
m_col = 0;
|
||||||
|
m_row = 0;
|
||||||
|
m_colOffset = 0;
|
||||||
|
|
||||||
I2CManager.begin();
|
I2CManager.begin();
|
||||||
I2CManager.setClock(400000L); // Set max supported I2C speed
|
I2CManager.setClock(400000L); // Set max supported I2C speed
|
||||||
for (byte address = 0x3c; address <= 0x3d; address++) {
|
for (byte address = 0x3c; address <= 0x3d; address++) {
|
||||||
if (I2CManager.exists(address)) {
|
if (I2CManager.exists(address)) {
|
||||||
|
m_i2cAddr = address;
|
||||||
|
if (m_displayWidth==132 && m_displayHeight==64) {
|
||||||
|
// SH1106 display. This uses 128x64 centered within a 132x64 OLED.
|
||||||
|
m_colOffset = 2;
|
||||||
|
I2CManager.write_P(address, SH1106_132x64init, sizeof(SH1106_132x64init));
|
||||||
|
} else if (m_displayWidth==128 && (m_displayHeight==64 || m_displayHeight==32)) {
|
||||||
|
// SSD1306 128x64 or 128x32
|
||||||
|
I2CManager.write_P(address, Adafruit128xXXinit, sizeof(Adafruit128xXXinit));
|
||||||
|
if (m_displayHeight == 32)
|
||||||
|
I2CManager.write(address, 5, 0, // Set command mode
|
||||||
|
SSD1306_SETMULTIPLEX, 0x1F, // ratio 32
|
||||||
|
SSD1306_SETCOMPINS, 0x02); // sequential COM pins, disable remap
|
||||||
|
} else {
|
||||||
|
DIAG(F("OLED configuration option not recognised"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Device found
|
// Device found
|
||||||
DIAG(F("%dx%d OLED display configured on I2C:x%x"), width, height, address);
|
DIAG(F("%dx%d OLED display configured on I2C:x%x"), width, height, address);
|
||||||
if (width == 132)
|
|
||||||
begin(&SH1106_132x64, address);
|
|
||||||
else if (height == 32)
|
|
||||||
begin(&Adafruit128x32, address);
|
|
||||||
else
|
|
||||||
begin(&Adafruit128x64, address);
|
|
||||||
// Set singleton address
|
// Set singleton address
|
||||||
lcdDisplay = this;
|
lcdDisplay = this;
|
||||||
clear();
|
clear();
|
||||||
@@ -132,23 +197,6 @@ void SSD1306AsciiWire::clearNative() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialise device
|
|
||||||
void SSD1306AsciiWire::begin(const DevType* dev, uint8_t i2cAddr) {
|
|
||||||
m_i2cAddr = i2cAddr;
|
|
||||||
m_col = 0;
|
|
||||||
m_row = 0;
|
|
||||||
const uint8_t* table = (const uint8_t*)GETFLASHP(&dev->initcmds);
|
|
||||||
uint8_t size = GETFLASH(&dev->initSize);
|
|
||||||
m_displayWidth = GETFLASH(&dev->lcdWidth);
|
|
||||||
m_displayHeight = GETFLASH(&dev->lcdHeight);
|
|
||||||
m_colOffset = GETFLASH(&dev->colOffset);
|
|
||||||
I2CManager.write_P(m_i2cAddr, table, size);
|
|
||||||
if (m_displayHeight == 32)
|
|
||||||
I2CManager.write(m_i2cAddr, 5, 0, // Set command mode
|
|
||||||
SSD1306_SETMULTIPLEX, 0x1F, // ratio 32
|
|
||||||
SSD1306_SETCOMPINS, 0x02); // sequential COM pins, disable remap
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
// Set cursor position (by text line)
|
// Set cursor position (by text line)
|
||||||
@@ -209,82 +257,6 @@ size_t SSD1306AsciiWire::writeNative(uint8_t ch) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
|
||||||
// this section is based on https://github.com/adafruit/Adafruit_SSD1306
|
|
||||||
|
|
||||||
/** Initialization commands for a 128x32 or 128x64 SSD1306 oled display. */
|
|
||||||
const uint8_t FLASH SSD1306AsciiWire::Adafruit128xXXinit[] = {
|
|
||||||
// Init sequence for Adafruit 128x32/64 OLED module
|
|
||||||
0x00, // Set to command mode
|
|
||||||
SSD1306_DISPLAYOFF,
|
|
||||||
SSD1306_SETDISPLAYCLOCKDIV, 0x80, // the suggested ratio 0x80
|
|
||||||
SSD1306_SETMULTIPLEX, 0x3F, // ratio 64 (initially)
|
|
||||||
SSD1306_SETDISPLAYOFFSET, 0x0, // no offset
|
|
||||||
SSD1306_SETSTARTLINE | 0x0, // line #0
|
|
||||||
SSD1306_CHARGEPUMP, 0x14, // internal vcc
|
|
||||||
SSD1306_MEMORYMODE, 0x02, // page mode
|
|
||||||
SSD1306_SEGREMAP | 0x1, // column 127 mapped to SEG0
|
|
||||||
SSD1306_COMSCANDEC, // column scan direction reversed
|
|
||||||
SSD1306_SETCOMPINS, 0X12, // set COM pins
|
|
||||||
SSD1306_SETCONTRAST, 0x7F, // contrast level 127
|
|
||||||
SSD1306_SETPRECHARGE, 0xF1, // pre-charge period (1, 15)
|
|
||||||
SSD1306_SETVCOMDETECT, 0x40, // vcomh regulator level
|
|
||||||
SSD1306_DISPLAYALLON_RESUME,
|
|
||||||
SSD1306_NORMALDISPLAY,
|
|
||||||
SSD1306_DISPLAYON
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Initialize a 128x32 SSD1306 oled display. */
|
|
||||||
const DevType FLASH SSD1306AsciiWire::Adafruit128x32 = {
|
|
||||||
Adafruit128xXXinit,
|
|
||||||
sizeof(Adafruit128xXXinit),
|
|
||||||
128,
|
|
||||||
32,
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Initialize a 128x64 oled display. */
|
|
||||||
const DevType FLASH SSD1306AsciiWire::Adafruit128x64 = {
|
|
||||||
Adafruit128xXXinit,
|
|
||||||
sizeof(Adafruit128xXXinit),
|
|
||||||
128,
|
|
||||||
64,
|
|
||||||
0
|
|
||||||
};
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// This section is based on https://github.com/stanleyhuangyc/MultiLCD
|
|
||||||
|
|
||||||
/** Initialization commands for a 128x64 SH1106 oled display. */
|
|
||||||
const uint8_t FLASH SSD1306AsciiWire::SH1106_132x64init[] = {
|
|
||||||
0x00, // Set to command mode
|
|
||||||
SSD1306_DISPLAYOFF,
|
|
||||||
SSD1306_SETDISPLAYCLOCKDIV, 0X80, // set osc division
|
|
||||||
SSD1306_SETMULTIPLEX, 0x3F, // ratio 64
|
|
||||||
SSD1306_SETDISPLAYOFFSET, 0X00, // set display offset
|
|
||||||
SSD1306_SETSTARTPAGE | 0X0, // set page address
|
|
||||||
SSD1306_SETSTARTLINE | 0x0, // set start line
|
|
||||||
SH1106_SET_PUMP_MODE, SH1106_PUMP_ON, // set charge pump enable
|
|
||||||
SSD1306_SEGREMAP | 0X1, // set segment remap
|
|
||||||
SSD1306_COMSCANDEC, // Com scan direction
|
|
||||||
SSD1306_SETCOMPINS, 0X12, // set COM pins
|
|
||||||
SSD1306_SETCONTRAST, 0x80, // 128
|
|
||||||
SSD1306_SETPRECHARGE, 0X1F, // set pre-charge period
|
|
||||||
SSD1306_SETVCOMDETECT, 0x40, // set vcomh
|
|
||||||
SH1106_SET_PUMP_VOLTAGE | 0X2, // 8.0 volts
|
|
||||||
SSD1306_NORMALDISPLAY, // normal / reverse
|
|
||||||
SSD1306_DISPLAYON
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Initialize a 132x64 oled SH1106 display. */
|
|
||||||
const DevType FLASH SSD1306AsciiWire::SH1106_132x64 = {
|
|
||||||
SH1106_132x64init,
|
|
||||||
sizeof(SH1106_132x64init),
|
|
||||||
128,
|
|
||||||
64,
|
|
||||||
2 // SH1106 is a 132x64 controller but most OLEDs are only attached
|
|
||||||
// to columns 2-129.
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@@ -32,21 +32,6 @@
|
|||||||
//#define NOLOWERCASE
|
//#define NOLOWERCASE
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Device initialization structure.
|
|
||||||
|
|
||||||
struct DevType {
|
|
||||||
/* Pointer to initialization command bytes. */
|
|
||||||
const uint8_t* initcmds;
|
|
||||||
/* Number of initialization bytes */
|
|
||||||
const uint8_t initSize;
|
|
||||||
/* Width of the display in pixels */
|
|
||||||
const uint8_t lcdWidth;
|
|
||||||
/** Height of the display in pixels. */
|
|
||||||
const uint8_t lcdHeight;
|
|
||||||
/* Column offset RAM to display. Used to pick start column of SH1106. */
|
|
||||||
const uint8_t colOffset;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
class SSD1306AsciiWire : public LCDDisplay {
|
class SSD1306AsciiWire : public LCDDisplay {
|
||||||
public:
|
public:
|
||||||
@@ -55,25 +40,17 @@ class SSD1306AsciiWire : public LCDDisplay {
|
|||||||
SSD1306AsciiWire(int width, int height);
|
SSD1306AsciiWire(int width, int height);
|
||||||
|
|
||||||
// Initialize the display controller.
|
// Initialize the display controller.
|
||||||
void begin(const DevType* dev, uint8_t i2cAddr);
|
void begin(uint8_t i2cAddr);
|
||||||
|
|
||||||
// Clear the display and set the cursor to (0, 0).
|
// Clear the display and set the cursor to (0, 0).
|
||||||
void clearNative() override;
|
void clearNative() override;
|
||||||
|
|
||||||
// Set cursor to start of specified text line
|
// Set cursor to start of specified text line
|
||||||
void setRowNative(byte line) override;
|
void setRowNative(byte line) override;
|
||||||
|
|
||||||
// Initialize the display controller.
|
|
||||||
void init(const DevType* dev);
|
|
||||||
|
|
||||||
// Write one character to OLED
|
// Write one character to OLED
|
||||||
size_t writeNative(uint8_t c) override;
|
size_t writeNative(uint8_t c) override;
|
||||||
|
|
||||||
// Display characteristics / initialisation
|
|
||||||
static const DevType FLASH Adafruit128x32;
|
|
||||||
static const DevType FLASH Adafruit128x64;
|
|
||||||
static const DevType FLASH SH1106_132x64;
|
|
||||||
|
|
||||||
bool isBusy() override { return requestBlock.isBusy(); }
|
bool isBusy() override { return requestBlock.isBusy(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
25
Sensors.cpp
25
Sensors.cpp
@@ -1,8 +1,10 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2021 Neil McKechnie
|
||||||
* © 2021, modified by Neil McKechnie. All rights reserved.
|
* © 2020-2021 Harald Barth
|
||||||
|
* © 2020-2021 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC API
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
* This is free software: you can redistribute it and/or modify
|
* This is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -67,8 +69,11 @@ decide to ignore the <q ID> return and only react to <Q ID> triggers.
|
|||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
|
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
|
#include "CommandDistributor.h"
|
||||||
#include "Sensors.h"
|
#include "Sensors.h"
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
#include "EEStore.h"
|
#include "EEStore.h"
|
||||||
|
#endif
|
||||||
#include "IODevice.h"
|
#include "IODevice.h"
|
||||||
|
|
||||||
|
|
||||||
@@ -85,7 +90,7 @@ decide to ignore the <q ID> return and only react to <Q ID> triggers.
|
|||||||
// second part of the list is determined from by the 'firstPollSensor' pointer.
|
// second part of the list is determined from by the 'firstPollSensor' pointer.
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
void Sensor::checkAll(Print *stream){
|
void Sensor::checkAll(){
|
||||||
uint16_t sensorCount = 0;
|
uint16_t sensorCount = 0;
|
||||||
|
|
||||||
#ifdef USE_NOTIFY
|
#ifdef USE_NOTIFY
|
||||||
@@ -133,10 +138,8 @@ void Sensor::checkAll(Print *stream){
|
|||||||
readingSensor->active = readingSensor->inputState;
|
readingSensor->active = readingSensor->inputState;
|
||||||
readingSensor->latchDelay = minReadCount; // Reset counter
|
readingSensor->latchDelay = minReadCount; // Reset counter
|
||||||
|
|
||||||
if (stream != NULL) {
|
CommandDistributor::broadcastSensor(readingSensor->data.snum,readingSensor->active);
|
||||||
StringFormatter::send(stream, F("<%c %d>\n"), readingSensor->active ? 'Q' : 'q', readingSensor->data.snum);
|
pause = true; // Don't check any more sensors on this entry
|
||||||
pause = true; // Don't check any more sensors on this entry
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move to next sensor in list.
|
// Move to next sensor in list.
|
||||||
@@ -275,7 +278,7 @@ bool Sensor::remove(int n){
|
|||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
void Sensor::load(){
|
void Sensor::load(){
|
||||||
struct SensorData data;
|
struct SensorData data;
|
||||||
Sensor *tt;
|
Sensor *tt;
|
||||||
@@ -303,7 +306,7 @@ void Sensor::store(){
|
|||||||
EEStore::eeStore->data.nSensors++;
|
EEStore::eeStore->data.nSensors++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
Sensor *Sensor::firstSensor=NULL;
|
Sensor *Sensor::firstSensor=NULL;
|
||||||
@@ -314,4 +317,4 @@ unsigned long Sensor::lastReadCycle=0;
|
|||||||
Sensor *Sensor::firstPollSensor = NULL;
|
Sensor *Sensor::firstPollSensor = NULL;
|
||||||
Sensor *Sensor::lastSensor = NULL;
|
Sensor *Sensor::lastSensor = NULL;
|
||||||
bool Sensor::inputChangeCallbackRegistered = false;
|
bool Sensor::inputChangeCallbackRegistered = false;
|
||||||
#endif
|
#endif
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2021 Neil McKechnie
|
||||||
|
* © 2020-2021 Harald Barth
|
||||||
|
* © 2020-2021 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC API
|
* This file is part of Asbelos DCC API
|
||||||
*
|
*
|
||||||
@@ -68,12 +71,14 @@ public:
|
|||||||
Sensor *nextSensor;
|
Sensor *nextSensor;
|
||||||
|
|
||||||
void setState(int state);
|
void setState(int state);
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
static void load();
|
static void load();
|
||||||
static void store();
|
static void store();
|
||||||
|
#endif
|
||||||
static Sensor *create(int id, VPIN vpin, int pullUp);
|
static Sensor *create(int id, VPIN vpin, int pullUp);
|
||||||
static Sensor* get(int id);
|
static Sensor* get(int id);
|
||||||
static bool remove(int id);
|
static bool remove(int id);
|
||||||
static void checkAll(Print *stream);
|
static void checkAll();
|
||||||
static void printAll(Print *stream);
|
static void printAll(Print *stream);
|
||||||
static unsigned long lastReadCycle; // value of micros at start of last read cycle
|
static unsigned long lastReadCycle; // value of micros at start of last read cycle
|
||||||
static const unsigned int cycleInterval = 10000; // min time between consecutive reads of each sensor in microsecs.
|
static const unsigned int cycleInterval = 10000; // min time between consecutive reads of each sensor in microsecs.
|
||||||
|
82
SerialManager.cpp
Normal file
82
SerialManager.cpp
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* © 2021 Chris Harlow
|
||||||
|
* © 2022 Harald Barth
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is part of DCC++EX
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "SerialManager.h"
|
||||||
|
#include "DCCEXParser.h"
|
||||||
|
SerialManager * SerialManager::first=NULL;
|
||||||
|
|
||||||
|
SerialManager::SerialManager(Stream * myserial) {
|
||||||
|
serial=myserial;
|
||||||
|
next=first;
|
||||||
|
first=this;
|
||||||
|
bufferLength=0;
|
||||||
|
inCommandPayload=false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerialManager::init() {
|
||||||
|
while (!Serial && millis() < 5000); // wait max 5s for Serial to start
|
||||||
|
Serial.begin(115200);
|
||||||
|
new SerialManager(&Serial);
|
||||||
|
#ifdef SERIAL3_COMMANDS
|
||||||
|
Serial3.begin(115200);
|
||||||
|
new SerialManager(&Serial3);
|
||||||
|
#endif
|
||||||
|
#ifdef SERIAL2_COMMANDS
|
||||||
|
Serial2.begin(115200);
|
||||||
|
new SerialManager(&Serial2);
|
||||||
|
#endif
|
||||||
|
#ifdef SERIAL1_COMMANDS
|
||||||
|
Serial1.begin(115200);
|
||||||
|
new SerialManager(&Serial1);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerialManager::broadcast(RingStream * ring) {
|
||||||
|
for (SerialManager * s=first;s;s=s->next) s->broadcast2(ring);
|
||||||
|
}
|
||||||
|
void SerialManager::broadcast2(RingStream * ring) {
|
||||||
|
ring->printBuffer(serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerialManager::loop() {
|
||||||
|
for (SerialManager * s=first;s;s=s->next) s->loop2();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerialManager::loop2() {
|
||||||
|
while (serial->available()) {
|
||||||
|
char ch = serial->read();
|
||||||
|
if (ch == '<') {
|
||||||
|
inCommandPayload = true;
|
||||||
|
bufferLength = 0;
|
||||||
|
buffer[0] = '\0';
|
||||||
|
}
|
||||||
|
else if (ch == '>') {
|
||||||
|
buffer[bufferLength] = '\0';
|
||||||
|
DCCEXParser::parse(serial, buffer, NULL);
|
||||||
|
inCommandPayload = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (inCommandPayload) {
|
||||||
|
if (bufferLength < (COMMAND_BUFFER_SIZE-1)) buffer[bufferLength++] = ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,7 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* © 2021, Harald Barth.
|
* © 2021 Chris Harlow
|
||||||
*
|
* All rights reserved.
|
||||||
* This file is part of CommandStation-EX
|
*
|
||||||
|
* This file is part of DCC++EX
|
||||||
*
|
*
|
||||||
* This is free software: you can redistribute it and/or modify
|
* This is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -17,23 +18,32 @@
|
|||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
#ifndef SerialManager_h
|
||||||
#ifndef WifiESP32_h
|
#define SerialManager_h
|
||||||
#define WifiESP32_h
|
|
||||||
|
|
||||||
#include "FSH.h"
|
#include "Arduino.h"
|
||||||
|
#include "defines.h"
|
||||||
|
#include "RingStream.h"
|
||||||
|
|
||||||
class WifiESP
|
#ifndef COMMAND_BUFFER_SIZE
|
||||||
{
|
#define COMMAND_BUFFER_SIZE 100
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class SerialManager {
|
||||||
public:
|
public:
|
||||||
static bool setup(const char *wifiESSID,
|
static void init();
|
||||||
const char *wifiPassword,
|
|
||||||
const char *hostname,
|
|
||||||
const int port,
|
|
||||||
const byte channel);
|
|
||||||
static void loop();
|
static void loop();
|
||||||
private:
|
static void broadcast(RingStream * ring);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static SerialManager * first;
|
||||||
|
SerialManager(Stream * myserial);
|
||||||
|
void loop2();
|
||||||
|
void broadcast2(RingStream * ring);
|
||||||
|
Stream * serial;
|
||||||
|
SerialManager * next;
|
||||||
|
byte bufferLength;
|
||||||
|
byte buffer[COMMAND_BUFFER_SIZE];
|
||||||
|
bool inCommandPayload;
|
||||||
};
|
};
|
||||||
#endif //WifiESP8266_h
|
#endif
|
||||||
#endif //ESP8266
|
|
68
Turnouts.cpp
68
Turnouts.cpp
@@ -1,10 +1,13 @@
|
|||||||
/*
|
/*
|
||||||
* © 2021 Restructured Neil McKechnie
|
* © 2021 Neil McKechnie
|
||||||
|
* © 2021 M Steve Todd
|
||||||
|
* © 2021 Fred Decker
|
||||||
|
* © 2020-2021 Harald Barth
|
||||||
|
* © 2020-2021 Chris Harlow
|
||||||
* © 2013-2016 Gregg E. Berman
|
* © 2013-2016 Gregg E. Berman
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* All rights reserved.
|
||||||
* © 2020, Harald Barth.
|
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC API
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
* This is free software: you can redistribute it and/or modify
|
* This is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -22,8 +25,11 @@
|
|||||||
|
|
||||||
|
|
||||||
#include "defines.h" // includes config.h
|
#include "defines.h" // includes config.h
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
#include "EEStore.h"
|
#include "EEStore.h"
|
||||||
|
#endif
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
|
#include "CommandDistributor.h"
|
||||||
#include "RMFT2.h"
|
#include "RMFT2.h"
|
||||||
#include "Turnouts.h"
|
#include "Turnouts.h"
|
||||||
#include "DCC.h"
|
#include "DCC.h"
|
||||||
@@ -68,11 +74,7 @@
|
|||||||
turnoutlistHash++;
|
turnoutlistHash++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed;
|
|
||||||
void Turnout::printState(Print *stream) {
|
|
||||||
StringFormatter::send(stream, F("<H %d %d>\n"),
|
|
||||||
_turnoutData.id, !_turnoutData.closed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove nominated turnout from turnout linked list and delete the object.
|
// Remove nominated turnout from turnout linked list and delete the object.
|
||||||
/* static */ bool Turnout::remove(uint16_t id) {
|
/* static */ bool Turnout::remove(uint16_t id) {
|
||||||
@@ -116,10 +118,7 @@
|
|||||||
RMFT2::turnoutEvent(id, closeFlag);
|
RMFT2::turnoutEvent(id, closeFlag);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Send message to JMRI etc. over Serial USB. This is done here
|
CommandDistributor::broadcastTurnout(id, closeFlag);
|
||||||
// to ensure that the message is sent when the turnout operation
|
|
||||||
// is not initiated by a Serial command.
|
|
||||||
tt->printState(&Serial);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,25 +139,25 @@
|
|||||||
bool ok = tt->setClosedInternal(closeFlag);
|
bool ok = tt->setClosedInternal(closeFlag);
|
||||||
|
|
||||||
if (ok) {
|
if (ok) {
|
||||||
turnoutlistHash++; // let withrottle know something changed
|
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
// Write byte containing new closed/thrown state to EEPROM if required. Note that eepromAddress
|
// Write byte containing new closed/thrown state to EEPROM if required. Note that eepromAddress
|
||||||
// is always zero for LCN turnouts.
|
// is always zero for LCN turnouts.
|
||||||
if (EEStore::eeStore->data.nTurnouts > 0 && tt->_eepromAddress > 0)
|
if (EEStore::eeStore->data.nTurnouts > 0 && tt->_eepromAddress > 0)
|
||||||
EEPROM.put(tt->_eepromAddress, tt->_turnoutData.flags);
|
EEPROM.put(tt->_eepromAddress, tt->_turnoutData.flags);
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(RMFT_ACTIVE)
|
#if defined(RMFT_ACTIVE)
|
||||||
RMFT2::turnoutEvent(id, closeFlag);
|
RMFT2::turnoutEvent(id, closeFlag);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Send message to JMRI etc. over Serial USB. This is done here
|
// Send message to JMRI etc.
|
||||||
// to ensure that the message is sent when the turnout operation
|
CommandDistributor::broadcastTurnout(id, closeFlag);
|
||||||
// is not initiated by a Serial command.
|
|
||||||
tt->printState(&Serial);
|
|
||||||
}
|
}
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
// Load all turnout objects
|
// Load all turnout objects
|
||||||
/* static */ void Turnout::load() {
|
/* static */ void Turnout::load() {
|
||||||
for (uint16_t i=0; i<EEStore::eeStore->data.nTurnouts; i++) {
|
for (uint16_t i=0; i<EEStore::eeStore->data.nTurnouts; i++) {
|
||||||
@@ -212,13 +211,7 @@
|
|||||||
#endif
|
#endif
|
||||||
return tt;
|
return tt;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
// Display, on the specified stream, the current state of the turnout (1=thrown or 0=closed).
|
|
||||||
/* static */ void Turnout::printState(uint16_t id, Print *stream) {
|
|
||||||
Turnout *tt = get(id);
|
|
||||||
if (tt) tt->printState(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*************************************************************************************
|
/*************************************************************************************
|
||||||
* ServoTurnout - Turnout controlled by servo device.
|
* ServoTurnout - Turnout controlled by servo device.
|
||||||
@@ -277,6 +270,7 @@
|
|||||||
|
|
||||||
// Load a Servo turnout definition from EEPROM. The common Turnout data has already been read at this point.
|
// Load a Servo turnout definition from EEPROM. The common Turnout data has already been read at this point.
|
||||||
Turnout *ServoTurnout::load(struct TurnoutData *turnoutData) {
|
Turnout *ServoTurnout::load(struct TurnoutData *turnoutData) {
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
ServoTurnoutData servoTurnoutData;
|
ServoTurnoutData servoTurnoutData;
|
||||||
// Read class-specific data from EEPROM
|
// Read class-specific data from EEPROM
|
||||||
EEPROM.get(EEStore::pointer(), servoTurnoutData);
|
EEPROM.get(EEStore::pointer(), servoTurnoutData);
|
||||||
@@ -286,6 +280,10 @@
|
|||||||
Turnout *tt = ServoTurnout::create(turnoutData->id, servoTurnoutData.vpin, servoTurnoutData.thrownPosition,
|
Turnout *tt = ServoTurnout::create(turnoutData->id, servoTurnoutData.vpin, servoTurnoutData.thrownPosition,
|
||||||
servoTurnoutData.closedPosition, servoTurnoutData.profile, turnoutData->closed);
|
servoTurnoutData.closedPosition, servoTurnoutData.profile, turnoutData->closed);
|
||||||
return tt;
|
return tt;
|
||||||
|
#else
|
||||||
|
(void)turnoutData;
|
||||||
|
return NULL;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed
|
// For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed
|
||||||
@@ -308,6 +306,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ServoTurnout::save() {
|
void ServoTurnout::save() {
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
// Write turnout definition and current position to EEPROM
|
// Write turnout definition and current position to EEPROM
|
||||||
// First write common servo data, then
|
// First write common servo data, then
|
||||||
// write the servo-specific data
|
// write the servo-specific data
|
||||||
@@ -315,6 +314,7 @@
|
|||||||
EEStore::advance(sizeof(_turnoutData));
|
EEStore::advance(sizeof(_turnoutData));
|
||||||
EEPROM.put(EEStore::pointer(), _servoTurnoutData);
|
EEPROM.put(EEStore::pointer(), _servoTurnoutData);
|
||||||
EEStore::advance(sizeof(_servoTurnoutData));
|
EEStore::advance(sizeof(_servoTurnoutData));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*************************************************************************************
|
/*************************************************************************************
|
||||||
@@ -367,6 +367,7 @@
|
|||||||
|
|
||||||
// Load a DCC turnout definition from EEPROM. The common Turnout data has already been read at this point.
|
// Load a DCC turnout definition from EEPROM. The common Turnout data has already been read at this point.
|
||||||
/* static */ Turnout *DCCTurnout::load(struct TurnoutData *turnoutData) {
|
/* static */ Turnout *DCCTurnout::load(struct TurnoutData *turnoutData) {
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
DCCTurnoutData dccTurnoutData;
|
DCCTurnoutData dccTurnoutData;
|
||||||
// Read class-specific data from EEPROM
|
// Read class-specific data from EEPROM
|
||||||
EEPROM.get(EEStore::pointer(), dccTurnoutData);
|
EEPROM.get(EEStore::pointer(), dccTurnoutData);
|
||||||
@@ -376,6 +377,10 @@
|
|||||||
DCCTurnout *tt = new DCCTurnout(turnoutData->id, dccTurnoutData.address, dccTurnoutData.subAddress);
|
DCCTurnout *tt = new DCCTurnout(turnoutData->id, dccTurnoutData.address, dccTurnoutData.subAddress);
|
||||||
|
|
||||||
return tt;
|
return tt;
|
||||||
|
#else
|
||||||
|
(void)turnoutData;
|
||||||
|
return NULL;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCCTurnout::print(Print *stream) {
|
void DCCTurnout::print(Print *stream) {
|
||||||
@@ -396,6 +401,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DCCTurnout::save() {
|
void DCCTurnout::save() {
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
// Write turnout definition and current position to EEPROM
|
// Write turnout definition and current position to EEPROM
|
||||||
// First write common servo data, then
|
// First write common servo data, then
|
||||||
// write the servo-specific data
|
// write the servo-specific data
|
||||||
@@ -403,6 +409,7 @@
|
|||||||
EEStore::advance(sizeof(_turnoutData));
|
EEStore::advance(sizeof(_turnoutData));
|
||||||
EEPROM.put(EEStore::pointer(), _dccTurnoutData);
|
EEPROM.put(EEStore::pointer(), _dccTurnoutData);
|
||||||
EEStore::advance(sizeof(_dccTurnoutData));
|
EEStore::advance(sizeof(_dccTurnoutData));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -441,6 +448,7 @@
|
|||||||
|
|
||||||
// Load a VPIN turnout definition from EEPROM. The common Turnout data has already been read at this point.
|
// Load a VPIN turnout definition from EEPROM. The common Turnout data has already been read at this point.
|
||||||
/* static */ Turnout *VpinTurnout::load(struct TurnoutData *turnoutData) {
|
/* static */ Turnout *VpinTurnout::load(struct TurnoutData *turnoutData) {
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
VpinTurnoutData vpinTurnoutData;
|
VpinTurnoutData vpinTurnoutData;
|
||||||
// Read class-specific data from EEPROM
|
// Read class-specific data from EEPROM
|
||||||
EEPROM.get(EEStore::pointer(), vpinTurnoutData);
|
EEPROM.get(EEStore::pointer(), vpinTurnoutData);
|
||||||
@@ -450,6 +458,10 @@
|
|||||||
VpinTurnout *tt = new VpinTurnout(turnoutData->id, vpinTurnoutData.vpin, turnoutData->closed);
|
VpinTurnout *tt = new VpinTurnout(turnoutData->id, vpinTurnoutData.vpin, turnoutData->closed);
|
||||||
|
|
||||||
return tt;
|
return tt;
|
||||||
|
#else
|
||||||
|
(void)turnoutData;
|
||||||
|
return NULL;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report 1 for thrown, 0 for closed.
|
// Report 1 for thrown, 0 for closed.
|
||||||
@@ -465,6 +477,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VpinTurnout::save() {
|
void VpinTurnout::save() {
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
// Write turnout definition and current position to EEPROM
|
// Write turnout definition and current position to EEPROM
|
||||||
// First write common servo data, then
|
// First write common servo data, then
|
||||||
// write the servo-specific data
|
// write the servo-specific data
|
||||||
@@ -472,6 +485,7 @@
|
|||||||
EEStore::advance(sizeof(_turnoutData));
|
EEStore::advance(sizeof(_turnoutData));
|
||||||
EEPROM.put(EEStore::pointer(), _vpinTurnoutData);
|
EEPROM.put(EEStore::pointer(), _vpinTurnoutData);
|
||||||
EEStore::advance(sizeof(_vpinTurnoutData));
|
EEStore::advance(sizeof(_vpinTurnoutData));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
19
Turnouts.h
19
Turnouts.h
@@ -1,9 +1,13 @@
|
|||||||
/*
|
/*
|
||||||
* © 2021 Restructured Neil McKechnie
|
* © 2021 Neil McKechnie
|
||||||
|
* © 2021 M Steve Todd
|
||||||
|
* © 2021 Fred Decker
|
||||||
|
* © 2020-2021 Harald Barth
|
||||||
|
* © 2020-2021 Chris Harlow
|
||||||
* © 2013-2016 Gregg E. Berman
|
* © 2013-2016 Gregg E. Berman
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC API
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
* This is free software: you can redistribute it and/or modify
|
* This is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,7 +29,7 @@
|
|||||||
//#define EESTOREDEBUG
|
//#define EESTOREDEBUG
|
||||||
#include "Arduino.h"
|
#include "Arduino.h"
|
||||||
#include "IODevice.h"
|
#include "IODevice.h"
|
||||||
|
#include "StringFormatter.h"
|
||||||
|
|
||||||
// Turnout type definitions
|
// Turnout type definitions
|
||||||
enum {
|
enum {
|
||||||
@@ -155,19 +159,20 @@ public:
|
|||||||
|
|
||||||
inline static Turnout *first() { return _firstTurnout; }
|
inline static Turnout *first() { return _firstTurnout; }
|
||||||
|
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
// Load all turnout definitions.
|
// Load all turnout definitions.
|
||||||
static void load();
|
static void load();
|
||||||
// Load one turnout definition
|
// Load one turnout definition
|
||||||
static Turnout *loadTurnout();
|
static Turnout *loadTurnout();
|
||||||
// Save all turnout definitions
|
// Save all turnout definitions
|
||||||
static void store();
|
static void store();
|
||||||
|
#endif
|
||||||
static void printAll(Print *stream) {
|
static void printAll(Print *stream) {
|
||||||
for (Turnout *tt = _firstTurnout; tt != 0; tt = tt->_nextTurnout)
|
for (Turnout *tt = _firstTurnout; tt != 0; tt = tt->_nextTurnout)
|
||||||
tt->printState(stream);
|
StringFormatter::send(stream, F("<H %d %d>\n"),tt->getId(), tt->isThrown());
|
||||||
}
|
}
|
||||||
|
|
||||||
static void printState(uint16_t id, Print *stream);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
597
WiThrottle.cpp
597
WiThrottle.cpp
@@ -1,8 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2021 Neil McKechnie
|
||||||
* © 2020, Harald Barth
|
* © 2021 Mike S
|
||||||
|
* © 2020-2022 Harald Barth
|
||||||
|
* © 2020-2021 M Steve Todd
|
||||||
|
* © 2020-2021 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC API
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
* This is free software: you can redistribute it and/or modify
|
* This is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -50,7 +54,7 @@
|
|||||||
#include "GITHUB_SHA.h"
|
#include "GITHUB_SHA.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "RMFT2.h"
|
#include "RMFT2.h"
|
||||||
|
#include "CommandDistributor.h"
|
||||||
|
|
||||||
#define LOOPLOCOS(THROTTLECHAR, CAB) for (int loco=0;loco<MAX_MY_LOCO;loco++) \
|
#define LOOPLOCOS(THROTTLECHAR, CAB) for (int loco=0;loco<MAX_MY_LOCO;loco++) \
|
||||||
if ((myLocos[loco].throttle==THROTTLECHAR || '*'==THROTTLECHAR) && (CAB<0 || myLocos[loco].cab==CAB))
|
if ((myLocos[loco].throttle==THROTTLECHAR || '*'==THROTTLECHAR) && (CAB<0 || myLocos[loco].cab==CAB))
|
||||||
@@ -111,18 +115,18 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
|||||||
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d)<-[%e]"),millis(),clientid,cmd);
|
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d)<-[%e]"),millis(),clientid,cmd);
|
||||||
|
|
||||||
if (initSent) {
|
if (initSent) {
|
||||||
// Send power state if different than last sent
|
|
||||||
bool currentPowerState = (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON);
|
|
||||||
if (lastPowerState != currentPowerState) {
|
|
||||||
StringFormatter::send(stream,F("PPA%x\n"),currentPowerState);
|
|
||||||
lastPowerState = currentPowerState;
|
|
||||||
}
|
|
||||||
// Send turnout list if changed since last sent (will replace list on client)
|
// Send turnout list if changed since last sent (will replace list on client)
|
||||||
if (turnoutListHash != Turnout::turnoutlistHash) {
|
if (turnoutListHash != Turnout::turnoutlistHash) {
|
||||||
StringFormatter::send(stream,F("PTL"));
|
StringFormatter::send(stream,F("PTL"));
|
||||||
for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){
|
for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){
|
||||||
int id=tt->getId();
|
int id=tt->getId();
|
||||||
StringFormatter::send(stream,F("]\\[%d}|{%d}|{%c"), id, id, Turnout::isClosed(id)?'2':'4');
|
StringFormatter::send(stream,F("]\\[%d}|{"), id);
|
||||||
|
#ifdef RMFT_ACTIVE
|
||||||
|
RMFT2::emitTurnoutDescription(stream,id);
|
||||||
|
#else
|
||||||
|
StringFormatter::send(stream,F("%d"), id);
|
||||||
|
#endif
|
||||||
|
StringFormatter::send(stream,F("}|{%c"), Turnout::isClosed(id)?'2':'4');
|
||||||
}
|
}
|
||||||
StringFormatter::send(stream,F("\n"));
|
StringFormatter::send(stream,F("\n"));
|
||||||
turnoutListHash = Turnout::turnoutlistHash; // keep a copy of hash for later comparison
|
turnoutListHash = Turnout::turnoutlistHash; // keep a copy of hash for later comparison
|
||||||
@@ -133,100 +137,105 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
|||||||
exRailSent=true;
|
exRailSent=true;
|
||||||
#ifdef RMFT_ACTIVE
|
#ifdef RMFT_ACTIVE
|
||||||
RMFT2::emitWithrottleRouteList(stream);
|
RMFT2::emitWithrottleRouteList(stream);
|
||||||
#endif
|
#endif
|
||||||
|
// allow heartbeat to slow down once all metadata sent
|
||||||
|
StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_SECONDS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (cmd[0]) {
|
while (cmd[0]) {
|
||||||
switch (cmd[0]) {
|
switch (cmd[0]) {
|
||||||
case '*': // heartbeat control
|
case '*': // heartbeat control
|
||||||
if (cmd[1]=='+') heartBeatEnable=true;
|
if (cmd[1]=='+') heartBeatEnable=true;
|
||||||
else if (cmd[1]=='-') heartBeatEnable=false;
|
else if (cmd[1]=='-') heartBeatEnable=false;
|
||||||
break;
|
break;
|
||||||
case 'P':
|
case 'P':
|
||||||
if (cmd[1]=='P' && cmd[2]=='A' ) { //PPA power mode
|
if (cmd[1]=='P' && cmd[2]=='A' ) { //PPA power mode
|
||||||
DCCWaveform::mainTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
DCCWaveform::mainTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
||||||
if (MotorDriver::commonFaultPin) // commonFaultPin prevents individual track handling
|
if (MotorDriver::commonFaultPin) // commonFaultPin prevents individual track handling
|
||||||
DCCWaveform::progTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
DCCWaveform::progTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
||||||
StringFormatter::send(stream,F("PPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON);
|
CommandDistributor::broadcastPower();
|
||||||
lastPowerState = (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON); //remember power state sent for comparison later
|
}
|
||||||
}
|
|
||||||
#if defined(RMFT_ACTIVE)
|
#if defined(RMFT_ACTIVE)
|
||||||
else if (cmd[1]=='R' && cmd[2]=='A' && cmd[3]=='2' ) { // Route activate
|
else if (cmd[1]=='R' && cmd[2]=='A' && cmd[3]=='2' ) { // Route activate
|
||||||
// exrail routes are RA2Rn , Animations are RA2An
|
// exrail routes are RA2Rn , Animations are RA2An
|
||||||
int route=getInt(cmd+5);
|
int route=getInt(cmd+5);
|
||||||
uint16_t cab=cmd[4]=='A' ? mostRecentCab : 0;
|
uint16_t cab=cmd[4]=='A' ? mostRecentCab : 0;
|
||||||
RMFT2::createNewTask(route, cab);
|
RMFT2::createNewTask(route, cab);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
else if (cmd[1]=='T' && cmd[2]=='A') { // PTA accessory toggle
|
else if (cmd[1]=='T' && cmd[2]=='A') { // PTA accessory toggle
|
||||||
int id=getInt(cmd+4);
|
int id=getInt(cmd+4);
|
||||||
if (!Turnout::exists(id)) {
|
if (!Turnout::exists(id)) {
|
||||||
// If turnout does not exist, create it
|
// If turnout does not exist, create it
|
||||||
int addr = ((id - 1) / 4) + 1;
|
int addr = ((id - 1) / 4) + 1;
|
||||||
int subaddr = (id - 1) % 4;
|
int subaddr = (id - 1) % 4;
|
||||||
DCCTurnout::create(id,addr,subaddr);
|
DCCTurnout::create(id,addr,subaddr);
|
||||||
StringFormatter::send(stream, F("HmTurnout %d created\n"),id);
|
StringFormatter::send(stream, F("HmTurnout %d created\n"),id);
|
||||||
}
|
}
|
||||||
switch (cmd[3]) {
|
switch (cmd[3]) {
|
||||||
// T and C according to RCN-213 where 0 is Stop, Red, Thrown, Diverging.
|
// T and C according to RCN-213 where 0 is Stop, Red, Thrown, Diverging.
|
||||||
case 'T':
|
case 'T':
|
||||||
Turnout::setClosed(id,false);
|
Turnout::setClosed(id,false);
|
||||||
break;
|
break;
|
||||||
case 'C':
|
case 'C':
|
||||||
Turnout::setClosed(id,true);
|
Turnout::setClosed(id,true);
|
||||||
break;
|
break;
|
||||||
case '2':
|
case '2':
|
||||||
Turnout::setClosed(id,!Turnout::isClosed(id));
|
Turnout::setClosed(id,!Turnout::isClosed(id));
|
||||||
break;
|
break;
|
||||||
default :
|
default :
|
||||||
Turnout::setClosed(id,true);
|
Turnout::setClosed(id,true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
StringFormatter::send(stream, F("PTA%c%d\n"),Turnout::isClosed(id)?'2':'4',id );
|
StringFormatter::send(stream, F("PTA%c%d\n"),Turnout::isClosed(id)?'2':'4',id );
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'N': // Heartbeat (2), only send if connection completed by 'HU' message
|
case 'N': // Heartbeat (2), only send if connection completed by 'HU' message
|
||||||
if (initSent) {
|
if (initSent) {
|
||||||
StringFormatter::send(stream, F("*%d\n"),HEARTBEAT_SECONDS); // return timeout value
|
StringFormatter::send(stream, F("*%d\n"),HEARTBEAT_SECONDS); // return timeout value
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'M': // multithrottle
|
case 'M': // multithrottle
|
||||||
multithrottle(stream, cmd);
|
multithrottle(stream, cmd);
|
||||||
break;
|
break;
|
||||||
case 'H': // send initial connection info after receiving "HU" message
|
case 'H': // send initial connection info after receiving "HU" message
|
||||||
if (cmd[1] == 'U') {
|
if (cmd[1] == 'U') {
|
||||||
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
|
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
|
||||||
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||||
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
|
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
|
||||||
StringFormatter::send(stream,F("PPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON);
|
StringFormatter::send(stream,F("PPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON);
|
||||||
lastPowerState = (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON); //remember power state sent for comparison later
|
#ifdef RMFT_ACTIVE
|
||||||
StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_SECONDS);
|
RMFT2::emitWithrottleRoster(stream);
|
||||||
initSent = true;
|
#endif
|
||||||
}
|
// set heartbeat to 1 second because we need to sync the metadata
|
||||||
break;
|
StringFormatter::send(stream,F("*1\n"));
|
||||||
case 'Q': //
|
initSent = true;
|
||||||
LOOPLOCOS('*', -1) { // tell client to drop any locos still assigned to this WiThrottle
|
}
|
||||||
if (myLocos[loco].throttle!='\0') {
|
break;
|
||||||
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab);
|
case 'Q': //
|
||||||
}
|
LOOPLOCOS('*', -1) { // tell client to drop any locos still assigned to this WiThrottle
|
||||||
}
|
if (myLocos[loco].throttle!='\0') {
|
||||||
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) Quit"),millis(),clientid);
|
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab);
|
||||||
delete this;
|
}
|
||||||
break;
|
}
|
||||||
}
|
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) Quit"),millis(),clientid);
|
||||||
// skip over cmd until 0 or past \r or \n
|
delete this;
|
||||||
while(*cmd !='\0' && *cmd != '\r' && *cmd !='\n') cmd++;
|
break;
|
||||||
if (*cmd!='\0') cmd++; // skip \r or \n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int WiThrottle::getInt(byte * cmd) {
|
|
||||||
int i=0;
|
|
||||||
while (cmd[0]>='0' && cmd[0]<='9') {
|
|
||||||
i=i*10 + (cmd[0]-'0');
|
|
||||||
cmd++;
|
|
||||||
}
|
}
|
||||||
return i;
|
// skip over cmd until 0 or past \r or \n
|
||||||
|
while(*cmd !='\0' && *cmd != '\r' && *cmd !='\n') cmd++;
|
||||||
|
if (*cmd!='\0') cmd++; // skip \r or \n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int WiThrottle::getInt(byte * cmd) {
|
||||||
|
int i=0;
|
||||||
|
while (cmd[0]>='0' && cmd[0]<='9') {
|
||||||
|
i=i*10 + (cmd[0]-'0');
|
||||||
|
cmd++;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
int WiThrottle::getLocoId(byte * cmd) {
|
int WiThrottle::getLocoId(byte * cmd) {
|
||||||
@@ -236,143 +245,183 @@ int WiThrottle::getLocoId(byte * cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
|
void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
|
||||||
char throttleChar=cmd[1];
|
char throttleChar=cmd[1];
|
||||||
int locoid=getLocoId(cmd+3); // -1 for *
|
int locoid=getLocoId(cmd+3); // -1 for *
|
||||||
byte * aval=cmd;
|
byte * aval=cmd;
|
||||||
while(*aval !=';' && *aval !='\0') aval++;
|
while(*aval !=';' && *aval !='\0') aval++;
|
||||||
if (*aval) aval+=2; // skip ;>
|
if (*aval) aval+=2; // skip ;>
|
||||||
|
|
||||||
// DIAG(F("Multithrottle aval=%c cab=%d"), aval[0],locoid);
|
// DIAG(F("Multithrottle aval=%c cab=%d"), aval[0],locoid);
|
||||||
switch(cmd[2]) {
|
switch(cmd[2]) {
|
||||||
case '+': // add loco request
|
case '+': // add loco request
|
||||||
if (cmd[3]=='*') {
|
if (cmd[3]=='*') {
|
||||||
// M+* means get loco from prog track, then join tracks ready to drive away
|
// M+* means get loco from prog track, then join tracks ready to drive away
|
||||||
// Stash the things the callback will need later
|
// Stash the things the callback will need later
|
||||||
stashStream= stream;
|
stashStream= stream;
|
||||||
stashClient=stream->peekTargetMark();
|
stashClient=stream->peekTargetMark();
|
||||||
stashThrottleChar=throttleChar;
|
stashThrottleChar=throttleChar;
|
||||||
stashInstance=this;
|
stashInstance=this;
|
||||||
// ask DCC to call us back when the loco id has been read
|
// ask DCC to call us back when the loco id has been read
|
||||||
DCC::getLocoId(getLocoCallback); // will remove any previous join
|
DCC::getLocoId(getLocoCallback); // will remove any previous join
|
||||||
return; // return nothing in stream as response is sent later in the callback
|
return; // return nothing in stream as response is sent later in the callback
|
||||||
}
|
}
|
||||||
//return error if address zero requested
|
//return error if address zero requested
|
||||||
if (locoid==0) {
|
if (locoid==0) {
|
||||||
StringFormatter::send(stream, F("HMAddress '0' not supported!\n"), cmd[3] ,locoid);
|
StringFormatter::send(stream, F("HMAddress '0' not supported!\n"), cmd[3] ,locoid);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//return error if L or S from request doesn't match DCC++ assumptions
|
//return error if L or S from request doesn't match DCC++ assumptions
|
||||||
if (cmd[3] != LorS(locoid)) {
|
if (cmd[3] != LorS(locoid)) {
|
||||||
StringFormatter::send(stream, F("HMLength '%c' not valid for %d!\n"), cmd[3] ,locoid);
|
StringFormatter::send(stream, F("HMLength '%c' not valid for %d!\n"), cmd[3] ,locoid);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//use first empty "slot" on this client's list, will be added to DCC registration list
|
//use first empty "slot" on this client's list, will be added to DCC registration list
|
||||||
for (int loco=0;loco<MAX_MY_LOCO;loco++) {
|
for (int loco=0;loco<MAX_MY_LOCO;loco++) {
|
||||||
if (myLocos[loco].throttle=='\0') {
|
if (myLocos[loco].throttle=='\0') {
|
||||||
myLocos[loco].throttle=throttleChar;
|
myLocos[loco].throttle=throttleChar;
|
||||||
myLocos[loco].cab=locoid;
|
myLocos[loco].cab=locoid;
|
||||||
mostRecentCab=locoid;
|
myLocos[loco].functionMap=DCC::getFunctionMap(locoid);
|
||||||
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
|
myLocos[loco].broadcastPending=true; // means speed/dir will be sent later
|
||||||
//Get known Fn states from DCC
|
mostRecentCab=locoid;
|
||||||
for(int fKey=0; fKey<=28; fKey++) {
|
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
|
||||||
|
int fkeys=29;
|
||||||
|
myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle
|
||||||
|
|
||||||
|
#ifdef RMFT_ACTIVE
|
||||||
|
const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid);
|
||||||
|
if (!functionNames) {
|
||||||
|
// no roster, use presets as above
|
||||||
|
}
|
||||||
|
else if (GETFLASH(functionNames)=='\0') {
|
||||||
|
// "" = Roster but no functions given
|
||||||
|
fkeys=0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// we have function names...
|
||||||
|
// scan names list emitting names, counting functions and
|
||||||
|
// flagging non-toggling things like horn.
|
||||||
|
myLocos[loco].functionToggles =0;
|
||||||
|
StringFormatter::send(stream, F("M%cL%c%d<;>]\\["), throttleChar,cmd[3],locoid);
|
||||||
|
fkeys=0;
|
||||||
|
bool firstchar=true;
|
||||||
|
for (int fx=0;;fx++) {
|
||||||
|
char c=GETFLASH(functionNames+fx);
|
||||||
|
if (c=='\0') {
|
||||||
|
fkeys++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (c=='/') {
|
||||||
|
fkeys++;
|
||||||
|
StringFormatter::send(stream,F("]\\["));
|
||||||
|
firstchar=true;
|
||||||
|
}
|
||||||
|
else if (firstchar && c=='*') {
|
||||||
|
myLocos[loco].functionToggles |= 1UL<<fkeys;
|
||||||
|
firstchar=false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
firstchar=false;
|
||||||
|
stream->write(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StringFormatter::send(stream,F("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for(int fKey=0; fKey<fkeys; fKey++) {
|
||||||
int fstate=DCC::getFn(locoid,fKey);
|
int fstate=DCC::getFn(locoid,fKey);
|
||||||
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),throttleChar,cmd[3],locoid,fstate,fKey);
|
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),throttleChar,cmd[3],locoid,fstate,fKey);
|
||||||
}
|
}
|
||||||
StringFormatter::send(stream, F("M%cA%c%d<;>V%d\n"), throttleChar, cmd[3], locoid, DCCToWiTSpeed(DCC::getThrottleSpeed(locoid)));
|
//speed and direction will be published at next broadcast cycle
|
||||||
StringFormatter::send(stream, F("M%cA%c%d<;>R%d\n"), throttleChar, cmd[3], locoid, DCC::getThrottleDirection(locoid));
|
StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128
|
||||||
StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128
|
return;
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
}
|
StringFormatter::send(stream, F("HMMax locos (%d) exceeded, %d not added!\n"), MAX_MY_LOCO ,locoid);
|
||||||
StringFormatter::send(stream, F("HMMax locos (%d) exceeded, %d not added!\n"), MAX_MY_LOCO ,locoid);
|
break;
|
||||||
break;
|
case '-': // remove loco(s) from this client (leave in DCC registration)
|
||||||
case '-': // remove loco(s) from this client (leave in DCC registration)
|
LOOPLOCOS(throttleChar, locoid) {
|
||||||
LOOPLOCOS(throttleChar, locoid) {
|
myLocos[loco].throttle='\0';
|
||||||
myLocos[loco].throttle='\0';
|
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab);
|
||||||
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab);
|
}
|
||||||
}
|
|
||||||
|
break;
|
||||||
break;
|
case 'A':
|
||||||
case 'A':
|
locoAction(stream,aval, throttleChar, locoid);
|
||||||
locoAction(stream,aval, throttleChar, locoid);
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WiThrottle::locoAction(RingStream * stream, byte* aval, char throttleChar, int cab){
|
void WiThrottle::locoAction(RingStream * stream, byte* aval, char throttleChar, int cab){
|
||||||
// Note cab=-1 for all cabs in the consist called throttleChar.
|
// Note cab=-1 for all cabs in the consist called throttleChar.
|
||||||
// DIAG(F("Loco Action aval=%c%c throttleChar=%c, cab=%d"), aval[0],aval[1],throttleChar, cab);
|
// DIAG(F("Loco Action aval=%c%c throttleChar=%c, cab=%d"), aval[0],aval[1],throttleChar, cab);
|
||||||
switch (aval[0]) {
|
(void) stream;
|
||||||
case 'V': // Vspeed
|
switch (aval[0]) {
|
||||||
{
|
case 'V': // Vspeed
|
||||||
int witSpeed=getInt(aval+1);
|
{
|
||||||
LOOPLOCOS(throttleChar, cab) {
|
int witSpeed=getInt(aval+1);
|
||||||
mostRecentCab=myLocos[loco].cab;
|
LOOPLOCOS(throttleChar, cab) {
|
||||||
DCC::setThrottle(myLocos[loco].cab, WiTToDCCSpeed(witSpeed), DCC::getThrottleDirection(myLocos[loco].cab));
|
mostRecentCab=myLocos[loco].cab;
|
||||||
StringFormatter::send(stream,F("M%cA%c%d<;>V%d\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab, witSpeed);
|
DCC::setThrottle(myLocos[loco].cab, WiTToDCCSpeed(witSpeed), DCC::getThrottleDirection(myLocos[loco].cab));
|
||||||
}
|
// SetThrottle will cause speed change broadcast
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case 'F': //F onOff function
|
break;
|
||||||
{
|
case 'F': // Function key pressed/released
|
||||||
bool funcstate;
|
{
|
||||||
bool pressed=aval[1]=='1';
|
bool pressed=aval[1]=='1';
|
||||||
int fKey = getInt(aval+2);
|
int fKey = getInt(aval+2);
|
||||||
LOOPLOCOS(throttleChar, cab) {
|
LOOPLOCOS(throttleChar, cab) {
|
||||||
funcstate = DCC::changeFn(myLocos[loco].cab, fKey, pressed);
|
bool unsetOnRelease = myLocos[loco].functionToggles & (1L<<fKey);
|
||||||
if(funcstate==0 || funcstate==1)
|
if (unsetOnRelease) DCC::setFn(myLocos[loco].cab,fKey, pressed);
|
||||||
StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"), throttleChar, LorS(myLocos[loco].cab),
|
else if (pressed) DCC::changeFn(myLocos[loco].cab, fKey);
|
||||||
myLocos[loco].cab, funcstate, fKey);
|
}
|
||||||
}
|
break;
|
||||||
}
|
}
|
||||||
break;
|
case 'q':
|
||||||
case 'q':
|
if (aval[1]=='V' || aval[1]=='R' ) { //qV or qR
|
||||||
if (aval[1]=='V') { //qV
|
// just flag the loco for broadcast and it will happen.
|
||||||
LOOPLOCOS(throttleChar, cab) {
|
LOOPLOCOS(throttleChar, cab) {
|
||||||
StringFormatter::send(stream,F("M%cA%c%d<;>V%d\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab, DCCToWiTSpeed(DCC::getThrottleSpeed(myLocos[loco].cab)));
|
myLocos[loco].broadcastPending=true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (aval[1]=='R') { // qR
|
break;
|
||||||
LOOPLOCOS(throttleChar, cab) {
|
case 'R':
|
||||||
StringFormatter::send(stream,F("M%cA%c%d<;>R%d\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab, DCC::getThrottleDirection(myLocos[loco].cab));
|
{
|
||||||
}
|
bool forward=aval[1]!='0';
|
||||||
}
|
LOOPLOCOS(throttleChar, cab) {
|
||||||
break;
|
mostRecentCab=myLocos[loco].cab;
|
||||||
case 'R':
|
DCC::setThrottle(myLocos[loco].cab, DCC::getThrottleSpeed(myLocos[loco].cab), forward);
|
||||||
{
|
// setThrottle will cause a broadcast so notification will be sent
|
||||||
bool forward=aval[1]!='0';
|
}
|
||||||
LOOPLOCOS(throttleChar, cab) {
|
}
|
||||||
mostRecentCab=myLocos[loco].cab;
|
break;
|
||||||
DCC::setThrottle(myLocos[loco].cab, DCC::getThrottleSpeed(myLocos[loco].cab), forward);
|
case 'X':
|
||||||
StringFormatter::send(stream,F("M%cA%c%d<;>R%d\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab, forward);
|
//Emergency Stop (speed code 1)
|
||||||
}
|
LOOPLOCOS(throttleChar, cab) {
|
||||||
}
|
DCC::setThrottle(myLocos[loco].cab, 1, DCC::getThrottleDirection(myLocos[loco].cab));
|
||||||
break;
|
// setThrottle will cause a broadcast so notification will be sent
|
||||||
case 'X':
|
}
|
||||||
//Emergency Stop (speed code 1)
|
break;
|
||||||
LOOPLOCOS(throttleChar, cab) {
|
case 'I': // Idle, set speed to 0
|
||||||
DCC::setThrottle(myLocos[loco].cab, 1, DCC::getThrottleDirection(myLocos[loco].cab));
|
case 'Q': // Quit, set speed to 0
|
||||||
StringFormatter::send(stream,F("M%cA%c%d<;>V%d\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab, -1);
|
LOOPLOCOS(throttleChar, cab) {
|
||||||
}
|
mostRecentCab=myLocos[loco].cab;
|
||||||
break;
|
DCC::setThrottle(myLocos[loco].cab, 0, DCC::getThrottleDirection(myLocos[loco].cab));
|
||||||
case 'I': // Idle, set speed to 0
|
// setThrottle will cause a broadcast so notification will be sent
|
||||||
case 'Q': // Quit, set speed to 0
|
}
|
||||||
LOOPLOCOS(throttleChar, cab) {
|
break;
|
||||||
mostRecentCab=myLocos[loco].cab;
|
}
|
||||||
DCC::setThrottle(myLocos[loco].cab, 0, DCC::getThrottleDirection(myLocos[loco].cab));
|
|
||||||
StringFormatter::send(stream,F("M%cA%c%d<;>V%d\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab, 0);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert between DCC++ speed values and WiThrottle speed values
|
// convert between DCC++ speed values and WiThrottle speed values
|
||||||
int WiThrottle::DCCToWiTSpeed(int DCCSpeed) {
|
int WiThrottle::DCCToWiTSpeed(int DCCSpeed) {
|
||||||
if (DCCSpeed == 0) return 0; //stop is stop
|
if (DCCSpeed == 0) return 0; //stop is stop
|
||||||
if (DCCSpeed == 1) return -1; //eStop value
|
if (DCCSpeed == 1) return -1; //eStop value
|
||||||
return DCCSpeed - 1; //offset others by 1
|
return DCCSpeed - 1; //offset others by 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert between WiThrottle speed values and DCC++ speed values
|
// convert between WiThrottle speed values and DCC++ speed values
|
||||||
int WiThrottle::WiTToDCCSpeed(int WiTSpeed) {
|
int WiThrottle::WiTToDCCSpeed(int WiTSpeed) {
|
||||||
if (WiTSpeed == 0) return 0; //stop is stop
|
if (WiTSpeed == 0) return 0; //stop is stop
|
||||||
if (WiTSpeed == -1) return 1; //eStop value
|
if (WiTSpeed == -1) return 1; //eStop value
|
||||||
@@ -380,24 +429,17 @@ int WiThrottle::WiTToDCCSpeed(int WiTSpeed) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WiThrottle::loop(RingStream * stream) {
|
void WiThrottle::loop(RingStream * stream) {
|
||||||
// for each WiThrottle, check the heartbeat
|
// for each WiThrottle, check the heartbeat and broadcast needed
|
||||||
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
|
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
|
||||||
wt->checkHeartbeat();
|
wt->checkHeartbeat(stream);
|
||||||
|
|
||||||
// TODO... any broadcasts to be done
|
|
||||||
(void)stream;
|
|
||||||
/* MUST follow this model in this loop.
|
|
||||||
* stream->mark();
|
|
||||||
* send 1 digit client id, and any data
|
|
||||||
* stream->commit()
|
|
||||||
*/
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WiThrottle::checkHeartbeat() {
|
void WiThrottle::checkHeartbeat(RingStream * stream) {
|
||||||
// if eStop time passed... eStop any locos still assigned to this client and then drop the connection
|
// if eStop time passed... eStop any locos still assigned to this client and then drop the connection
|
||||||
if(heartBeatEnable && (millis()-heartBeat > ESTOP_SECONDS*1000)) {
|
if(heartBeatEnable && (millis()-heartBeat > ESTOP_SECONDS*1000)) {
|
||||||
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) eStop(%ds) timeout, drop connection"), millis(), clientid, ESTOP_SECONDS);
|
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) eStop(%ds) timeout, drop connection"), millis(), clientid, ESTOP_SECONDS);
|
||||||
LOOPLOCOS('*', -1) {
|
LOOPLOCOS('*', -1) {
|
||||||
if (myLocos[loco].throttle!='\0') {
|
if (myLocos[loco].throttle!='\0') {
|
||||||
if (Diag::WITHROTTLE) DIAG(F("%l eStopping cab %d"),millis(),myLocos[loco].cab);
|
if (Diag::WITHROTTLE) DIAG(F("%l eStopping cab %d"),millis(),myLocos[loco].cab);
|
||||||
@@ -405,15 +447,65 @@ void WiThrottle::checkHeartbeat() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete this;
|
delete this;
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// send any outstanding speed/direction/function changes for this clients locos
|
||||||
|
// Changes may have been caused by this client, or another non-Withrottle or Exrail
|
||||||
|
bool streamHasBeenMarked=false;
|
||||||
|
LOOPLOCOS('*', -1) {
|
||||||
|
if (myLocos[loco].throttle!='\0' && myLocos[loco].broadcastPending) {
|
||||||
|
if (!streamHasBeenMarked) {
|
||||||
|
stream->mark(clientid);
|
||||||
|
streamHasBeenMarked=true;
|
||||||
|
}
|
||||||
|
myLocos[loco].broadcastPending=false;
|
||||||
|
int cab=myLocos[loco].cab;
|
||||||
|
char lors=LorS(cab);
|
||||||
|
char throttle=myLocos[loco].throttle;
|
||||||
|
StringFormatter::send(stream,F("M%cA%c%d<;>V%d\n"),
|
||||||
|
throttle, lors , cab, DCCToWiTSpeed(DCC::getThrottleSpeed(cab)));
|
||||||
|
StringFormatter::send(stream,F("M%cA%c%d<;>R%d\n"),
|
||||||
|
throttle, lors , cab, DCC::getThrottleDirection(cab));
|
||||||
|
|
||||||
|
// compare the DCC functionmap with the local copy and send changes
|
||||||
|
uint32_t dccFunctionMap=DCC::getFunctionMap(cab);
|
||||||
|
uint32_t myFunctionMap=myLocos[loco].functionMap;
|
||||||
|
myLocos[loco].functionMap=dccFunctionMap;
|
||||||
|
|
||||||
|
// loop the maps sending any bit changed
|
||||||
|
// Loop is terminated as soon as no changes are left
|
||||||
|
for (byte fn=0;dccFunctionMap!=myFunctionMap;fn++) {
|
||||||
|
if ((dccFunctionMap&1) != (myFunctionMap&1)) {
|
||||||
|
StringFormatter::send(stream,F("M%cA%c%d<;>F%c%d\n"),
|
||||||
|
throttle, lors , cab, (dccFunctionMap&1)?'1':'0',fn);
|
||||||
|
}
|
||||||
|
// shift just checked bit off end of both maps
|
||||||
|
dccFunctionMap>>=1;
|
||||||
|
myFunctionMap>>=1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (streamHasBeenMarked) stream->commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WiThrottle::markForBroadcast(int cab) {
|
||||||
|
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
|
||||||
|
wt->markForBroadcast2(cab);
|
||||||
|
}
|
||||||
|
void WiThrottle::markForBroadcast2(int cab) {
|
||||||
|
LOOPLOCOS('*', cab) {
|
||||||
|
myLocos[loco].broadcastPending=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
char WiThrottle::LorS(int cab) {
|
char WiThrottle::LorS(int cab) {
|
||||||
return (cab<127)?'S':'L';
|
return (cab<=HIGHEST_SHORT_ADDR)?'S':'L';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drive Away feature. Callback handling
|
// Drive Away feature. Callback handling
|
||||||
|
|
||||||
RingStream * WiThrottle::stashStream;
|
RingStream * WiThrottle::stashStream;
|
||||||
WiThrottle * WiThrottle::stashInstance;
|
WiThrottle * WiThrottle::stashInstance;
|
||||||
byte WiThrottle::stashClient;
|
byte WiThrottle::stashClient;
|
||||||
@@ -421,13 +513,26 @@ char WiThrottle::stashThrottleChar;
|
|||||||
|
|
||||||
void WiThrottle::getLocoCallback(int16_t locoid) {
|
void WiThrottle::getLocoCallback(int16_t locoid) {
|
||||||
stashStream->mark(stashClient);
|
stashStream->mark(stashClient);
|
||||||
if (locoid<0) StringFormatter::send(stashStream,F("HMNo loco found on prog track\n"));
|
|
||||||
|
if (locoid<=0)
|
||||||
|
StringFormatter::send(stashStream,F("HMNo loco found on prog track\n"));
|
||||||
else {
|
else {
|
||||||
char addcmd[20]={'M',stashThrottleChar,'+',LorS(locoid) };
|
// short or long
|
||||||
itoa(locoid,addcmd+4,10);
|
char addrchar;
|
||||||
stashInstance->multithrottle(stashStream, (byte *)addcmd);
|
if (locoid & LONG_ADDR_MARKER) { // long addr
|
||||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
locoid = locoid ^ LONG_ADDR_MARKER;
|
||||||
DCC::setProgTrackSyncMain(true); // <1 JOIN> so we can drive loco away
|
addrchar = 'L';
|
||||||
|
} else
|
||||||
|
addrchar = 'S';
|
||||||
|
if (addrchar == 'L' && locoid <= HIGHEST_SHORT_ADDR )
|
||||||
|
StringFormatter::send(stashStream,F("HMLong addr %d <= %d not supported\n"), locoid,HIGHEST_SHORT_ADDR);
|
||||||
|
else {
|
||||||
|
char addcmd[20]={'M',stashThrottleChar,'+', addrchar};
|
||||||
|
itoa(locoid,addcmd+4,10);
|
||||||
|
stashInstance->multithrottle(stashStream, (byte *)addcmd);
|
||||||
|
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||||
|
DCC::setProgTrackSyncMain(true); // <1 JOIN> so we can drive loco away
|
||||||
|
}
|
||||||
}
|
}
|
||||||
stashStream->commit();
|
stashStream->commit();
|
||||||
}
|
}
|
||||||
|
18
WiThrottle.h
18
WiThrottle.h
@@ -1,5 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2021 Mike S
|
||||||
|
* © 2020-2021 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC API
|
* This file is part of Asbelos DCC API
|
||||||
*
|
*
|
||||||
@@ -24,6 +26,9 @@
|
|||||||
struct MYLOCO {
|
struct MYLOCO {
|
||||||
char throttle; //indicates which throttle letter on client, often '0','1' or '2'
|
char throttle; //indicates which throttle letter on client, often '0','1' or '2'
|
||||||
int cab; //address of this loco
|
int cab; //address of this loco
|
||||||
|
bool broadcastPending;
|
||||||
|
uint32_t functionMap;
|
||||||
|
uint32_t functionToggles;
|
||||||
};
|
};
|
||||||
|
|
||||||
class WiThrottle {
|
class WiThrottle {
|
||||||
@@ -31,14 +36,15 @@ class WiThrottle {
|
|||||||
static void loop(RingStream * stream);
|
static void loop(RingStream * stream);
|
||||||
void parse(RingStream * stream, byte * cmd);
|
void parse(RingStream * stream, byte * cmd);
|
||||||
static WiThrottle* getThrottle( int wifiClient);
|
static WiThrottle* getThrottle( int wifiClient);
|
||||||
|
static void markForBroadcast(int cab);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
WiThrottle( int wifiClientId);
|
WiThrottle( int wifiClientId);
|
||||||
~WiThrottle();
|
~WiThrottle();
|
||||||
|
|
||||||
static const int MAX_MY_LOCO=10; // maximum number of locos assigned to a single client
|
static const int MAX_MY_LOCO=10; // maximum number of locos assigned to a single client
|
||||||
static const int HEARTBEAT_SECONDS=4; // heartbeat at 4secs to provide messaging transport
|
static const int HEARTBEAT_SECONDS=10; // heartbeat at 4secs to provide messaging transport
|
||||||
static const int ESTOP_SECONDS=8; // eStop if no incoming messages for more than 8secs
|
static const int ESTOP_SECONDS=20; // eStop if no incoming messages for more than 8secs
|
||||||
static WiThrottle* firstThrottle;
|
static WiThrottle* firstThrottle;
|
||||||
static int getInt(byte * cmd);
|
static int getInt(byte * cmd);
|
||||||
static int getLocoId(byte * cmd);
|
static int getLocoId(byte * cmd);
|
||||||
@@ -62,8 +68,8 @@ class WiThrottle {
|
|||||||
void multithrottle(RingStream * stream, byte * cmd);
|
void multithrottle(RingStream * stream, byte * cmd);
|
||||||
void locoAction(RingStream * stream, byte* aval, char throttleChar, int cab);
|
void locoAction(RingStream * stream, byte* aval, char throttleChar, int cab);
|
||||||
void accessory(RingStream *, byte* cmd);
|
void accessory(RingStream *, byte* cmd);
|
||||||
void checkHeartbeat();
|
void checkHeartbeat(RingStream * stream);
|
||||||
|
void markForBroadcast2(int cab);
|
||||||
// callback stuff to support prog track acquire
|
// callback stuff to support prog track acquire
|
||||||
static RingStream * stashStream;
|
static RingStream * stashStream;
|
||||||
static WiThrottle * stashInstance;
|
static WiThrottle * stashInstance;
|
||||||
|
246
WifiESP32.cpp
246
WifiESP32.cpp
@@ -1,246 +0,0 @@
|
|||||||
/*
|
|
||||||
© 2021, Harald Barth.
|
|
||||||
|
|
||||||
This file is part of CommandStation-EX
|
|
||||||
|
|
||||||
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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include "defines.h"
|
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
|
||||||
#include <WiFi.h>
|
|
||||||
#include "WifiESP32.h"
|
|
||||||
#include "DIAG.h"
|
|
||||||
#include "RingStream.h"
|
|
||||||
#include "CommandDistributor.h"
|
|
||||||
/*
|
|
||||||
#include "soc/rtc_wdt.h"
|
|
||||||
#include "esp_task_wdt.h"
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "soc/timer_group_struct.h"
|
|
||||||
#include "soc/timer_group_reg.h"
|
|
||||||
void feedTheDog0(){
|
|
||||||
// feed dog 0
|
|
||||||
TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable
|
|
||||||
TIMERG0.wdt_feed=1; // feed dog
|
|
||||||
TIMERG0.wdt_wprotect=0; // write protect
|
|
||||||
// feed dog 1
|
|
||||||
//TIMERG1.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable
|
|
||||||
//TIMERG1.wdt_feed=1; // feed dog
|
|
||||||
//TIMERG1.wdt_wprotect=0; // write protect
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
void enableCoreWDT(byte core){
|
|
||||||
TaskHandle_t idle = xTaskGetIdleTaskHandleForCPU(core);
|
|
||||||
if(idle == NULL){
|
|
||||||
DIAG(F("Get idle rask on core %d failed"),core);
|
|
||||||
} else {
|
|
||||||
if(esp_task_wdt_add(idle) != ESP_OK){
|
|
||||||
DIAG(F("Failed to add Core %d IDLE task to WDT"),core);
|
|
||||||
} else {
|
|
||||||
DIAG(F("Added Core %d IDLE task to WDT"),core);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void disableCoreWDT(byte core){
|
|
||||||
TaskHandle_t idle = xTaskGetIdleTaskHandleForCPU(core);
|
|
||||||
if(idle == NULL || esp_task_wdt_delete(idle) != ESP_OK){
|
|
||||||
DIAG(F("Failed to remove Core %d IDLE task from WDT"),core);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
static std::vector<WiFiClient> clients; // a list to hold all clients
|
|
||||||
static WiFiServer *server = NULL;
|
|
||||||
static RingStream *outboundRing = new RingStream(2048);
|
|
||||||
static bool APmode = false;
|
|
||||||
|
|
||||||
void wifiLoop(void *){
|
|
||||||
for(;;){
|
|
||||||
WifiESP::loop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WifiESP::setup(const char *SSid,
|
|
||||||
const char *password,
|
|
||||||
const char *hostname,
|
|
||||||
int port,
|
|
||||||
const byte channel) {
|
|
||||||
bool havePassword = true;
|
|
||||||
bool haveSSID = true;
|
|
||||||
bool wifiUp = false;
|
|
||||||
uint8_t tries = 40;
|
|
||||||
|
|
||||||
// tests
|
|
||||||
// enableCoreWDT(1);
|
|
||||||
// disableCoreWDT(0);
|
|
||||||
|
|
||||||
const char *yourNetwork = "Your network ";
|
|
||||||
if (strncmp(yourNetwork, SSid, 13) == 0 || strncmp("", SSid, 13) == 0)
|
|
||||||
haveSSID = false;
|
|
||||||
if (strncmp(yourNetwork, password, 13) == 0 || strncmp("", password, 13) == 0)
|
|
||||||
havePassword = false;
|
|
||||||
|
|
||||||
if (haveSSID && havePassword) {
|
|
||||||
WiFi.mode(WIFI_STA);
|
|
||||||
WiFi.setAutoReconnect(true);
|
|
||||||
WiFi.begin(SSid, password);
|
|
||||||
while (WiFi.status() != WL_CONNECTED && tries) {
|
|
||||||
Serial.print('.');
|
|
||||||
tries--;
|
|
||||||
delay(500);
|
|
||||||
}
|
|
||||||
if (WiFi.status() == WL_CONNECTED) {
|
|
||||||
DIAG(F("Wifi STA IP %s"),WiFi.localIP().toString().c_str());
|
|
||||||
wifiUp = true;
|
|
||||||
} else {
|
|
||||||
DIAG(F("Could not connect to Wifi SSID %s"),SSid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!haveSSID) {
|
|
||||||
// prepare all strings
|
|
||||||
String strSSID("DCC_");
|
|
||||||
String strPass("PASS_");
|
|
||||||
String strMac = WiFi.macAddress();
|
|
||||||
strMac.remove(0,9);
|
|
||||||
strMac.replace(":","");
|
|
||||||
strMac.replace(":","");
|
|
||||||
strSSID.concat(strMac);
|
|
||||||
strPass.concat(strMac);
|
|
||||||
|
|
||||||
WiFi.mode(WIFI_AP);
|
|
||||||
if (WiFi.softAP(strSSID.c_str(),
|
|
||||||
havePassword ? password : strPass.c_str(),
|
|
||||||
channel, false, 8)) {
|
|
||||||
DIAG(F("Wifi AP SSID %s PASS %s"),strSSID.c_str(),havePassword ? password : strPass.c_str());
|
|
||||||
DIAG(F("Wifi AP IP %s"),WiFi.softAPIP().toString().c_str());
|
|
||||||
wifiUp = true;
|
|
||||||
APmode = true;
|
|
||||||
} else {
|
|
||||||
DIAG(F("Could not set up AP with Wifi SSID %s"),strSSID.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (!wifiUp) {
|
|
||||||
DIAG(F("Wifi setup all fail (STA and AP mode)"));
|
|
||||||
// no idea to go on
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
server = new WiFiServer(port); // start listening on tcp port
|
|
||||||
server->begin();
|
|
||||||
// server started here
|
|
||||||
|
|
||||||
//start loop task
|
|
||||||
if (pdPASS != xTaskCreatePinnedToCore(
|
|
||||||
wifiLoop, /* Task function. */
|
|
||||||
"wifiLoop",/* name of task. */
|
|
||||||
10000, /* Stack size of task */
|
|
||||||
NULL, /* parameter of the task */
|
|
||||||
1, /* priority of the task */
|
|
||||||
NULL, /* Task handle to keep track of created task */
|
|
||||||
0)) { /* pin task to core 0 */
|
|
||||||
DIAG(F("Could not create wifiLoop task"));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// report server started after wifiLoop creation
|
|
||||||
// when everything looks good
|
|
||||||
DIAG(F("Server up port %d"),port);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WifiESP::loop() {
|
|
||||||
int clientId; //tmp loop var
|
|
||||||
|
|
||||||
// really no good way to check for LISTEN especially in AP mode?
|
|
||||||
if (APmode || WiFi.status() == WL_CONNECTED) {
|
|
||||||
if (server->hasClient()) {
|
|
||||||
// loop over all clients and remove inactive
|
|
||||||
for (clientId=0; clientId<clients.size(); clientId++){
|
|
||||||
// check if client is there and alive
|
|
||||||
if(!clients[clientId].connected()) {
|
|
||||||
clients[clientId].stop();
|
|
||||||
clients.erase(clients.begin()+clientId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WiFiClient client;
|
|
||||||
while (client = server->available()) {
|
|
||||||
clients.push_back(client);
|
|
||||||
DIAG(F("New client %s"), client.remoteIP().toString().c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// loop over all connected clients
|
|
||||||
for (clientId=0; clientId<clients.size(); clientId++){
|
|
||||||
if(clients[clientId].connected()) {
|
|
||||||
int len;
|
|
||||||
if ((len = clients[clientId].available()) > 0) {
|
|
||||||
// read data from client
|
|
||||||
byte cmd[len+1];
|
|
||||||
for(int i=0; i<len; i++) {
|
|
||||||
cmd[i]=clients[clientId].read();
|
|
||||||
}
|
|
||||||
cmd[len]=0;
|
|
||||||
outboundRing->mark(clientId);
|
|
||||||
CommandDistributor::parse(clientId,cmd,outboundRing);
|
|
||||||
outboundRing->commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // all clients
|
|
||||||
|
|
||||||
// something to write out?
|
|
||||||
clientId=outboundRing->peek();
|
|
||||||
if (clientId >= 0) {
|
|
||||||
if ((unsigned int)clientId > clients.size()) {
|
|
||||||
// something is wrong with the ringbuffer position
|
|
||||||
outboundRing->info();
|
|
||||||
} else {
|
|
||||||
// we have data to send in outboundRing
|
|
||||||
if(clients[clientId].connected()) {
|
|
||||||
outboundRing->read(); // read over peek()
|
|
||||||
int count=outboundRing->count();
|
|
||||||
{
|
|
||||||
char buffer[count+1];
|
|
||||||
for(int i=0;i<count;i++) {
|
|
||||||
int c = outboundRing->read();
|
|
||||||
if (c >= 0)
|
|
||||||
buffer[i] = (char)c;
|
|
||||||
else {
|
|
||||||
DIAG(F("Ringread fail at %d"),i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buffer[count]=0;
|
|
||||||
clients[clientId].write(buffer,count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} //connected
|
|
||||||
|
|
||||||
// when loop() is running on core0 we must
|
|
||||||
// feed the core0 wdt ourselves as yield()
|
|
||||||
// is not necessarily yielding to a low
|
|
||||||
// prio task. On core1 this is not a problem
|
|
||||||
// as there the wdt is disabled by the
|
|
||||||
// arduio IDE startup routines.
|
|
||||||
if (xPortGetCoreID() == 0)
|
|
||||||
feedTheDog0();
|
|
||||||
yield();
|
|
||||||
}
|
|
||||||
#endif //ESP32
|
|
270
WifiESP8266.cpp
270
WifiESP8266.cpp
@@ -1,270 +0,0 @@
|
|||||||
/*
|
|
||||||
© 2021, Harald Barth.
|
|
||||||
|
|
||||||
This file is part of CommandStation-EX
|
|
||||||
|
|
||||||
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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "defines.h"
|
|
||||||
#if defined(ARDUINO_ARCH_ESP8266)
|
|
||||||
#include <ESP8266WiFi.h>
|
|
||||||
#include <ESPAsyncTCP.h>
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "WifiESP8266.h"
|
|
||||||
#include "DIAG.h"
|
|
||||||
#include "RingStream.h"
|
|
||||||
#include "CommandDistributor.h"
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
static std::vector<AsyncClient*> clients; // a list to hold all clients
|
|
||||||
static AsyncServer *server;
|
|
||||||
|
|
||||||
static RingStream *outboundRing = new RingStream(2048);
|
|
||||||
|
|
||||||
static void handleError(void* arg, AsyncClient* client, int8_t error) {
|
|
||||||
(void)arg;
|
|
||||||
DIAG(F("connection error %s from client %s"), client->errorToString(error), client->remoteIP().toString().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
static void handleData(void* arg, AsyncClient* client, void *data, size_t len) {
|
|
||||||
(void)arg;
|
|
||||||
//DIAG(F("data received from client %s"), client->remoteIP().toString().c_str());
|
|
||||||
uint8_t clientId;
|
|
||||||
for (clientId=0; clientId<clients.size(); clientId++){
|
|
||||||
if (clients[clientId] == client) break;
|
|
||||||
}
|
|
||||||
if (clientId < clients.size()) {
|
|
||||||
byte cmd[len+1];
|
|
||||||
memcpy(cmd,data,len);
|
|
||||||
cmd[len]=0;
|
|
||||||
outboundRing->mark(clientId);
|
|
||||||
CommandDistributor::parse(clientId,cmd,outboundRing);
|
|
||||||
outboundRing->commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//static AsyncClient *debugclient = NULL;
|
|
||||||
|
|
||||||
bool sendData(AsyncClient *client, char* data, size_t count) {
|
|
||||||
size_t willsend = 0;
|
|
||||||
|
|
||||||
// reply to client
|
|
||||||
if (client->canSend()) {
|
|
||||||
while (count > 0) {
|
|
||||||
if (client->connected())
|
|
||||||
willsend = client->add(data, count); // add checks for space()
|
|
||||||
else
|
|
||||||
willsend = 0;
|
|
||||||
if (willsend < count) {
|
|
||||||
DIAG(F("Willsend %d of count %d"), willsend, count);
|
|
||||||
}
|
|
||||||
if (client->connected() && client->send()) {
|
|
||||||
count = count - willsend;
|
|
||||||
data = data + willsend;
|
|
||||||
} else {
|
|
||||||
DIAG(F("Could not send promised %d"), count);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Did send all bytes we wanted
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
DIAG(F("Aborting: Busy or space=0"));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void deleteClient(AsyncClient* client) {
|
|
||||||
uint8_t clientId;
|
|
||||||
for (clientId=0; clientId<clients.size(); clientId++){
|
|
||||||
if (clients[clientId] == client) break;
|
|
||||||
}
|
|
||||||
if (clientId < clients.size()) {
|
|
||||||
clients[clientId] = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
static void handleDisconnect(void* arg, AsyncClient* client) {
|
|
||||||
(void)arg;
|
|
||||||
DIAG(F("Client disconnected"));
|
|
||||||
deleteClient(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void handleTimeOut(void* arg, AsyncClient* client, uint32_t time) {
|
|
||||||
(void)arg;
|
|
||||||
(void)time;
|
|
||||||
DIAG(F("client ACK timeout ip: %s"), client->remoteIP().toString().c_str());
|
|
||||||
deleteClient(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void handleNewClient(void* arg, AsyncClient* client) {
|
|
||||||
(void)arg;
|
|
||||||
DIAG(F("New client %s"), client->remoteIP().toString().c_str());
|
|
||||||
|
|
||||||
// add to list
|
|
||||||
clients.push_back(client);
|
|
||||||
|
|
||||||
// register events
|
|
||||||
client->onData(&handleData, NULL);
|
|
||||||
client->onError(&handleError, NULL);
|
|
||||||
client->onDisconnect(&handleDisconnect, NULL);
|
|
||||||
client->onTimeout(&handleTimeOut, NULL);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Things one _might_ want to do:
|
|
||||||
Disable soft watchdog: ESP.wdtDisable()
|
|
||||||
Enable soft watchdog: ESP.wdtEnable(X) ignores the value of X and enables it for fixed
|
|
||||||
time at least in version 3.0.2 of the esp8266 package.
|
|
||||||
|
|
||||||
Internet says:
|
|
||||||
|
|
||||||
I manage to complety disable the hardware watchdog on ESP8266 in order to run the benchmark CoreMark.
|
|
||||||
|
|
||||||
void hw_wdt_disable(){
|
|
||||||
*((volatile uint32_t*) 0x60000900) &= ~(1); // Hardware WDT OFF
|
|
||||||
}
|
|
||||||
|
|
||||||
void hw_wdt_enable(){
|
|
||||||
*((volatile uint32_t*) 0x60000900) |= 1; // Hardware WDT ON
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
bool WifiESP::setup(const char *SSid,
|
|
||||||
const char *password,
|
|
||||||
const char *hostname,
|
|
||||||
int port,
|
|
||||||
const byte channel) {
|
|
||||||
bool havePassword = true;
|
|
||||||
bool haveSSID = true;
|
|
||||||
bool wifiUp = false;
|
|
||||||
|
|
||||||
// We are server and should not sleep
|
|
||||||
wifi_set_sleep_type(NONE_SLEEP_T);
|
|
||||||
// connects to access point
|
|
||||||
|
|
||||||
const char *yourNetwork = "Your network ";
|
|
||||||
if (strncmp(yourNetwork, SSid, 13) == 0 || strncmp("", SSid, 13) == 0)
|
|
||||||
haveSSID = false;
|
|
||||||
if (strncmp(yourNetwork, password, 13) == 0 || strncmp("", password, 13) == 0)
|
|
||||||
havePassword = false;
|
|
||||||
|
|
||||||
if (haveSSID && havePassword) {
|
|
||||||
WiFi.mode(WIFI_STA);
|
|
||||||
WiFi.setAutoReconnect(true);
|
|
||||||
WiFi.begin(SSid, password);
|
|
||||||
while (WiFi.status() != WL_CONNECTED) {
|
|
||||||
Serial.print('.');
|
|
||||||
delay(500);
|
|
||||||
}
|
|
||||||
if (WiFi.status() == WL_CONNECTED) {
|
|
||||||
DIAG(F("Wifi STA IP %s"),WiFi.localIP().toString().c_str());
|
|
||||||
wifiUp = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!haveSSID) {
|
|
||||||
// prepare all strings
|
|
||||||
String strSSID("DCC_");
|
|
||||||
String strPass("PASS_");
|
|
||||||
String strMac = WiFi.macAddress();
|
|
||||||
strMac.remove(0,9);
|
|
||||||
strMac.replace(":","");
|
|
||||||
strMac.replace(":","");
|
|
||||||
strSSID.concat(strMac);
|
|
||||||
strPass.concat(strMac);
|
|
||||||
|
|
||||||
WiFi.mode(WIFI_AP);
|
|
||||||
if (WiFi.softAP(strSSID.c_str(),
|
|
||||||
havePassword ? password : strPass.c_str(),
|
|
||||||
channel, false, 8)) {
|
|
||||||
DIAG(F("Wifi AP SSID %s PASS %s"),strSSID.c_str(),havePassword ? password : strPass.c_str());
|
|
||||||
DIAG(F("Wifi AP IP %s"),WiFi.softAPIP().toString().c_str());
|
|
||||||
wifiUp = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (!wifiUp) {
|
|
||||||
DIAG(F("Wifi all fail"));
|
|
||||||
// no idea to go on
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
server = new AsyncServer(port); // start listening on tcp port
|
|
||||||
|
|
||||||
server->onClient(&handleNewClient, server);
|
|
||||||
server->begin();
|
|
||||||
DIAG(F("Server up port %d"),port);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WifiESP::loop() {
|
|
||||||
AsyncClient *client = NULL;
|
|
||||||
// Do something with outboundRing
|
|
||||||
// call sendData
|
|
||||||
int clientId=outboundRing->peek();
|
|
||||||
if (clientId >= 0) {
|
|
||||||
if ((unsigned int)clientId > clients.size()) {
|
|
||||||
// something is wrong with the ringbuffer position
|
|
||||||
outboundRing->info();
|
|
||||||
client = NULL;
|
|
||||||
} else {
|
|
||||||
client = clients[clientId];
|
|
||||||
}
|
|
||||||
// if (client != debugclient) {
|
|
||||||
// DIAG(F("new client pointer = %x from id %d"), client, clientId);
|
|
||||||
// debugclient = client;
|
|
||||||
// }
|
|
||||||
} else {
|
|
||||||
client = NULL;
|
|
||||||
}
|
|
||||||
if (clientId>=0 && client && client->connected() && client->canSend()) {
|
|
||||||
outboundRing->read();
|
|
||||||
int count=outboundRing->count();
|
|
||||||
//DIAG(F("Wifi reply client=%d, count=%d"), clientId,count);
|
|
||||||
{
|
|
||||||
char buffer[count+1];
|
|
||||||
for(int i=0;i<count;i++) {
|
|
||||||
int c = outboundRing->read();
|
|
||||||
if (c >= 0)
|
|
||||||
buffer[i] = (char)c;
|
|
||||||
else {
|
|
||||||
DIAG(F("Ringread fail at %d"),i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buffer[count]=0;
|
|
||||||
//DIAG(F("SEND:%s COUNT:%d"),buffer,count);
|
|
||||||
uint8_t tries = 3;
|
|
||||||
while (! sendData(client, buffer, count)) {
|
|
||||||
DIAG(F("senData fail"));
|
|
||||||
yield();
|
|
||||||
if (tries == 0) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#ifdef ESP_DEBUG
|
|
||||||
static unsigned long last = 0;
|
|
||||||
if (millis() - last > 60000) {
|
|
||||||
last = millis();
|
|
||||||
DIAG(F("+"));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
ESP.wdtFeed();
|
|
||||||
}
|
|
||||||
#endif //ESP_FAMILY
|
|
@@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* © 2021, Harald Barth.
|
|
||||||
*
|
|
||||||
* This file is part of CommandStation-EX
|
|
||||||
*
|
|
||||||
* 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#if defined(ARDUINO_ARCH_ESP8266)
|
|
||||||
#ifndef WifiESP8266_h
|
|
||||||
#define WifiESP8266_h
|
|
||||||
|
|
||||||
#include "FSH.h"
|
|
||||||
|
|
||||||
class WifiESP
|
|
||||||
{
|
|
||||||
|
|
||||||
public:
|
|
||||||
static bool setup(const char *wifiESSID,
|
|
||||||
const char *wifiPassword,
|
|
||||||
const char *hostname,
|
|
||||||
const int port,
|
|
||||||
const byte channel);
|
|
||||||
static void loop();
|
|
||||||
private:
|
|
||||||
};
|
|
||||||
#endif //WifiESP8266_h
|
|
||||||
#endif //ESP8266
|
|
@@ -1,4 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
|
* © 2021 Fred Decker
|
||||||
|
* © 2021 Fred Decker
|
||||||
|
* © 2020-2021 Chris Harlow
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2020, Chris Harlow. All rights reserved.
|
||||||
* © 2020, Harald Barth.
|
* © 2020, Harald Barth.
|
||||||
*
|
*
|
||||||
@@ -152,7 +155,7 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
|
|||||||
if (ch=='E' || ch=='l') { // ERROR or "link is not valid"
|
if (ch=='E' || ch=='l') { // ERROR or "link is not valid"
|
||||||
if (clientPendingCIPSEND>=0) {
|
if (clientPendingCIPSEND>=0) {
|
||||||
// A CIPSEND was errored... just toss it away
|
// A CIPSEND was errored... just toss it away
|
||||||
purgeCurrentCIPSEND();
|
purgeCurrentCIPSEND();
|
||||||
}
|
}
|
||||||
loopState=SKIPTOEND;
|
loopState=SKIPTOEND;
|
||||||
break;
|
break;
|
||||||
@@ -231,6 +234,7 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
|
|||||||
if (ch=='C') {
|
if (ch=='C') {
|
||||||
// got "x C" before CLOSE or CONNECTED, or CONNECT FAILED
|
// got "x C" before CLOSE or CONNECTED, or CONNECT FAILED
|
||||||
if (runningClientId==clientPendingCIPSEND) purgeCurrentCIPSEND();
|
if (runningClientId==clientPendingCIPSEND) purgeCurrentCIPSEND();
|
||||||
|
else CommandDistributor::forget(runningClientId);
|
||||||
}
|
}
|
||||||
loopState=SKIPTOEND;
|
loopState=SKIPTOEND;
|
||||||
break;
|
break;
|
||||||
@@ -245,8 +249,9 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
|
|||||||
|
|
||||||
void WifiInboundHandler::purgeCurrentCIPSEND() {
|
void WifiInboundHandler::purgeCurrentCIPSEND() {
|
||||||
// A CIPSEND was sent but errored... or the client closed just toss it away
|
// A CIPSEND was sent but errored... or the client closed just toss it away
|
||||||
|
CommandDistributor::forget(clientPendingCIPSEND);
|
||||||
DIAG(F("Wifi: DROPPING CIPSEND=%d,%d"),clientPendingCIPSEND,currentReplySize);
|
DIAG(F("Wifi: DROPPING CIPSEND=%d,%d"),clientPendingCIPSEND,currentReplySize);
|
||||||
for (int i=0;i<=currentReplySize;i++) outboundRing->read();
|
for (int i=0;i<currentReplySize;i++) outboundRing->read();
|
||||||
pendingCipsend=false;
|
pendingCipsend=false;
|
||||||
clientPendingCIPSEND=-1;
|
clientPendingCIPSEND=-1;
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
|
* © 2021 Harald Barth
|
||||||
|
* © 2021 Fred Decker
|
||||||
* (c) 2021 Fred Decker. All rights reserved.
|
* (c) 2021 Fred Decker. All rights reserved.
|
||||||
* (c) 2020 Chris Harlow. All rights reserved.
|
* (c) 2020 Chris Harlow. All rights reserved.
|
||||||
*
|
*
|
||||||
|
@@ -1,26 +1,27 @@
|
|||||||
/*
|
/*
|
||||||
© 2020, Chris Harlow. All rights reserved.
|
* © 2021 Fred Decker
|
||||||
© 2020, Harald Barth.
|
* © 2020-2022 Harald Barth
|
||||||
|
* © 2020-2022 Chris Harlow
|
||||||
This file is part of CommandStation-EX
|
* All rights reserved.
|
||||||
|
*
|
||||||
This is free software: you can redistribute it and/or modify
|
* This file is part of CommandStation-EX
|
||||||
it under the terms of the GNU General Public License as published by
|
*
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
* This is free software: you can redistribute it and/or modify
|
||||||
(at your option) any later version.
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
It is distributed in the hope that it will be useful,
|
* (at your option) any later version.
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
*
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* It is distributed in the hope that it will be useful,
|
||||||
GNU General Public License for more details.
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
You should have received a copy of the GNU General Public License
|
* GNU General Public License for more details.
|
||||||
along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
*
|
||||||
*/
|
* You should have received a copy of the GNU General Public License
|
||||||
#include "WifiInterface.h" /* config.h included there */
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
#ifndef ESP_FAMILY
|
*/
|
||||||
#ifndef ARDUINO_AVR_UNO_WIFI_REV2
|
#ifndef ARDUINO_AVR_UNO_WIFI_REV2
|
||||||
// This code is NOT compiled on a unoWifiRev2 processor which uses a different architecture
|
// This code is NOT compiled on a unoWifiRev2 processor which uses a different architecture
|
||||||
|
#include "WifiInterface.h" /* config.h included there */
|
||||||
#include <avr/pgmspace.h>
|
#include <avr/pgmspace.h>
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
@@ -76,13 +77,13 @@ bool WifiInterface::setup(long serial_link_speed,
|
|||||||
(void) channel;
|
(void) channel;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if NUM_SERIAL > 0
|
#if NUM_SERIAL > 0 && !defined(SERIAL1_COMMANDS)
|
||||||
Serial1.begin(serial_link_speed);
|
Serial1.begin(serial_link_speed);
|
||||||
wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port, channel);
|
wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port, channel);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Other serials are tried, depending on hardware.
|
// Other serials are tried, depending on hardware.
|
||||||
#if NUM_SERIAL > 1
|
#if NUM_SERIAL > 1 && !defined(SERIAL2_COMMANDS)
|
||||||
if (wifiUp == WIFI_NOAT)
|
if (wifiUp == WIFI_NOAT)
|
||||||
{
|
{
|
||||||
Serial2.begin(serial_link_speed);
|
Serial2.begin(serial_link_speed);
|
||||||
@@ -90,7 +91,7 @@ bool WifiInterface::setup(long serial_link_speed,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if NUM_SERIAL > 2
|
#if NUM_SERIAL > 2 && !defined(SERIAL3_COMMANDS)
|
||||||
if (wifiUp == WIFI_NOAT)
|
if (wifiUp == WIFI_NOAT)
|
||||||
{
|
{
|
||||||
Serial3.begin(serial_link_speed);
|
Serial3.begin(serial_link_speed);
|
||||||
@@ -278,10 +279,16 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
|||||||
|
|
||||||
StringFormatter::send(wifiStream, F("AT+CIPSERVER=0\r\n")); // turn off tcp server (to clean connections before CIPMUX=1)
|
StringFormatter::send(wifiStream, F("AT+CIPSERVER=0\r\n")); // turn off tcp server (to clean connections before CIPMUX=1)
|
||||||
checkForOK(1000, true); // ignore result in case it already was off
|
checkForOK(1000, true); // ignore result in case it already was off
|
||||||
|
|
||||||
StringFormatter::send(wifiStream, F("AT+CIPMUX=1\r\n")); // configure for multiple connections
|
StringFormatter::send(wifiStream, F("AT+CIPMUX=1\r\n")); // configure for multiple connections
|
||||||
if (!checkForOK(1000, true)) return WIFI_DISCONNECTED;
|
if (!checkForOK(1000, true)) return WIFI_DISCONNECTED;
|
||||||
|
|
||||||
|
if(!oldCmd) { // no idea to test this on old firmware
|
||||||
|
StringFormatter::send(wifiStream, F("AT+MDNS=1,\"%S\",\"withrottle\",%d\r\n"),
|
||||||
|
hostname, port); // mDNS responder
|
||||||
|
checkForOK(1000, true); // dont care if not supported
|
||||||
|
}
|
||||||
|
|
||||||
StringFormatter::send(wifiStream, F("AT+CIPSERVER=1,%d\r\n"), port); // turn on server on port
|
StringFormatter::send(wifiStream, F("AT+CIPSERVER=1,%d\r\n"), port); // turn on server on port
|
||||||
if (!checkForOK(1000, true)) return WIFI_DISCONNECTED;
|
if (!checkForOK(1000, true)) return WIFI_DISCONNECTED;
|
||||||
#endif //DONT_TOUCH_WIFI_CONF
|
#endif //DONT_TOUCH_WIFI_CONF
|
||||||
@@ -317,19 +324,45 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
|||||||
|
|
||||||
|
|
||||||
// This function is used to allow users to enter <+ commands> through the DCCEXParser
|
// This function is used to allow users to enter <+ commands> through the DCCEXParser
|
||||||
|
// <+command> sends AT+command to the ES and returns to the caller.
|
||||||
// Once the user has made whatever changes to the AT commands, a <+X> command can be used
|
// Once the user has made whatever changes to the AT commands, a <+X> command can be used
|
||||||
// to force on the connectd flag so that the loop will start picking up wifi traffic.
|
// to force on the connectd flag so that the loop will start picking up wifi traffic.
|
||||||
// If the settings are corrupted <+RST> will clear this and then you must restart the arduino.
|
// If the settings are corrupted <+RST> will clear this and then you must restart the arduino.
|
||||||
|
|
||||||
|
// Using the <+> command with no command string causes the code to enter an echo loop so that all
|
||||||
|
// input is directed to the ES and all ES output written to the USB Serial.
|
||||||
|
// The sequence "!!!" returns the Arduino to the normal loop mode
|
||||||
|
|
||||||
|
|
||||||
void WifiInterface::ATCommand(const byte * command) {
|
void WifiInterface::ATCommand(HardwareSerial * stream,const byte * command) {
|
||||||
command++;
|
command++;
|
||||||
|
if (*command=='\0') { // User gave <+> command
|
||||||
|
stream->print(F("\nES AT command passthrough mode, use ! to exit\n"));
|
||||||
|
while(stream->available()) stream->read(); // Drain serial input first
|
||||||
|
bool startOfLine=true;
|
||||||
|
while(true) {
|
||||||
|
while (wifiStream->available()) stream->write(wifiStream->read());
|
||||||
|
if (stream->available()) {
|
||||||
|
int cx=stream->read();
|
||||||
|
// A newline followed by !!! is an exit
|
||||||
|
if (cx=='\n' || cx=='\r') startOfLine=true;
|
||||||
|
else if (startOfLine && cx=='!') break;
|
||||||
|
else startOfLine=false;
|
||||||
|
stream->write(cx);
|
||||||
|
wifiStream->write(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream->print(F("Passthrough Ended"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (*command=='X') {
|
if (*command=='X') {
|
||||||
connected = true;
|
connected = true;
|
||||||
DIAG(F("++++++ Wifi Connction forced on ++++++++"));
|
DIAG(F("++++++ Wifi Connction forced on ++++++++"));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
StringFormatter:: send(wifiStream, F("AT+%s\r\n"), command);
|
StringFormatter:: send(wifiStream, F("AT+%s\r\n"), command);
|
||||||
checkForOK(10000, true);
|
checkForOK(10000, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,5 +404,4 @@ void WifiInterface::loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif //ARDUINO_AVR_UNO_WIFI_REV2
|
#endif
|
||||||
#endif //ESP_FAMILY
|
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2020-2021 Chris Harlow
|
||||||
* © 2020, Harald Barth.
|
* © 2020, Harald Barth.
|
||||||
*
|
* All rights reserved.
|
||||||
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
* This is free software: you can redistribute it and/or modify
|
* This is free software: you can redistribute it and/or modify
|
||||||
@@ -19,8 +20,6 @@
|
|||||||
*/
|
*/
|
||||||
#ifndef WifiInterface_h
|
#ifndef WifiInterface_h
|
||||||
#define WifiInterface_h
|
#define WifiInterface_h
|
||||||
#include "defines.h"
|
|
||||||
#ifndef ESP_FAMILY
|
|
||||||
#include "FSH.h"
|
#include "FSH.h"
|
||||||
#include "DCCEXParser.h"
|
#include "DCCEXParser.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
@@ -39,7 +38,7 @@ public:
|
|||||||
const int port,
|
const int port,
|
||||||
const byte channel);
|
const byte channel);
|
||||||
static void loop();
|
static void loop();
|
||||||
static void ATCommand(const byte *command);
|
static void ATCommand(HardwareSerial * stream,const byte *command);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static wifiSerialState setup(Stream &setupStream, const FSH *SSSid, const FSH *password,
|
static wifiSerialState setup(Stream &setupStream, const FSH *SSSid, const FSH *password,
|
||||||
@@ -52,5 +51,4 @@ private:
|
|||||||
static bool checkForOK(const unsigned int timeout, const FSH *waitfor, bool echo, bool escapeEcho = true);
|
static bool checkForOK(const unsigned int timeout, const FSH *waitfor, bool echo, bool escapeEcho = true);
|
||||||
static bool connected;
|
static bool connected;
|
||||||
};
|
};
|
||||||
#endif //ESP_FAMILY
|
#endif
|
||||||
#endif
|
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* COPYRIGHT (c) 2020 Fred Decker
|
* © 2021 Neil McKechnie
|
||||||
|
* © 2020-2021 Harald Barth
|
||||||
|
* © 2020-2021 Fred Decker
|
||||||
|
* © 2020-2021 Chris Harlow
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
@@ -41,32 +44,7 @@ The configuration file for DCC-EX Command Station
|
|||||||
// |
|
// |
|
||||||
// +-----------------------v
|
// +-----------------------v
|
||||||
//
|
//
|
||||||
//#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
|
#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
|
||||||
|
|
||||||
// https://randomnerdtutorials.com/esp8266-pinout-reference-gpios/
|
|
||||||
// 4 high at boot
|
|
||||||
// BUG: WE STILL NEED AT LEAST ONE TIMER_* motor shield defined, otherwise the packet scheduling does not work
|
|
||||||
// BUG: WE DO NOT HAVE ANY RMT_PROG CAPABILITY yet.
|
|
||||||
#define ESP32_MOTOR_SHIELD F("ESP32"), \
|
|
||||||
NULL /*new MotorDriver(16, 17, UNUSED_PIN, UNUSED_PIN, 36, 2.00, 2000, UNUSED_PIN, TIMER_MAIN)*/, \
|
|
||||||
new MotorDriver(18, 19, UNUSED_PIN, UNUSED_PIN, 39, 2.00, 2000, UNUSED_PIN, TIMER_PROG), \
|
|
||||||
new MotorDriver(16, 23, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 2.00, 2000, UNUSED_PIN, RMT_MAIN)
|
|
||||||
|
|
||||||
// ESP32 ADC1 only supported GPIO pins 32 to 39, for example
|
|
||||||
// ADC1 CH4 = GPIO32, ADC1 CH5 = GPIO33, ADC1 CH0 = GPIO36
|
|
||||||
//
|
|
||||||
// Adjust pin usage according to info in
|
|
||||||
// https://randomnerdtutorials.com/esp32-adc-analog-read-arduino-ide/
|
|
||||||
// https://randomnerdtutorials.com/esp32-pinout-reference-gpios/
|
|
||||||
//
|
|
||||||
// Adjust conversion factor according to your voltage divider.
|
|
||||||
//
|
|
||||||
#define ESP32_MOTOR_SHIELD F("ESP32"), \
|
|
||||||
new MotorDriver(16, 17, UNUSED_PIN, UNUSED_PIN, 32, 2.00, 2000, UNUSED_PIN),\
|
|
||||||
new MotorDriver(18, 19, UNUSED_PIN, UNUSED_PIN, 33, 2.00, 2000, UNUSED_PIN)
|
|
||||||
|
|
||||||
#define MOTOR_SHIELD_TYPE ESP32_MOTOR_SHIELD
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// The IP port to talk to a WIFI or Ethernet shield.
|
// The IP port to talk to a WIFI or Ethernet shield.
|
||||||
@@ -78,8 +56,6 @@ The configuration file for DCC-EX Command Station
|
|||||||
// NOTE: Only supported on Arduino Mega
|
// NOTE: Only supported on Arduino Mega
|
||||||
// Set to false if you not even want it on the Arduino Mega
|
// Set to false if you not even want it on the Arduino Mega
|
||||||
//
|
//
|
||||||
// Currently ESP32 single core only works with WIFI ON because of Watchdog code
|
|
||||||
// and if you have an ESP32 you probably want WIFI anyway.
|
|
||||||
#define ENABLE_WIFI true
|
#define ENABLE_WIFI true
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -155,6 +131,27 @@ The configuration file for DCC-EX Command Station
|
|||||||
// Define scroll mode as 0, 1 or 2
|
// Define scroll mode as 0, 1 or 2
|
||||||
#define SCROLLMODE 1
|
#define SCROLLMODE 1
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// DISABLE EEPROM
|
||||||
|
//
|
||||||
|
// If you do not need the EEPROM at all, you can disable all the code that saves
|
||||||
|
// data in the EEPROM. You might want to do that if you are in a Arduino UNO
|
||||||
|
// and want to use the EX-RAIL automation. Otherwise you do not have enough RAM
|
||||||
|
// to do that. Of course, then none of the EEPROM related commands works.
|
||||||
|
//
|
||||||
|
// #define DISABLE_EEPROM
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// REDEFINE WHERE SHORT/LONG ADDR break is. According to NMRA the last short address
|
||||||
|
// is 127 and the first long address is 128. There are manufacturers which have
|
||||||
|
// another view. Lenz CS for example have considered addresses long from 100. If
|
||||||
|
// you want to change to that mode, do
|
||||||
|
//#define HIGHEST_SHORT_ADDR 99
|
||||||
|
// If you want to run all your locos addressed long format, you could even do a
|
||||||
|
//#define HIGHEST_SHORT_ADDR 0
|
||||||
|
// We do not support to use the same address, for example 100(long) and 100(short)
|
||||||
|
// at the same time, there must be a border.
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// DEFINE TURNOUTS/ACCESSORIES FOLLOW NORM RCN-213
|
// DEFINE TURNOUTS/ACCESSORIES FOLLOW NORM RCN-213
|
||||||
@@ -168,10 +165,32 @@ The configuration file for DCC-EX Command Station
|
|||||||
// don't add it to your config.h.
|
// don't add it to your config.h.
|
||||||
//#define DCC_TURNOUTS_RCN_213
|
//#define DCC_TURNOUTS_RCN_213
|
||||||
|
|
||||||
// The following #define likewise inverts the behaviour of the <a> command
|
// By default, the driver which defines a DCC accessory decoder
|
||||||
// for triggering DCC Accessory Decoders, so that <a addr subaddr 0> generates a
|
// does send out the same state change on the DCC packet as it
|
||||||
|
// receives. This means a VPIN state=1 sends D=1 (close turnout
|
||||||
|
// or signal green) in the DCC packet. This can be reversed if
|
||||||
|
// necessary.
|
||||||
|
//#define HAL_ACCESSORY_COMMAND_REVERSE
|
||||||
|
|
||||||
|
// If you have issues with that the direction of the accessory commands is
|
||||||
|
// reversed (for example when converting from another CS to DCC-EX) then
|
||||||
|
// you can use this to revese the sense of all accessory commmands sent
|
||||||
|
// over DCC++. This #define likewise inverts the behaviour of the <a> command
|
||||||
|
// for triggering DCC Accessory Decoders, so that <a addr subaddr 0> generates a
|
||||||
// DCC packet with D=1 (close turnout) and <a addr subaddr 1> generates D=0
|
// DCC packet with D=1 (close turnout) and <a addr subaddr 1> generates D=0
|
||||||
// (throw turnout).
|
// (throw turnout).
|
||||||
//#define DCC_ACCESSORY_RCN_213
|
//#define DCC_ACCESSORY_RCN_213
|
||||||
|
//
|
||||||
|
// HANDLING MULTIPLE SERIAL THROTTLES
|
||||||
|
// The command station always operates with the default Serial port.
|
||||||
|
// Diagnostics are only emitted on the default serial port and not broadcast.
|
||||||
|
// Other serial throttles may be added to the Serial1, Serial2, Serial3 ports
|
||||||
|
// which may or may not exist on your CPU. (Mega has all 3)
|
||||||
|
// To monitor a throttle on one or more serial ports, uncomment the defines below.
|
||||||
|
// NOTE: do not define here the WiFi shield serial port or your wifi will not work.
|
||||||
|
//
|
||||||
|
//#define SERIAL1_COMMAND
|
||||||
|
//#define SERIAL2_COMMAND
|
||||||
|
//#define SERIAL3_COMMAND
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
98
defines.h
98
defines.h
@@ -1,22 +1,26 @@
|
|||||||
/*
|
/*
|
||||||
© 2020,2021 Harald Barth.
|
* © 2021 Neil McKechnie
|
||||||
|
* © 2021 Mike S
|
||||||
This file is part of CommandStation-EX
|
* © 2021 Fred Decker
|
||||||
|
* © 2020-2021 Harald Barth
|
||||||
This is free software: you can redistribute it and/or modify
|
* © 2020-2021 Chris Harlow
|
||||||
it under the terms of the GNU General Public License as published by
|
*
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
* This file is part of CommandStation-EX
|
||||||
(at your option) any later version.
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify
|
||||||
It is distributed in the hope that it will be useful,
|
* it under the terms of the GNU General Public License as published by
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* (at your option) any later version.
|
||||||
GNU General Public License for more details.
|
*
|
||||||
|
* It is distributed in the hope that it will be useful,
|
||||||
You should have received a copy of the GNU General Public License
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
* 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 DEFINES_H
|
#ifndef DEFINES_H
|
||||||
#define DEFINES_H
|
#define DEFINES_H
|
||||||
@@ -31,47 +35,37 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
#if defined(ARDUINO_ARCH_ESP8266)
|
|
||||||
#define ESP_FAMILY
|
|
||||||
//#define ESP_DEBUG
|
|
||||||
#define SLOW_ANALOG_READ
|
|
||||||
#endif
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
|
||||||
#define ESP_FAMILY
|
|
||||||
#define SLOW_ANALOG_READ
|
|
||||||
#else
|
|
||||||
#define portENTER_CRITICAL(A) do {} while (0)
|
|
||||||
#define portEXIT_CRITICAL(A) do {} while (0)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// WIFI_ON: All prereqs for running with WIFI are met
|
// WIFI_ON: All prereqs for running with WIFI are met
|
||||||
// Note: WIFI_CHANNEL may not exist in early config.h files so is added here if needed.
|
// Note: WIFI_CHANNEL may not exist in early config.h files so is added here if needed.
|
||||||
|
|
||||||
#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO) || defined(ESP_FAMILY))
|
#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO)) || defined(ARDUINO_AVR_NANO_EVERY)
|
||||||
#define BIG_RAM
|
#define BIG_RAM
|
||||||
#endif
|
#endif
|
||||||
|
#if ENABLE_WIFI
|
||||||
#if ENABLE_WIFI && defined(BIG_RAM)
|
#if defined(BIG_RAM)
|
||||||
#define WIFI_ON true
|
#define WIFI_ON true
|
||||||
#ifndef WIFI_CHANNEL
|
#ifndef WIFI_CHANNEL
|
||||||
#define WIFI_CHANNEL 1
|
#define WIFI_CHANNEL 1
|
||||||
#endif
|
#endif
|
||||||
|
#else
|
||||||
|
#define WIFI_WARNING
|
||||||
|
#define WIFI_ON false
|
||||||
|
#endif
|
||||||
#else
|
#else
|
||||||
#define WIFI_ON false
|
#define WIFI_ON false
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if ENABLE_ETHERNET && defined(BIG_RAM)
|
#if ENABLE_ETHERNET
|
||||||
#define ETHERNET_ON true
|
#if defined(BIG_RAM)
|
||||||
|
#define ETHERNET_ON true
|
||||||
|
#else
|
||||||
|
#define ETHERNET_WARNING
|
||||||
|
#define ETHERNET_ON false
|
||||||
|
#endif
|
||||||
#else
|
#else
|
||||||
#define ETHERNET_ON false
|
#define ETHERNET_ON false
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if WIFI_ON && ETHERNET_ON
|
#if WIFI_ON && ETHERNET_ON
|
||||||
@@ -85,8 +79,12 @@
|
|||||||
//
|
//
|
||||||
#define WIFI_SERIAL_LINK_SPEED 115200
|
#define WIFI_SERIAL_LINK_SPEED 115200
|
||||||
|
|
||||||
#if __has_include ( "myAutomation.h") && defined(BIG_RAM)
|
#if __has_include ( "myAutomation.h")
|
||||||
#define RMFT_ACTIVE
|
#if defined(BIG_RAM) || defined(DISABLE_EEPROM)
|
||||||
|
#define RMFT_ACTIVE
|
||||||
|
#else
|
||||||
|
#define EXRAIL_WARNING
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
67
esp32wdt.txt
67
esp32wdt.txt
@@ -1,67 +0,0 @@
|
|||||||
The ESP-IDF has interrupt watchdogs and task watchdogs. Normally on
|
|
||||||
each core there is a very low prio idle task (IDLE0, ILDE1) that feeds
|
|
||||||
the watchdog (an internal timer) and if that timer expires a
|
|
||||||
reboot/reset happens. This thought to enure that even the lowest prio
|
|
||||||
tasks get run ever.
|
|
||||||
|
|
||||||
Now enter the Arduino IDE generated task loop(). When there are two cores,
|
|
||||||
loop() is run on core1. If loop runs continiously, IDLE1 is never run and
|
|
||||||
triggers the watchdog. It is said that this can be previented by one
|
|
||||||
of the following:
|
|
||||||
|
|
||||||
1. Call delay(X) with big enough X
|
|
||||||
2. Call yield()
|
|
||||||
|
|
||||||
While the delay() method works, big enough X to run idle seem to be in
|
|
||||||
the ms range and there are definitely applications that can not accept
|
|
||||||
a several ms long pause in loop().
|
|
||||||
|
|
||||||
The yield() method does not work because it only seems not to yield to
|
|
||||||
a low prio task like IDLE1 in all circumstances.
|
|
||||||
|
|
||||||
Then the makers of the Arduino IDE did get the brilliant idea to
|
|
||||||
disable that IDLE1 calls the watchdog. Then loop() can spin on core1
|
|
||||||
and other tasks (like wifi or interrupts or whatever) can run on core0
|
|
||||||
and are watched by the IDLE0 watchdog. All swell and well. Almost.
|
|
||||||
|
|
||||||
Enter: SINGLE CORE ESP32
|
|
||||||
|
|
||||||
As the IDLE0 watchdog is not disabled it will fire when loop() runs on
|
|
||||||
core0. The next idea is to feed the watchdog from loop() just
|
|
||||||
alongside the yield. There is a function called esp_task_wdt_feed(),
|
|
||||||
so can that be used to feed the watchdog? Yes and no. While it
|
|
||||||
will feed the watchdog, there is as well as check in the ESP-IDF
|
|
||||||
that the watchdog is fed from ALL tasks that should feed it. So
|
|
||||||
if the setup is that IDLE0 should feed the watchdog, we can not
|
|
||||||
get away by calling esp_task_wdt_feed() from loop(). BUMMER!
|
|
||||||
|
|
||||||
But there seems to be a way around this. The watchdog is implemented
|
|
||||||
by low level timers/counters and these are accessible. So we can feed
|
|
||||||
the dog behind the back of the ESP-IDF:
|
|
||||||
|
|
||||||
#include "soc/timer_group_struct.h"
|
|
||||||
#include "soc/timer_group_reg.h"
|
|
||||||
void feedTheDog(){
|
|
||||||
// feed dog 0
|
|
||||||
TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable
|
|
||||||
TIMERG0.wdt_feed=1; // feed dog
|
|
||||||
TIMERG0.wdt_wprotect=0; // write protect
|
|
||||||
// feed dog 1
|
|
||||||
TIMERG1.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable
|
|
||||||
TIMERG1.wdt_feed=1; // feed dog
|
|
||||||
TIMERG1.wdt_wprotect=0; // write protect
|
|
||||||
}
|
|
||||||
|
|
||||||
As I do not have a single core ESP32 I tested this by enabling the
|
|
||||||
IDLE1 watchdog (which normally is disabled) and checking that I
|
|
||||||
do get watchdog resets. Then I call feedTheDog() from loop()
|
|
||||||
and the resets disappear. So I guess the feeding operation is
|
|
||||||
successful. For a single core ESP32 of course only dog0 has
|
|
||||||
to be fed.
|
|
||||||
|
|
||||||
Feed dog directly behind back of the ESP-IDF routines:
|
|
||||||
https://forum.arduino.cc/t/esp32-a-better-way-than-vtaskdelay-to-get-around-watchdog-crash/596889/13
|
|
||||||
Disable/Endable WDT code:
|
|
||||||
https://github.com/espressif/arduino-esp32/commit/b8f8502f
|
|
||||||
Get/set taskid on cores:
|
|
||||||
https://techtutorialsx.com/2017/05/09/esp32-get-task-execution-core/
|
|
@@ -1,6 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020,2021 Harald Barth
|
* © 2021 Neil McKechnie
|
||||||
* © 2021, Neil McKechnie
|
* © 2021 Mike S
|
||||||
|
* © 2020 Harald Barth
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC-EX
|
* This file is part of Asbelos DCC-EX
|
||||||
*
|
*
|
||||||
@@ -27,8 +28,6 @@ extern "C" char* sbrk(int);
|
|||||||
#elif defined(__AVR__)
|
#elif defined(__AVR__)
|
||||||
extern char *__brkval;
|
extern char *__brkval;
|
||||||
extern char *__malloc_heap_start;
|
extern char *__malloc_heap_start;
|
||||||
#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
|
||||||
// supported but nothing needed here
|
|
||||||
#else
|
#else
|
||||||
#error Unsupported board type
|
#error Unsupported board type
|
||||||
#endif
|
#endif
|
||||||
@@ -36,7 +35,7 @@ extern char *__malloc_heap_start;
|
|||||||
|
|
||||||
static volatile int minimum_free_memory = __INT_MAX__;
|
static volatile int minimum_free_memory = __INT_MAX__;
|
||||||
|
|
||||||
#if !defined(__IMXRT1062__) && !defined(ARDUINO_ARCH_ESP8266) && !defined(ARDUINO_ARCH_ESP32)
|
#if !defined(__IMXRT1062__)
|
||||||
static inline int freeMemory() {
|
static inline int freeMemory() {
|
||||||
char top;
|
char top;
|
||||||
#if defined(__arm__)
|
#if defined(__arm__)
|
||||||
@@ -57,18 +56,7 @@ int minimumFreeMemory() {
|
|||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
|
||||||
// ESP8266 and ESP32
|
|
||||||
static inline int freeMemory() {
|
|
||||||
return ESP.getFreeHeap();
|
|
||||||
}
|
|
||||||
// Return low memory value.
|
|
||||||
int minimumFreeMemory() {
|
|
||||||
int retval = minimum_free_memory;
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
#else
|
#else
|
||||||
// All types of TEENSYs
|
|
||||||
#if defined(ARDUINO_TEENSY40)
|
#if defined(ARDUINO_TEENSY40)
|
||||||
static const unsigned DTCM_START = 0x20000000UL;
|
static const unsigned DTCM_START = 0x20000000UL;
|
||||||
static const unsigned OCRAM_START = 0x20200000UL;
|
static const unsigned OCRAM_START = 0x20200000UL;
|
||||||
@@ -121,3 +109,4 @@ void updateMinimumFreeMemory(unsigned char extraBytes) {
|
|||||||
if (spare < 0) spare = 0;
|
if (spare < 0) spare = 0;
|
||||||
if (spare < minimum_free_memory) minimum_free_memory = spare;
|
if (spare < minimum_free_memory) minimum_free_memory = spare;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Harald Barth
|
* © 2021 Neil McKechnie
|
||||||
* © 2021, Neil McKechnie
|
* © 2020 Harald Barth
|
||||||
*
|
*
|
||||||
* This file is part of DCC-EX
|
* This file is part of DCC-EX
|
||||||
*
|
*
|
||||||
|
192
myAutomation2.h
Normal file
192
myAutomation2.h
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
|
||||||
|
/* This is an automation example file.
|
||||||
|
* The presence of a file calle "myAutomation.h" brings EX-RAIL code into
|
||||||
|
* the command station.
|
||||||
|
* The auotomation may have multiple concurrent tasks.
|
||||||
|
* A task may drive one loco through a ROUTE or may simply
|
||||||
|
* automate some other part of the layout without any loco.
|
||||||
|
*
|
||||||
|
* At startup, a single task is created to execute the first
|
||||||
|
* instruction after ROUTES.
|
||||||
|
* This task may simply follow a route, or may SCHEDULE
|
||||||
|
* further tasks (thats is.. send a loco out along a route).
|
||||||
|
*
|
||||||
|
* Where the loco id is not known at compile time, a new task
|
||||||
|
* can be creatd with the command:
|
||||||
|
* </ SCHEDULE [cab] route>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Include the name to pin mappings for my layout
|
||||||
|
#include "myLayout.h"
|
||||||
|
|
||||||
|
ALIAS(ROUTE_1,1)
|
||||||
|
ALIAS(UP_MOUNTAIN,8)
|
||||||
|
ALIAS(UP_MOUNTAIN_FROM_PROG,88)
|
||||||
|
ALIAS(INNER_LOOP,7)
|
||||||
|
ALIAS(INNER_FROM_PROG,77)
|
||||||
|
|
||||||
|
//EXRAIL // myAutomation must start with the EXRAIL instruction
|
||||||
|
// This is the default starting route, AKA ROUTE(0)
|
||||||
|
// START(999) // this is just a diagnostic test cycle
|
||||||
|
PRINT("started")
|
||||||
|
LCD(0,"EXRAIL RULES")
|
||||||
|
SERIAL("I had one of them but the leg fell off!")
|
||||||
|
DONE // This just ends the startup thread
|
||||||
|
|
||||||
|
|
||||||
|
/*AUTOSTART*/ ROUTE(ROUTE_1,"Close All")
|
||||||
|
LCD(1,"Bingo")
|
||||||
|
CLOSE(TOP_TURNOUT) DELAY(10)
|
||||||
|
CLOSE(Y_TURNOUT) DELAY(10)
|
||||||
|
CLOSE(MIDDLE_TURNOUT) DELAY(10)
|
||||||
|
CLOSE(JOIN_TURNOUT) DELAY(10)
|
||||||
|
CLOSE(LOWER_TURNOUT) DELAY(10)
|
||||||
|
CLOSE(CROSSOVER_TURNOUT) DELAY(10)
|
||||||
|
CLOSE(PROG_TURNOUT) DELAY(10)
|
||||||
|
PRINT("Close All completed")
|
||||||
|
|
||||||
|
ENDTASK
|
||||||
|
|
||||||
|
|
||||||
|
SEQUENCE(UP_MOUNTAIN) // starting at the lower closed turnout siding and going up the mountain
|
||||||
|
PRINT("Up Mountain started")
|
||||||
|
DELAY(10000) // wait 10 seconds
|
||||||
|
RESERVE(BLOCK_LOWER_MOUNTAIN)
|
||||||
|
CLOSE(LOWER_TURNOUT) CLOSE(JOIN_TURNOUT)
|
||||||
|
FWD(60) AT(Y_LOWER)
|
||||||
|
RESERVE(BLOCK_X_MOUNTAIN)
|
||||||
|
CLOSE(Y_TURNOUT) CLOSE(MIDDLE_TURNOUT)
|
||||||
|
FWD(40) AT(MIDDLE_C_BUFFER) STOP
|
||||||
|
FREE(BLOCK_X_MOUNTAIN) FREE(BLOCK_LOWER_MOUNTAIN)
|
||||||
|
DELAY(10000)
|
||||||
|
RESERVE(BLOCK_UPPER_MOUNTAIN) RESERVE(BLOCK_X_MOUNTAIN)
|
||||||
|
CLOSE(MIDDLE_TURNOUT) THROW(Y_TURNOUT) THROW(TOP_TURNOUT)
|
||||||
|
REV(55)
|
||||||
|
AFTER(Y_UPPER) FREE(BLOCK_X_MOUNTAIN)
|
||||||
|
REV(55) AT(TOP_T_BUFFER) STOP // At top of mountain
|
||||||
|
FREE(BLOCK_UPPER_MOUNTAIN)
|
||||||
|
DELAY(5000)
|
||||||
|
RESERVE(BLOCK_UPPER_MOUNTAIN)
|
||||||
|
THROW(TOP_TURNOUT)
|
||||||
|
FWD(60) AT(Y_UPPER)
|
||||||
|
RESERVE(BLOCK_X_MOUNTAIN)
|
||||||
|
THROW(Y_TURNOUT) CLOSE(MIDDLE_TURNOUT)
|
||||||
|
FWD(40) AT(MIDDLE_C_BUFFER) STOP
|
||||||
|
FREE(BLOCK_UPPER_MOUNTAIN) FREE(BLOCK_X_MOUNTAIN)
|
||||||
|
DELAY(6000)
|
||||||
|
RESERVE(BLOCK_LOWER_MOUNTAIN) RESERVE(BLOCK_X_MOUNTAIN)
|
||||||
|
CLOSE(MIDDLE_TURNOUT) CLOSE(Y_TURNOUT) CLOSE(JOIN_TURNOUT) CLOSE(LOWER_TURNOUT)
|
||||||
|
REV(60)
|
||||||
|
AFTER(Y_LOWER) FREE(BLOCK_X_MOUNTAIN)
|
||||||
|
AT(LOWER_C_BUFFER) STOP
|
||||||
|
FREE(BLOCK_LOWER_MOUNTAIN)
|
||||||
|
FOLLOW(UP_MOUNTAIN)
|
||||||
|
|
||||||
|
AUTOMATION(UP_MOUNTAIN_FROM_PROG,"Send up mountain from prog")
|
||||||
|
JOIN
|
||||||
|
RESERVE(BLOCK_LOWER_MOUNTAIN)
|
||||||
|
RESERVE(BLOCK_X_INNER)
|
||||||
|
RESERVE(BLOCK_X_OUTER)
|
||||||
|
// safe to cross
|
||||||
|
THROW(PROG_TURNOUT) THROW(CROSSOVER_TURNOUT) THROW(JOIN_TURNOUT)
|
||||||
|
FWD(45)
|
||||||
|
AFTER(JOIN_AFTER) STOP
|
||||||
|
CLOSE(PROG_TURNOUT) CLOSE(CROSSOVER_TURNOUT) CLOSE(JOIN_TURNOUT)
|
||||||
|
FREE(BLOCK_X_OUTER) FREE(BLOCK_X_INNER)
|
||||||
|
CLOSE(LOWER_TURNOUT)
|
||||||
|
REV(40) AT(LOWER_C_BUFFER) STOP
|
||||||
|
FREE(BLOCK_LOWER_MOUNTAIN)
|
||||||
|
FOLLOW(UP_MOUNTAIN)
|
||||||
|
|
||||||
|
SEQUENCE(INNER_LOOP)
|
||||||
|
FWD(50)
|
||||||
|
AT(CROSSOVER_INNER_BEFORE)
|
||||||
|
RESERVE(BLOCK_X_INNER)
|
||||||
|
CLOSE(CROSSOVER_TURNOUT)
|
||||||
|
FWD(50)
|
||||||
|
AFTER(CROSSOVER_INNER_AFTER)
|
||||||
|
FREE(BLOCK_X_INNER)
|
||||||
|
FOLLOW(INNER_LOOP)
|
||||||
|
|
||||||
|
|
||||||
|
// Turnout definitions
|
||||||
|
TURNOUT(TOP_TURNOUT, TOP_TURNOUT,0,"Top Station")
|
||||||
|
TURNOUT(Y_TURNOUT, Y_TURNOUT,0,"Mountain join")
|
||||||
|
TURNOUT(MIDDLE_TURNOUT, MIDDLE_TURNOUT,0,"Middle Station")
|
||||||
|
TURNOUT(JOIN_TURNOUT,JOIN_TURNOUT,0)
|
||||||
|
TURNOUT(LOWER_TURNOUT,LOWER_TURNOUT,0)
|
||||||
|
TURNOUT(CROSSOVER_TURNOUT,CROSSOVER_TURNOUT,0)
|
||||||
|
TURNOUT(PROG_TURNOUT,PROG_TURNOUT,0)
|
||||||
|
|
||||||
|
// Single slip protection
|
||||||
|
ONTHROW(2)
|
||||||
|
THROW(1)
|
||||||
|
DONE
|
||||||
|
ONCLOSE(1)
|
||||||
|
CLOSE(2)
|
||||||
|
DONE
|
||||||
|
|
||||||
|
|
||||||
|
ROUTE(61,"Call return test")
|
||||||
|
PRINT("In 61 test 1")
|
||||||
|
CALL(62)
|
||||||
|
PRINT("In 61 test 2")
|
||||||
|
CALL(62)
|
||||||
|
PRINT("In 61 test 3")
|
||||||
|
ACTIVATE(100,2)
|
||||||
|
DEACTIVATE(100,2)
|
||||||
|
DONE
|
||||||
|
|
||||||
|
SEQUENCE(62)
|
||||||
|
PRINT("In seq 62")
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
ROUTE(63,"Signal test 40,41,42")
|
||||||
|
SIGNAL(40,41,42)
|
||||||
|
DELAY(2000)
|
||||||
|
RED(40)
|
||||||
|
DELAY(2000)
|
||||||
|
AMBER(40)
|
||||||
|
DELAY(2000)
|
||||||
|
GREEN(40)
|
||||||
|
FOLLOW(63)
|
||||||
|
|
||||||
|
|
||||||
|
ROUTE(64,"Func test 6772")
|
||||||
|
XFON(6772,1)
|
||||||
|
DELAY(5000)
|
||||||
|
XFOFF(6772,1)
|
||||||
|
DELAY(5000)
|
||||||
|
FOLLOW(64)
|
||||||
|
|
||||||
|
ROUTE(65,"Negative sensor test")
|
||||||
|
PRINT(" WAIT for -176")
|
||||||
|
AT(-176)
|
||||||
|
PRINT(" WAIT for 176")
|
||||||
|
AT(176)
|
||||||
|
PRINT("done")
|
||||||
|
DONE
|
||||||
|
|
||||||
|
ROUTE(123,"Activate stuff")
|
||||||
|
ACTIVATEL(5)
|
||||||
|
ACTIVATE(7,2)
|
||||||
|
DEACTIVATE(3,2)
|
||||||
|
DEACTIVATEL(6)
|
||||||
|
DONE
|
||||||
|
|
||||||
|
ONACTIVATEL(5)
|
||||||
|
PRINT("ACT 5")
|
||||||
|
DONE
|
||||||
|
ONACTIVATE(7,2)
|
||||||
|
PRINT("ACT 7,2")
|
||||||
|
DONE
|
||||||
|
ONDEACTIVATE(7,2)
|
||||||
|
PRINT("DEACT 7,2")
|
||||||
|
DONE
|
||||||
|
ONDEACTIVATEL(5)
|
||||||
|
PRINT("DEACT 5")
|
||||||
|
DONE
|
||||||
|
|
||||||
|
|
||||||
|
|
@@ -11,6 +11,10 @@
|
|||||||
// To prevent this, temporarily rename the file to myHal.txt or similar.
|
// To prevent this, temporarily rename the file to myHal.txt or similar.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
// The #if directive prevent compile errors for Uno and Nano by excluding the
|
||||||
|
// HAL directives from the build.
|
||||||
|
#if !defined(IO_NO_HAL)
|
||||||
|
|
||||||
// Include devices you need.
|
// Include devices you need.
|
||||||
#include "IODevice.h"
|
#include "IODevice.h"
|
||||||
#include "IO_HCSR04.h" // Ultrasonic range sensor
|
#include "IO_HCSR04.h" // Ultrasonic range sensor
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
|
|
||||||
#define VERSION "3.2.0 ESP32"
|
#define VERSION "3.2.0 rc12"
|
||||||
// 3.2.0 Major functional and non-functional changes.
|
// 3.2.0 Major functional and non-functional changes.
|
||||||
// New HAL added for I/O (digital and analogue inputs and outputs, servos etc).
|
// New HAL added for I/O (digital and analogue inputs and outputs, servos etc).
|
||||||
// Support for MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules.
|
// Support for MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules.
|
||||||
@@ -21,6 +21,11 @@
|
|||||||
// Configuration options to globally flip polarity of DCC Accessory states when driven
|
// Configuration options to globally flip polarity of DCC Accessory states when driven
|
||||||
// from <a> command and <T> command.
|
// from <a> command and <T> command.
|
||||||
// Increased use of display for showing loco decoder programming information.
|
// Increased use of display for showing loco decoder programming information.
|
||||||
|
// Can disable EEPROM code
|
||||||
|
// Can define border between long and short addresses
|
||||||
|
// Turnout and accessory states (thrown/closed = 0/1 or 1/0) can be set to match RCN-213
|
||||||
|
// Bugfix: one-off error in CIPSEND drop
|
||||||
|
// Compiles on Nano Every
|
||||||
// ...
|
// ...
|
||||||
// 3.1.7 Bugfix: Unknown locos should have speed forward
|
// 3.1.7 Bugfix: Unknown locos should have speed forward
|
||||||
// 3.1.6 Make output ID two bytes and guess format/size of registered outputs found in EEPROM
|
// 3.1.6 Make output ID two bytes and guess format/size of registered outputs found in EEPROM
|
||||||
|
Reference in New Issue
Block a user