From 28efc0ffef8cc1f421b70c0d09783c6eac9d200b Mon Sep 17 00:00:00 2001 From: dexslab Date: Tue, 22 Sep 2020 14:57:11 -0400 Subject: [PATCH 01/49] the start of new feature the config.h template including fix for github build action --- .github/workflows/main.yml | 2 + .gitignore | 1 + CommandStation-EX.ino | 181 ++++++++++++++++++++++++++---------- DCCEX.h | 7 -- config.example.h | 185 +++++++++++++++++++++++++++++++++++++ 5 files changed, 322 insertions(+), 54 deletions(-) delete mode 100644 DCCEX.h create mode 100644 config.example.h diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 84fdf3e..614fed9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,5 +13,7 @@ jobs: run: pip install wheel - name: Install PlatformIO Core run: pip install -U https://github.com/platformio/platformio/archive/v4.2.1.zip + - name: Copy generic config over + run: cp config.example.h config.h - name: Compile Command Station (AVR) run: python -m platformio run diff --git a/.gitignore b/.gitignore index f49f28f..e5404bb 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ Release/* .gcc-flags.json .pio/ .vscode/ +config.h \ No newline at end of file diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index e3d7cac..101f3f6 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -1,74 +1,161 @@ -/* - * © 2020, Chris Harlow. All rights reserved. - * - * This file is a demonstattion of setting up a DCC-EX - * Command station to support direct connection of WiThrottle devices - * such as "Engine Driver". If you contriol your layout through JMRI - * then DON'T connect throttles to this wifi, connect them to JMRI. - * - * This is just 3 statements longer than the basic setup. - * - * THIS SETUP DOES NOT APPLY TO ARDUINO UNO WITH ONLY A SINGLE SERIAL PORT. - * REFER TO SEPARATE EXAMPLE. - */ +//////////////////////////////////////////////////////////////////////////////////// +// © 2020, Chris Harlow. All rights reserved. +// +// This file is a demonstattion of setting up a DCC-EX +// Command station to support direct connection of WiThrottle devices +// such as "Engine Driver". If you contriol your layout through JMRI +// then DON'T connect throttles to this wifi, connect them to JMRI. +// +// This is just 3 statements longer than the basic setup. +// +// THIS SETUP DOES NOT APPLY TO ARDUINO UNO WITH ONLY A SINGLE SERIAL PORT. +// REFER TO SEPARATE EXAMPLE. +//////////////////////////////////////////////////////////////////////////////////// -#include "DCCEX.h" +#include "config.h" +#include "DCC.h" +#include "DIAG.h" +#include "DCCEXParser.h" +#if ENABLE_WIFI +#include "WifiInterface.h" +#endif +#if ENABLE_FREE_MEM_WARNING +#include "freeMemory.h" +int ramLowWatermark = 32767; // This figure gets overwritten dynamically in loop() +#endif -#ifdef ARDUINO_AVR_UNO - #include - SoftwareSerial Serial1(15,16); // YOU must get these pins correct to use Wifi on a UNO - #define WIFI_BAUD 9600 -#else - #define WIFI_BAUD 115200 -#endif +// this code is here to demonstrate use of the DCC API and other techniques + +// myFilter is an example of an OPTIONAL command filter used to intercept < > commands from +// the usb or wifi streamm. It demonstrates how a command may be intercepted +// or even a new command created without having to break open the API library code. +// The filter is permitted to use or modify the parameter list before passing it on to +// the standard parser. By setting the opcode to 0, the standard parser will +// just ignore the command on the assumption that you have already handled it. +// +// The filter must be enabled by calling the DCC EXParser::setFilter method, see use in setup(). +#if ENABLE_CUSTOM_FILTER +void myComandFilter(Print *stream, byte &opcode, byte ¶mCount, int p[]) +{ + (void)stream; // avoid compiler warning if we don't access this parameter + switch (opcode) + { + case '!': // Create a bespoke new command to clear all loco reminders or specific locos e.g + if (paramCount == 0) + DCC::forgetAllLocos(); + else + for (int i = 0; i < paramCount; i++) + DCC::forgetLoco(p[i]); + opcode = 0; // tell parser to ignore this command as we have done it already + break; + default: // drop through and parser will use the command unaltered. + break; + } +} + +// This is an OPTIONAL example of a HTTP filter... +// If you have configured wifi and an HTTP request is received on the Wifi connection +// it will normally be rejected 404 Not Found. + +// If you wish to handle HTTP requests, you can create a filter and ask the WifiInterface to +// call your code for each detected http request. + +void myHttpFilter(Print *stream, byte *cmd) +{ + (void)cmd; // Avoid compiler warning because this example doesnt use this parameter + + // BEWARE - As soon as you start responding, the cmd buffer is trashed! + // You must get everything you need from it before using StringFormatter::send! + + StringFormatter::send(stream, F("HTTP/1.1 200 OK\nContent-Type: text/html\nConnnection: close\n\n")); + StringFormatter::send(stream, F("This is my HTTP filter responding.
")); +} +#endif + +// Callback functions are necessary if you call any API that must wait for a response from the +// programming track. The API must return immediately otherwise other loop() functions would be blocked. +// Your callback function will be invoked when the data arrives from the prog track. +// See the DCC:getLocoId example in the setup function. +#if ENABLE_CUSTOM_CALLBACK +void myCallback(int result) +{ + DIAG(F("\n getting Loco Id callback result=%d"), result); +} +#endif // Create a serial command parser... Enables certain diagnostics and commands -// to be issued from the USB serial console +// to be issued from the USB serial console // This is NOT intended for JMRI.... -DCCEXParser serialParser; +DCCEXParser serialParser; -void setup() { +void setup() +{ // The main sketch has responsibilities during 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 Serial.begin(115200); - // Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi - // NOTE: References to Serial1 are for the serial port used to connect - // your wifi chip/shield. - - Serial1.begin(WIFI_BAUD); - WifiInterface::setup(Serial1, F("Your network name"), F("your network password"),F("DCCEX"),3532); - - // Responsibility 3: Start the DCC engine. - // 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. +// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi +// NOTE: References to Serial1 are for the serial port used to connect +// your wifi chip/shield. - // STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h +// Optionally tell the command parser to use my example filter. +// This will intercept JMRI commands from both USB and Wifi +#if ENABLE_CUSTOM_FILTER + DCCEXParser::setFilter(myComandFilter); +#endif - // Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the - // waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2 - - DCC::begin(STANDARD_MOTOR_SHIELD); +#if ENABLE_CUSTOM_CALLBACK + // This is just for demonstration purposes + DIAG(F("\n===== DCCEX demonstrating DCC::getLocoId() call ==========\n")); + DCC::getLocoId(myCallback); // myCallback will be called with the result + DIAG(F("\n===== DCC::getLocoId has returned, but the callback wont be executed until we are in loop() ======\n")); +#endif +#if ENABLE_WIFI + Serial1.begin(WIFI_SERIAL_LINK_SPEED); + WifiInterface::setup(Serial1, F("Your network name"), F("your network password"), F("DCCEX"), 3532); +#endif + + // Responsibility 3: Start the DCC engine. + // 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 + + // Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the + // waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2 + + DCC::begin(STANDARD_MOTOR_SHIELD); } -void loop() { +void loop() +{ // The main sketch has responsibilities during loop() - + // Responsibility 1: Handle DCC background processes // (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 3: Optionally handle any incoming WiFi traffic +#if ENABLE_WIFI WifiInterface::loop(); +#endif - +// Optionally report any decrease in memory (will automatically trigger on first call) +#if ENABLE_FREE_MEM_WARNING + int freeNow = freeMemory(); + if (freeNow < ramLowWatermark) + { + ramLowWatermark = freeNow; + DIAG(F("\nFree RAM=%d\n"), ramLowWatermark); + } +#endif } diff --git a/DCCEX.h b/DCCEX.h deleted file mode 100644 index e94b193..0000000 --- a/DCCEX.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef DCCEX_h -#define DCCEX_h -#include "DCC.h" -#include "DIAG.h" -#include "DCCEXParser.h" -#include "WifiInterface.h" -#endif diff --git a/config.example.h b/config.example.h new file mode 100644 index 0000000..c0fc4d3 --- /dev/null +++ b/config.example.h @@ -0,0 +1,185 @@ +/********************************************************************** + +Config.h +COPYRIGHT (c) 2013-2016 Gregg E. Berman +COPYRIGHT (c) 2020 Fred Decker + +The configuration file for DCC++ EX Command Station + +**********************************************************************/ + +///////////////////////////////////////////////////////////////////////////////////// +// NOTE: Before connecting these boards and selecting one in this software +// check the quick install guides!!! Some of these boards require a voltage +// generating resitor on the current sense pin of the device. Failure to select +// the correct resistor could damage the sense pin on your Arduino or destroy +// the device. +// +// DEFINE MOTOR_SHIELD_TYPE ACCORDING TO THE FOLLOWING TABLE: +// +// STANDARD_MOTOR_SHIELD = ARDUINO MOTOR SHIELD (MAX 18V/2A PER CHANNEL) Arduino Motor shield Rev3 based on the L298 +// POLOLU_MOTOR_SHIELD = POLOLU MC33926 MOTOR SHIELD (MAX 28V/2.5 PER CHANNEL) Pololu MC33926 Motor Driver (shield or carrier) +// FUNDUMOTO_SHIELD = FunduMoto Motor Shield +// FIREBOX_MK1 = Firebox MK1 +// FIREBOX_MK1S = Firebox MK1S + + +#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE PROGRAM TRACK CURRENT LIMIT IN MILLIAMPS + +#define TRIP_CURRENT_PROG 250 + + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE NUMBER OF MAIN TRACK REGISTER + +#define MAX_MAIN_REGISTERS 12 + +///////////////////////////////////////////////////////////////////////////////////// +// +// NOTE: Only the Mega currently supports WiFi since space is a constraint on the Uno +// at this time. It may be implemented in the future. +// +// +// +// Enable Use of WiFI for the Command Station for DCC-EX communications + +#define ENABLE_WIFI false + +#if ENABLE_WIFI + ///////////////////////////////////////////////////////////////////////////////////// + // + // DEFINE WiFi Parameters + // + #define WIFI_SSID "" + #define WIFI_PASSWORD "" + #define WIFI_HOSTNAME "" + + + // This defines the speed at which the Arduino will communicate with the ESP8266 module. + // When using the ESP8266 on an Uno it is recommended to use 9600, for Mega2560 the + // higher speed can be used. Set this based on your ESP8266 module configuration. + // Common defaults are 9600 or 115200. + #define WIFI_SERIAL_LINK_SPEED 115200 +#endif + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE STATIC IP ADDRESS *OR* COMMENT OUT TO USE DHCP +// + +//#define IP_ADDRESS { 192, 168, 1, 200 } + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE PORT TO USE FOR ETHERNET COMMUNICATIONS INTERFACE +// +// Uncomment to use Ethernet + +// #define ETHERNET_PORT 2560 + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE MAC ADDRESS ARRAY FOR ETHERNET COMMUNICATIONS INTERFACE +// +// Uncomment to use with Ethernet Shields +// +// NOTE: This is not used with ESP8266 WiFi modules. + +// #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF } + +///////////////////////////////////////////////////////////////////////////////////// +// +// Allows using a pin as a trigger for a scope or analyzer so we can capture only +// the important parts of the data stream +// +// USE_TRIGGERPIN: Enable code that switches the trigger pin on and off at end +// of the preamble. This takes some clock cycles in the +// interrupt routine for the main track. +// USE_TRIGGERPIN_PER_BIT: As above but for every bit. This only makes sense +// if USE_TRIGGERPIN is set. +// +// The value of the TRIGGERPIN is defined in DCCppEX.h because it might +// be board specific +// +//#define USE_TRIGGERPIN +//#define USE_TRIGGERPIN_PER_BIT + +///////////////////////////////////////////////////////////////////////////////////// +// +// Define only of you need the store to EEPROM feature. This takes RAM and +// you may need to use less MAX_MAIN_REGISTERS to compensate (at least on the UNO) + +#define EESTORE + +///////////////////////////////////////////////////////////////////////////////////// +// +// This shows the status and version at startup. This takes RAM. You can comment +// this line if you need to increase MAX_MAIN_REGISTERS(at least on the UNO) + +#define SHOWCONFIG + +///////////////////////////////////////////////////////////////////////////////////// +// +// This is different from the above config display which only shows one line at startup +// This defines a pin that when jumpered to ground before powering up the Arduinio, +// will display more detailed settings for diagnostics. You must remove the jumper and +// restart the Arduino to return to normal operation + +#define SHOW_CONFIG_DETAIL_PIN A2 + +///////////////////////////////////////////////////////////////////////////////////// +// +// PREAMBLE_MAIN: Length of the preamble on the main track. Per standard this should +// be at least 14 bits but if some equipment wants to insert a RailCom +// cutout this should be at least 16 bits. +// PERAMBLE_PROG: Length of the preamble on the programming track. Per standard this +// should be at least 22 bits + +#define PREAMBLE_MAIN 16 // TODO: Finish configurable preamble code +#define PREAMBLE_PROG 22 + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE LCD SCREEN USAGE BY THE BASE STATION +// +// Note: This feature requires an I2C enabled LCD screen using a PCF8574 based chipset. +// or one using a Hitachi HD44780. +// +// To enable, uncomment the line below and make sure only the correct LIB_TYPE line +// is uncommented below to select the library used for your LCD backpack + +//#define ENABLE_LCD + +#ifdef ENABLE_LCD + #define LIB_TYPE_PCF8574 + //#define LIB_TYPE_I2C + // This defines the I2C address for the LCD device + #define LCD_ADDRESS 0x27 //common defaults are 0x27 and 0x3F + + // This defines the number of columns the LCD device has + #define LCD_COLUMNS 16 + + // This defines the number of lines the LCD device has + #define LCD_LINES 2 +#endif + + +///////////////////////////////////////////////////////////////////////////////////// +// +// Enable custom command filtering +#define ENABLE_CUSTOM_FILTER false + +///////////////////////////////////////////////////////////////////////////////////// +// +// Enable custom command filtering +#define ENABLE_CUSTOM_CALLBACK false + +///////////////////////////////////////////////////////////////////////////////////// +// +// Enable custom command filtering +#define ENABLE_FREE_MEM_WARNING false \ No newline at end of file From 9ad8dd6cbd9b45fd9042253b51843b13d8bf7323 Mon Sep 17 00:00:00 2001 From: dexslab Date: Tue, 22 Sep 2020 15:20:33 -0400 Subject: [PATCH 02/49] Implement Serial 123 Branch by hand due to merge conflicts --- CommandStation-EX.ino | 16 +++++++++++++++- config.example.h | 1 + 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 101f3f6..5a5b83c 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -116,8 +116,22 @@ void setup() #endif #if ENABLE_WIFI + bool wifiUp = false; + Serial1.begin(WIFI_SERIAL_LINK_SPEED); - WifiInterface::setup(Serial1, F("Your network name"), F("your network password"), F("DCCEX"), 3532); + wifiUp = WifiInterface::setup(Serial1, WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, WIFI_PORT); +#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) + if (!wifiUp) + { + Serial2.begin(WIFI_SERIAL_LINK_SPEED); + wifiUp = WifiInterface::setup(Serial2, WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, WIFI_PORT); + } + if (!wifiUp) + { + Serial3.begin(WIFI_SERIAL_LINK_SPEED); + wifiUp = WifiInterface::setup(Serial3, WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, WIFI_PORT); + } +#endif #endif // Responsibility 3: Start the DCC engine. diff --git a/config.example.h b/config.example.h index c0fc4d3..7726bca 100644 --- a/config.example.h +++ b/config.example.h @@ -58,6 +58,7 @@ The configuration file for DCC++ EX Command Station #define WIFI_SSID "" #define WIFI_PASSWORD "" #define WIFI_HOSTNAME "" + #define WIFI_PORT 3252 // This defines the speed at which the Arduino will communicate with the ESP8266 module. From eb887fdbd207381b678c8cd190d41d220adfc584 Mon Sep 17 00:00:00 2001 From: dexslab Date: Tue, 22 Sep 2020 15:45:06 -0400 Subject: [PATCH 03/49] Finish wifi interface fixes from serial123 branch, Fixes for flash string missing --- CommandStation-EX.ino | 9 +-- WifiInterface.cpp | 135 +++++++++++++++++++++++++++--------------- WifiInterface.h | 2 +- 3 files changed, 93 insertions(+), 53 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 5a5b83c..4e5095d 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -116,20 +116,21 @@ void setup() #endif #if ENABLE_WIFI +#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) bool wifiUp = false; Serial1.begin(WIFI_SERIAL_LINK_SPEED); - wifiUp = WifiInterface::setup(Serial1, WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, WIFI_PORT); -#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) + wifiUp = WifiInterface::setup(Serial1, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), WIFI_PORT); + if (!wifiUp) { Serial2.begin(WIFI_SERIAL_LINK_SPEED); - wifiUp = WifiInterface::setup(Serial2, WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, WIFI_PORT); + wifiUp = WifiInterface::setup(Serial2, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), WIFI_PORT); } if (!wifiUp) { Serial3.begin(WIFI_SERIAL_LINK_SPEED); - wifiUp = WifiInterface::setup(Serial3, WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, WIFI_PORT); + wifiUp = WifiInterface::setup(Serial3, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), WIFI_PORT); } #endif #endif diff --git a/WifiInterface.cpp b/WifiInterface.cpp index c08a33e..06fa11b 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -16,6 +16,8 @@ You should have received a copy of the GNU General Public License along with CommandStation. If not, see . */ + +#include #include "WifiInterface.h" #include "DIAG.h" #include "StringFormatter.h" @@ -39,13 +41,15 @@ MemStream WifiInterface::streamer(buffer, MAX_WIFI_BUFFER); Stream * WifiInterface::wifiStream = NULL; HTTP_CALLBACK WifiInterface::httpCallback = 0; - -void WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid, const __FlashStringHelper* password, +bool WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid, const __FlashStringHelper* password, const __FlashStringHelper* hostname, int port) { + static uint8_t ntry = 0; + ntry++; wifiStream = &setupStream; - DIAG(F("\n++++++ Wifi Setup In Progress ++++++++\n")); + DIAG(F("\n++ Wifi Setup Try %d ++\n"), ntry); + connected = setup2( SSid, password, hostname, port); if (connected) { @@ -53,12 +57,14 @@ void WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid checkForOK(200, OK_SEARCH, true); } - DIAG(F("\n++++++ Wifi Setup %S ++++++++\n"), connected ? F("OK") : F("FAILED")); + DIAG(F("\n++ Wifi Setup %S ++\n"), connected ? F("OK") : F("FAILED")); + return connected; } bool WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringHelper* password, const __FlashStringHelper* hostname, int port) { - int ipOK = 0; + bool ipOK = false; + bool oldCmd = false; char macAddress[17]; // mac address extraction @@ -71,66 +77,99 @@ bool WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringH return true; } - + StringFormatter::send(wifiStream, F("AT\r\n")); // Is something here that understands AT? + if(!checkForOK(200, OK_SEARCH, true)) + return false; // No AT compatible WiFi module here + StringFormatter::send(wifiStream, F("ATE1\r\n")); // Turn on the echo, se we can see what's happening - checkForOK(2000, OK_SEARCH, true); // Makes this visible on the console + checkForOK(2000, OK_SEARCH, true); // Makes this visible on the console // Display the AT version information StringFormatter::send(wifiStream, F("AT+GMR\r\n")); checkForOK(2000, OK_SEARCH, true, false); // Makes this visible on the console - delay(8000); // give a preconfigured ES8266 a chance to connect to a router - - StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); + StringFormatter::send(wifiStream, F("AT+CWMODE=1\r\n")); // configure as "station" = WiFi client + checkForOK(1000, OK_SEARCH, true); // Not always OK, sometimes "no change" - // looking fpr mac addr eg +CIFSR:APMAC,"be:dd:c2:5c:6b:b7" - if (checkForOK(5000, (const char*) F("+CIFSR:APMAC,\""), true,false)) { - // Copy 17 byte mac address - for (int i=0; i<17;i++) { - while(!wifiStream->available()); - macAddress[i]=wifiStream->read(); - StringFormatter::printEscape(macAddress[i]); - } - } - char macTail[]={macAddress[9],macAddress[10],macAddress[12],macAddress[13],macAddress[15],macAddress[16],'\0'}; + // If the source code looks unconfigured, check if the + // ESP8266 is preconfigured. We check the first 13 chars + // of the password. + if (strncmp_P("Your network ",(const char*)password,13) == 0) { + delay(8000); // give a preconfigured ES8266 a chance to connect to a router + + StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); + if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false)) + if (!checkForOK(1000, (const char*) F("0.0.0.0"), true,false)) + ipOK = true; + } else { + + if (!ipOK) { + + // Older ES versions have AT+CWJAP, newer ones have AT+CWJAP_CUR and AT+CWHOSTNAME + StringFormatter::send(wifiStream, F("AT+CWJAP?\r\n")); + if (checkForOK(2000, OK_SEARCH, true)) { + oldCmd=true; + while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE + + // AT command early version supports CWJAP/CWSAP + if (SSid) { + StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password); + ipOK = checkForOK(16000, OK_SEARCH, true); + } + DIAG(F("\n**\n")); + + } else { + // later version supports CWJAP_CUR + + StringFormatter::send(wifiStream, F("AT+CWHOSTNAME=\"%S\"\r\n"), hostname); // Set Host name for Wifi Client + checkForOK(2000, OK_SEARCH, true); // dont care if not supported - if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false)) - if (!checkForOK(1000, (const char*) F("0.0.0.0"), true,false)) - ipOK = 1; + if (SSid) { + StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password); + ipOK = checkForOK(20000, OK_SEARCH, true); + } + } + delay(8000); // give a preconfigured ES8266 a chance to connect to a router + + if (ipOK) { + // But we really only have the ESSID and password correct + // Let's check for IP + ipOK = false; + StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); + if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false)) + if (!checkForOK(1000, (const char*) F("0.0.0.0"), true,false)) + ipOK = true; + } + } + } if (!ipOK) { - StringFormatter::send(wifiStream, F("AT+CWMODE=3\r\n")); // configure as server or access point + // If we have not managed to get this going in station mode, go for AP mode + + StringFormatter::send(wifiStream, F("AT+CWMODE=2\r\n")); // configure as AccessPoint. checkForOK(1000, OK_SEARCH, true); // Not always OK, sometimes "no change" - // Older ES versions have AT+CWJAP, newer ones have AT+CWJAP_CUR and AT+CWHOSTNAME - StringFormatter::send(wifiStream, F("AT+CWJAP?\r\n")); - if (checkForOK(2000, OK_SEARCH, true)) { - while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE - - // AT command early version supports CWJAP/CWSAP - if (SSid) { - StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password); - checkForOK(16000, OK_SEARCH, true); // can ignore failure as AP mode may still be ok + // Figure out MAC addr + StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); + // looking fpr mac addr eg +CIFSR:APMAC,"be:dd:c2:5c:6b:b7" + if (checkForOK(5000, (const char*) F("+CIFSR:APMAC,\""), true,false)) { + // Copy 17 byte mac address + for (int i=0; i<17;i++) { + while(!wifiStream->available()); + macAddress[i]=wifiStream->read(); + StringFormatter::printEscape(macAddress[i]); } - DIAG(F("\n**\n")); - - // establish the APname + } + char macTail[]={macAddress[9],macAddress[10],macAddress[12],macAddress[13],macAddress[15],macAddress[16],'\0'}; + + if (oldCmd) { + while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE + StringFormatter::send(wifiStream, F("AT+CWSAP=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), macTail, macTail); checkForOK(16000, OK_SEARCH, true); // can ignore failure as AP mode may still be ok - } - else { - // later version supports CWJAP_CUR - - StringFormatter::send(wifiStream, F("AT+CWHOSTNAME=\"%S\"\r\n"), hostname); // Set Host name for Wifi Client - checkForOK(2000, OK_SEARCH, true); // dont care if not supported + } else { - - if (SSid) { - StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password); - checkForOK(20000, OK_SEARCH, true); // can ignore failure as AP mode may still be ok - } - StringFormatter::send(wifiStream, F("AT+CWSAP_CUR=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), macTail, macTail); checkForOK(20000, OK_SEARCH, true); // can ignore failure as SSid mode may still be ok diff --git a/WifiInterface.h b/WifiInterface.h index 7baa2c3..10941fa 100644 --- a/WifiInterface.h +++ b/WifiInterface.h @@ -29,7 +29,7 @@ typedef void (*HTTP_CALLBACK)(Print * stream, byte * cmd); class WifiInterface { public: - static void setup(Stream & setupStream, const __FlashStringHelper* SSSid, const __FlashStringHelper* password, + static bool setup(Stream & setupStream, const __FlashStringHelper* SSSid, const __FlashStringHelper* password, const __FlashStringHelper* hostname, int port); static void loop(); static void ATCommand(const byte * command); From 37ab4d222f90836e66acff8c50838d0438d4e202 Mon Sep 17 00:00:00 2001 From: dexslab Date: Tue, 22 Sep 2020 15:57:26 -0400 Subject: [PATCH 04/49] Fix for flash memory usage per Haba --- CommandStation-EX.ino | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 4e5095d..e454d14 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -118,21 +118,26 @@ void setup() #if ENABLE_WIFI #if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) bool wifiUp = false; + const __FlashStringHelper *wifiESSID = F(WIFI_SSID); + const __FlashStringHelper *wifiPassword = F(WIFI_PASSWORD); + const __FlashStringHelper *dccex = F(WIFI_HOSTNAME); + const uint16_t port = WIFI_PORT; Serial1.begin(WIFI_SERIAL_LINK_SPEED); - wifiUp = WifiInterface::setup(Serial1, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), WIFI_PORT); - + wifiUp = WifiInterface::setup(Serial1, wifiESSID, wifiPassword, dccex, port); +#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) if (!wifiUp) { - Serial2.begin(WIFI_SERIAL_LINK_SPEED); - wifiUp = WifiInterface::setup(Serial2, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), WIFI_PORT); + Serial2.begin(WIFI_BAUD); + wifiUp = WifiInterface::setup(Serial2, wifiESSID, wifiPassword, dccex, port); } if (!wifiUp) { - Serial3.begin(WIFI_SERIAL_LINK_SPEED); - wifiUp = WifiInterface::setup(Serial3, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), WIFI_PORT); + Serial3.begin(WIFI_BAUD); + wifiUp = WifiInterface::setup(Serial3, wifiESSID, wifiPassword, dccex, port); } #endif +#endif #endif // Responsibility 3: Start the DCC engine. @@ -145,7 +150,7 @@ void setup() // Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the // waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2 - DCC::begin(STANDARD_MOTOR_SHIELD); + DCC::begin(MOTOR_SHIELD_TYPE); } void loop() From c9bc63e65916b36bb695b0c57796f19fafa7c9fe Mon Sep 17 00:00:00 2001 From: dexslab Date: Tue, 22 Sep 2020 15:59:34 -0400 Subject: [PATCH 05/49] Cleanup for wifi enabled and if on a mega --- CommandStation-EX.ino | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index e454d14..e2660e6 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -115,8 +115,7 @@ void setup() DIAG(F("\n===== DCC::getLocoId has returned, but the callback wont be executed until we are in loop() ======\n")); #endif -#if ENABLE_WIFI -#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) +#if ENABLE_WIFI && defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) bool wifiUp = false; const __FlashStringHelper *wifiESSID = F(WIFI_SSID); const __FlashStringHelper *wifiPassword = F(WIFI_PASSWORD); @@ -125,7 +124,6 @@ void setup() Serial1.begin(WIFI_SERIAL_LINK_SPEED); wifiUp = WifiInterface::setup(Serial1, wifiESSID, wifiPassword, dccex, port); -#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) if (!wifiUp) { Serial2.begin(WIFI_BAUD); @@ -136,8 +134,6 @@ void setup() Serial3.begin(WIFI_BAUD); wifiUp = WifiInterface::setup(Serial3, wifiESSID, wifiPassword, dccex, port); } -#endif -#endif #endif // Responsibility 3: Start the DCC engine. From d0ecfbc71ca4e0e6736ff87562759a94089fc525 Mon Sep 17 00:00:00 2001 From: dexslab Date: Tue, 22 Sep 2020 16:02:11 -0400 Subject: [PATCH 06/49] parenthesis are your firends --- CommandStation-EX.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index e2660e6..c0128fc 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -115,7 +115,7 @@ void setup() DIAG(F("\n===== DCC::getLocoId has returned, but the callback wont be executed until we are in loop() ======\n")); #endif -#if ENABLE_WIFI && defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) +#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) bool wifiUp = false; const __FlashStringHelper *wifiESSID = F(WIFI_SSID); const __FlashStringHelper *wifiPassword = F(WIFI_PASSWORD); From 03c23abea2ee3351ad809982b059971b178b415d Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 22 Sep 2020 22:11:50 +0200 Subject: [PATCH 07/49] Set default values meaning unconfigured --- config.example.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config.example.h b/config.example.h index 7726bca..8048a5d 100644 --- a/config.example.h +++ b/config.example.h @@ -55,8 +55,8 @@ The configuration file for DCC++ EX Command Station // // DEFINE WiFi Parameters // - #define WIFI_SSID "" - #define WIFI_PASSWORD "" + #define WIFI_SSID "Your network name" + #define WIFI_PASSWORD "Your network passwd" #define WIFI_HOSTNAME "" #define WIFI_PORT 3252 @@ -183,4 +183,4 @@ The configuration file for DCC++ EX Command Station ///////////////////////////////////////////////////////////////////////////////////// // // Enable custom command filtering -#define ENABLE_FREE_MEM_WARNING false \ No newline at end of file +#define ENABLE_FREE_MEM_WARNING false From 9bb9fa5e845355aabc52de71638ba71ab886aa27 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 22 Sep 2020 23:10:52 +0200 Subject: [PATCH 08/49] rewrite freeMemory code --- DCC.h | 2 +- config.example.h | 2 +- freeMemory.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ freeMemory.h | 37 ++++++++++++++++++++----------------- 4 files changed, 64 insertions(+), 19 deletions(-) create mode 100644 freeMemory.cpp diff --git a/DCC.h b/DCC.h index 05e9f9e..61d61f0 100644 --- a/DCC.h +++ b/DCC.h @@ -50,7 +50,7 @@ SKIPTARGET=0xFF // jump to target // Allocations with memory implications..! // Base system takes approx 900 bytes + 8 per loco. Turnouts, Sensors etc are dynamically created #ifdef ARDUINO_AVR_UNO - const byte MAX_LOCOS=20; + const byte MAX_LOCOS=30; #else const byte MAX_LOCOS=50; #endif diff --git a/config.example.h b/config.example.h index 8048a5d..5dbbd2b 100644 --- a/config.example.h +++ b/config.example.h @@ -37,7 +37,7 @@ The configuration file for DCC++ EX Command Station // // DEFINE NUMBER OF MAIN TRACK REGISTER -#define MAX_MAIN_REGISTERS 12 +#define MAX_MAIN_REGISTERS 22 ///////////////////////////////////////////////////////////////////////////////////// // diff --git a/freeMemory.cpp b/freeMemory.cpp new file mode 100644 index 0000000..30fa16d --- /dev/null +++ b/freeMemory.cpp @@ -0,0 +1,42 @@ +/* + * © 2020, Harald Barth + * + * This file is part of Asbelos 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 . + */ + +#include "freeMemory.h" + +// thanks go to https://github.com/mpflaga/Arduino-MemoryFree +#if defined(__arm__) +extern "C" char* sbrk(int); +#elif defined(__AVR__) +extern char *__brkval; +extern char *__malloc_heap_start; +#else +#error Unsupported board type +#endif + + +int freeMemory() { + char top; +#if defined(__arm__) + return &top - reinterpret_cast(sbrk(0)); +#elif defined(__AVR__) + return __brkval ? &top - __brkval : &top - __malloc_heap_start; +#else +#error bailed out alredy above +#endif +} diff --git a/freeMemory.h b/freeMemory.h index e3af2fe..2bd35c1 100644 --- a/freeMemory.h +++ b/freeMemory.h @@ -1,20 +1,23 @@ +/* + * © 2020, 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 . + */ + #ifndef freeMemory_h #define freeMemory_h - -// thanks go to https://github.com/mpflaga/Arduino-MemoryFree -#ifdef __arm__ -// should use uinstd.h to define sbrk but Due causes a conflict -extern "C" char* sbrk(int incr); -#else // __ARM__ -extern char *__brkval; -#endif // __arm__ - -int freeMemory() { - char top; -#ifdef __arm__ - return &top - reinterpret_cast(sbrk(0)); -#else // __arm__ - return __brkval ? &top - __brkval : &top - __malloc_heap_start; -#endif // __arm__ -} +int freeMemory(); #endif From 623187281d2ca525d79b62ab73c92f4053a01a54 Mon Sep 17 00:00:00 2001 From: dexslab Date: Tue, 22 Sep 2020 17:15:27 -0400 Subject: [PATCH 09/49] Wifi baud how dare you --- CommandStation-EX.ino | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index c0128fc..3bc52ff 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -126,12 +126,12 @@ void setup() wifiUp = WifiInterface::setup(Serial1, wifiESSID, wifiPassword, dccex, port); if (!wifiUp) { - Serial2.begin(WIFI_BAUD); + Serial2.begin(WIFI_SERIAL_LINK_SPEED); wifiUp = WifiInterface::setup(Serial2, wifiESSID, wifiPassword, dccex, port); } if (!wifiUp) { - Serial3.begin(WIFI_BAUD); + Serial3.begin(WIFI_SERIAL_LINK_SPEED); wifiUp = WifiInterface::setup(Serial3, wifiESSID, wifiPassword, dccex, port); } #endif @@ -161,7 +161,7 @@ void loop() serialParser.loop(Serial); // Responsibility 3: Optionally handle any incoming WiFi traffic -#if ENABLE_WIFI +#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) WifiInterface::loop(); #endif From 9e1d85f9bb0cca941c1f7ec3eba8fd42eec54c25 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 22 Sep 2020 23:24:30 +0200 Subject: [PATCH 10/49] make number of slots in Uno configurable --- DCC.h | 3 ++- config.example.h | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/DCC.h b/DCC.h index 61d61f0..00f402d 100644 --- a/DCC.h +++ b/DCC.h @@ -19,6 +19,7 @@ #ifndef DCC_h #define DCC_h #include +#include "config.h" #include "MotorDriver.h" #include "MotorDrivers.h" @@ -50,7 +51,7 @@ SKIPTARGET=0xFF // jump to target // Allocations with memory implications..! // Base system takes approx 900 bytes + 8 per loco. Turnouts, Sensors etc are dynamically created #ifdef ARDUINO_AVR_UNO - const byte MAX_LOCOS=30; + const byte MAX_LOCOS=MAX_MAIN_REGISTERS; #else const byte MAX_LOCOS=50; #endif diff --git a/config.example.h b/config.example.h index 5dbbd2b..43bcb2a 100644 --- a/config.example.h +++ b/config.example.h @@ -36,6 +36,8 @@ The configuration file for DCC++ EX Command Station ///////////////////////////////////////////////////////////////////////////////////// // // DEFINE NUMBER OF MAIN TRACK REGISTER +// THIS ONLY MATTERS ON THE UNO (and compatible) WHERE SPACE IS TIGHT +// THIS IS IGNORED ON OTHER ARDUINOS WHERE THAT IS 50 #define MAX_MAIN_REGISTERS 22 From 737300796db32e761e6c9a63286865af4c2919d1 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Wed, 23 Sep 2020 16:22:20 +0100 Subject: [PATCH 11/49] Correct <+> command return --- DCCEXParser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 858f2a5..7be633d 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -338,7 +338,7 @@ void DCCEXParser::parse(Print * stream, byte *com, bool blocking) { case '+' : // Complex Wifi interface command (not usual parse) WifiInterface::ATCommand(com); - break; + return; default: //anything else will diagnose and drop out to DIAG(F("\nOpcode=%c params=%d\n"),opcode,params); From dce241d1aec2aacfc3d6fa90b54c2b6fd62ef107 Mon Sep 17 00:00:00 2001 From: FrightRisk <37218136+FrightRisk@users.noreply.github.com> Date: Wed, 23 Sep 2020 11:54:09 -0400 Subject: [PATCH 12/49] more experiments with dccexparser --- DCCEXParser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 858f2a5..5721708 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -30,7 +30,7 @@ #include "EEStore.h" #include "DIAG.h" -const char VERSION[] PROGMEM ="0.1.9"; +const char VERSION[] PROGMEM ="0.2.0"; // These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter. // To discover new keyword numbers , use the <$ YOURKEYWORD> command From ba79668a8f71a2248d41bb9377bf1f434a842a9c Mon Sep 17 00:00:00 2001 From: FrightRisk <37218136+FrightRisk@users.noreply.github.com> Date: Wed, 23 Sep 2020 15:17:01 -0400 Subject: [PATCH 13/49] Add Ardunio and Motor Board types to cmd --- CommandStation-EX.ino | 2 +- DCC.h | 27 +++++++++++++++++++++++++++ DCCEXParser.cpp | 2 +- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 3bc52ff..d9e16ac 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -146,7 +146,7 @@ void setup() // Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the // waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2 - DCC::begin(MOTOR_SHIELD_TYPE); + DCC::begin(MOTOR_BOARD); } void loop() diff --git a/DCC.h b/DCC.h index 00f402d..4e1053c 100644 --- a/DCC.h +++ b/DCC.h @@ -139,4 +139,31 @@ private: static const byte BIT_OFF=0x00; }; +#ifdef ARDUINO_AVR_MEGA // is using Mega 1280, define as Mega 2560 (pinouts and functionality are identical) + #define ARDUINO_AVR_MEGA2560 +#endif + +#if defined(ARDUINO_AVR_UNO) + #define ARDUINO_TYPE "UNO" +#elif defined(ARDUINO_AVR_NANO) + #define ARDUINO_TYPE "NANO" +#elif defined(ARDUINO_AVR_MEGA2560) + #define ARDUINO_TYPE "MEGA" +#else + #error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560 +#endif + +#if defined(STANDARD_MOTOR_SHIELD) + #define MOTOR_BOARD_TYPE "Ardu" +#elif defined(POLOLU_MOTOR_SHIELD) + #define MOTOR_BOARD_TYPE "Polo" +#elif defined(FUNDUMOTO_SHIELD) + #define MOTOR_BOARD_TYPE "Fundu" +#elif defined(FIREBOX_MK1) + #define MOTOR_BOARD_TYPE "FireBox1" +#elif if defined(FIREBOX_MK1S) + #define MOTOR_BOARD_TYPE "FireBox1S" +#endif + + #endif diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 80b08da..69e3dc1 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -304,7 +304,7 @@ void DCCEXParser::parse(Print * stream, byte *com, bool blocking) { case 's': // StringFormatter::send(stream,F(""),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON ); - StringFormatter::send(stream,F(""), VERSION, F(GITHUB_SHA)); + StringFormatter::send(stream,F(""), VERSION, F(ARDUINO_TYPE), F(MOTOR_BOARD_TYPE), F(GITHUB_SHA)); // TODO Send stats of speed reminders table // TODO send status of turnouts etc etc return; From ff7fb3870b8ce830a778983375bc13c33ff5eb45 Mon Sep 17 00:00:00 2001 From: dexslab Date: Wed, 23 Sep 2020 17:54:47 -0400 Subject: [PATCH 14/49] Fix for uno compile with wifi interface that is not needed and fix for compile on both motorshield type not being used properly --- CommandStation-EX.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index d9e16ac..f9557ef 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -16,7 +16,7 @@ #include "DCC.h" #include "DIAG.h" #include "DCCEXParser.h" -#if ENABLE_WIFI +#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) #include "WifiInterface.h" #endif #if ENABLE_FREE_MEM_WARNING @@ -146,7 +146,7 @@ void setup() // Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the // waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2 - DCC::begin(MOTOR_BOARD); + DCC::begin(MOTOR_SHIELD_TYPE); } void loop() From 3df0ae16df59746a784cd57ec464b6e9d78a24d3 Mon Sep 17 00:00:00 2001 From: FrightRisk <37218136+FrightRisk@users.noreply.github.com> Date: Wed, 23 Sep 2020 21:53:33 -0400 Subject: [PATCH 15/49] Move version to its own .h file --- version.h | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 version.h diff --git a/version.h b/version.h new file mode 100644 index 0000000..6e45e70 --- /dev/null +++ b/version.h @@ -0,0 +1,10 @@ +#ifndef version_h +#define version_h + +#include "StringFormatter.h" + +// const char VERSION[] PROGMEM ="0.2.0"; +#define VERSION "0.2.0" + + +#endif \ No newline at end of file From 3d94d8347ac3a1d4473725dc93003f6cc8360ce9 Mon Sep 17 00:00:00 2001 From: FrightRisk <37218136+FrightRisk@users.noreply.github.com> Date: Wed, 23 Sep 2020 21:54:01 -0400 Subject: [PATCH 16/49] Add support for 2 or 4 line LCD Display --- CommandStation-EX.ino | 44 ++++++++++++++++++++++++++++++++++++++++++- DCC.h | 11 +++++++++++ DCCEXParser.cpp | 2 +- platformio.ini | 2 ++ 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index f9557ef..48ee1fe 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -16,6 +16,7 @@ #include "DCC.h" #include "DIAG.h" #include "DCCEXParser.h" +#include "version.h" #if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) #include "WifiInterface.h" #endif @@ -24,6 +25,18 @@ int ramLowWatermark = 32767; // This figure gets overwritten dynamically in loop() #endif +//////////////////////////////////////////////////////////////// +// +// Enables an I2C 2x24 or 4x24 LCD Screen +#ifdef ENABLE_LCD +bool lcdEnabled = false; + #if defined(LIB_TYPE_PCF8574) + LiquidCrystal_PCF8574 lcdDisplay(LCD_ADDRESS); + #elif defined(LIB_TYPE_I2C) + LiquidCrystal_I2C lcdDisplay = LiquidCrystal_I2C(LCD_ADDRESS, LCD_COLUMNS, LCD_LINES); + #endif +#endif + // this code is here to demonstrate use of the DCC API and other techniques // myFilter is an example of an OPTIONAL command filter used to intercept < > commands from @@ -92,6 +105,35 @@ DCCEXParser serialParser; void setup() { + //////////////////////////////////////////// + // + // More display stuff. Need to put this in a .h file and make + // it a class + #ifdef ENABLE_LCD + Wire.begin(); + // Check that we can find the LCD by its address before attempting to use it. + Wire.beginTransmission(LCD_ADDRESS); + if(Wire.endTransmission() == 0) { + lcdEnabled = true; + lcdDisplay.begin(LCD_COLUMNS, LCD_LINES); + lcdDisplay.setBacklight(255); + lcdDisplay.clear(); + lcdDisplay.setCursor(0, 0); + lcdDisplay.print("DCC++ EX v"); + lcdDisplay.print(VERSION); + lcdDisplay.setCursor(0, 1); + #if COMM_INTERFACE >= 1 + lcdDisplay.print("IP: PENDING"); + #else + lcdDisplay.print("SERIAL: READY"); + #endif + #if LCD_LINES > 2 + lcdDisplay.setCursor(0, 3); + lcdDisplay.print("TRACK POWER: OFF"); + #endif + } +#endif + // The main sketch has responsibilities during setup() // Responsibility 1: Start the usb connection for diagnostics @@ -146,7 +188,7 @@ void setup() // Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the // waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2 - DCC::begin(MOTOR_SHIELD_TYPE); + DCC::begin(MOTOR_BOARD); } void loop() diff --git a/DCC.h b/DCC.h index 4e1053c..0ba7a5f 100644 --- a/DCC.h +++ b/DCC.h @@ -165,5 +165,16 @@ private: #define MOTOR_BOARD_TYPE "FireBox1S" #endif +#ifdef ENABLE_LCD +#include + #if defined(LIB_TYPE_PCF8574) + #include + extern LiquidCrystal_PCF8574 lcdDisplay; + #elif defined(LIB_TYPE_I2C) + #include + extern LiquidCrystal_I2C lcdDisplay; + #endif +extern bool lcdEnabled; +#endif #endif diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 69e3dc1..f7ba4ef 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -26,11 +26,11 @@ #include "Sensors.h" #include "freeMemory.h" #include "GITHUB_SHA.h" +#include "version.h" #include "EEStore.h" #include "DIAG.h" -const char VERSION[] PROGMEM ="0.2.0"; // These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter. // To discover new keyword numbers , use the <$ YOURKEYWORD> command diff --git a/platformio.ini b/platformio.ini index 58fd2a1..0c91321 100644 --- a/platformio.ini +++ b/platformio.ini @@ -32,6 +32,7 @@ framework = arduino lib_deps = ${env.lib_deps} DIO2 + mathertel/LiquidCrystal_PCF8574 @ ^1.2.0 [env:mega328] platform = atmelavr @@ -56,3 +57,4 @@ framework = arduino lib_deps = ${env.lib_deps} DIO2 + mathertel/LiquidCrystal_PCF8574 @ ^1.2.0 From 0b603d4dfdc2acad1582e5556b73c8723e79e7b3 Mon Sep 17 00:00:00 2001 From: dexslab Date: Wed, 23 Sep 2020 22:49:18 -0400 Subject: [PATCH 17/49] Change motorshield back to whats in config, change from 1.2 version needed --- CommandStation-EX.ino | 2 +- platformio.ini | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 48ee1fe..fc92ef5 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -188,7 +188,7 @@ void setup() // Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the // waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2 - DCC::begin(MOTOR_BOARD); + DCC::begin(MOTOR_SHIELD_TYPE); } void loop() diff --git a/platformio.ini b/platformio.ini index 0c91321..7160904 100644 --- a/platformio.ini +++ b/platformio.ini @@ -32,7 +32,7 @@ framework = arduino lib_deps = ${env.lib_deps} DIO2 - mathertel/LiquidCrystal_PCF8574 @ ^1.2.0 + mathertel/LiquidCrystal_PCF8574 [env:mega328] platform = atmelavr @@ -57,4 +57,4 @@ framework = arduino lib_deps = ${env.lib_deps} DIO2 - mathertel/LiquidCrystal_PCF8574 @ ^1.2.0 + mathertel/LiquidCrystal_PCF8574 From caef013d35aa593ef523a1f6de2188a3dbc818c8 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 24 Sep 2020 09:51:09 +0100 Subject: [PATCH 18/49] Supply motor shield name for --- DCC.cpp | 8 +++++++- DCC.h | 18 ++++++------------ DCCEXParser.cpp | 2 +- MotorDrivers.h | 10 +++++----- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index e7ca624..5749afe 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -41,8 +41,10 @@ const byte FN_GROUP_3=0x04; const byte FN_GROUP_4=0x08; const byte FN_GROUP_5=0x10; +__FlashStringHelper* DCC::shieldName=NULL; -void DCC::begin(MotorDriver * mainDriver, MotorDriver* progDriver, byte timerNumber) { +void DCC::begin(const __FlashStringHelper* motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver, byte timerNumber) { + shieldName=motorShieldName; DCCWaveform::begin(mainDriver,progDriver, timerNumber); } @@ -206,6 +208,10 @@ void DCC::setProgTrackSyncMain(bool on) { DCCWaveform::progTrackSyncMain=on; } +__FlashStringHelper* DCC::getMotorShieldName() { + return shieldName; +} + const ackOp PROGMEM WRITE_BIT0_PROG[] = { BASELINE, W0,WACK, diff --git a/DCC.h b/DCC.h index 0ba7a5f..25af22c 100644 --- a/DCC.h +++ b/DCC.h @@ -60,7 +60,7 @@ SKIPTARGET=0xFF // jump to target class DCC { public: - static void begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber=1); + static void begin(const __FlashStringHelper* motorShieldName, MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber=1); static void loop(); // Public DCC API functions @@ -91,6 +91,9 @@ class DCC { static void forgetLoco(int cab); // removes any speed reminders for this loco static void forgetAllLocos(); // removes all speed reminders static void displayCabList(Print * stream); + + static __FlashStringHelper* getMotorShieldName(); + private: struct LOCO { int loco; @@ -104,6 +107,8 @@ private: static void setFunctionInternal( int cab, byte fByte, byte eByte); static bool issueReminder(int reg); static int nextLoco; + static __FlashStringHelper* shieldName; + static LOCO speedTable[MAX_LOCOS]; static byte cv1(byte opcode, int cv); static byte cv2(int cv); @@ -153,17 +158,6 @@ private: #error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560 #endif -#if defined(STANDARD_MOTOR_SHIELD) - #define MOTOR_BOARD_TYPE "Ardu" -#elif defined(POLOLU_MOTOR_SHIELD) - #define MOTOR_BOARD_TYPE "Polo" -#elif defined(FUNDUMOTO_SHIELD) - #define MOTOR_BOARD_TYPE "Fundu" -#elif defined(FIREBOX_MK1) - #define MOTOR_BOARD_TYPE "FireBox1" -#elif if defined(FIREBOX_MK1S) - #define MOTOR_BOARD_TYPE "FireBox1S" -#endif #ifdef ENABLE_LCD #include diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index f7ba4ef..e10e028 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -304,7 +304,7 @@ void DCCEXParser::parse(Print * stream, byte *com, bool blocking) { case 's': // StringFormatter::send(stream,F(""),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON ); - StringFormatter::send(stream,F(""), VERSION, F(ARDUINO_TYPE), F(MOTOR_BOARD_TYPE), F(GITHUB_SHA)); + StringFormatter::send(stream,F(""), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA)); // TODO Send stats of speed reminders table // TODO send status of turnouts etc etc return; diff --git a/MotorDrivers.h b/MotorDrivers.h index f31aeac..c81bc37 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -16,27 +16,27 @@ const byte UNUSED_PIN = 255; // float senseFactor, unsigned int tripMilliamps, byte faultPin); // Arduino standard Motor Shield -#define STANDARD_MOTOR_SHIELD \ +#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ new MotorDriver(3 , 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \ new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 250 , UNUSED_PIN) // Pololu Motor Shield -#define POLOLU_MOTOR_SHIELD \ +#define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \ new MotorDriver(4, 7, UNUSED_PIN, 9 , A0, 18, 2000, 12), \ new MotorDriver(2, 8, UNUSED_PIN, 10, A1, 18, 250 , UNUSED_PIN) // Firebox Mk1 -#define FIREBOX_MK1 \ +#define FIREBOX_MK1 F("FIREBOX_MK1"), \ new MotorDriver(3, 6, 7, UNUSED_PIN, A5, 9.766, 5500, UNUSED_PIN), \ new MotorDriver(4, 8, 9, UNUSED_PIN, A1, 5.00, 250 , UNUSED_PIN) // Firebox Mk1S -#define FIREBOX_MK1S \ +#define FIREBOX_MK1S F("FIREBOX_MK1A"), \ new MotorDriver(24, 21, 22, 25, 23, 9.766, 5500, UNUSED_PIN), \ new MotorDriver(30, 27, 28, 31, 29, 5.00, 250 , UNUSED_PIN) // FunduMoto Motor Shield -#define FUNDUMOTO_SHIELD \ +#define FUNDUMOTO_SHIELD F("FUNDUMOTO_SHIELD"), \ new MotorDriver(10 , 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \ new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 250 , UNUSED_PIN) From b810fb45c73253c0497e4fab91a01d377056279c Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 24 Sep 2020 10:58:08 +0200 Subject: [PATCH 19/49] config.h only in .ino --- DCC.h | 1 - 1 file changed, 1 deletion(-) diff --git a/DCC.h b/DCC.h index 0ba7a5f..76aca0e 100644 --- a/DCC.h +++ b/DCC.h @@ -19,7 +19,6 @@ #ifndef DCC_h #define DCC_h #include -#include "config.h" #include "MotorDriver.h" #include "MotorDrivers.h" From 812cee364fd91a14f9b56be3eb9f243ae04aa173 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 24 Sep 2020 11:06:57 +0200 Subject: [PATCH 20/49] compiler warning --- DCC.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCC.cpp b/DCC.cpp index 5749afe..66319aa 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -44,7 +44,7 @@ const byte FN_GROUP_5=0x10; __FlashStringHelper* DCC::shieldName=NULL; void DCC::begin(const __FlashStringHelper* motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver, byte timerNumber) { - shieldName=motorShieldName; + shieldName=(__FlashStringHelper*)motorShieldName; DCCWaveform::begin(mainDriver,progDriver, timerNumber); } From 9c5396bbc2b669285f5b142ed8976d6ae358749c Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 24 Sep 2020 11:27:21 +0200 Subject: [PATCH 21/49] not worth the hassle --- DCC.h | 2 +- config.example.h | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/DCC.h b/DCC.h index 1254998..9c5be1e 100644 --- a/DCC.h +++ b/DCC.h @@ -50,7 +50,7 @@ SKIPTARGET=0xFF // jump to target // Allocations with memory implications..! // Base system takes approx 900 bytes + 8 per loco. Turnouts, Sensors etc are dynamically created #ifdef ARDUINO_AVR_UNO - const byte MAX_LOCOS=MAX_MAIN_REGISTERS; + const byte MAX_LOCOS=20; #else const byte MAX_LOCOS=50; #endif diff --git a/config.example.h b/config.example.h index 43bcb2a..42b87d8 100644 --- a/config.example.h +++ b/config.example.h @@ -32,15 +32,6 @@ The configuration file for DCC++ EX Command Station #define TRIP_CURRENT_PROG 250 - -///////////////////////////////////////////////////////////////////////////////////// -// -// DEFINE NUMBER OF MAIN TRACK REGISTER -// THIS ONLY MATTERS ON THE UNO (and compatible) WHERE SPACE IS TIGHT -// THIS IS IGNORED ON OTHER ARDUINOS WHERE THAT IS 50 - -#define MAX_MAIN_REGISTERS 22 - ///////////////////////////////////////////////////////////////////////////////////// // // NOTE: Only the Mega currently supports WiFi since space is a constraint on the Uno From 10495957ddcc07a75be910f21314d4aa9acc7511 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 24 Sep 2020 11:59:01 +0200 Subject: [PATCH 22/49] remove things that do not need to be configurable --- CommandStation-EX.ino | 6 +++++- config.example.h | 46 ++++++++++++++----------------------------- 2 files changed, 20 insertions(+), 32 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index fc92ef5..551c44f 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -12,6 +12,10 @@ // REFER TO SEPARATE EXAMPLE. //////////////////////////////////////////////////////////////////////////////////// +// This defines the speed at which the Arduino will communicate with the ESP8266 module. +// Currently only Arduino Mega and 115200 is supported. +#define WIFI_SERIAL_LINK_SPEED 115200 + #include "config.h" #include "DCC.h" #include "DIAG.h" @@ -162,7 +166,7 @@ void setup() const __FlashStringHelper *wifiESSID = F(WIFI_SSID); const __FlashStringHelper *wifiPassword = F(WIFI_PASSWORD); const __FlashStringHelper *dccex = F(WIFI_HOSTNAME); - const uint16_t port = WIFI_PORT; + const uint16_t port = IP_PORT; Serial1.begin(WIFI_SERIAL_LINK_SPEED); wifiUp = WifiInterface::setup(Serial1, wifiESSID, wifiPassword, dccex, port); diff --git a/config.example.h b/config.example.h index 42b87d8..33845f4 100644 --- a/config.example.h +++ b/config.example.h @@ -34,32 +34,24 @@ The configuration file for DCC++ EX Command Station ///////////////////////////////////////////////////////////////////////////////////// // -// NOTE: Only the Mega currently supports WiFi since space is a constraint on the Uno -// at this time. It may be implemented in the future. -// -// +// The IP port to talk to a WIFI or Ethernet shield. // -// Enable Use of WiFI for the Command Station for DCC-EX communications +#define IP_PORT 2560 -#define ENABLE_WIFI false +///////////////////////////////////////////////////////////////////////////////////// +// +// NOTE: Only supported on Arduino Mega +// Set to false if you not even want it on the Arduino Mega +// +#define ENABLE_WIFI true -#if ENABLE_WIFI - ///////////////////////////////////////////////////////////////////////////////////// - // - // DEFINE WiFi Parameters - // - #define WIFI_SSID "Your network name" - #define WIFI_PASSWORD "Your network passwd" - #define WIFI_HOSTNAME "" - #define WIFI_PORT 3252 - - - // This defines the speed at which the Arduino will communicate with the ESP8266 module. - // When using the ESP8266 on an Uno it is recommended to use 9600, for Mega2560 the - // higher speed can be used. Set this based on your ESP8266 module configuration. - // Common defaults are 9600 or 115200. - #define WIFI_SERIAL_LINK_SPEED 115200 -#endif +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE WiFi Parameters (only in effect if WIFI is on) +// +#define WIFI_SSID "Your network name" +#define WIFI_PASSWORD "Your network passwd" +#define WIFI_HOSTNAME "dccex" ///////////////////////////////////////////////////////////////////////////////////// // @@ -68,14 +60,6 @@ The configuration file for DCC++ EX Command Station //#define IP_ADDRESS { 192, 168, 1, 200 } -///////////////////////////////////////////////////////////////////////////////////// -// -// DEFINE PORT TO USE FOR ETHERNET COMMUNICATIONS INTERFACE -// -// Uncomment to use Ethernet - -// #define ETHERNET_PORT 2560 - ///////////////////////////////////////////////////////////////////////////////////// // // DEFINE MAC ADDRESS ARRAY FOR ETHERNET COMMUNICATIONS INTERFACE From b6b8bfb04dbe1101bbd2ecc48722dd1abe649428 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 25 Sep 2020 12:37:30 +0100 Subject: [PATCH 23/49] Fix Auto-poweron in blocking mode --- DCC.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCC.cpp b/DCC.cpp index 66319aa..ebf51de 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -574,7 +574,7 @@ void DCC::ackManagerLoop(bool blocking) { DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); DCCWaveform::progTrack.sentResetsSincePacket = 0; DCCWaveform::progTrack.autoPowerOff=true; - return; + if (!blocking) return; } if (checkResets(blocking, DCCWaveform::progTrack.autoPowerOff ? 20 : 3)) return; DCCWaveform::progTrack.setAckBaseline(); From bc8b01fbd687bd02478028df74226d59de1beeba Mon Sep 17 00:00:00 2001 From: dexslab Date: Fri, 25 Sep 2020 13:51:08 -0400 Subject: [PATCH 24/49] Minor fixes start Uno Wifi Rev 2 fixes --- CommandStation-EX.ino | 46 +- DCC.h | 195 ++++----- DCCEXParser.cpp | 969 +++++++++++++++++++++++------------------- MemStream.h | 29 +- MotorDrivers.h | 40 +- Timer.cpp | 9 +- WifiInterface.cpp | 2 + WifiInterface.h | 54 +-- 8 files changed, 731 insertions(+), 613 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 551c44f..21487f9 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -29,16 +29,23 @@ int ramLowWatermark = 32767; // This figure gets overwritten dynamically in loop() #endif +#if defined(ARDUINO_ARCH_MEGAAVR) +#include +#endif + + + + //////////////////////////////////////////////////////////////// // // Enables an I2C 2x24 or 4x24 LCD Screen -#ifdef ENABLE_LCD +#if ENABLE_LCD bool lcdEnabled = false; - #if defined(LIB_TYPE_PCF8574) - LiquidCrystal_PCF8574 lcdDisplay(LCD_ADDRESS); - #elif defined(LIB_TYPE_I2C) - LiquidCrystal_I2C lcdDisplay = LiquidCrystal_I2C(LCD_ADDRESS, LCD_COLUMNS, LCD_LINES); - #endif +#if defined(LIB_TYPE_PCF8574) +LiquidCrystal_PCF8574 lcdDisplay(LCD_ADDRESS); +#elif defined(LIB_TYPE_I2C) +LiquidCrystal_I2C lcdDisplay = LiquidCrystal_I2C(LCD_ADDRESS, LCD_COLUMNS, LCD_LINES); +#endif #endif // this code is here to demonstrate use of the DCC API and other techniques @@ -109,15 +116,16 @@ DCCEXParser serialParser; void setup() { - //////////////////////////////////////////// - // - // More display stuff. Need to put this in a .h file and make - // it a class - #ifdef ENABLE_LCD +//////////////////////////////////////////// +// +// More display stuff. Need to put this in a .h file and make +// it a class +#if ENABLE_LCD Wire.begin(); // Check that we can find the LCD by its address before attempting to use it. Wire.beginTransmission(LCD_ADDRESS); - if(Wire.endTransmission() == 0) { + if (Wire.endTransmission() == 0) + { lcdEnabled = true; lcdDisplay.begin(LCD_COLUMNS, LCD_LINES); lcdDisplay.setBacklight(255); @@ -126,15 +134,15 @@ void setup() lcdDisplay.print("DCC++ EX v"); lcdDisplay.print(VERSION); lcdDisplay.setCursor(0, 1); - #if COMM_INTERFACE >= 1 +#if COMM_INTERFACE >= 1 lcdDisplay.print("IP: PENDING"); - #else +#else lcdDisplay.print("SERIAL: READY"); - #endif - #if LCD_LINES > 2 - lcdDisplay.setCursor(0, 3); - lcdDisplay.print("TRACK POWER: OFF"); - #endif +#endif +#if LCD_LINES > 2 + lcdDisplay.setCursor(0, 3); + lcdDisplay.print("TRACK POWER: OFF"); +#endif } #endif diff --git a/DCC.h b/DCC.h index 9c5be1e..1e1058b 100644 --- a/DCC.h +++ b/DCC.h @@ -24,149 +24,150 @@ typedef void (*ACK_CALLBACK)(int result); -enum ackOp { // Program opcodes for the ack Manager -BASELINE, // ensure enough resets sent before starting and obtain baseline current -W0,W1, // issue write bit (0..1) packet -WB, // issue write byte packet -VB, // Issue validate Byte packet -V0, // Issue validate bit=0 packet -V1, // issue validate bit=1 packlet -WACK, // wait for ack (or absence of ack) -ITC1, // If True Callback(1) (if prevous WACK got an ACK) -ITC0, // If True callback(0); -ITCB, // If True callback(byte) -NAKFAIL, // if false callback(-1) -FAIL, // callback(-1) -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) -SETBIT, // sets bit number to next prog byte -SETCV, // sets cv number to next prog byte -STASHLOCOID, // keeps current byte value for later -COMBINELOCOID, // combines current value with stashed value and returns it -ITSKIP, // skip to SKIPTARGET if ack true -SKIPTARGET=0xFF // jump to target +enum ackOp +{ // Program opcodes for the ack Manager + BASELINE, // ensure enough resets sent before starting and obtain baseline current + W0, + W1, // issue write bit (0..1) packet + WB, // issue write byte packet + VB, // Issue validate Byte packet + V0, // Issue validate bit=0 packet + V1, // issue validate bit=1 packlet + WACK, // wait for ack (or absence of ack) + ITC1, // If True Callback(1) (if prevous WACK got an ACK) + ITC0, // If True callback(0); + ITCB, // If True callback(byte) + NAKFAIL, // if false callback(-1) + FAIL, // callback(-1) + 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) + SETBIT, // sets bit number to next prog byte + SETCV, // sets cv number to next prog byte + STASHLOCOID, // keeps current byte value for later + COMBINELOCOID, // combines current value with stashed value and returns it + ITSKIP, // skip to SKIPTARGET if ack true + SKIPTARGET = 0xFF // jump to target }; // Allocations with memory implications..! // Base system takes approx 900 bytes + 8 per loco. Turnouts, Sensors etc are dynamically created - #ifdef ARDUINO_AVR_UNO - const byte MAX_LOCOS=20; - #else - const byte MAX_LOCOS=50; - #endif +#ifdef ARDUINO_AVR_UNO +const byte MAX_LOCOS = 20; +#else +const byte MAX_LOCOS = 50; +#endif - -class DCC { - public: - - static void begin(const __FlashStringHelper* motorShieldName, MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber=1); +class DCC +{ +public: + static void begin(const __FlashStringHelper *motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver, byte timerNumber = 1); static void loop(); // Public DCC API functions - static void setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection); + static void setThrottle(uint16_t cab, uint8_t tSpeed, bool tDirection); static uint8_t getThrottleSpeed(int cab); static bool getThrottleDirection(int cab); static void writeCVByteMain(int cab, int cv, byte bValue); static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue); - static void setFunction( int cab, byte fByte, byte eByte); - static void setFn( int cab, byte functionNumber, bool on); - static int changeFn( int cab, byte functionNumber, bool pressed); - static void updateGroupflags(byte & flags, int functionNumber); - static void setAccessory(int aAdd, byte aNum, bool activate) ; - static bool writeTextPacket( byte *b, int nBytes); - static void setProgTrackSyncMain(bool on); // when true, prog track becomes driveable - - // ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1 - static void readCV(int cv, ACK_CALLBACK callback, bool blocking=false); - static void readCVBit(int cv, byte bitNum, ACK_CALLBACK callback, bool blocking=false); // -1 for error - static void writeCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking=false) ; - static void writeCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking=false); - static void verifyCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking=false) ; - static void verifyCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking=false); - - static void getLocoId(ACK_CALLBACK callback, bool blocking=false); + static void setFunction(int cab, byte fByte, byte eByte); + static void setFn(int cab, byte functionNumber, bool on); + static int changeFn(int cab, byte functionNumber, bool pressed); + static void updateGroupflags(byte &flags, int functionNumber); + static void setAccessory(int aAdd, byte aNum, bool activate); + static bool writeTextPacket(byte *b, int nBytes); + static void setProgTrackSyncMain(bool on); // when true, prog track becomes driveable + + // ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1 + static void readCV(int cv, ACK_CALLBACK callback, bool blocking = false); + static void readCVBit(int cv, byte bitNum, ACK_CALLBACK callback, bool blocking = false); // -1 for error + static void writeCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking = false); + static void writeCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking = false); + static void verifyCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking = false); + static void verifyCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking = false); + + static void getLocoId(ACK_CALLBACK callback, bool blocking = false); // Enhanced API functions - static void forgetLoco(int cab); // removes any speed reminders for this loco - static void forgetAllLocos(); // removes all speed reminders - static void displayCabList(Print * stream); + static void forgetLoco(int cab); // removes any speed reminders for this loco + static void forgetAllLocos(); // removes all speed reminders + static void displayCabList(Print *stream); - static __FlashStringHelper* getMotorShieldName(); - -private: - struct LOCO { - int loco; - byte speedCode; - byte groupFlags; - unsigned long functions; + static __FlashStringHelper *getMotorShieldName(); + +private: + struct LOCO + { + int loco; + byte speedCode; + byte groupFlags; + unsigned long functions; }; static byte loopStatus; - static void setThrottle2( uint16_t cab, uint8_t speedCode); + static void setThrottle2(uint16_t cab, uint8_t speedCode); static void updateLocoReminder(int loco, byte speedCode); - static void setFunctionInternal( int cab, byte fByte, byte eByte); + static void setFunctionInternal(int cab, byte fByte, byte eByte); static bool issueReminder(int reg); static int nextLoco; - static __FlashStringHelper* shieldName; - + static __FlashStringHelper *shieldName; + static LOCO speedTable[MAX_LOCOS]; static byte cv1(byte opcode, int cv); static byte cv2(int cv); static int lookupSpeedTable(int locoId); static void issueReminders(); static void callback(int value); - -// ACK MANAGER - static ackOp const * ackManagerProg; - static byte ackManagerByte; - static byte ackManagerBitNum; - static int ackManagerCv; - static byte ackManagerStash; + + // ACK MANAGER + static ackOp const *ackManagerProg; + static byte ackManagerByte; + static byte ackManagerBitNum; + static int ackManagerCv; + static byte ackManagerStash; static bool ackReceived; - static ACK_CALLBACK ackManagerCallback; + static ACK_CALLBACK ackManagerCallback; static void ackManagerSetup(int cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback, bool blocking); static void ackManagerLoop(bool blocking); static bool checkResets(bool blocking, uint8_t numResets); - static const int PROG_REPEATS=8; // repeats of programming commands (some decoders need at least 8 to be reliable) - - + static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable) // NMRA codes # - static const byte SET_SPEED=0x3f; + static const byte SET_SPEED = 0x3f; static const byte WRITE_BYTE_MAIN = 0xEC; static const byte WRITE_BIT_MAIN = 0xE8; static const byte WRITE_BYTE = 0x7C; - static const byte VERIFY_BYTE= 0x74; - static const byte BIT_MANIPULATE=0x78; - static const byte WRITE_BIT=0xF0; - static const byte VERIFY_BIT=0xE0; - static const byte BIT_ON=0x08; - static const byte BIT_OFF=0x00; + static const byte VERIFY_BYTE = 0x74; + static const byte BIT_MANIPULATE = 0x78; + static const byte WRITE_BIT = 0xF0; + static const byte VERIFY_BIT = 0xE0; + static const byte BIT_ON = 0x08; + static const byte BIT_OFF = 0x00; }; -#ifdef ARDUINO_AVR_MEGA // is using Mega 1280, define as Mega 2560 (pinouts and functionality are identical) - #define ARDUINO_AVR_MEGA2560 +#ifdef ARDUINO_AVR_MEGA // is using Mega 1280, define as Mega 2560 (pinouts and functionality are identical) +#define ARDUINO_AVR_MEGA2560 #endif #if defined(ARDUINO_AVR_UNO) - #define ARDUINO_TYPE "UNO" +#define ARDUINO_TYPE "UNO" #elif defined(ARDUINO_AVR_NANO) - #define ARDUINO_TYPE "NANO" +#define ARDUINO_TYPE "NANO" #elif defined(ARDUINO_AVR_MEGA2560) - #define ARDUINO_TYPE "MEGA" +#define ARDUINO_TYPE "MEGA" +#elif defined(ARDUINO_ARCH_MEGAAVR) +#define ARDUINO_TYPE "UNOWIFIR2" #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 - -#ifdef ENABLE_LCD +#if ENABLE_LCD #include - #if defined(LIB_TYPE_PCF8574) - #include - extern LiquidCrystal_PCF8574 lcdDisplay; - #elif defined(LIB_TYPE_I2C) - #include - extern LiquidCrystal_I2C lcdDisplay; - #endif +#if defined(LIB_TYPE_PCF8574) +#include +extern LiquidCrystal_PCF8574 lcdDisplay; +#elif defined(LIB_TYPE_I2C) +#include +extern LiquidCrystal_I2C lcdDisplay; +#endif extern bool lcdEnabled; #endif diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index e10e028..8b26da7 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -20,7 +20,9 @@ #include "DCCEXParser.h" #include "DCC.h" #include "DCCWaveform.h" +#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) #include "WifiInterface.h" +#endif #include "Turnouts.h" #include "Outputs.h" #include "Sensors.h" @@ -31,524 +33,611 @@ #include "EEStore.h" #include "DIAG.h" - // These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter. // To discover new keyword numbers , use the <$ YOURKEYWORD> command -const int HASH_KEYWORD_PROG=-29718; -const int HASH_KEYWORD_MAIN=11339; -const int HASH_KEYWORD_JOIN=-30750; -const int HASH_KEYWORD_CABS=-11981; -const int HASH_KEYWORD_RAM=25982; -const int HASH_KEYWORD_CMD=9962; -const int HASH_KEYWORD_WIT=31594; -const int HASH_KEYWORD_WIFI=-5583; -const int HASH_KEYWORD_ACK=3113; -const int HASH_KEYWORD_ON=2657; -const int HASH_KEYWORD_DCC=6436; -const int HASH_KEYWORD_SLOW=-17209; - +const int HASH_KEYWORD_PROG = -29718; +const int HASH_KEYWORD_MAIN = 11339; +const int HASH_KEYWORD_JOIN = -30750; +const int HASH_KEYWORD_CABS = -11981; +const int HASH_KEYWORD_RAM = 25982; +const int HASH_KEYWORD_CMD = 9962; +const int HASH_KEYWORD_WIT = 31594; +const int HASH_KEYWORD_WIFI = -5583; +const int HASH_KEYWORD_ACK = 3113; +const int HASH_KEYWORD_ON = 2657; +const int HASH_KEYWORD_DCC = 6436; +const int HASH_KEYWORD_SLOW = -17209; int DCCEXParser::stashP[MAX_PARAMS]; bool DCCEXParser::stashBusy; - - Print * DCCEXParser::stashStream=NULL; + +Print *DCCEXParser::stashStream = NULL; // This is a JMRI command parser, one instance per incoming stream // It doesnt know how the string got here, nor how it gets back. // It knows nothing about hardware or tracks... it just parses strings and // 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("\nBuffer flush")); - bufferLength=0; - inCommandPayload=false; +void DCCEXParser::flush() +{ + if (Diag::CMD) + DIAG(F("\nBuffer flush")); + bufferLength = 0; + inCommandPayload = false; } -void DCCEXParser::loop(Stream & stream) { - while(stream.available()) { - if (bufferLength==MAX_BUFFER) { - flush(); +void DCCEXParser::loop(Stream &stream) +{ + 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, false); // Parse this allowing async responses + inCommandPayload = false; + break; + } + else if (inCommandPayload) + { + buffer[bufferLength++] = ch; + } } - char ch = stream.read(); - if (ch == '<') { - inCommandPayload = true; - bufferLength=0; - buffer[0]='\0'; - } - else if (ch == '>') { - buffer[bufferLength]='\0'; - parse( & stream, buffer, false); // Parse this allowing async responses - inCommandPayload = false; - break; - } else if(inCommandPayload) { - buffer[bufferLength++]= ch; +} + +int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd) +{ + byte state = 1; + byte parameterCount = 0; + int runningValue = 0; + const byte *remainingCmd = cmd + 1; // skips the opcode + bool signNegative = false; + + // clear all parameters in case not enough found + for (int i = 0; i < MAX_PARAMS; i++) + result[i] = 0; + + while (parameterCount < MAX_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 sign + signNegative = false; + runningValue = 0; + state = 3; + if (hot != '-') + continue; + signNegative = true; + break; + case 3: // building a parameter + if (hot >= '0' && hot <= '9') + { + runningValue = 10 * runningValue + (hot - '0'); + break; + } + if (hot >= 'A' && hot <= 'Z') + { + // Since JMRI got modified to send keywords in some rare cases, we need this + // Super Kluge to turn keywords into a hash value that can be recognised later + runningValue = ((runningValue << 5) + runningValue) ^ hot; + break; + } + result[parameterCount] = runningValue * (signNegative ? -1 : 1); + parameterCount++; + state = 1; + continue; + } + remainingCmd++; } - } - } - - int DCCEXParser::splitValues( int result[MAX_PARAMS], const byte * cmd) { - byte state=1; - byte parameterCount=0; - int runningValue=0; - const byte * remainingCmd=cmd+1; // skips the opcode - bool signNegative=false; - - // clear all parameters in case not enough found - for (int i=0;i') return parameterCount; - state=2; - continue; - - case 2: // checking sign - signNegative=false; - runningValue=0; - state=3; - if (hot!='-') continue; - signNegative=true; - break; - case 3: // building a parameter - if (hot>='0' && hot<='9') { - runningValue=10*runningValue+(hot-'0'); - break; - } - if (hot>='A' && hot<='Z') { - // Since JMRI got modified to send keywords in some rare cases, we need this - // Super Kluge to turn keywords into a hash value that can be recognised later - runningValue = ((runningValue << 5) + runningValue) ^ hot; - break; - } - result[parameterCount] = runningValue * (signNegative ?-1:1); - parameterCount++; - state=1; - continue; - } - remainingCmd++; - } - return parameterCount; + return parameterCount; } -FILTER_CALLBACK DCCEXParser::filterCallback=0; -void DCCEXParser::setFilter(FILTER_CALLBACK filter) { - filterCallback=filter; +FILTER_CALLBACK DCCEXParser::filterCallback = 0; +void DCCEXParser::setFilter(FILTER_CALLBACK filter) +{ + filterCallback = filter; } - + // See documentation on DCC class for info on this section -void DCCEXParser::parse(Print * stream, byte *com, bool blocking) { - if (Diag::CMD) DIAG(F("\nPARSING:%s\n"),com); - (void) EEPROM; // tell compiler not to warn thi is unused - int p[MAX_PARAMS]; - while (com[0]=='<' || com[0]==' ') com++; // strip off any number of < or spaces - byte params=splitValues(p, com); - byte opcode=com[0]; - - if (filterCallback) filterCallback(stream,opcode,params,p); - +void DCCEXParser::parse(Print *stream, byte *com, bool blocking) +{ + if (Diag::CMD) + DIAG(F("\nPARSING:%s\n"), com); + (void)EEPROM; // tell compiler not to warn thi is unused + int p[MAX_PARAMS]; + while (com[0] == '<' || com[0] == ' ') + com++; // strip off any number of < or spaces + byte params = splitValues(p, com); + byte opcode = com[0]; + + if (filterCallback) + filterCallback(stream, opcode, params, p); + // Functions return from this switch if complete, break from switch implies error to send - switch(opcode) { - case '\0': return; // filterCallback asked us to ignore - case 't': // THROTTLE + switch (opcode) + { + case '\0': + return; // filterCallback asked us to ignore + case 't': // THROTTLE + { + int cab; + int tspeed; + int direction; + + if (params == 4) + { // + cab = p[1]; + tspeed = p[2]; + direction = p[3]; + } + else if (params == 3) + { // + cab = p[0]; + tspeed = p[1]; + direction = p[2]; + } + else + break; + + // Convert JMRI bizarre -1=emergency stop, 0-126 as speeds + // to DCC 0=stop, 1= emergency stop, 2-127 speeds + if (tspeed > 126 || tspeed < -1) + break; // invalid JMRI speed code + if (tspeed < 0) + tspeed = 1; // emergency stop DCC speed + else if (tspeed > 0) + tspeed++; // map 1-126 -> 2-127 + if (cab == 0 && tspeed > 1) + break; // ignore broadcasts of speed>1 + + if (direction < 0 || direction > 1) + break; // invalid direction code + + DCC::setThrottle(cab, tspeed, direction); + if (params == 4) + StringFormatter::send(stream, F(""), p[0], p[2], p[3]); + else + StringFormatter::send(stream, F("")); + return; + } + case 'f': // FUNCTION + if (parsef(stream, params, p)) + return; + break; + + case 'a': // ACCESSORY + if (p[2] != (p[2] & 1)) + return; + DCC::setAccessory(p[0], p[1], p[2] == 1); + return; + + case 'T': // TURNOUT + if (parseT(stream, params, p)) + return; + break; + + case 'Z': // OUTPUT + if (parseZ(stream, params, p)) + return; + break; + + case 'S': // SENSOR + if (parseS(stream, params, p)) + return; + break; + + case 'w': // WRITE CV on MAIN + DCC::writeCVByteMain(p[0], p[1], p[2]); + return; + + case 'b': // WRITE CV BIT ON MAIN + DCC::writeCVBitMain(p[0], p[1], p[2], p[3]); + return; + + case 'W': // WRITE CV ON PROG + if (!stashCallback(stream, p)) + break; + DCC::writeCVByte(p[0], p[1], callback_W, blocking); + return; + + case 'V': // VERIFY CV ON PROG + if (params == 2) + { // + if (!stashCallback(stream, p)) + break; + DCC::verifyCVByte(p[0], p[1], callback_Vbyte, blocking); + return; + } + if (params == 3) { - int cab; - int tspeed; - int direction; - - if (params==4) { // - cab=p[1]; - tspeed=p[2]; - direction=p[3]; - } - else if (params==3) { // - cab=p[0]; - tspeed=p[1]; - direction=p[2]; - } - else break; - - // Convert JMRI bizarre -1=emergency stop, 0-126 as speeds - // to DCC 0=stop, 1= emergency stop, 2-127 speeds - if (tspeed>126 || tspeed<-1) break; // invalid JMRI speed code - if (tspeed<0) tspeed=1; // emergency stop DCC speed - else if (tspeed>0) tspeed++; // map 1-126 -> 2-127 - if (cab == 0 && tspeed>1) break; // ignore broadcasts of speed>1 - - if (direction<0 || direction>1) break; // invalid direction code - - DCC::setThrottle(cab,tspeed,direction); - if (params==4) StringFormatter::send(stream,F(""), p[0], p[2],p[3]); - else StringFormatter::send(stream,F("")); - return; - } - case 'f': // FUNCTION - if (parsef(stream,params,p)) return; - break; - - case 'a': // ACCESSORY - if(p[2] != (p[2] & 1)) return; - DCC::setAccessory(p[0],p[1],p[2]==1); - return; - - case 'T': // TURNOUT - if (parseT(stream,params,p)) return; - break; - - case 'Z': // OUTPUT - if (parseZ(stream,params,p)) return; - break; - - case 'S': // SENSOR - if (parseS(stream,params,p)) return; - break; - - case 'w': // WRITE CV on MAIN - DCC::writeCVByteMain(p[0],p[1],p[2]); - return; - - case 'b': // WRITE CV BIT ON MAIN - DCC::writeCVBitMain(p[0],p[1],p[2],p[3]); - return; - - case 'W': // WRITE CV ON PROG - if (!stashCallback(stream,p)) break; - DCC::writeCVByte(p[0],p[1],callback_W,blocking); - return; - - case 'V': // VERIFY CV ON PROG - if (params==2) { // - if (!stashCallback(stream,p)) break; - DCC::verifyCVByte(p[0],p[1],callback_Vbyte,blocking); - return; - } - if (params==3) { - if (!stashCallback(stream,p)) break; - DCC::verifyCVBit(p[0],p[1],p[2],callback_Vbit,blocking); - return; + if (!stashCallback(stream, p)) + break; + DCC::verifyCVBit(p[0], p[1], p[2], callback_Vbit, blocking); + return; } break; - - case 'B': // WRITE CV BIT ON PROG - if (!stashCallback(stream,p)) break; - DCC::writeCVBit(p[0],p[1],p[2],callback_B,blocking); + + case 'B': // WRITE CV BIT ON PROG + if (!stashCallback(stream, p)) + break; + DCC::writeCVBit(p[0], p[1], p[2], callback_B, blocking); return; - - - case 'R': // READ CV ON PROG - if (params==3) { // - if (!stashCallback(stream,p)) break; - DCC::readCV(p[0],callback_R,blocking); - return; + + case 'R': // READ CV ON PROG + if (params == 3) + { // + if (!stashCallback(stream, p)) + break; + DCC::readCV(p[0], callback_R, blocking); + return; } - if (params==0) { // New read loco id - if (!stashCallback(stream,p)) break; - DCC::getLocoId(callback_Rloco,blocking); - return; + if (params == 0) + { // New read loco id + if (!stashCallback(stream, p)) + break; + DCC::getLocoId(callback_Rloco, blocking); + return; } break; - - case '1': // POWERON <1 [MAIN|PROG]> - case '0': // POWEROFF <0 [MAIN | PROG] > - if (params>1) break; + + case '1': // POWERON <1 [MAIN|PROG]> + case '0': // POWEROFF <0 [MAIN | PROG] > + if (params > 1) + break; { - POWERMODE mode= opcode=='1'?POWERMODE::ON:POWERMODE::OFF; - DCC::setProgTrackSyncMain(false); // Only <1 JOIN> will set this on, all others set it off - if (params==0) { - DCCWaveform::mainTrack.setPowerMode(mode); - DCCWaveform::progTrack.setPowerMode(mode); - StringFormatter::send(stream,F(""),opcode); - return; - } - switch (p[0]) { - case HASH_KEYWORD_MAIN: - DCCWaveform::mainTrack.setPowerMode(mode); - StringFormatter::send(stream,F(""),opcode); - return; - - case HASH_KEYWORD_PROG: - DCCWaveform::progTrack.setPowerMode(mode); - StringFormatter::send(stream,F(""),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(""),opcode); - } - else StringFormatter::send(stream,F("")); - return; - - } - break; - } - return; - - case 'c': // READ CURRENT - StringFormatter::send(stream,F(""), DCCWaveform::mainTrack.getLastCurrent()); - return; + POWERMODE mode = opcode == '1' ? POWERMODE::ON : POWERMODE::OFF; + DCC::setProgTrackSyncMain(false); // Only <1 JOIN> will set this on, all others set it off + if (params == 0) + { + DCCWaveform::mainTrack.setPowerMode(mode); + DCCWaveform::progTrack.setPowerMode(mode); + StringFormatter::send(stream, F(""), opcode); + return; + } + switch (p[0]) + { + case HASH_KEYWORD_MAIN: + DCCWaveform::mainTrack.setPowerMode(mode); + StringFormatter::send(stream, F(""), opcode); + return; - case 'Q': // SENSORS - Sensor::checkAll(); - for(Sensor * tt=Sensor::firstSensor;tt!=NULL;tt=tt->nextSensor){ - StringFormatter::send(stream,F("<%c %d>"), tt->active?'Q':'q', tt->data.snum); + case HASH_KEYWORD_PROG: + DCCWaveform::progTrack.setPowerMode(mode); + StringFormatter::send(stream, F(""), 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(""), opcode); + } + else + StringFormatter::send(stream, F("")); + return; + } + break; } return; - case 's': // - StringFormatter::send(stream,F(""),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON ); - StringFormatter::send(stream,F(""), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA)); - // TODO Send stats of speed reminders table - // TODO send status of turnouts etc etc + case 'c': // READ CURRENT + StringFormatter::send(stream, F(""), DCCWaveform::mainTrack.getLastCurrent()); return; - case 'E': // STORE EPROM + case 'Q': // SENSORS + Sensor::checkAll(); + for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor) + { + StringFormatter::send(stream, F("<%c %d>"), tt->active ? 'Q' : 'q', tt->data.snum); + } + return; + + case 's': // + StringFormatter::send(stream, F(""), DCCWaveform::mainTrack.getPowerMode() == POWERMODE::ON); + StringFormatter::send(stream, F(""), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA)); + // TODO Send stats of speed reminders table + // TODO send status of turnouts etc etc + return; + + case 'E': // STORE EPROM EEStore::store(); - StringFormatter::send(stream,F(""), EEStore::eeStore->data.nTurnouts, EEStore::eeStore->data.nSensors, EEStore::eeStore->data.nOutputs); + StringFormatter::send(stream, F(""), EEStore::eeStore->data.nTurnouts, EEStore::eeStore->data.nSensors, EEStore::eeStore->data.nOutputs); return; - case 'e': // CLEAR EPROM + case 'e': // CLEAR EPROM EEStore::clear(); StringFormatter::send(stream, F("")); return; - case ' ': // < > - StringFormatter::send(stream,F("\n")); - return; - - case 'D': // < > - if (parseD(stream,params,p)) return; + case ' ': // < > + StringFormatter::send(stream, F("\n")); return; - case '#': // NUMBER OF LOCOSLOTS <#> - StringFormatter::send(stream,F("<# %d>"), MAX_LOCOS); - return; + case 'D': // < > + if (parseD(stream, params, p)) + return; + return; + + case '#': // NUMBER OF LOCOSLOTS <#> + StringFormatter::send(stream, F("<# %d>"), MAX_LOCOS); + return; case 'F': // New command to call the new Loco Function API - if (Diag::CMD) 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); - return; - - case '+' : // Complex Wifi interface command (not usual parse) - WifiInterface::ATCommand(com); - return; - - default: //anything else will diagnose and drop out to - DIAG(F("\nOpcode=%c params=%d\n"),opcode,params); - for (int i=0;i + DIAG(F("\nOpcode=%c params=%d\n"), opcode, params); + for (int i = 0; i < params; i++) + DIAG(F("p[%d]=%d (0x%x)\n"), i, p[i], p[i]); break; - - } // end of opcode switch + + } // end of opcode switch // Any fallout here sends an - StringFormatter::send(stream, F("")); + StringFormatter::send(stream, F("")); } -bool DCCEXParser::parseZ( Print * stream,int params, int p[]){ - - - switch (params) { - - case 2: // - { - Output * o=Output::get(p[0]); - if(o==NULL) return false; - o->activate(p[1]); - StringFormatter::send(stream,F(""), p[0],p[1]); - } - return true; +bool DCCEXParser::parseZ(Print *stream, int params, int p[]) +{ - case 3: // - Output::create(p[0],p[1],p[2],1); - return true; + switch (params) + { - case 1: // - return Output::remove(p[0]); - - case 0: // - { - bool gotone=false; - for(Output * tt=Output::firstOutput;tt!=NULL;tt=tt->nextOutput){ - gotone=true; - StringFormatter::send(stream,F(""), tt->data.id, tt->data.pin, tt->data.iFlag, tt->data.oStatus); - } - return gotone; - } - default: - return false; - } - } - -//=================================== -bool DCCEXParser::parsef(Print * stream, int params, int p[]) { - // JMRI sends this info in DCC message format but it's not exactly - // convenient for other processing - if (params==2) { - byte groupcode=p[1] & 0xE0; - if (groupcode == 0x80) { - byte normalized= (p[1]<<1 & 0x1e ) | (p[1]>>4 & 0x01); - funcmap(p[0],normalized,0,4); - } - else if (groupcode == 0xC0) { - funcmap(p[0],p[1],5,8); - } - else if (groupcode == 0xA0) { - funcmap(p[0],p[1],9,12); - } - } - if (params==3) { - if (p[1]==222) funcmap(p[0],p[2],13,20); - else if (p[1]==223) funcmap(p[0],p[2],21,28); + case 2: // + { + Output *o = Output::get(p[0]); + if (o == NULL) + return false; + o->activate(p[1]); + StringFormatter::send(stream, F(""), p[0], p[1]); } - (void)stream;// NO RESPONSE - return true; -} + return true; -void DCCEXParser::funcmap(int cab, byte value, byte fstart, byte fstop) { - for (int i=fstart;i<=fstop;i++) { - DCC::setFn(cab, i, value & 1); - value>>=1; - } + case 3: // + Output::create(p[0], p[1], p[2], 1); + return true; + + case 1: // + return Output::remove(p[0]); + + case 0: // + { + bool gotone = false; + for (Output *tt = Output::firstOutput; tt != NULL; tt = tt->nextOutput) + { + gotone = true; + StringFormatter::send(stream, F(""), tt->data.id, tt->data.pin, tt->data.iFlag, tt->data.oStatus); + } + return gotone; + } + default: + return false; + } } //=================================== -bool DCCEXParser::parseT(Print * stream, int params, int p[]) { - switch(params){ - case 0: // show all turnouts - { - bool gotOne=false; - for(Turnout *tt=Turnout::firstTurnout;tt!=NULL;tt=tt->nextTurnout){ - gotOne=true; - StringFormatter::send(stream,F(""), tt->data.id, tt->data.tStatus & STATUS_ACTIVE); - } - return gotOne; // will if none found - } - - case 1: // delete turnout - if (!Turnout::remove(p[0])) return false; - StringFormatter::send(stream,F("")); - return true; - - case 2: // activate turnout - { - Turnout* tt=Turnout::get(p[0]); - if (!tt) return false; - tt->activate(p[1]); - StringFormatter::send(stream,F(""), tt->data.id, tt->data.tStatus & STATUS_ACTIVE); - } - return true; - - case 3: // define turnout - if (!Turnout::create(p[0],p[1],p[2])) return false; - StringFormatter::send(stream,F("")); - return true; - - default: - return false; // will +bool DCCEXParser::parsef(Print *stream, int params, int p[]) +{ + // JMRI sends this info in DCC message format but it's not exactly + // convenient for other processing + if (params == 2) + { + byte groupcode = p[1] & 0xE0; + if (groupcode == 0x80) + { + byte normalized = (p[1] << 1 & 0x1e) | (p[1] >> 4 & 0x01); + funcmap(p[0], normalized, 0, 4); } + else if (groupcode == 0xC0) + { + funcmap(p[0], p[1], 5, 8); + } + else if (groupcode == 0xA0) + { + funcmap(p[0], p[1], 9, 12); + } + } + if (params == 3) + { + if (p[1] == 222) + funcmap(p[0], p[2], 13, 20); + else if (p[1] == 223) + funcmap(p[0], p[2], 21, 28); + } + (void)stream; // NO RESPONSE + return true; } -bool DCCEXParser::parseS( Print * stream,int params, int p[]) { - - switch(params){ - case 3: // create sensor. pullUp indicator (0=LOW/1=HIGH) - Sensor::create(p[0],p[1],p[2]); - return true; +void DCCEXParser::funcmap(int cab, byte value, byte fstart, byte fstop) +{ + for (int i = fstart; i <= fstop; i++) + { + DCC::setFn(cab, i, value & 1); + value >>= 1; + } +} - case 1: // S id> remove sensor - if (Sensor::remove(p[0])) return true; - break; - - case 0: // lit sensor states - for(Sensor * tt=Sensor::firstSensor;tt!=NULL;tt=tt->nextSensor){ - StringFormatter::send(stream, F(""), tt->data.snum, tt->data.pin, tt->data.pullUp); - } - return true; - - default: // invalid number of arguments - break; +//=================================== +bool DCCEXParser::parseT(Print *stream, int params, int p[]) +{ + switch (params) + { + case 0: // show all turnouts + { + bool gotOne = false; + for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout) + { + gotOne = true; + StringFormatter::send(stream, F(""), tt->data.id, tt->data.tStatus & STATUS_ACTIVE); } + return gotOne; // will if none found + } + + case 1: // delete turnout + if (!Turnout::remove(p[0])) + return false; + StringFormatter::send(stream, F("")); + return true; + + case 2: // activate turnout + { + Turnout *tt = Turnout::get(p[0]); + if (!tt) + return false; + tt->activate(p[1]); + StringFormatter::send(stream, F(""), tt->data.id, tt->data.tStatus & STATUS_ACTIVE); + } + return true; + + case 3: // define turnout + if (!Turnout::create(p[0], p[1], p[2])) + return false; + StringFormatter::send(stream, F("")); + return true; + + default: + return false; // will + } +} + +bool DCCEXParser::parseS(Print *stream, int params, int p[]) +{ + + switch (params) + { + case 3: // create sensor. pullUp indicator (0=LOW/1=HIGH) + Sensor::create(p[0], p[1], p[2]); + return true; + + case 1: // S id> remove sensor + if (Sensor::remove(p[0])) + return true; + break; + + case 0: // lit sensor states + for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor) + { + StringFormatter::send(stream, F(""), tt->data.snum, tt->data.pin, tt->data.pullUp); + } + return true; + + default: // invalid number of arguments + break; + } return false; } -bool DCCEXParser::parseD( Print * stream,int params, int p[]) { - if (params==0) return false; - bool onOff=(params>0) && (p[1]==1 || p[1]==HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off - switch(p[0]){ - case HASH_KEYWORD_CABS: // - DCC::displayCabList(stream); - return true; +bool DCCEXParser::parseD(Print *stream, int params, int p[]) +{ + if (params == 0) + return false; + bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off + switch (p[0]) + { + case HASH_KEYWORD_CABS: // + DCC::displayCabList(stream); + return true; - case HASH_KEYWORD_RAM: // - StringFormatter::send(stream,F("\nFree memory=%d\n"),freeMemory()); - break; + case HASH_KEYWORD_RAM: // + StringFormatter::send(stream, F("\nFree memory=%d\n"), freeMemory()); + break; - case HASH_KEYWORD_ACK: // - Diag::ACK=onOff; - return true; + case HASH_KEYWORD_ACK: // + Diag::ACK = onOff; + return true; - case HASH_KEYWORD_CMD: // - Diag::CMD=onOff; - return true; + case HASH_KEYWORD_CMD: // + Diag::CMD = onOff; + return true; - case HASH_KEYWORD_WIFI: // - Diag::WIFI=onOff; - return true; + case HASH_KEYWORD_WIFI: // + Diag::WIFI = onOff; + return true; - case HASH_KEYWORD_WIT: // - Diag::WITHROTTLE=onOff; - return true; + case HASH_KEYWORD_WIT: // + Diag::WITHROTTLE = onOff; + return true; - case HASH_KEYWORD_DCC: - DCCWaveform::setDiagnosticSlowWave(params>=1 && p[1]==HASH_KEYWORD_SLOW); - return true; - default: // invalid/unknown - break; - } + case HASH_KEYWORD_DCC: + DCCWaveform::setDiagnosticSlowWave(params >= 1 && p[1] == HASH_KEYWORD_SLOW); + return true; + default: // invalid/unknown + break; + } return false; } - - // CALLBACKS must be static -bool DCCEXParser::stashCallback(Print * stream,int p[MAX_PARAMS]) { - if (stashBusy || asyncBanned) return false; - stashBusy=true; - stashStream=stream; - memcpy(stashP,p,MAX_PARAMS*sizeof(p[0])); - return true; - } - void DCCEXParser::callback_W(int result) { - StringFormatter::send(stashStream,F(""), stashP[2], stashP[3],stashP[0],result==1?stashP[1]:-1); - stashBusy=false; - } - -void DCCEXParser::callback_B(int result) { - StringFormatter::send(stashStream,F(""), stashP[3],stashP[4], stashP[0],stashP[1],result==1?stashP[2]:-1); - stashBusy=false; +// CALLBACKS must be static +bool DCCEXParser::stashCallback(Print *stream, int p[MAX_PARAMS]) +{ + if (stashBusy || asyncBanned) + return false; + stashBusy = true; + stashStream = stream; + memcpy(stashP, p, MAX_PARAMS * sizeof(p[0])); + return true; } -void DCCEXParser::callback_Vbit(int result) { - StringFormatter::send(stashStream,F(""), stashP[0], stashP[1],result); - stashBusy=false; -} -void DCCEXParser::callback_Vbyte(int result) { - StringFormatter::send(stashStream,F(""), stashP[0],result); - stashBusy=false; +void DCCEXParser::callback_W(int result) +{ + StringFormatter::send(stashStream, F(""), stashP[2], stashP[3], stashP[0], result == 1 ? stashP[1] : -1); + stashBusy = false; } -void DCCEXParser::callback_R(int result) { - StringFormatter::send(stashStream,F(""),stashP[1],stashP[2],stashP[0],result); - stashBusy=false; +void DCCEXParser::callback_B(int result) +{ + StringFormatter::send(stashStream, F(""), stashP[3], stashP[4], stashP[0], stashP[1], result == 1 ? stashP[2] : -1); + stashBusy = false; +} +void DCCEXParser::callback_Vbit(int result) +{ + StringFormatter::send(stashStream, F(""), stashP[0], stashP[1], result); + stashBusy = false; +} +void DCCEXParser::callback_Vbyte(int result) +{ + StringFormatter::send(stashStream, F(""), stashP[0], result); + stashBusy = false; } -void DCCEXParser::callback_Rloco(int result) { - StringFormatter::send(stashStream,F(""),result); - stashBusy=false; +void DCCEXParser::callback_R(int result) +{ + StringFormatter::send(stashStream, F(""), stashP[1], stashP[2], stashP[0], result); + stashBusy = false; +} + +void DCCEXParser::callback_Rloco(int result) +{ + StringFormatter::send(stashStream, F(""), result); + stashBusy = false; } - diff --git a/MemStream.h b/MemStream.h index 444864f..61ef6bb 100644 --- a/MemStream.h +++ b/MemStream.h @@ -24,34 +24,43 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define MemStream_h #include +#if defined(ARDUINO_ARCH_MEGAAVR) +#include +#else #include +#endif + #include class MemStream : public Stream { private: - uint8_t * _buffer; - const uint16_t _len; - bool _buffer_overflow; - uint16_t _pos_read; - uint16_t _pos_write; - bool _allowWrite; - + uint8_t *_buffer; + const uint16_t _len; + bool _buffer_overflow; + uint16_t _pos_read; + uint16_t _pos_write; + bool _allowWrite; public: // public methods - MemStream(uint8_t *buffer, const uint16_t len, uint16_t content_len = 0, bool allowWrite=true); + MemStream(uint8_t *buffer, const uint16_t len, uint16_t content_len = 0, bool allowWrite = true); ~MemStream() {} operator const uint8_t *() const { return _buffer; } - operator const char *() const { return (const char*)_buffer; } + operator const char *() const { return (const char *)_buffer; } uint16_t current_length() const { return _pos_write; } bool listen() { return true; } void end() {} bool isListening() { return true; } - bool overflow() { bool ret = _buffer_overflow; _buffer_overflow = false; return ret; } + bool overflow() + { + bool ret = _buffer_overflow; + _buffer_overflow = false; + return ret; + } int peek(); virtual size_t write(uint8_t byte); diff --git a/MotorDrivers.h b/MotorDrivers.h index c81bc37..79bcd4f 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -1,5 +1,8 @@ #ifndef MotorDrivers_h #define MotorDrivers_h +#if defined(ARDUINO_ARCH_MEGAAVR) +#include +#endif // *** PLEASE NOTE *** THIS FILE IS **NOT** INTENDED TO BE EDITED WHEN CONFIGURING A SYSTEM. // It will be overwritten if the library is updated. @@ -8,36 +11,35 @@ // A configuration defined by macro here can be used in your sketch. // A custom hardware setup will require your sketch to create MotorDriver instances // similar to those defined here, WITHOUT editing this file. - const byte UNUSED_PIN = 255; // MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, byte brake_pin, byte current_pin, // float senseFactor, unsigned int tripMilliamps, byte faultPin); - + // Arduino standard Motor Shield -#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ - new MotorDriver(3 , 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \ - new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 250 , UNUSED_PIN) +#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ + new MotorDriver(3, 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \ + new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, TRIP_CURRENT_PROG, UNUSED_PIN) // Pololu Motor Shield -#define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \ - new MotorDriver(4, 7, UNUSED_PIN, 9 , A0, 18, 2000, 12), \ - new MotorDriver(2, 8, UNUSED_PIN, 10, A1, 18, 250 , UNUSED_PIN) +#define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \ + new MotorDriver(4, 7, UNUSED_PIN, 9, A0, 18, 2000, 12), \ + new MotorDriver(2, 8, UNUSED_PIN, 10, A1, 18, TRIP_CURRENT_PROG, UNUSED_PIN) -// Firebox Mk1 -#define FIREBOX_MK1 F("FIREBOX_MK1"), \ - new MotorDriver(3, 6, 7, UNUSED_PIN, A5, 9.766, 5500, UNUSED_PIN), \ - new MotorDriver(4, 8, 9, UNUSED_PIN, A1, 5.00, 250 , UNUSED_PIN) +// Firebox Mk1 +#define FIREBOX_MK1 F("FIREBOX_MK1"), \ + new MotorDriver(3, 6, 7, UNUSED_PIN, A5, 9.766, 5500, UNUSED_PIN), \ + new MotorDriver(4, 8, 9, UNUSED_PIN, A1, 5.00, TRIP_CURRENT_PROG, UNUSED_PIN) -// Firebox Mk1S -#define FIREBOX_MK1S F("FIREBOX_MK1A"), \ - new MotorDriver(24, 21, 22, 25, 23, 9.766, 5500, UNUSED_PIN), \ - new MotorDriver(30, 27, 28, 31, 29, 5.00, 250 , UNUSED_PIN) +// Firebox Mk1S +#define FIREBOX_MK1S F("FIREBOX_MK1A"), \ + new MotorDriver(24, 21, 22, 25, 23, 9.766, 5500, UNUSED_PIN), \ + new MotorDriver(30, 27, 28, 31, 29, 5.00, TRIP_CURRENT_PROG, UNUSED_PIN) // FunduMoto Motor Shield -#define FUNDUMOTO_SHIELD F("FUNDUMOTO_SHIELD"), \ - new MotorDriver(10 , 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \ - new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 250 , UNUSED_PIN) +#define FUNDUMOTO_SHIELD F("FUNDUMOTO_SHIELD"), \ + new MotorDriver(10, 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \ + new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, TRIP_CURRENT_PROG, UNUSED_PIN) #endif diff --git a/Timer.cpp b/Timer.cpp index 1430e9c..3a73401 100644 --- a/Timer.cpp +++ b/Timer.cpp @@ -85,10 +85,15 @@ ISR(TIMER2_OVF_vect) #include "ATMEGA4809/Timer.h" -Timer TimerA(0); +Timer TimerA(1); +Timer TimerB(2); -ISR(TCA0_OVF_vect) { +ISR(TIMER1_OVF_vect) { TimerA.isrCallback(); } +ISR(TIMER2_OVF_vect) { + TimerB.isrCallback(); +} + #endif diff --git a/WifiInterface.cpp b/WifiInterface.cpp index 06fa11b..9ea5738 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -22,6 +22,8 @@ #include "DIAG.h" #include "StringFormatter.h" #include "WiThrottle.h" + + const char PROGMEM READY_SEARCH[] = "\r\nready\r\n"; const char PROGMEM OK_SEARCH[] = "\r\nOK\r\n"; const char PROGMEM END_DETAIL_SEARCH[] = "@ 1000"; diff --git a/WifiInterface.h b/WifiInterface.h index 10941fa..003ef2d 100644 --- a/WifiInterface.h +++ b/WifiInterface.h @@ -21,37 +21,39 @@ #define WifiInterface_h #include "DCCEXParser.h" #include "MemStream.h" + #include #include -typedef void (*HTTP_CALLBACK)(Print * stream, byte * cmd); +typedef void (*HTTP_CALLBACK)(Print *stream, byte *cmd); -class WifiInterface { +class WifiInterface +{ - public: - static bool setup(Stream & setupStream, const __FlashStringHelper* SSSid, const __FlashStringHelper* password, - const __FlashStringHelper* hostname, int port); - static void loop(); - static void ATCommand(const byte * command); - static void setHTTPCallback(HTTP_CALLBACK callback); - - private: - static Stream * wifiStream; - static DCCEXParser parser; - static bool setup2( const __FlashStringHelper* SSSid, const __FlashStringHelper* password, - const __FlashStringHelper* hostname, int port); - static bool checkForOK(const unsigned int timeout, const char* waitfor, bool echo, bool escapeEcho=true); - static bool isHTTP(); - static HTTP_CALLBACK httpCallback; - static bool connected; - static bool closeAfter; - static byte loopstate; - static int datalength; - static int connectionId; - static unsigned long loopTimeoutStart; - static const byte MAX_WIFI_BUFFER=250; - static byte buffer[MAX_WIFI_BUFFER+1]; - static MemStream streamer; +public: + static bool setup(Stream &setupStream, const __FlashStringHelper *SSSid, const __FlashStringHelper *password, + const __FlashStringHelper *hostname, int port); + static void loop(); + static void ATCommand(const byte *command); + static void setHTTPCallback(HTTP_CALLBACK callback); + +private: + static Stream *wifiStream; + static DCCEXParser parser; + static bool setup2(const __FlashStringHelper *SSSid, const __FlashStringHelper *password, + const __FlashStringHelper *hostname, int port); + static bool checkForOK(const unsigned int timeout, const char *waitfor, bool echo, bool escapeEcho = true); + static bool isHTTP(); + static HTTP_CALLBACK httpCallback; + static bool connected; + static bool closeAfter; + static byte loopstate; + static int datalength; + static int connectionId; + static unsigned long loopTimeoutStart; + static const byte MAX_WIFI_BUFFER = 250; + static byte buffer[MAX_WIFI_BUFFER + 1]; + static MemStream streamer; }; #endif From 32f1d7d890d44b0d99297de1fdbb589698a15ee9 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 25 Sep 2020 22:14:20 +0200 Subject: [PATCH 25/49] Make all Wifi code disappear if Wifi is not enabled. --- CommandStation-EX.ino | 11 ++++------- DCCEXParser.cpp | 9 ++++++--- WifiInterface.cpp | 7 +++++-- WifiInterface.h | 9 ++++++--- defines.h | 35 +++++++++++++++++++++++++++++++++++ 5 files changed, 56 insertions(+), 15 deletions(-) create mode 100644 defines.h diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 21487f9..6613d9e 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -12,16 +12,13 @@ // REFER TO SEPARATE EXAMPLE. //////////////////////////////////////////////////////////////////////////////////// -// This defines the speed at which the Arduino will communicate with the ESP8266 module. -// Currently only Arduino Mega and 115200 is supported. -#define WIFI_SERIAL_LINK_SPEED 115200 - #include "config.h" +#include "defines.h" #include "DCC.h" #include "DIAG.h" #include "DCCEXParser.h" #include "version.h" -#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) +#ifdef WIFI_ON #include "WifiInterface.h" #endif #if ENABLE_FREE_MEM_WARNING @@ -169,7 +166,7 @@ void setup() DIAG(F("\n===== DCC::getLocoId has returned, but the callback wont be executed until we are in loop() ======\n")); #endif -#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) +#ifdef WIFI_ON bool wifiUp = false; const __FlashStringHelper *wifiESSID = F(WIFI_SSID); const __FlashStringHelper *wifiPassword = F(WIFI_PASSWORD); @@ -215,7 +212,7 @@ void loop() serialParser.loop(Serial); // Responsibility 3: Optionally handle any incoming WiFi traffic -#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) +#ifdef WIFI_ON WifiInterface::loop(); #endif diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 8b26da7..282237b 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -1,7 +1,8 @@ /* * © 2020, Chris Harlow. 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 * it under the terms of the GNU General Public License as published by @@ -16,11 +17,13 @@ * You should have received a copy of the GNU General Public License * along with CommandStation. If not, see . */ +#include "config.h" +#include "defines.h" #include "StringFormatter.h" #include "DCCEXParser.h" #include "DCC.h" #include "DCCWaveform.h" -#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) +#ifdef WIFI_ON #include "WifiInterface.h" #endif #include "Turnouts.h" @@ -391,7 +394,7 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking) 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); return; -#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) +#ifdef WIFI_ON case '+': // Complex Wifi interface command (not usual parse) WifiInterface::ATCommand(com); return; diff --git a/WifiInterface.cpp b/WifiInterface.cpp index 9ea5738..6dcacae 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -1,7 +1,8 @@ /* © 2020, Chris Harlow. 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 it under the terms of the GNU General Public License as published by @@ -17,8 +18,9 @@ along with CommandStation. If not, see . */ +#include "WifiInterface.h" /* config.h and defines.h included here */ +#ifdef WIFI_ON /* from config.h and defines.h */ #include -#include "WifiInterface.h" #include "DIAG.h" #include "StringFormatter.h" #include "WiThrottle.h" @@ -400,3 +402,4 @@ void WifiInterface::loop() { loopTimeoutStart = millis(); loopstate = 10; // non-blocking loop waits for > before sending } +#endif // WIFI_ON diff --git a/WifiInterface.h b/WifiInterface.h index 003ef2d..fbfde77 100644 --- a/WifiInterface.h +++ b/WifiInterface.h @@ -1,7 +1,8 @@ /* * © 2020, Chris Harlow. 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 * it under the terms of the GNU General Public License as published by @@ -16,9 +17,11 @@ * You should have received a copy of the GNU General Public License * along with CommandStation. If not, see . */ - #ifndef WifiInterface_h #define WifiInterface_h +#include "config.h" +#include "defines.h" +#ifdef WIFI_ON #include "DCCEXParser.h" #include "MemStream.h" @@ -55,5 +58,5 @@ private: static byte buffer[MAX_WIFI_BUFFER + 1]; static MemStream streamer; }; - +#endif // WIFI_ON #endif diff --git a/defines.h b/defines.h new file mode 100644 index 0000000..8279b8b --- /dev/null +++ b/defines.h @@ -0,0 +1,35 @@ +/* + © 2020, 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 . + +*/ + +//////////////////////////////////////////////////////////////////////////////// +// +// WIFI_ON: All prereqs for running with WIFI are met +// +#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) +#define WIFI_ON +#endif + +//////////////////////////////////////////////////////////////////////////////// +// +// This defines the speed at which the Arduino will communicate with the ESP8266 module. +// Currently only devices which can communicate at 115200 are supported. +// +#define WIFI_SERIAL_LINK_SPEED 115200 + From 6035e4d9ad615bee68c04142acd52551f3e694b6 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 25 Sep 2020 22:27:37 +0200 Subject: [PATCH 26/49] Set preamblebits main to 16 and remove all preamble config from config.h --- DCCWaveform.h | 2 +- config.example.h | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/DCCWaveform.h b/DCCWaveform.h index 96e5887..b1c6f7d 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -30,7 +30,7 @@ const int MIN_ACK_PULSE_DURATION = 2000; const int MAX_ACK_PULSE_DURATION = 8500; -const int PREAMBLE_BITS_MAIN = 20; +const int PREAMBLE_BITS_MAIN = 16; const int PREAMBLE_BITS_PROG = 22; diff --git a/config.example.h b/config.example.h index 33845f4..d8fd4ba 100644 --- a/config.example.h +++ b/config.example.h @@ -110,17 +110,6 @@ The configuration file for DCC++ EX Command Station #define SHOW_CONFIG_DETAIL_PIN A2 -///////////////////////////////////////////////////////////////////////////////////// -// -// PREAMBLE_MAIN: Length of the preamble on the main track. Per standard this should -// be at least 14 bits but if some equipment wants to insert a RailCom -// cutout this should be at least 16 bits. -// PERAMBLE_PROG: Length of the preamble on the programming track. Per standard this -// should be at least 22 bits - -#define PREAMBLE_MAIN 16 // TODO: Finish configurable preamble code -#define PREAMBLE_PROG 22 - ///////////////////////////////////////////////////////////////////////////////////// // // DEFINE LCD SCREEN USAGE BY THE BASE STATION From bff7555102f52ef5debb423c27ce16b0ba0603ce Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 25 Sep 2020 22:49:53 +0200 Subject: [PATCH 27/49] NUM_SERIAL tells us how many serial lines to check --- CommandStation-EX.ino | 6 +++++- defines.h | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 6613d9e..e9b33eb 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -175,17 +175,21 @@ void setup() Serial1.begin(WIFI_SERIAL_LINK_SPEED); wifiUp = WifiInterface::setup(Serial1, wifiESSID, wifiPassword, dccex, port); +#if NUM_SERIAL > 1 if (!wifiUp) { Serial2.begin(WIFI_SERIAL_LINK_SPEED); wifiUp = WifiInterface::setup(Serial2, wifiESSID, wifiPassword, dccex, port); } +#if NUM_SERIAL > 2 if (!wifiUp) { Serial3.begin(WIFI_SERIAL_LINK_SPEED); wifiUp = WifiInterface::setup(Serial3, wifiESSID, wifiPassword, dccex, port); } -#endif +#endif // >2 +#endif // >1 +#endif // WIFI_ON // Responsibility 3: Start the DCC engine. // Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s) diff --git a/defines.h b/defines.h index 8279b8b..700687b 100644 --- a/defines.h +++ b/defines.h @@ -33,3 +33,13 @@ // #define WIFI_SERIAL_LINK_SPEED 115200 +//////////////////////////////////////////////////////////////////////////////// +// +// Figure out number of serial ports depending on hardware +// +#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) +#define NUM_SERIAL 3 +#endif +#ifndef NUM_SERIAL +#define NUM_SERIAL 1 +#endif From 9ebe13828a9693341379fd1db5db6b3f2ce7cfc2 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Sat, 26 Sep 2020 08:10:43 +0100 Subject: [PATCH 28/49] Correct objdump --- objdump.bat | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/objdump.bat b/objdump.bat index bee2d0f..c09ebf0 100644 --- a/objdump.bat +++ b/objdump.bat @@ -1,12 +1,12 @@ ECHO ON FOR /F "delims=" %%i IN ('dir %TMP%\arduino_build_* /b /ad-h /t:c /od') DO SET a=%%i echo Most recent subfolder: %a% >%TMP%\OBJDUMP_%a%.txt -avr-objdump --private=mem-usage %TMP%\%a%\DCCEX.ino.elf >>%TMP%\OBJDUMP_%a%.txt +avr-objdump --private=mem-usage %TMP%\%a%\CommandStation-EX.ino.elf >>%TMP%\OBJDUMP_%a%.txt ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt -avr-objdump -x -C %TMP%\%a%\DCCEX.ino.elf | find ".text" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt +avr-objdump -x -C %TMP%\%a%\CommandStation-EX.ino.elf | find ".text" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt -avr-objdump -x -C %TMP%\%a%\DCCEX.ino.elf | find ".data" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt +avr-objdump -x -C %TMP%\%a%\CommandStation-EX.ino.elf | find ".data" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt -avr-objdump -x -C %TMP%\%a%\DCC.ino.elf | find ".bss" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt +avr-objdump -x -C %TMP%\%a%\CommandStation-EX.ino.elf | find ".bss" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt notepad %TMP%\OBJDUMP_%a%.txt EXIT From 59d77f2362cb60a550618333c769ff8edbebff5c Mon Sep 17 00:00:00 2001 From: Asbelos Date: Sat, 26 Sep 2020 08:42:24 +0100 Subject: [PATCH 29/49] Remove unnecessary #iddefs These are only avoiding files that alrerady self-destruct... so they don't need to be avoided. --- CommandStation-EX.ino | 11 ++--------- DCCEXParser.cpp | 2 -- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index e9b33eb..36b4660 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -18,17 +18,8 @@ #include "DIAG.h" #include "DCCEXParser.h" #include "version.h" -#ifdef WIFI_ON #include "WifiInterface.h" -#endif -#if ENABLE_FREE_MEM_WARNING -#include "freeMemory.h" -int ramLowWatermark = 32767; // This figure gets overwritten dynamically in loop() -#endif - -#if defined(ARDUINO_ARCH_MEGAAVR) #include -#endif @@ -222,6 +213,8 @@ void loop() // Optionally report any decrease in memory (will automatically trigger on first call) #if ENABLE_FREE_MEM_WARNING + static int ramLowWatermark = 32767; // replaced on first loop + int freeNow = freeMemory(); if (freeNow < ramLowWatermark) { diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 282237b..7433576 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -23,9 +23,7 @@ #include "DCCEXParser.h" #include "DCC.h" #include "DCCWaveform.h" -#ifdef WIFI_ON #include "WifiInterface.h" -#endif #include "Turnouts.h" #include "Outputs.h" #include "Sensors.h" From 411a6b1130ba15230401a85388b3a3e93eabeab3 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Sat, 26 Sep 2020 08:49:51 +0100 Subject: [PATCH 30/49] Move bulk includes out of .ino I left config.h in the .ino so it shows the user that this will be necessary. --- CommandStation-EX.ino | 10 +--------- DCCEX.h | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 9 deletions(-) create mode 100644 DCCEX.h diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 36b4660..cba919c 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -13,15 +13,7 @@ //////////////////////////////////////////////////////////////////////////////////// #include "config.h" -#include "defines.h" -#include "DCC.h" -#include "DIAG.h" -#include "DCCEXParser.h" -#include "version.h" -#include "WifiInterface.h" -#include - - +#include "DCCEX.h" //////////////////////////////////////////////////////////////// diff --git a/DCCEX.h b/DCCEX.h new file mode 100644 index 0000000..fd118ce --- /dev/null +++ b/DCCEX.h @@ -0,0 +1,15 @@ +// This include is intended to visually simplify the .ino for the end users. +// If there were any #ifdefs required they are much better handled in here. + +#ifndef DCCEX_h +#define DCCEX_h + +#include "defines.h" +#include "DCC.h" +#include "DIAG.h" +#include "DCCEXParser.h" +#include "version.h" +#include "WifiInterface.h" +#include + +#endif From 162c7e6e11c1550b12835e709f53ac4a50b0641c Mon Sep 17 00:00:00 2001 From: Asbelos Date: Sat, 26 Sep 2020 09:40:49 +0100 Subject: [PATCH 31/49] UNTESTED Reduce #ifdef hell on wifi This fix to the structure of the Wifi code alows for a better link optimisation and so it is no longer necessary for the wifi code or header to read values from the config.h file. Compilation shows that >11Kb of code is eliminated by the linker if the setup and loop are not called. ~including the interface does not really change anything --- WifiInterface.cpp | 34 ++++++++++++++++------------------ WifiInterface.h | 7 +------ 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/WifiInterface.cpp b/WifiInterface.cpp index 6dcacae..4d9a7e3 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -19,7 +19,6 @@ */ #include "WifiInterface.h" /* config.h and defines.h included here */ -#ifdef WIFI_ON /* from config.h and defines.h */ #include #include "DIAG.h" #include "StringFormatter.h" @@ -41,7 +40,7 @@ unsigned long WifiInterface::loopTimeoutStart = 0; int WifiInterface::datalength = 0; int WifiInterface::connectionId; byte WifiInterface::buffer[MAX_WIFI_BUFFER+1]; -MemStream WifiInterface::streamer(buffer, MAX_WIFI_BUFFER); +MemStream * WifiInterface::streamer; Stream * WifiInterface::wifiStream = NULL; HTTP_CALLBACK WifiInterface::httpCallback = 0; @@ -60,7 +59,7 @@ bool WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid StringFormatter::send(wifiStream, F("ATE0\r\n")); // turn off the echo checkForOK(200, OK_SEARCH, true); } - + streamer=new MemStream(buffer, MAX_WIFI_BUFFER); DIAG(F("\n++ Wifi Setup %S ++\n"), connected ? F("OK") : F("FAILED")); return connected; } @@ -300,15 +299,15 @@ void WifiInterface::loop() { case 6: // reading for length if (ch == ':') loopstate = (datalength == 0) ? 99 : 7; // 99 is getout without reading next char else datalength = datalength * 10 + (ch - '0'); - streamer.flush(); // basically sets write point at start of buffer + streamer->flush(); // basically sets write point at start of buffer break; case 7: // reading data - streamer.write(ch); // NOTE: The MemStream will throw away bytes that do not fit in the buffer. + streamer->write(ch); // NOTE: The MemStream will throw away bytes that do not fit in the buffer. // This protects against buffer overflows even with things as innocent // as a browser which send massive, irrlevent HTTP headers. datalength--; if (datalength == 0) { - buffer[streamer.available()]='\0'; // mark end of buffer, so it can be used as a string later + buffer[streamer->available()]='\0'; // mark end of buffer, so it can be used as a string later loopstate = 99; } break; @@ -351,8 +350,8 @@ void WifiInterface::loop() { loopstate = 1; } if (ch == 'K') { // assume its in SEND OK - if (Diag::WIFI) DIAG(F("\n\n Wifi BUSY RETRYING.. AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available()); - StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available()); + if (Diag::WIFI) DIAG(F("\n\n Wifi BUSY RETRYING.. AT+CIPSEND=%d,%d\r\n"), connectionId, streamer->available()); + StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer->available()); loopTimeoutStart = millis(); loopstate = 10; // non-blocking loop waits for > before sending break; @@ -365,7 +364,7 @@ void WifiInterface::loop() { // AT this point we have read an incoming message into the buffer if (Diag::WIFI) DIAG(F("\n%l Wifi(%d)<-[%e]\n"), millis(),connectionId, buffer); - streamer.setBufferContentPosition(0, 0); // reset write position to start of buffer + streamer->setBufferContentPosition(0, 0); // reset write position to start of buffer // SIDE EFFECT WARNING::: // We know that parser will read the entire buffer before starting to write to it. // Otherwise we would have to copy the buffer elsewhere and RAM is in short supply. @@ -374,18 +373,18 @@ void WifiInterface::loop() { // Intercept HTTP requests if (isHTTP()) { - if (httpCallback) httpCallback(&streamer, buffer); + if (httpCallback) httpCallback(streamer, buffer); else { StringFormatter::send(streamer, F("HTTP/1.1 404 Not Found\nContent-Type: text/html\nConnnection: close\n\n")); StringFormatter::send(streamer, F("This is not a web server.
")); } closeAfter = true; } - else if (buffer[0] == '<') parser.parse(&streamer, buffer, true); // tell JMRI parser that ACKS are blocking because we can't handle the async + else if (buffer[0] == '<') parser.parse(streamer, buffer, true); // tell JMRI parser that ACKS are blocking because we can't handle the async - else WiThrottle::getThrottle(connectionId)->parse(streamer, buffer); + else WiThrottle::getThrottle(connectionId)->parse(*streamer, buffer); - if (streamer.available() == 0) { + if (streamer->available() == 0) { // No reply if (closeAfter) { if (Diag::WIFI) DIAG(F("AT+CIPCLOSE=%d\r\n"), connectionId); @@ -395,11 +394,10 @@ void WifiInterface::loop() { return; } // prepare to send reply - buffer[streamer.available()]='\0'; // mark end of buffer, so it can be used as a string later - if (Diag::WIFI) DIAG(F("%l WiFi(%d)->[%e] l(%d)\n"), millis(), connectionId, buffer, streamer.available()); - if (Diag::WIFI) DIAG(F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available()); - StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available()); + buffer[streamer->available()]='\0'; // mark end of buffer, so it can be used as a string later + if (Diag::WIFI) DIAG(F("%l WiFi(%d)->[%e] l(%d)\n"), millis(), connectionId, buffer, streamer->available()); + if (Diag::WIFI) DIAG(F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer->available()); + StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer->available()); loopTimeoutStart = millis(); loopstate = 10; // non-blocking loop waits for > before sending } -#endif // WIFI_ON diff --git a/WifiInterface.h b/WifiInterface.h index fbfde77..ecf9005 100644 --- a/WifiInterface.h +++ b/WifiInterface.h @@ -19,12 +19,8 @@ */ #ifndef WifiInterface_h #define WifiInterface_h -#include "config.h" -#include "defines.h" -#ifdef WIFI_ON #include "DCCEXParser.h" #include "MemStream.h" - #include #include @@ -56,7 +52,6 @@ private: static unsigned long loopTimeoutStart; static const byte MAX_WIFI_BUFFER = 250; static byte buffer[MAX_WIFI_BUFFER + 1]; - static MemStream streamer; + static MemStream * streamer; }; -#endif // WIFI_ON #endif From 36e6c3cd48ad24a46d6bd54976d07d938622797c Mon Sep 17 00:00:00 2001 From: Asbelos Date: Sat, 26 Sep 2020 10:54:11 +0100 Subject: [PATCH 32/49] Decouple WifiInterface from Parser This removes the need for WifiInterfrace <+> command processing to be included in the link. so parser does not need to see the config settings for wifi. If Wifi doesnt set the At command callback, parser will return for a <+> command --- DCCEXParser.cpp | 19 ++++++++++++------- DCCEXParser.h | 3 +++ WifiInterface.cpp | 2 ++ 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 7433576..82cd9d9 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -17,13 +17,10 @@ * You should have received a copy of the GNU General Public License * along with CommandStation. If not, see . */ -#include "config.h" -#include "defines.h" #include "StringFormatter.h" #include "DCCEXParser.h" #include "DCC.h" #include "DCCWaveform.h" -#include "WifiInterface.h" #include "Turnouts.h" #include "Outputs.h" #include "Sensors.h" @@ -157,10 +154,15 @@ int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd) } FILTER_CALLBACK DCCEXParser::filterCallback = 0; +AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0; void DCCEXParser::setFilter(FILTER_CALLBACK filter) { filterCallback = filter; } +void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback) +{ + atCommandCallback = callback; +} // See documentation on DCC class for info on this section void DCCEXParser::parse(Print *stream, byte *com, bool blocking) @@ -392,11 +394,14 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking) 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); return; -#ifdef WIFI_ON + case '+': // Complex Wifi interface command (not usual parse) - WifiInterface::ATCommand(com); - return; -#endif + if (atCommandCallback) { + atCommandCallback(com); + return; + } + break; + default: //anything else will diagnose and drop out to DIAG(F("\nOpcode=%c params=%d\n"), opcode, params); for (int i = 0; i < params; i++) diff --git a/DCCEXParser.h b/DCCEXParser.h index e154ff9..5721f6c 100644 --- a/DCCEXParser.h +++ b/DCCEXParser.h @@ -21,6 +21,7 @@ #include typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int p[]); +typedef void (*AT_COMMAND_CALLBACK)(const byte * command); struct DCCEXParser { @@ -29,6 +30,7 @@ struct DCCEXParser void parse(Print * stream, byte * command, bool blocking); void flush(); static void setFilter(FILTER_CALLBACK filter); + static void setAtCommandCallback(AT_COMMAND_CALLBACK filter); static const int MAX_PARAMS=10; // Must not exceed this private: @@ -59,6 +61,7 @@ struct DCCEXParser static void callback_Vbit(int result); static void callback_Vbyte(int result); static FILTER_CALLBACK filterCallback; + static AT_COMMAND_CALLBACK atCommandCallback; static void funcmap(int cab, byte value, byte fstart, byte fstop); }; diff --git a/WifiInterface.cpp b/WifiInterface.cpp index 4d9a7e3..f027ea0 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -60,6 +60,8 @@ bool WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid checkForOK(200, OK_SEARCH, true); } streamer=new MemStream(buffer, MAX_WIFI_BUFFER); + parser.setAtCommandCallback(ATCommand); + DIAG(F("\n++ Wifi Setup %S ++\n"), connected ? F("OK") : F("FAILED")); return connected; } From c3e17fcf04b993ca26e47e500b8c7ed6a453008f Mon Sep 17 00:00:00 2001 From: Asbelos Date: Sat, 26 Sep 2020 12:01:00 +0100 Subject: [PATCH 33/49] Add EthernetInterface (UNTESTED, UNCALLED) Thanks to Gregor....I have modified his originals to eliminate the static initialisation memory loss. These do not show up in the object code if they are not referenced. --- DCCEX.h | 2 + EthernetInterface.cpp | 304 ++++++++++++++++++++++++++++++++++++++++++ EthernetInterface.h | 107 +++++++++++++++ objdump.bat | 10 +- 4 files changed, 419 insertions(+), 4 deletions(-) create mode 100644 EthernetInterface.cpp create mode 100644 EthernetInterface.h diff --git a/DCCEX.h b/DCCEX.h index fd118ce..091bd47 100644 --- a/DCCEX.h +++ b/DCCEX.h @@ -10,6 +10,8 @@ #include "DCCEXParser.h" #include "version.h" #include "WifiInterface.h" +#include "EthernetInterface.h" + #include #endif diff --git a/EthernetInterface.cpp b/EthernetInterface.cpp new file mode 100644 index 0000000..3498871 --- /dev/null +++ b/EthernetInterface.cpp @@ -0,0 +1,304 @@ +/* + * © 2020,Gregor Baues, Chris Harlow. All rights reserved. + * + * This file is part of DCC-EX/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 . + * + * Ethernet Interface added by Gregor Baues + */ + +#include "EthernetInterface.h" +#include "DIAG.h" +#include "StringFormatter.h" + +//#include +#include +#include + + +// Support Functions +/** + * @brief Aquire IP Address from DHCP; if that fails try a statically configured address + * + * @return true + * @return false + */ +bool EthernetInterface::setupConnection() +{ + + singleton=this; + + DIAG(F("\nInitialize Ethernet with DHCP:")); + server = EthernetServer(LISTEN_PORT); // Ethernet Server listening on default port LISTEN_PORT + ip = IPAddress(IP_ADDRESS); // init with fixed IP address needed to get to the server + connected = false; // Connection status + streamer= new MemStream(buffer, MAX_ETH_BUFFER, MAX_ETH_BUFFER, true); // streamer who writes the results to the buffer + + if (Ethernet.begin(EthernetInterface::mac) == 0) + { + DIAG(F("\nFailed to configure Ethernet using DHCP ... Trying with fixed IP")); + Ethernet.begin(EthernetInterface::mac, EthernetInterface::ip); // default ip address + + if (Ethernet.hardwareStatus() == EthernetNoHardware) + { + DIAG(F("\nEthernet shield was not found. Sorry, can't run without hardware. :(")); + return false; + }; + if (Ethernet.linkStatus() == LinkOFF) + { + DIAG(F("\nEthernet cable is not connected.")); + return false; + } + } + + ip = Ethernet.localIP(); // reassign the obtained ip address + + DIAG(F("\nLocal IP address: [%d.%d.%d.%d]"), ip[0], ip[1], ip[2], ip[3]); + DIAG(F("\nListening on port: [%d]"), port); + dnsip = Ethernet.dnsServerIP(); + DIAG(F("\nDNS server IP address: [%d.%d.%d.%d] "), ip[0], ip[1], ip[2], ip[3]); + return true; +} + +/** + * @brief Handles command requests recieved via UDP. UDP is a connection less, unreliable protocol as it doesn't maintain state but fast. + * + */ + void EthernetInterface::udpHandler() { + singleton->udpHandler2(); + } + void EthernetInterface::udpHandler2() +{ + + int packetSize = Udp.parsePacket(); + if (packetSize) + { + DIAG(F("\nReceived packet of size:[%d]\n"), packetSize); + IPAddress remote = Udp.remoteIP(); + DIAG(F("From: [%d.%d.%d.%d:"), remote[0], remote[1], remote[2], remote[3]); + char portBuffer[6]; + DIAG(F("%s]\n"), utoa(Udp.remotePort(), portBuffer, 10)); // DIAG has issues with unsigend int's so go through utoa + + // read the packet into packetBufffer + Udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE); + + DIAG(F("Command: [%s]\n"), packetBuffer); + + streamer->flush(); + + Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); + + ethParser.parse(streamer, (byte *)packetBuffer, true); // set to true so it is sync cf. WifiInterface + + if (streamer->available() == 0) + { + DIAG(F("\nNo response\n")); + } + else + { + // send the reply + DIAG(F("Response: %s\n"), (char *)buffer); + Udp.write((char *)buffer); + Udp.endPacket(); + } + + memset(packetBuffer, 0, UDP_TX_PACKET_MAX_SIZE); // reset PacktBuffer + return; + } +} + +/** + * @brief Handles command requests recieved via TCP. Supports up to the max# of simultaneous requests which is 8. The connection gets closed as soon as we finished processing + * + */ + void EthernetInterface::tcpHandler() +{ + singleton->tcpHandler2(); +} + void EthernetInterface::tcpHandler2() +{ + // get client from the server + EthernetClient client = getServer().accept(); + + // check for new client + if (client) + { + for (byte i = 0; i < MAX_SOCK_NUM; i++) + { + if (!clients[i]) + { + // On accept() the EthernetServer doesn't track the client anymore + // so we store it in our client array + clients[i] = client; + break; + } + } + } + + // check for incoming data from all possible clients + for (byte i = 0; i < MAX_SOCK_NUM; i++) + { + if (clients[i] && clients[i].available() > 0) + { + // read bytes from a client + int count = clients[i].read(buffer, MAX_ETH_BUFFER); + buffer[count] = '\0'; // terminate the string properly + DIAG(F("\nReceived packet of size:[%d]\n"), count); + DIAG(F("From Client #: [%d]\n"), i); + DIAG(F("Command: [%s]\n"), buffer); + + // as we use buffer for recv and send we have to reset the write position + streamer->setBufferContentPosition(0, 0); + + ethParser.parse(streamer, buffer, true); // set to true to that the execution in DCC is sync + + if (streamer->available() == 0) + { + DIAG(F("No response\n")); + } + else + { + buffer[streamer->available()] = '\0'; // mark end of buffer, so it can be used as a string later + DIAG(F("Response: %s\n"), (char *)buffer); + if (clients[i].connected()) + { + clients[i].write(buffer, streamer->available()); + } + } + } + // stop any clients which disconnect + for (byte i = 0; i < MAX_SOCK_NUM; i++) + { + if (clients[i] && !clients[i].connected()) + { + DIAG(F("Disconnect client #%d \n"), i); + clients[i].stop(); + } + } + } +} + +// Class Functions +/** + * @brief Setup Ethernet Connection + * + * @param pt Protocol used + * @param localPort Port number for the connection + */ +void EthernetInterface::setup(protocolType pt, uint16_t localPort) +{ + DIAG(F("\n++++++ Ethernet Setup In Progress ++++++++\n")); + port = localPort; + if (setupConnection()) + { + DIAG(F("\nProtocol: [%s]\n"), pt ? "UDP" : "TCP"); + switch (pt) + { + case UDP: + { + if (Udp.begin(localPort)) + { + connected = true; + protocolHandler = udpHandler; + } + else + { + DIAG(F("\nUDP client failed to start")); + connected = false; + } + break; + }; + case TCP: + { + Ethernet.begin(mac, ip); + EthernetServer server(localPort); + setServer(server); + server.begin(); + connected = true; + protocolHandler = tcpHandler; + break; + }; + default: + { + DIAG(F("Unkown Ethernet protocol; Setup failed")); + connected = false; + return; + } + } + } + else + { + connected = false; + }; + DIAG(F("\n++++++ Ethernet Setup %S ++++++++\n"), connected ? F("OK") : F("FAILED")); +}; + +/** + * @brief Setup Ethernet on default port and user choosen protocol + * + * @param pt Protocol UDP or TCP + */ +void EthernetInterface::setup(protocolType pt) +{ + setup(pt, LISTEN_PORT); +}; + +/** + * @brief Ethernet setup with defaults TCP / Listen Port + * + */ +void EthernetInterface::setup() +{ + setup(TCP, LISTEN_PORT); +} + +/** + * @brief Main loop for the EthernetInterface + * + */ +void EthernetInterface::loop() +{ + switch (Ethernet.maintain()) + { + case 1: + //renewed fail + DIAG(F("\nError: renewed fail")); + break; + + case 2: + //renewed success + DIAG(F("\nRenewed success: ")); + ip = Ethernet.localIP(); // reassign the obtained ip address + DIAG(F("\nLocal IP address: [%d.%d.%d.%d]"),ip[0], ip[1], ip[2], ip[3]); + break; + + case 3: + //rebind fail + DIAG(F("Error: rebind fail")); + break; + + case 4: + //rebind success + DIAG(F("Rebind success")); + ip = Ethernet.localIP(); // reassign the obtained ip address + DIAG(F("\nLocal IP address: [%d.%d.%d.%d]"), ip[0], ip[1], ip[2], ip[3]); + break; + + default: + //nothing happened + break; + } + protocolHandler(); +} diff --git a/EthernetInterface.h b/EthernetInterface.h new file mode 100644 index 0000000..b702f04 --- /dev/null +++ b/EthernetInterface.h @@ -0,0 +1,107 @@ +/* + * © 2020,Gregor Baues, Chris Harlow. All rights reserved. + * + * This file is part of DCC-EX/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 . + * + * Ethernet Interface added by Gregor Baues + */ + +#ifndef EthernetInterface_h +#define EthernetInterface_h + +#include "DCCEXParser.h" +#include "MemStream.h" +#include +#include +#include + +/* some generated mac addresses as EthernetShields don't have one by default in HW. + * Sometimes they come on a sticker on the EthernetShield then use this address otherwise + * just choose one from below or generate one yourself. Only condition is that there is no + * other device on your network with the same Mac address. + * + * 52:b8:8a:8e:ce:21 + * e3:e9:73:e1:db:0d + * 54:2b:13:52:ac:0c + * c2:d8:d4:7d:7c:cb + * 86:cf:fa:9f:07:79 + */ + +/** + * @brief Network Configuration + * + */ +#define MAC_ADDRESS { 0x52, 0xB8, 0x8A, 0x8E, 0xCE, 0x21 } // MAC address of your networking card found on the sticker on your card or take one from above +#define IP_ADDRESS 10, 0, 0, 101 // Just in case we don't get an adress from DHCP try a static one; make sure + // this one is not used elsewhere and corresponds to your network layout +#define LISTEN_PORT 3366 // default listen port for the server +#define MAX_ETH_BUFFER 250 + +typedef void (*HTTP_CALLBACK)(Print * stream, byte * cmd); + +enum protocolType { + TCP, + UDP +}; + +typedef void (*protocolCallback)(); + +class EthernetInterface { + +private: + EthernetServer server; + + public: + DCCEXParser ethParser; + bool connected; + byte mac[6]; + IPAddress ip; + uint16_t port; + IPAddress dnsip; + + void setup(protocolType pt, uint16_t lp); // specific port nummber + void setup(protocolType pt); // uses default port number + void setup(); // all defaults (protocol/port) + + protocolCallback protocolHandler; + + void loop(); + + private: + static EthernetInterface * singleton; + + char packetBuffer[UDP_TX_PACKET_MAX_SIZE]; // buffer to hold incoming UDP packet, + uint8_t buffer[MAX_ETH_BUFFER]; // buffer provided to the streamer to be filled with the reply (used by TCP also for the recv) + MemStream * streamer; // streamer who writes the results to the buffer + EthernetClient clients[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield + + bool setupConnection(); + static void udpHandler(); + static void tcpHandler(); + void udpHandler2(); + void tcpHandler2(); + EthernetUDP Udp; + + EthernetServer getServer() { + return server; + }; + void setServer(EthernetServer s) { + server = s; + }; +}; + +#endif diff --git a/objdump.bat b/objdump.bat index bee2d0f..af30cb5 100644 --- a/objdump.bat +++ b/objdump.bat @@ -1,12 +1,14 @@ ECHO ON FOR /F "delims=" %%i IN ('dir %TMP%\arduino_build_* /b /ad-h /t:c /od') DO SET a=%%i echo Most recent subfolder: %a% >%TMP%\OBJDUMP_%a%.txt -avr-objdump --private=mem-usage %TMP%\%a%\DCCEX.ino.elf >>%TMP%\OBJDUMP_%a%.txt +SET ELF=%TMP%\%a%\CommandStation-EX.ino.elf + +avr-objdump --private=mem-usage %ELF% >>%TMP%\OBJDUMP_%a%.txt ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt -avr-objdump -x -C %TMP%\%a%\DCCEX.ino.elf | find ".text" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt +avr-objdump -x -C %ELF% | find ".text" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt -avr-objdump -x -C %TMP%\%a%\DCCEX.ino.elf | find ".data" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt +avr-objdump -x -C %ELF% | find ".data" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt -avr-objdump -x -C %TMP%\%a%\DCC.ino.elf | find ".bss" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt +avr-objdump -x -C %ELF% | find ".bss" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt notepad %TMP%\OBJDUMP_%a%.txt EXIT From 38fe65fcca3057c17e940249fb5405925d34e6d7 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Sat, 26 Sep 2020 16:29:11 +0100 Subject: [PATCH 34/49] Drop advanced feature samples from basic ino --- CommandStation-EX.ino | 88 +++---------------------------------------- 1 file changed, 6 insertions(+), 82 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index cba919c..5f1f87e 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -2,14 +2,11 @@ // © 2020, Chris Harlow. All rights reserved. // // This file is a demonstattion of setting up a DCC-EX -// Command station to support direct connection of WiThrottle devices +// Command station with optional support for direct connection of WiThrottle devices // such as "Engine Driver". If you contriol your layout through JMRI // then DON'T connect throttles to this wifi, connect them to JMRI. // -// This is just 3 statements longer than the basic setup. -// -// THIS SETUP DOES NOT APPLY TO ARDUINO UNO WITH ONLY A SINGLE SERIAL PORT. -// REFER TO SEPARATE EXAMPLE. +// THE WIFI FEATURE IS NOT SUPPORTED ON ARDUINO DEVICES WITH ONLY 2KB RAM. //////////////////////////////////////////////////////////////////////////////////// #include "config.h" @@ -28,74 +25,14 @@ LiquidCrystal_I2C lcdDisplay = LiquidCrystal_I2C(LCD_ADDRESS, LCD_COLUMNS, LCD_L #endif #endif -// this code is here to demonstrate use of the DCC API and other techniques - -// myFilter is an example of an OPTIONAL command filter used to intercept < > commands from -// the usb or wifi streamm. It demonstrates how a command may be intercepted -// or even a new command created without having to break open the API library code. -// The filter is permitted to use or modify the parameter list before passing it on to -// the standard parser. By setting the opcode to 0, the standard parser will -// just ignore the command on the assumption that you have already handled it. -// -// The filter must be enabled by calling the DCC EXParser::setFilter method, see use in setup(). -#if ENABLE_CUSTOM_FILTER -void myComandFilter(Print *stream, byte &opcode, byte ¶mCount, int p[]) -{ - (void)stream; // avoid compiler warning if we don't access this parameter - switch (opcode) - { - case '!': // Create a bespoke new command to clear all loco reminders or specific locos e.g - if (paramCount == 0) - DCC::forgetAllLocos(); - else - for (int i = 0; i < paramCount; i++) - DCC::forgetLoco(p[i]); - opcode = 0; // tell parser to ignore this command as we have done it already - break; - default: // drop through and parser will use the command unaltered. - break; - } -} - -// This is an OPTIONAL example of a HTTP filter... -// If you have configured wifi and an HTTP request is received on the Wifi connection -// it will normally be rejected 404 Not Found. - -// If you wish to handle HTTP requests, you can create a filter and ask the WifiInterface to -// call your code for each detected http request. - -void myHttpFilter(Print *stream, byte *cmd) -{ - (void)cmd; // Avoid compiler warning because this example doesnt use this parameter - - // BEWARE - As soon as you start responding, the cmd buffer is trashed! - // You must get everything you need from it before using StringFormatter::send! - - StringFormatter::send(stream, F("HTTP/1.1 200 OK\nContent-Type: text/html\nConnnection: close\n\n")); - StringFormatter::send(stream, F("This is my HTTP filter responding.
")); -} -#endif - -// Callback functions are necessary if you call any API that must wait for a response from the -// programming track. The API must return immediately otherwise other loop() functions would be blocked. -// Your callback function will be invoked when the data arrives from the prog track. -// See the DCC:getLocoId example in the setup function. -#if ENABLE_CUSTOM_CALLBACK -void myCallback(int result) -{ - DIAG(F("\n getting Loco Id callback result=%d"), result); -} -#endif - -// Create a serial command parser... Enables certain diagnostics and commands -// to be issued from the USB serial console -// This is NOT intended for JMRI.... - +// Create a serial command parser for the USB connection, +// This supports JMRI or manual diagnostics and commands +// to be issued from the USB serial console. DCCEXParser serialParser; void setup() { - + //////////////////////////////////////////// // // More display stuff. Need to put this in a .h file and make @@ -136,19 +73,6 @@ void setup() // NOTE: References to Serial1 are for the serial port used to connect // your wifi chip/shield. -// Optionally tell the command parser to use my example filter. -// This will intercept JMRI commands from both USB and Wifi -#if ENABLE_CUSTOM_FILTER - DCCEXParser::setFilter(myComandFilter); -#endif - -#if ENABLE_CUSTOM_CALLBACK - // This is just for demonstration purposes - DIAG(F("\n===== DCCEX demonstrating DCC::getLocoId() call ==========\n")); - DCC::getLocoId(myCallback); // myCallback will be called with the result - DIAG(F("\n===== DCC::getLocoId has returned, but the callback wont be executed until we are in loop() ======\n")); -#endif - #ifdef WIFI_ON bool wifiUp = false; const __FlashStringHelper *wifiESSID = F(WIFI_SSID); From 2929ce167bf4f70b04dd5299942b98703b5f1e00 Mon Sep 17 00:00:00 2001 From: Fred Date: Sat, 26 Sep 2020 14:34:59 -0400 Subject: [PATCH 35/49] Update platformio.ini Add Ethernet library dependency when that is enabled --- platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/platformio.ini b/platformio.ini index 58fd2a1..8565f33 100644 --- a/platformio.ini +++ b/platformio.ini @@ -32,6 +32,7 @@ framework = arduino lib_deps = ${env.lib_deps} DIO2 + arduino-libraries/Ethernet @ 2.0.0 [env:mega328] platform = atmelavr From c52610ae9c95bc09a2dd93990df766bc0d9f3b21 Mon Sep 17 00:00:00 2001 From: Fred Date: Sat, 26 Sep 2020 14:55:19 -0400 Subject: [PATCH 36/49] Update platformio.ini --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 8565f33..562b2e8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -32,7 +32,7 @@ framework = arduino lib_deps = ${env.lib_deps} DIO2 - arduino-libraries/Ethernet @ 2.0.0 + arduino-libraries/Ethernet@^2.0.0 [env:mega328] platform = atmelavr From 36a59c5c8a99fd5b62626847ddc2c6f39d34faa4 Mon Sep 17 00:00:00 2001 From: Fred Date: Sat, 26 Sep 2020 15:03:22 -0400 Subject: [PATCH 37/49] Update platformio.ini Add dependency for Ethernet library for Mega --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 562b2e8..5a24d33 100644 --- a/platformio.ini +++ b/platformio.ini @@ -32,7 +32,7 @@ framework = arduino lib_deps = ${env.lib_deps} DIO2 - arduino-libraries/Ethernet@^2.0.0 + arduino-libraries/Ethernet@2.0.0 [env:mega328] platform = atmelavr From 6fb11d5ac92728186de698b1f681c2d1ae8f37d8 Mon Sep 17 00:00:00 2001 From: Fred Date: Sat, 26 Sep 2020 15:06:27 -0400 Subject: [PATCH 38/49] Update platformio.ini Add dependency for Ethernet library for Mega --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 5a24d33..2a34592 100644 --- a/platformio.ini +++ b/platformio.ini @@ -32,7 +32,7 @@ framework = arduino lib_deps = ${env.lib_deps} DIO2 - arduino-libraries/Ethernet@2.0.0 + arduino-libraries/Ethernet [env:mega328] platform = atmelavr From 79201a911c101b5a69668af82666f7260314a7d0 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 26 Sep 2020 22:28:29 +0200 Subject: [PATCH 39/49] fix objdump.sh --- objdump.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/objdump.sh b/objdump.sh index 2e18af2..6e8e7d3 100755 --- a/objdump.sh +++ b/objdump.sh @@ -4,9 +4,10 @@ ARDUINOBIN=$(ls -l $(type -p arduino)| awk '{print $NF ; exit 0}') PATH=$(dirname "$ARDUINOBIN")/hardware/tools/avr/bin:$PATH -avr-objdump --private=mem-usage /tmp/arduino_build_233823/Blinkhabaplus.ino.elf +LASTBUILD=$(ls -tr /tmp/arduino_build_*/*.ino.elf | tail -1) +avr-objdump --private=mem-usage "$LASTBUILD" for segment in .text .data .bss ; do echo '++++++++++++++++++++++++++++++++++' - avr-objdump -x -C /tmp/arduino_build_233823/Blinkhabaplus.ino.elf | awk '$2 == "'$segment'" && $3 != 0 {print $3,$2} ; $4 == "'$segment'" && $5 != 0 { print $5,$6}' | sort -r + avr-objdump -x -C "$LASTBUILD" | awk '$2 == "'$segment'" && $3 != 0 {print $3,$2} ; $4 == "'$segment'" && $5 != 0 { print $5,$6}' | sort -r done From 59383dd6d571f3e70ebf99016afc94ed2da2233d Mon Sep 17 00:00:00 2001 From: Fred Date: Sat, 26 Sep 2020 16:52:13 -0400 Subject: [PATCH 40/49] Update platformio.ini Update platformio build check to include the Ethernet and SPI libraries for a mega when ethernet is used --- platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/platformio.ini b/platformio.ini index 2a34592..86da502 100644 --- a/platformio.ini +++ b/platformio.ini @@ -33,6 +33,7 @@ lib_deps = ${env.lib_deps} DIO2 arduino-libraries/Ethernet + SPI [env:mega328] platform = atmelavr From 00202e7966596db1561d6ddf6f372275dd68b73e Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 26 Sep 2020 22:56:35 +0200 Subject: [PATCH 41/49] remove unused configs --- config.example.h | 66 ++++++++---------------------------------------- 1 file changed, 10 insertions(+), 56 deletions(-) diff --git a/config.example.h b/config.example.h index d8fd4ba..bb3e302 100644 --- a/config.example.h +++ b/config.example.h @@ -15,23 +15,18 @@ The configuration file for DCC++ EX Command Station // the correct resistor could damage the sense pin on your Arduino or destroy // the device. // -// DEFINE MOTOR_SHIELD_TYPE ACCORDING TO THE FOLLOWING TABLE: +// DEFINE MOTOR_SHIELD_TYPE BELOW ACCORDING TO THE FOLLOWING TABLE: +// +// STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel +// POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track) +// FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection) +// FIREBOX_MK1 : The Firebox MK1 +// FIREBOX_MK1S : The Firebox MK1S +// | +// +-----------------------v // -// STANDARD_MOTOR_SHIELD = ARDUINO MOTOR SHIELD (MAX 18V/2A PER CHANNEL) Arduino Motor shield Rev3 based on the L298 -// POLOLU_MOTOR_SHIELD = POLOLU MC33926 MOTOR SHIELD (MAX 28V/2.5 PER CHANNEL) Pololu MC33926 Motor Driver (shield or carrier) -// FUNDUMOTO_SHIELD = FunduMoto Motor Shield -// FIREBOX_MK1 = Firebox MK1 -// FIREBOX_MK1S = Firebox MK1S - - #define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD -///////////////////////////////////////////////////////////////////////////////////// -// -// DEFINE PROGRAM TRACK CURRENT LIMIT IN MILLIAMPS - -#define TRIP_CURRENT_PROG 250 - ///////////////////////////////////////////////////////////////////////////////////// // // The IP port to talk to a WIFI or Ethernet shield. @@ -57,7 +52,6 @@ The configuration file for DCC++ EX Command Station // // DEFINE STATIC IP ADDRESS *OR* COMMENT OUT TO USE DHCP // - //#define IP_ADDRESS { 192, 168, 1, 200 } ///////////////////////////////////////////////////////////////////////////////////// @@ -67,49 +61,9 @@ The configuration file for DCC++ EX Command Station // Uncomment to use with Ethernet Shields // // NOTE: This is not used with ESP8266 WiFi modules. - +// // #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF } -///////////////////////////////////////////////////////////////////////////////////// -// -// Allows using a pin as a trigger for a scope or analyzer so we can capture only -// the important parts of the data stream -// -// USE_TRIGGERPIN: Enable code that switches the trigger pin on and off at end -// of the preamble. This takes some clock cycles in the -// interrupt routine for the main track. -// USE_TRIGGERPIN_PER_BIT: As above but for every bit. This only makes sense -// if USE_TRIGGERPIN is set. -// -// The value of the TRIGGERPIN is defined in DCCppEX.h because it might -// be board specific -// -//#define USE_TRIGGERPIN -//#define USE_TRIGGERPIN_PER_BIT - -///////////////////////////////////////////////////////////////////////////////////// -// -// Define only of you need the store to EEPROM feature. This takes RAM and -// you may need to use less MAX_MAIN_REGISTERS to compensate (at least on the UNO) - -#define EESTORE - -///////////////////////////////////////////////////////////////////////////////////// -// -// This shows the status and version at startup. This takes RAM. You can comment -// this line if you need to increase MAX_MAIN_REGISTERS(at least on the UNO) - -#define SHOWCONFIG - -///////////////////////////////////////////////////////////////////////////////////// -// -// This is different from the above config display which only shows one line at startup -// This defines a pin that when jumpered to ground before powering up the Arduinio, -// will display more detailed settings for diagnostics. You must remove the jumper and -// restart the Arduino to return to normal operation - -#define SHOW_CONFIG_DETAIL_PIN A2 - ///////////////////////////////////////////////////////////////////////////////////// // // DEFINE LCD SCREEN USAGE BY THE BASE STATION From 9917035534470d094618a11749696e0a7844a933 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 26 Sep 2020 23:33:47 +0200 Subject: [PATCH 42/49] PROG_TRIP_CURRENT defined here until made runtime changable --- MotorDrivers.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MotorDrivers.h b/MotorDrivers.h index 79bcd4f..b7ed3de 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -14,6 +14,10 @@ const byte UNUSED_PIN = 255; +// Trip current for programming track in mA. Change only if you really +// need to be non-NMRA-compliant because of decoders that are not either. +#define TRIP_CURRENT_PROG 250 + // MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, byte brake_pin, byte current_pin, // float senseFactor, unsigned int tripMilliamps, byte faultPin); From 60413b00e31334d3e8aca8e67cca3f3571320e2f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 26 Sep 2020 23:46:28 +0200 Subject: [PATCH 43/49] platformio conf --- platformio.ini | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/platformio.ini b/platformio.ini index 9fdcd26..e26a5e8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -43,6 +43,9 @@ framework = arduino lib_deps = ${env.lib_deps} DIO2 + arduino-libraries/Ethernet + SPI + mathertel/LiquidCrystal_PCF8574 [env:unowifiR2] platform = atmelmegaavr @@ -51,6 +54,9 @@ framework = arduino lib_deps = ${env.lib_deps} DIO2 + arduino-libraries/Ethernet + SPI + mathertel/LiquidCrystal_PCF8574 [env:uno] platform = atmelavr @@ -59,4 +65,6 @@ framework = arduino lib_deps = ${env.lib_deps} DIO2 + arduino-libraries/Ethernet + SPI mathertel/LiquidCrystal_PCF8574 From 6911d9ed01886b2c80552c3e09f1b0bad8893ccd Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 27 Sep 2020 00:44:45 +0200 Subject: [PATCH 44/49] better howto in README.md --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4bdc9c1..09437ac 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,20 @@ The DCC++ EX Command Station consists of an Arduino micro controller fitted with This repository, CommandStation-EX, contains a complete DCC++ EX Commmand Station sketch designed for compiling and uploading into an Arduino Uno, Mega, or Nano. All sketch files are in the folder named CommandStation-EX and its subforlders. More information about the sketch can be found in the included PDF file. -To utilize this sketch, you can use our automated installer, or download a zip file from this repository and open the file "CommandStation-EX.ino" after unzipping it to your Arduino IDE projects folder. Please do not rename the folder containing the sketch code, nor add any files in that folder. The Arduino IDE relies on the structure and name of the folder to properly display and compile the code. +To utilize this sketch, you can use the following methods: + +* our automated installer +* download a zip file from this repository (green Code button above) and unzip it +* use git clone on this repository + +With the manual methods you unzip or git clone to the Arduino IDE +projects folder and then open the file "CommandStation-EX.ino" in the +Arduino IDE. Please do not rename the folder containing the sketch +code, nor add any files in that folder. The Arduino IDE relies on the +structure and name of the folder to properly display and compile the +code. If you do not run the installer, you have to copy +config.example.h to config.h. If you do not have the standard config +you edit config.h according to the help texts in config.h. The latest production release of the Master branch is 3.0.1: From e02f29d132a4d3d8bf88b039364848aad7c1b12e Mon Sep 17 00:00:00 2001 From: Gregor Baues Date: Sun, 27 Sep 2020 11:03:05 +0200 Subject: [PATCH 45/49] plaformio.ini with monitor flags --- platformio.ini | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/platformio.ini b/platformio.ini index e26a5e8..f53a690 100644 --- a/platformio.ini +++ b/platformio.ini @@ -24,6 +24,8 @@ upload_protocol = atmel-ice lib_deps = ${env.lib_deps} SparkFun External EEPROM Arduino Library +monitor_speed = 115200 +monitor_flags = --echo [env:mega2560] platform = atmelavr @@ -35,6 +37,8 @@ lib_deps = arduino-libraries/Ethernet SPI mathertel/LiquidCrystal_PCF8574 +monitor_speed = 115200 +monitor_flags = --echo [env:mega328] platform = atmelavr @@ -46,6 +50,8 @@ lib_deps = arduino-libraries/Ethernet SPI mathertel/LiquidCrystal_PCF8574 +monitor_speed = 115200 +monitor_flags = --echo [env:unowifiR2] platform = atmelmegaavr @@ -57,6 +63,8 @@ lib_deps = arduino-libraries/Ethernet SPI mathertel/LiquidCrystal_PCF8574 +monitor_speed = 115200 +monitor_flags = --echo [env:uno] platform = atmelavr @@ -68,3 +76,5 @@ lib_deps = arduino-libraries/Ethernet SPI mathertel/LiquidCrystal_PCF8574 +monitor_speed = 115200 +monitor_flags = --echo From c14596a2528e3d1c911f89119d122987a56d788f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 27 Sep 2020 12:14:25 +0200 Subject: [PATCH 46/49] current trip values on PROG depending on state --- DCCWaveform.cpp | 13 ++++++++----- DCCWaveform.h | 4 +++- MotorDriver.cpp | 5 ++++- MotorDriver.h | 3 ++- MotorDrivers.h | 19 ++++++++----------- 5 files changed, 25 insertions(+), 19 deletions(-) diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index f66da28..dc4b503 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -123,10 +123,13 @@ void DCCWaveform::setPowerMode(POWERMODE mode) { void DCCWaveform::checkPowerOverload() { + static int progTripValue = motorDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once, hence static + if (millis() - lastSampleTaken < sampleDelay) return; lastSampleTaken = millis(); int tripValue= motorDriver->rawCurrentTripValue; - if (!isMainTrack && (ackPending || progTrackSyncMain)) tripValue=ACK_CURRENT_TRIP; + if (!isMainTrack && !ackPending && !progTrackSyncMain) + tripValue=progTripValue; switch (powerMode) { case POWERMODE::OFF: @@ -143,8 +146,8 @@ void DCCWaveform::checkPowerOverload() { if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT; } else { setPowerMode(POWERMODE::OVERLOAD); - unsigned int mA=motorDriver->convertToMilliamps(lastCurrent); - unsigned int maxmA=motorDriver->convertToMilliamps(tripValue); + unsigned int mA=motorDriver->raw2mA(lastCurrent); + unsigned int maxmA=motorDriver->raw2mA(tripValue); DIAG(F("\n*** %S TRACK POWER OVERLOAD current=%d max=%d offtime=%l ***\n"), isMainTrack ? F("MAIN") : F("PROG"), mA, maxmA, power_sample_overload_wait); power_good_counter=0; sampleDelay = power_sample_overload_wait; @@ -287,7 +290,7 @@ int DCCWaveform::getLastCurrent() { void DCCWaveform::setAckBaseline() { if (isMainTrack) return; ackThreshold=motorDriver->getCurrentRaw() + (int)(65 / motorDriver->senseFactor); - if (Diag::ACK) DIAG(F("\nACK-BASELINE %d/%dmA"),ackThreshold,motorDriver->convertToMilliamps(ackThreshold)); + if (Diag::ACK) DIAG(F("\nACK-BASELINE %d/%dmA"),ackThreshold,motorDriver->raw2mA(ackThreshold)); } void DCCWaveform::setAckPending() { @@ -303,7 +306,7 @@ void DCCWaveform::setAckPending() { byte DCCWaveform::getAck() { if (ackPending) return (2); // still waiting if (Diag::ACK) DIAG(F("\nACK-%S after %dmS max=%d/%dmA pulse=%duS"),ackDetected?F("OK"):F("FAIL"), ackCheckDuration, - ackMaxCurrent,motorDriver->convertToMilliamps(ackMaxCurrent), ackPulseDuration); + ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration); if (ackDetected) return (1); // Yes we had an ack return(0); // pending set off but not detected means no ACK. } diff --git a/DCCWaveform.h b/DCCWaveform.h index b1c6f7d..5af7144 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -105,7 +105,9 @@ class DCCWaveform { POWERMODE powerMode; unsigned long lastSampleTaken; unsigned int sampleDelay; - static const int ACK_CURRENT_TRIP=1000; // During ACK processing limit can be higher + // Trip current for programming track, 250mA. Change only if you really + // need to be non-NMRA-compliant because of decoders that are not either. + static const int TRIP_CURRENT_PROG=250; unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT; unsigned int power_good_counter = 0; diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 75b5d05..5420488 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -76,6 +76,9 @@ int MotorDriver::getCurrentRaw() { return analogReadFast(currentPin); } -unsigned int MotorDriver::convertToMilliamps( int raw) { +unsigned int MotorDriver::raw2mA( int raw) { return (unsigned int)(raw * senseFactor); } +int MotorDriver::mA2raw( unsigned int mA) { + return (int)(mA / senseFactor); +} diff --git a/MotorDriver.h b/MotorDriver.h index 8dbee0a..cce07ad 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -27,7 +27,8 @@ class MotorDriver { virtual void setSignal( bool high); virtual void setBrake( bool on); virtual int getCurrentRaw(); - virtual unsigned int convertToMilliamps( int rawValue); + virtual unsigned int raw2mA( int raw); + virtual int mA2raw( unsigned int mA); byte powerPin, signalPin, signalPin2, brakePin,currentPin,faultPin; float senseFactor; diff --git a/MotorDrivers.h b/MotorDrivers.h index b7ed3de..3695e0a 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -10,40 +10,37 @@ // This file contains configurations for known/supported motor shields. // A configuration defined by macro here can be used in your sketch. // A custom hardware setup will require your sketch to create MotorDriver instances -// similar to those defined here, WITHOUT editing this file. +// similar to those defined here, WITHOUT editing this file. You can put your +// custom defines in config.h. const byte UNUSED_PIN = 255; -// Trip current for programming track in mA. Change only if you really -// need to be non-NMRA-compliant because of decoders that are not either. -#define TRIP_CURRENT_PROG 250 - // MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, byte brake_pin, byte current_pin, // float senseFactor, unsigned int tripMilliamps, byte faultPin); // Arduino standard Motor Shield #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ new MotorDriver(3, 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \ - new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, TRIP_CURRENT_PROG, UNUSED_PIN) + new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN) // Pololu Motor Shield #define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \ - new MotorDriver(4, 7, UNUSED_PIN, 9, A0, 18, 2000, 12), \ - new MotorDriver(2, 8, UNUSED_PIN, 10, A1, 18, TRIP_CURRENT_PROG, UNUSED_PIN) + new MotorDriver(4, 7, UNUSED_PIN, 9, A0, 18, 3000, 12), \ + new MotorDriver(2, 8, UNUSED_PIN, 10, A1, 18, 3000, UNUSED_PIN) // Firebox Mk1 #define FIREBOX_MK1 F("FIREBOX_MK1"), \ new MotorDriver(3, 6, 7, UNUSED_PIN, A5, 9.766, 5500, UNUSED_PIN), \ - new MotorDriver(4, 8, 9, UNUSED_PIN, A1, 5.00, TRIP_CURRENT_PROG, UNUSED_PIN) + new MotorDriver(4, 8, 9, UNUSED_PIN, A1, 5.00, 1000, UNUSED_PIN) // Firebox Mk1S #define FIREBOX_MK1S F("FIREBOX_MK1A"), \ new MotorDriver(24, 21, 22, 25, 23, 9.766, 5500, UNUSED_PIN), \ - new MotorDriver(30, 27, 28, 31, 29, 5.00, TRIP_CURRENT_PROG, UNUSED_PIN) + new MotorDriver(30, 27, 28, 31, 29, 5.00, 1000, UNUSED_PIN) // FunduMoto Motor Shield #define FUNDUMOTO_SHIELD F("FUNDUMOTO_SHIELD"), \ new MotorDriver(10, 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \ - new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, TRIP_CURRENT_PROG, UNUSED_PIN) + new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN) #endif From 85d60dfbbd9a23caba84d926d55b373a8f9e50a8 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 27 Sep 2020 13:03:46 +0200 Subject: [PATCH 47/49] D PROGBOOST ON command --- DCC.cpp | 3 +++ DCC.h | 1 + DCCEXParser.cpp | 6 ++++++ DCCWaveform.cpp | 3 ++- DCCWaveform.h | 1 + 5 files changed, 13 insertions(+), 1 deletion(-) diff --git a/DCC.cpp b/DCC.cpp index ebf51de..29fa9d7 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -207,6 +207,9 @@ void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) { void DCC::setProgTrackSyncMain(bool on) { DCCWaveform::progTrackSyncMain=on; } +void DCC::setProgTrackBoost(bool on) { + DCCWaveform::progTrackBoosted=on; +} __FlashStringHelper* DCC::getMotorShieldName() { return shieldName; diff --git a/DCC.h b/DCC.h index 1e1058b..0e8fcc6 100644 --- a/DCC.h +++ b/DCC.h @@ -76,6 +76,7 @@ public: static void setAccessory(int aAdd, byte aNum, bool activate); static bool writeTextPacket(byte *b, int nBytes); static void setProgTrackSyncMain(bool on); // when true, prog track becomes driveable + static void setProgTrackBoost(bool on); // when true, special prog track current limit does not apply // ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1 static void readCV(int cv, ACK_CALLBACK callback, bool blocking = false); diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 82cd9d9..b52a968 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -45,6 +45,7 @@ const int HASH_KEYWORD_ACK = 3113; const int HASH_KEYWORD_ON = 2657; const int HASH_KEYWORD_DCC = 6436; const int HASH_KEYWORD_SLOW = -17209; +const int HASH_KEYWORD_PROGBOOST = -6353; int DCCEXParser::stashP[MAX_PARAMS]; bool DCCEXParser::stashBusy; @@ -598,6 +599,11 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[]) case HASH_KEYWORD_DCC: DCCWaveform::setDiagnosticSlowWave(params >= 1 && p[1] == HASH_KEYWORD_SLOW); return true; + + case HASH_KEYWORD_PROGBOOST: + DCC::setProgTrackBoost(true); + return true; + default: // invalid/unknown break; } diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index dc4b503..c67dcd6 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -30,6 +30,7 @@ DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false); bool DCCWaveform::progTrackSyncMain=false; +bool DCCWaveform::progTrackBoosted=false; VirtualTimer * DCCWaveform::interruptTimer=NULL; void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber) { @@ -128,7 +129,7 @@ void DCCWaveform::checkPowerOverload() { if (millis() - lastSampleTaken < sampleDelay) return; lastSampleTaken = millis(); int tripValue= motorDriver->rawCurrentTripValue; - if (!isMainTrack && !ackPending && !progTrackSyncMain) + if (!isMainTrack && !ackPending && !progTrackSyncMain && !progTrackBoosted) tripValue=progTripValue; switch (powerMode) { diff --git a/DCCWaveform.h b/DCCWaveform.h index 5af7144..d0f6d6e 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -67,6 +67,7 @@ class DCCWaveform { void setAckPending(); //prog track only byte getAck(); //prog track only 0=NACK, 1=ACK 2=keep waiting static bool progTrackSyncMain; // true when prog track is a siding switched to main + static bool progTrackBoosted; // true when prog track is not current limited inline void doAutoPowerOff() { if (autoPowerOff) { setPowerMode(POWERMODE::OFF); From 1f03d9759ddfa15e99d137052b69394ecb8baf25 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 27 Sep 2020 13:12:02 +0200 Subject: [PATCH 48/49] cap overcurrent off time to 10sec --- DCCWaveform.cpp | 5 ++++- DCCWaveform.h | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index c67dcd6..a70ab16 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -152,7 +152,10 @@ void DCCWaveform::checkPowerOverload() { DIAG(F("\n*** %S TRACK POWER OVERLOAD current=%d max=%d offtime=%l ***\n"), isMainTrack ? F("MAIN") : F("PROG"), mA, maxmA, power_sample_overload_wait); power_good_counter=0; sampleDelay = power_sample_overload_wait; - power_sample_overload_wait *= 2; + if (power_sample_overload_wait >= 10000) + power_sample_overload_wait = 10000; + else + power_sample_overload_wait *= 2; } break; case POWERMODE::OVERLOAD: diff --git a/DCCWaveform.h b/DCCWaveform.h index d0f6d6e..d607420 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -22,14 +22,16 @@ #include "MotorDriver.h" #include "ArduinoTimers.h" +// Wait times for power management. Unit: milliseconds const int POWER_SAMPLE_ON_WAIT = 100; const int POWER_SAMPLE_OFF_WAIT = 1000; const int POWER_SAMPLE_OVERLOAD_WAIT = 20; +// Ack time thresholds. Unit: microseconds const int MIN_ACK_PULSE_DURATION = 2000; const int MAX_ACK_PULSE_DURATION = 8500; - +// Number of preamble bits. const int PREAMBLE_BITS_MAIN = 16; const int PREAMBLE_BITS_PROG = 22; From 7cd9e687177c99bd1a05f6dcac622dedd62a849d Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 27 Sep 2020 13:26:29 +0200 Subject: [PATCH 49/49] turn PROGBOOST off at PROG off --- DCCEXParser.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index b52a968..8a96e23 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -318,6 +318,8 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking) { DCCWaveform::mainTrack.setPowerMode(mode); 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(""), opcode); return; } @@ -330,6 +332,8 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking) 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(""), opcode); return; case HASH_KEYWORD_JOIN: