From b891c124000526b3fc8106bbf628f500c2d1f86c Mon Sep 17 00:00:00 2001 From: Serge NOEL Date: Sun, 22 Feb 2026 11:44:25 +0000 Subject: [PATCH] Initialisation depot --- README.md | 87 +++++++++++++++++++++ include/pio_config_template.ini | 19 +++++ platformio.ini | 9 +++ src/DCC.cpp | 9 +++ src/DCC.h | 5 ++ src/DisplayHelper.cpp | 61 +++++++++++++++ src/DisplayHelper.h | 9 +++ src/WiFiManagerHelper.cpp | 97 +++++++++++++++++++++++ src/WiFiManagerHelper.h | 4 + src/XpressNet.cpp | 13 ++++ src/XpressNet.h | 6 ++ src/main.cpp | 72 +++++++++++++++++ src/main.h | 33 ++++++++ welcome.html | 22 ++++++ xpressnet.md | 132 ++++++++++++++++++++++++++++++++ 15 files changed, 578 insertions(+) create mode 100644 README.md create mode 100644 include/pio_config_template.ini create mode 100644 platformio.ini create mode 100644 src/DCC.cpp create mode 100644 src/DCC.h create mode 100644 src/DisplayHelper.cpp create mode 100644 src/DisplayHelper.h create mode 100644 src/WiFiManagerHelper.cpp create mode 100644 src/WiFiManagerHelper.h create mode 100644 src/XpressNet.cpp create mode 100644 src/XpressNet.h create mode 100644 src/main.cpp create mode 100644 src/main.h create mode 100644 welcome.html create mode 100644 xpressnet.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..2b0b700 --- /dev/null +++ b/README.md @@ -0,0 +1,87 @@ +# HW 364A ESP8266 DCC Command Station with XpressNet + +This is a minimal HW 364A ESP8266 project for a DCC Command Station using the LM18200 motor driver, now with XpressNet support for communication with throttles and other XpressNet devices. + + + +## Structure +- `src/main.cpp`: Main application entry point +- `src/DCC.cpp` / `src/DCC.h`: DCC signal generation and handling +- `src/XpressNet.cpp` / `src/XpressNet.h`: XpressNet protocol interface (stubs) +- `src/WiFiManagerHelper.cpp` / `src/WiFiManagerHelper.h`: WiFiManager-based configuration + + + +## Getting Started + +1. Open the project in PlatformIO or Arduino IDE. +2. Select the HW 364A ESP8266 as your target board. +3. Connect the LM18200 and XpressNet bus to the HW 364A ESP8266 as per your hardware setup. +4. Build and upload to your HW 364A ESP8266 board. +5. On first boot, connect to the WiFi AP named `DCC-CommandStation` to configure your WiFi credentials using the captive portal. + + + + +## Notes +- Wire the LM18200 motor driver to the ESP32 according to your motor control and DCC output requirements. Refer to the LM18200 datasheet for correct pinout and power handling. +- Use of included display +- This is a minimal template. You must implement the DCC signal generation logic for your hardware. +- XpressNet protocol functions are provided as stubs for further development. +- WiFiManager is used for easy WiFi configuration by the user. + + + +## Hardware Compatibility: HW 364A ESP8266 + +This project is designed for the HW 364A ESP8266 board. Ensure you select this board in PlatformIO or Arduino IDE for correct pin mapping and features. + +### Connecting the LM18200 Motor Driver + +The LM18200 is used to drive the DCC signal to the track. For RailCom cutout, you must implement a hardware or software cut-off during the RailCom window. + +**Basic Wiring:** +- **IN1/IN2:** Connect to ESP8266 GPIOs for DCC signal generation (e.g., GPIO 5 and GPIO 4). +- **PWM:** Connect to a PWM-capable GPIO if needed for DCC modulation. +- **VCC/GND:** Connect to your power supply and ground. +- **OUT1/OUT2:** Connect to the track. +- **RailCom Cutout:** + - To implement RailCom cutout, use a MOSFET or relay to disconnect the track output during the RailCom window. Control the MOSFET/relay from an ESP8266 GPIO. + - Alternatively, use a dedicated circuit to blank the DCC signal during the cutout period. + +**Example RailCom Cutout Circuit:** +``` +ESP8266 GPIO ----> MOSFET Gate +MOSFET Drain ----> Track Output +MOSFET Source ---> GND +``` +- Set the GPIO HIGH to enable track power, LOW to cut off for RailCom. + +### XpressNet Bus +- Connect the XpressNet bus (RS485 or TTL UART) to the ESP8266 UART pins. +- Use a level shifter if required for voltage compatibility. + +### Display +- Use an I2C OLED display (e.g., SSD1306). +- Connect SDA to GPIO 14 (D6), SCL to GPIO 12 (D5) as per sample code. + +--- + +## Software Features +- DCC signal generation (implement in DCC.cpp) +- RailCom cutout support (add cutout logic in DCC routines) +- XpressNet protocol (see xpressnet.md for commands and custom extensions) +- WiFi configuration via captive portal +- Optional XpressNet over IP (WebSocket) + +--- + +## References +- LM18200 Datasheet +- HW 364A ESP8266 Pinout +- [RailCom Protocol Overview](https://www.opendcc.de/elektronik/railcom/railcom_e.html) +- [XpressNet Protocol](https://www.opendcc.de/elektronik/xpressnet/xpressnet_e.html) + +--- + +For further details, see xpressnet.md and source files. diff --git a/include/pio_config_template.ini b/include/pio_config_template.ini new file mode 100644 index 0000000..4f4dd10 --- /dev/null +++ b/include/pio_config_template.ini @@ -0,0 +1,19 @@ +; PlatformIO Project Configuration File +; See https://docs.platformio.org/en/latest/projectconf.html + +[platformio] +default_envs = d1_mini32 + +[env:d1_mini32] +platform = espressif32 +board = d1_mini32 +framework = arduino +monitor_speed = 115200 +lib_deps = + tzapu/WiFiManager + Wire + LiquidCrystal_I2C + +; You can add custom build flags, upload options, etc. below +; build_flags = +; upload_speed = diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..ff5e5f2 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,9 @@ +[env:d1_mini] +platform = espressif8266 +board = d1_mini +framework = arduino +monitor_speed = 115200 +lib_deps = + tzapu/WiFiManager + Wire + adafruit/Adafruit SSD1306 diff --git a/src/DCC.cpp b/src/DCC.cpp new file mode 100644 index 0000000..3323f3d --- /dev/null +++ b/src/DCC.cpp @@ -0,0 +1,9 @@ +#include "DCC.h" + +void setupDCC() { + // Initialize DCC output pins and timers +} + +void sendDCCPacket(const uint8_t* packet, size_t length) { + // Generate DCC signal using LM18200 +} diff --git a/src/DCC.h b/src/DCC.h new file mode 100644 index 0000000..9b2e2ce --- /dev/null +++ b/src/DCC.h @@ -0,0 +1,5 @@ +#pragma once +#include + +void setupDCC(); +void sendDCCPacket(const uint8_t* packet, size_t length); diff --git a/src/DisplayHelper.cpp b/src/DisplayHelper.cpp new file mode 100644 index 0000000..7fc25e0 --- /dev/null +++ b/src/DisplayHelper.cpp @@ -0,0 +1,61 @@ +#include "DisplayHelper.h" +#include +#include +#include "main.h" + +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 64 +#define OLED_RESET -1 +#define SCREEN_ADDRESS 0x3C // If not work please scan the bus +#define OLED_SDA 14 // D6 +#define OLED_SCL 12 // D5 + +// Adafruit_SSD1306 *display; + + + + +// Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); + +void displayInit() { + display = new Adafruit_SSD1306(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); + Wire.begin(OLED_SDA, OLED_SCL); + display->begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS); + display->clearDisplay(); + display->setTextSize(1); + display->setTextColor(SSD1306_WHITE); + display->setCursor(0, 0); + display->println("DCC Cmd Station"); + display->display(); +} + +void displayShowIP(const String& ip) { + display->clearDisplay(); + display->setCursor(0, 0); + display->println("DCC Cmd Station"); + display->setCursor(0, 16); + display->println("WiFi Connected!"); + display->setCursor(0, 32); + display->print("IP: "); + display->println(ip); + display->display(); + delay(3000); // Show IP for 5 seconds before clearing + display->clearDisplay(); + display->setCursor(0, 0); + display->println("DCC Cmd Station"); + display->setCursor(0, 16); + display->print("IP: "); + display->println(ip); + display->display(); +} + +void displayShowPortal() { + display->clearDisplay(); + display->setCursor(0, 0); + display->println("DCC Cmd Station"); + display->setCursor(0, 16); + display->println("Please Connect to"); + display->setCursor(0, 32); + display->println("DCC-CommandStation"); + display->display(); +} diff --git a/src/DisplayHelper.h b/src/DisplayHelper.h new file mode 100644 index 0000000..0929119 --- /dev/null +++ b/src/DisplayHelper.h @@ -0,0 +1,9 @@ +#pragma once +#include +#include + +// extern Adafruit_SSD1306 display; + +void displayInit(); +void displayShowIP(const String& ip); +void displayShowPortal(); diff --git a/src/WiFiManagerHelper.cpp b/src/WiFiManagerHelper.cpp new file mode 100644 index 0000000..0183896 --- /dev/null +++ b/src/WiFiManagerHelper.cpp @@ -0,0 +1,97 @@ + +#include "WiFiManagerHelper.h" + + +#include +#include +#include +#include "DisplayHelper.h" +#include "main.h" + +#define EEPROM_SIZE 96 +#define SSID_ADDR 0 +#define PASS_ADDR 32 +#define MAX_CRED_LEN 32 + + +// Save WiFi credentials to EEPROM +void saveCredentialsToEEPROM(const char* ssid, const char* pass) { + EEPROM.begin(EEPROM_SIZE); + for (int i = 0; i < MAX_CRED_LEN; i++) { + EEPROM.write(SSID_ADDR + i, i < strlen(ssid) ? ssid[i] : 0); + EEPROM.write(PASS_ADDR + i, i < strlen(pass) ? pass[i] : 0); + } + EEPROM.commit(); + EEPROM.end(); +} + +// Load WiFi credentials from EEPROM +void loadCredentialsFromEEPROM(char* ssid, char* pass) { + EEPROM.begin(EEPROM_SIZE); + for (int i = 0; i < MAX_CRED_LEN; i++) { + ssid[i] = EEPROM.read(SSID_ADDR + i); + pass[i] = EEPROM.read(PASS_ADDR + i); + } + ssid[MAX_CRED_LEN - 1] = 0; + pass[MAX_CRED_LEN - 1] = 0; + EEPROM.end(); +} + +// Callback for WiFiManager to save credentials +void saveWifiCallback() { + Serial.println("[WiFiManager] Settings saved."); + WiFiManager wifiManager; + const char* ssid = WiFi.SSID().c_str(); + const char* pass = WiFi.psk().c_str(); + saveCredentialsToEEPROM(ssid, pass); +} + + +void setupWiFiManager() { + char ssid[MAX_CRED_LEN] = {0}; + char pass[MAX_CRED_LEN] = {0}; + + loadCredentialsFromEEPROM(ssid, pass); + // displayInit(); + + if (strlen(ssid) > 0) { + Serial.print("[WiFiManager] Trying stored credentials: "); + display->setCursor(0, 16); + display->print("Connecting to Wifi "); + // display->println(ssid); + display->display(); + Serial.println(ssid); + WiFi.begin(ssid, pass); + unsigned long start = millis(); + while (WiFi.status() != WL_CONNECTED && millis() - start < 10000) { + delay(500); + Serial.print("."); + } + Serial.println(); + if (WiFi.status() == WL_CONNECTED) { + Serial.print("[WiFiManager] Connected with stored credentials! IP: "); + Serial.println(WiFi.localIP()); + displayShowIP(WiFi.localIP().toString()); + return; + } else { + Serial.println("[WiFiManager] Failed to connect with stored credentials."); + } + } + + WiFiManager wifiManager; + wifiManager.setSaveConfigCallback(saveWifiCallback); + displayShowPortal(); + // Uncomment to reset saved settings for testing + // wifiManager.resetSettings(); + if (!wifiManager.autoConnect("DCC-CommandStation")) { + Serial.println("[WiFiManager] Failed to connect and hit timeout. Restarting..."); + delay(3000); + ESP.restart(); + delay(5000); + } + // Save credentials after successful connection + saveCredentialsToEEPROM(WiFi.SSID().c_str(), WiFi.psk().c_str()); + Serial.print("[WiFiManager] Connected! IP address: "); + Serial.println(WiFi.localIP()); + // displayShowIP(WiFi.localIP().toString()); +} diff --git a/src/WiFiManagerHelper.h b/src/WiFiManagerHelper.h new file mode 100644 index 0000000..2d0bb92 --- /dev/null +++ b/src/WiFiManagerHelper.h @@ -0,0 +1,4 @@ +#pragma once +#include + +void setupWiFiManager(); diff --git a/src/XpressNet.cpp b/src/XpressNet.cpp new file mode 100644 index 0000000..82ffa1b --- /dev/null +++ b/src/XpressNet.cpp @@ -0,0 +1,13 @@ +#include "XpressNet.h" + +void setupXpressNet() { + // Initialize XpressNet communication (e.g., Serial, pins) +} + +void handleXpressNet() { + // Poll and process XpressNet messages +} + +void sendXpressNetMessage(const uint8_t* data, size_t length) { + // Send a message to the XpressNet bus +} diff --git a/src/XpressNet.h b/src/XpressNet.h new file mode 100644 index 0000000..a1ee738 --- /dev/null +++ b/src/XpressNet.h @@ -0,0 +1,6 @@ +#pragma once +#include + +void setupXpressNet(); +void handleXpressNet(); +void sendXpressNetMessage(const uint8_t* data, size_t length); diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..90b08ab --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,72 @@ +#include +#include +#include +#include "DisplayHelper.h" +#include "WiFiManagerHelper.h" +#include "main.h" +#include + +#define SCREEN_WIDTH 128 // OLED display width, in pixels +#define SCREEN_HEIGHT 64 // OLED display height, in pixels +#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) +#define SCREEN_ADDRESS 0x3C // If not work please scan the bus +#define OLED_SDA 14 // D6 +#define OLED_SCL 12 // D5 + +// Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); +Adafruit_SSD1306 *display; + +// Mode for command station operation +// Remove conflicting definition, use extern from main.h +// Mode currentMode = MODE_OFF; + +// Create web server instance on port 80 +ESP8266WebServer server(80); + +// HTML content for welcome page +const char* welcome_html = R"HTML( + + + + + + Welcome to ESP8266 DCC Command Station + + + +
+

Welcome!

+

This is your ESP8266 DCC Command Station.

+

Configure WiFi, manage XpressNet, and control your layout from here.

+

For documentation, see XpressNet Commands and Hardware Setup.

+
+ + +)HTML"; + +void handleWelcome() { + server.send(200, "text/html", welcome_html); +} + +void setup() { + Serial.begin(115200); + delay(100); + Serial.println("[DEBUG] Serial started at 115200 baud"); + displayInit(); + Serial.println("[DEBUG] OLED display initialized"); + setupWiFiManager(); + server.on("/", handleWelcome); + server.begin(); + Serial.println("[DEBUG] Web server started"); +} + +void loop() { + server.handleClient(); + delay(1000); +} + diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..834b466 --- /dev/null +++ b/src/main.h @@ -0,0 +1,33 @@ +// expose display and other shared resources +#pragma once + +#include + +extern Adafruit_SSD1306 *display; +extern int currentMode; + +// static const uint8_t D0 = 16; +// static const uint8_t D1 = 5; +// static const uint8_t D2 = 4; +// static const uint8_t D3 = 0; +// static const uint8_t D4 = 2; +// static const uint8_t D5 = 14; +// static const uint8_t D6 = 12; +// static const uint8_t D7 = 13; +// static const uint8_t D8 = 15; +// static const uint8_t D9 = 3; +// static const uint8_t D10 = 1; + +// LM18200 pin definitions +#define PWM_PIN 5 // D1 +#define DIR_PIN 4 // D2 +#define BRAKE_PIN 0 // D3 +#define MARKLIN_RELAY_PIN 2 // D4 // Relay to swith setup (28V) to reverse polarity for Marklin relays + +// Mode for command station operation +enum Mode { + MODE_OFF, // No operation + MODE_DCC, // DCC command station mode + MODE_ANA, // Analog command station mode (PWM) + MODE_MAR // Märklin command station mode (PWM with setup for Marklin relays) +}; diff --git a/welcome.html b/welcome.html new file mode 100644 index 0000000..5139f76 --- /dev/null +++ b/welcome.html @@ -0,0 +1,22 @@ + + + + + + Welcome to ESP8266 DCC Command Station + + + +
+

Welcome!

+

This is your ESP8266 DCC Command Station.

+

Configure WiFi, manage XpressNet, and control your layout from here.

+

For documentation, see XpressNet Commands and Hardware Setup.

+
+ + diff --git a/xpressnet.md b/xpressnet.md new file mode 100644 index 0000000..4df62f0 --- /dev/null +++ b/xpressnet.md @@ -0,0 +1,132 @@ +# XpressNet Command Reference + +This document describes the available XpressNet commands and how to implement them in your command station firmware. + +## XpressNet Command Table + +| Command Name | Code (Hex) | Description | Typical Usage | +|-----------------------|------------|---------------------------------------------|------------------------------| +| Locomotive Speed | 0xE4 | Set speed and direction for a locomotive | Control train movement | +| Function Group 1 | 0xE4 | Control F0-F4 functions | Lights, sound, etc. | +| Function Group 2 | 0xE4 | Control F5-F8 functions | Extended functions | +| Accessory Control | 0x90 | Switch turnouts, signals, etc. | Control layout accessories | +| Read Feedback | 0xF2 | Query feedback modules | Detect occupancy, sensors | +| Programming on Main | 0xEF | Write CVs to decoders on main track | Decoder configuration | +| Programming Track | 0xED | Write/read CVs on programming track | Safe decoder programming | +| Request Version | 0x21 | Request command station version | Handshake, diagnostics | +| Emergency Stop | 0x80 | Stop all locomotives immediately | Safety/emergency | + +*Note: Actual codes and command structure may vary by implementation. Refer to the official XpressNet protocol documentation for full details.* + +## Implementing XpressNet in Your Command Station + +1. **Serial Communication**: XpressNet typically uses RS485 or TTL UART. Set up a serial interface at the required baud rate (commonly 62500 baud). + +2. **Command Parsing**: In your firmware, implement a parser that reads incoming bytes and matches them to the command table above. Use a state machine to handle multi-byte commands. + +3. **Command Handling**: + - For each recognized command, implement a handler function (e.g., `handleLocomotiveSpeed()`, `handleAccessoryControl()`). + - Extract parameters (address, speed, function bits, etc.) from the command bytes. + - Update your internal state or send DCC packets as needed. + +4. **Response Generation**: Some commands require a response (e.g., version request, feedback read). Format and send the appropriate reply bytes. + +5. **Error Handling**: Implement checks for invalid or unsupported commands and respond with error codes if required by the protocol. + +6. **Integration with DCC**: For commands that affect trains or accessories, translate XpressNet commands into DCC packets and send them to the track using your DCC output routines. + +### Example: Handling a Locomotive Speed Command +```cpp +void handleLocomotiveSpeed(const uint8_t* data, size_t len) { + // Parse address, speed, direction from data + // Update DCC packet buffer + // Send DCC packet to track +} +``` + +### References +- [XpressNet Protocol Specification](https://www.opendcc.de/elektronik/xpressnet/xpressnet_e.html) +- [DCC Protocol Overview](https://www.nmra.org/index-nmra-standards-and-recommended-practices) + +--- + +## Custom (DIY) XpressNet Extensions + +You can add your own special commands to XpressNet by using unused command codes. Below are examples for switching mode and power control. + +| Command Name | Code (Hex) | Payload Example | Description | +|---------------|------------|----------------|----------------------------| +| Switch Mode | 0xF0 | 0x00 | Switch to Analog mode | +| | | 0x01 | Switch to DCC mode | +| | | 0x02 | Switch to Marklin mode | +| Power Track | 0xF1 | 0x00 | Power OFF | +| | | 0x01 | Power ON | + +### Example Implementation in Firmware +```cpp +// Handle custom XpressNet commands +void handleCustomXpressNet(const uint8_t* data, size_t len) { + uint8_t cmd = data[0]; + switch (cmd) { + case 0xF0: // Switch Mode + if (len > 1) { + uint8_t mode = data[1]; + switch (mode) { + case 0x00: /* setAnalogMode(); */ break; + case 0x01: /* setDCCMode(); */ break; + case 0x02: /* setMarklinMode(); */ break; + } + } + break; + case 0xF1: // Power Track + if (len > 1) { + if (data[1] == 0x01) { + // powerOnTrack(); + } else { + // powerOffTrack(); + } + } + break; + // ... handle other custom commands ... + } +} +``` + +--- + +## XpressNet over IP (WebSocket) + +You can encapsulate XpressNet packets in WebSocket frames for remote control over IP. On the ESP8266, use a WebSocket server library (e.g., arduinoWebSockets). + +**Basic Steps:** +1. Start a WebSocket server on the ESP8266. +2. On message received, treat the payload as an XpressNet packet and process it. +3. Send any response packets back over WebSocket. + +**Example (pseudo-code):** +```cpp +#include +WebSocketsServer webSocket = WebSocketsServer(81); + +void onWebSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { + if (type == WStype_BIN) { + // Process XpressNet packet + handleXpressNet(payload, length); + // Optionally send response: + // webSocket.sendBIN(num, response, responseLen); + } +} + +void setup() { + // ... WiFi setup ... + webSocket.begin(); + webSocket.onEvent(onWebSocketEvent); +} + +void loop() { + webSocket.loop(); +} +``` + +--- +These extensions allow you to add custom features and remote control to your XpressNet command station.