Initialisation depot

This commit is contained in:
2026-02-22 11:44:25 +00:00
commit b891c12400
15 changed files with 578 additions and 0 deletions

87
README.md Normal file
View File

@@ -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.

View File

@@ -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 =

9
platformio.ini Normal file
View File

@@ -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

9
src/DCC.cpp Normal file
View File

@@ -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
}

5
src/DCC.h Normal file
View File

@@ -0,0 +1,5 @@
#pragma once
#include <Arduino.h>
void setupDCC();
void sendDCCPacket(const uint8_t* packet, size_t length);

61
src/DisplayHelper.cpp Normal file
View File

@@ -0,0 +1,61 @@
#include "DisplayHelper.h"
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#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();
}

9
src/DisplayHelper.h Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
#include <Arduino.h>
#include <Adafruit_SSD1306.h>
// extern Adafruit_SSD1306 display;
void displayInit();
void displayShowIP(const String& ip);
void displayShowPortal();

97
src/WiFiManagerHelper.cpp Normal file
View File

@@ -0,0 +1,97 @@
#include "WiFiManagerHelper.h"
#include <ESP8266WiFi.h>
#include <WiFiManager.h>
#include <EEPROM.h>
#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());
}

4
src/WiFiManagerHelper.h Normal file
View File

@@ -0,0 +1,4 @@
#pragma once
#include <Arduino.h>
void setupWiFiManager();

13
src/XpressNet.cpp Normal file
View File

@@ -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
}

6
src/XpressNet.h Normal file
View File

@@ -0,0 +1,6 @@
#pragma once
#include <Arduino.h>
void setupXpressNet();
void handleXpressNet();
void sendXpressNetMessage(const uint8_t* data, size_t length);

72
src/main.cpp Normal file
View File

@@ -0,0 +1,72 @@
#include <Arduino.h>
#include <Adafruit_SSD1306.h>
#include <Wire.h>
#include "DisplayHelper.h"
#include "WiFiManagerHelper.h"
#include "main.h"
#include <ESP8266WebServer.h>
#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(
<!DOCTYPE html>
<html lang=\"en\">
<head>
<meta charset=\"UTF-8\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
<title>Welcome to ESP8266 DCC Command Station</title>
<style>
body { font-family: Arial, sans-serif; background: #f4f4f4; color: #333; margin: 0; padding: 0; }
.container { max-width: 600px; margin: 40px auto; background: #fff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); padding: 32px; }
h1 { color: #0077cc; }
p { font-size: 1.2em; }
</style>
</head>
<body>
<div class=\"container\">
<h1>Welcome!</h1>
<p>This is your ESP8266 DCC Command Station.</p>
<p>Configure WiFi, manage XpressNet, and control your layout from here.</p>
<p>For documentation, see <a href=\"/xpressnet.md\">XpressNet Commands</a> and <a href=\"/README.md\">Hardware Setup</a>.</p>
</div>
</body>
</html>
)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);
}

33
src/main.h Normal file
View File

@@ -0,0 +1,33 @@
// expose display and other shared resources
#pragma once
#include <Adafruit_SSD1306.h>
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)
};

22
welcome.html Normal file
View File

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome to ESP8266 DCC Command Station</title>
<style>
body { font-family: Arial, sans-serif; background: #f4f4f4; color: #333; margin: 0; padding: 0; }
.container { max-width: 600px; margin: 40px auto; background: #fff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); padding: 32px; }
h1 { color: #0077cc; }
p { font-size: 1.2em; }
</style>
</head>
<body>
<div class="container">
<h1>Welcome!</h1>
<p>This is your ESP8266 DCC Command Station.</p>
<p>Configure WiFi, manage XpressNet, and control your layout from here.</p>
<p>For documentation, see <a href="/xpressnet.md">XpressNet Commands</a> and <a href="/README.md">Hardware Setup</a>.</p>
</div>
</body>
</html>

132
xpressnet.md Normal file
View File

@@ -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.h>
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.