mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-29 18:33:44 +02:00
Compare commits
1 Commits
v4.2.9-Dev
...
add-pca_tc
Author | SHA1 | Date | |
---|---|---|---|
|
51b88bdf45 |
13
.gitignore
vendored
13
.gitignore
vendored
@@ -8,14 +8,11 @@ Release/*
|
||||
.vscode/
|
||||
config.h
|
||||
.vscode/*
|
||||
# mySetup.h
|
||||
mySetup.h
|
||||
mySetup.cpp
|
||||
myHal.cpp
|
||||
# myAutomation.h
|
||||
myAutomation.h
|
||||
myFilter.cpp
|
||||
# myAutomation.h
|
||||
# myLayout.h
|
||||
my*.h
|
||||
!my*.example.h
|
||||
.vscode/extensions.json
|
||||
.vscode/extensions.json
|
||||
myAutomation.h
|
||||
myFilter.cpp
|
||||
myLayout.h
|
||||
|
@@ -28,145 +28,100 @@
|
||||
#include "defines.h"
|
||||
#include "DCCWaveform.h"
|
||||
#include "DCC.h"
|
||||
#include "TrackManager.h"
|
||||
|
||||
#if defined(BIG_MEMORY) | defined(WIFI_ON) | defined(ETHERNET_ON)
|
||||
// This section of CommandDistributor is simply not relevant on a uno or similar
|
||||
const byte NO_CLIENT=255;
|
||||
|
||||
#if WIFI_ON || ETHERNET_ON || defined(SERIAL1_COMMANDS) || defined(SERIAL2_COMMANDS) || defined(SERIAL3_COMMANDS)
|
||||
// use a buffer to allow broadcast
|
||||
StringBuffer * CommandDistributor::broadcastBufferWriter=new StringBuffer();
|
||||
template<typename... Targs> void CommandDistributor::broadcastReply(clientType type, Targs... msg){
|
||||
broadcastBufferWriter->flush();
|
||||
StringFormatter::send(broadcastBufferWriter, msg...);
|
||||
broadcastToClients(type);
|
||||
}
|
||||
#else
|
||||
// on a single USB connection config, write direct to Serial and ignore flush/shove
|
||||
template<typename... Targs> void CommandDistributor::broadcastReply(clientType type, Targs... msg){
|
||||
(void)type; //shut up compiler warning
|
||||
StringFormatter::send(&Serial, msg...);
|
||||
}
|
||||
#endif
|
||||
RingStream * CommandDistributor::ring=0;
|
||||
byte CommandDistributor::ringClient=NO_CLIENT;
|
||||
CommandDistributor::clientType CommandDistributor::clients[8]={
|
||||
NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE};
|
||||
RingStream * CommandDistributor::broadcastBufferWriter=new RingStream(100);
|
||||
|
||||
#ifdef CD_HANDLE_RING
|
||||
// wifi or ethernet ring streams with multiple client types
|
||||
RingStream * CommandDistributor::ring=0;
|
||||
CommandDistributor::clientType CommandDistributor::clients[8]={
|
||||
NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE};
|
||||
|
||||
// Parse is called by Withrottle or Ethernet interface to determine which
|
||||
// protocol the client is using and call the appropriate part of dcc++Ex
|
||||
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream) {
|
||||
if (Diag::WIFI && Diag::CMD)
|
||||
DIAG(F("Parse C=%d T=%d B=%s"),clientId, clients[clientId], buffer);
|
||||
ring=stream;
|
||||
|
||||
// First check if the client is not known
|
||||
// yet and in that case determinine type
|
||||
// NOTE: First character of transmission determines if this
|
||||
// client is using the DCC++ protocol where all commands start
|
||||
// with '<'
|
||||
if (clients[clientId] == NONE_TYPE) {
|
||||
if (buffer[0] == '<')
|
||||
clients[clientId]=COMMAND_TYPE;
|
||||
else
|
||||
clients[clientId]=WITHROTTLE_TYPE;
|
||||
}
|
||||
|
||||
// mark buffer that is sent to parser
|
||||
ring->mark(clientId);
|
||||
|
||||
// When type is known, send the string
|
||||
// to the right parser
|
||||
if (clients[clientId] == COMMAND_TYPE) {
|
||||
ringClient=stream->peekTargetMark();
|
||||
if (buffer[0] == '<') {
|
||||
clients[clientId]=COMMAND_TYPE;
|
||||
DCCEXParser::parse(stream, buffer, ring);
|
||||
} else if (clients[clientId] == WITHROTTLE_TYPE) {
|
||||
} else {
|
||||
clients[clientId]=WITHROTTLE_TYPE;
|
||||
WiThrottle::getThrottle(clientId)->parse(ring, buffer);
|
||||
}
|
||||
|
||||
if (ring->peekTargetMark()!=RingStream::NO_CLIENT) {
|
||||
// The commit call will either write the length bytes
|
||||
// OR rollback to the mark because the reply is empty
|
||||
// or the command generated more output than fits in
|
||||
// the buffer
|
||||
if (!ring->commit()) {
|
||||
DIAG(F("OUTBOUND FULL processing cmd:%s"),buffer);
|
||||
}
|
||||
} else {
|
||||
DIAG(F("CD parse: was alredy committed")); //XXX Could have been committed by broadcastClient?!
|
||||
}
|
||||
ringClient=NO_CLIENT;
|
||||
}
|
||||
|
||||
void CommandDistributor::forget(byte clientId) {
|
||||
if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId);
|
||||
clients[clientId]=NONE_TYPE;
|
||||
}
|
||||
#endif
|
||||
|
||||
// This will not be called on a uno
|
||||
void CommandDistributor::broadcastToClients(clientType type) {
|
||||
|
||||
byte rememberClient;
|
||||
(void)rememberClient; // shut up compiler warning
|
||||
void CommandDistributor::broadcast(bool includeWithrottleClients) {
|
||||
broadcastBufferWriter->write((byte)'\0');
|
||||
|
||||
// Broadcast to Serials
|
||||
if (type==COMMAND_TYPE) SerialManager::broadcast(broadcastBufferWriter->getString());
|
||||
/* Boadcast to Serials */
|
||||
SerialManager::broadcast(broadcastBufferWriter);
|
||||
|
||||
#ifdef CD_HANDLE_RING
|
||||
#if defined(WIFI_ON) | defined(ETHERNET_ON)
|
||||
// If we are broadcasting from a wifi/eth process we need to complete its output
|
||||
// before merging broadcasts in the ring, then reinstate it in case
|
||||
// the process continues to output to its client.
|
||||
if (ring) {
|
||||
if ((rememberClient = ring->peekTargetMark()) != RingStream::NO_CLIENT) {
|
||||
//DIAG(F("CD precommit client %d"), rememberClient);
|
||||
ring->commit();
|
||||
}
|
||||
// loop through ring clients
|
||||
for (byte clientId=0; clientId<sizeof(clients); clientId++) {
|
||||
if (clients[clientId]==type) {
|
||||
//DIAG(F("CD mark client %d"), clientId);
|
||||
ring->mark(clientId);
|
||||
ring->print(broadcastBufferWriter->getString());
|
||||
//DIAG(F("CD commit client %d"), clientId);
|
||||
ring->commit();
|
||||
}
|
||||
}
|
||||
// at this point ring is committed (NO_CLIENT) either from
|
||||
// 4 or 13 lines above.
|
||||
if (rememberClient != RingStream::NO_CLIENT) {
|
||||
//DIAG(F("CD postmark client %d"), rememberClient);
|
||||
ring->mark(rememberClient);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (ringClient!=NO_CLIENT) ring->commit();
|
||||
|
||||
/* loop through ring clients */
|
||||
for (byte clientId=0; clientId<sizeof(clients); clientId++) {
|
||||
if (clients[clientId]==NONE_TYPE) continue;
|
||||
if ( clients[clientId]==WITHROTTLE_TYPE && !includeWithrottleClients) continue;
|
||||
ring->mark(clientId);
|
||||
broadcastBufferWriter->printBuffer(ring);
|
||||
ring->commit();
|
||||
}
|
||||
if (ringClient!=NO_CLIENT) ring->mark(ringClient);
|
||||
|
||||
#endif
|
||||
broadcastBufferWriter->flush();
|
||||
}
|
||||
#else
|
||||
// For a UNO/NANO we can broadcast direct to just one Serial instead of the ring
|
||||
// Redirect ring output ditrect to Serial
|
||||
#define broadcastBufferWriter &Serial
|
||||
// and ignore the internal broadcast call.
|
||||
void CommandDistributor::broadcast(bool includeWithrottleClients) {
|
||||
(void)includeWithrottleClients;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Public broadcast functions below
|
||||
void CommandDistributor::broadcastSensor(int16_t id, bool on ) {
|
||||
broadcastReply(COMMAND_TYPE, F("<%c %d>\n"), on?'Q':'q', id);
|
||||
StringFormatter::send(broadcastBufferWriter,F("<%c %d>\n"), on?'Q':'q', id);
|
||||
broadcast(false);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) {
|
||||
// For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed;
|
||||
// The string below contains serial and Withrottle protocols which should
|
||||
// be safe for both types.
|
||||
broadcastReply(COMMAND_TYPE, F("<H %d %d>\n"),id, !isClosed);
|
||||
#ifdef CD_HANDLE_RING
|
||||
broadcastReply(WITHROTTLE_TYPE, F("PTA%c%d\n"), isClosed?'2':'4', id);
|
||||
StringFormatter::send(broadcastBufferWriter,F("<H %d %d>\n"),id, !isClosed);
|
||||
#if defined(WIFI_ON) | defined(ETHERNET_ON)
|
||||
StringFormatter::send(broadcastBufferWriter,F("PTA%c%d\n"), isClosed?'2':'4', id);
|
||||
#endif
|
||||
broadcast(true);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastLoco(byte slot) {
|
||||
DCC::LOCO * sp=&DCC::speedTable[slot];
|
||||
broadcastReply(COMMAND_TYPE, F("<l %d %d %d %l>\n"), sp->loco,slot,sp->speedCode,sp->functions);
|
||||
#ifdef CD_HANDLE_RING
|
||||
StringFormatter::send(broadcastBufferWriter,F("<l %d %d %d %l>\n"),
|
||||
sp->loco,slot,sp->speedCode,sp->functions);
|
||||
broadcast(false);
|
||||
#if defined(WIFI_ON) | defined(ETHERNET_ON)
|
||||
WiThrottle::markForBroadcast(sp->loco);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastPower() {
|
||||
bool main=TrackManager::getMainPower()==POWERMODE::ON;
|
||||
bool prog=TrackManager::getProgPower()==POWERMODE::ON;
|
||||
bool join=TrackManager::isJoined();
|
||||
bool main=DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON;
|
||||
bool prog=DCCWaveform::progTrack.getPowerMode()==POWERMODE::ON;
|
||||
bool join=DCCWaveform::progTrackSyncMain;
|
||||
const FSH * reason=F("");
|
||||
char state='1';
|
||||
if (main && prog && join) reason=F(" JOIN");
|
||||
@@ -174,16 +129,14 @@ void CommandDistributor::broadcastPower() {
|
||||
else if (main) reason=F(" MAIN");
|
||||
else if (prog) reason=F(" PROG");
|
||||
else state='0';
|
||||
broadcastReply(COMMAND_TYPE, F("<p%c%S>\n"),state,reason);
|
||||
#ifdef CD_HANDLE_RING
|
||||
broadcastReply(WITHROTTLE_TYPE, F("PPA%c\n"), main?'1':'0');
|
||||
#endif
|
||||
LCD(2,F("Power %S%S"),state=='1'?F("On"):F("Off"),reason);
|
||||
|
||||
StringFormatter::send(broadcastBufferWriter,
|
||||
F("<p%c%S>\nPPA%c\n"),state,reason, main?'1':'0');
|
||||
LCD(2,F("Power %S%S"),state=='1'?F("On"):F("Off"),reason);
|
||||
broadcast(true);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastText(const FSH * msg) {
|
||||
broadcastReply(COMMAND_TYPE, F("<I %S>\n"),msg);
|
||||
#ifdef CD_HANDLE_RING
|
||||
broadcastReply(WITHROTTLE_TYPE, F("Hm%S\n"), msg);
|
||||
#endif
|
||||
StringFormatter::send(broadcastBufferWriter,F("%S"),msg);
|
||||
broadcast(false);
|
||||
}
|
||||
|
@@ -23,23 +23,9 @@
|
||||
#define CommandDistributor_h
|
||||
#include "DCCEXParser.h"
|
||||
#include "RingStream.h"
|
||||
#include "StringBuffer.h"
|
||||
#include "defines.h"
|
||||
|
||||
#if WIFI_ON | ETHERNET_ON
|
||||
// Command Distributor must handle a RingStream of clients
|
||||
#define CD_HANDLE_RING
|
||||
#endif
|
||||
|
||||
class CommandDistributor {
|
||||
private:
|
||||
enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE};
|
||||
static void broadcastToClients(clientType type);
|
||||
static StringBuffer * broadcastBufferWriter;
|
||||
#ifdef CD_HANDLE_RING
|
||||
static RingStream * ring;
|
||||
static clientType clients[8];
|
||||
#endif
|
||||
|
||||
public :
|
||||
static void parse(byte clientId,byte* buffer, RingStream * ring);
|
||||
static void broadcastLoco(byte slot);
|
||||
@@ -47,8 +33,16 @@ public :
|
||||
static void broadcastTurnout(int16_t id, bool isClosed);
|
||||
static void broadcastPower();
|
||||
static void broadcastText(const FSH * msg);
|
||||
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
|
||||
static void forget(byte clientId);
|
||||
private:
|
||||
static void broadcast(bool includeWithrottleClients);
|
||||
static RingStream * ring;
|
||||
static RingStream * broadcastBufferWriter;
|
||||
static byte ringClient;
|
||||
|
||||
// each bit in broadcastlist = 1<<clientid
|
||||
enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE};
|
||||
static clientType clients[8];
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -18,14 +18,12 @@
|
||||
|
||||
#if __has_include ( "config.h")
|
||||
#include "config.h"
|
||||
#ifndef MOTOR_SHIELD_TYPE
|
||||
#error Your config.h must include a MOTOR_SHIELD_TYPE definition. If you see this warning in spite not having a config.h, you have a buggy preprocessor and must copy config.example.h to config.h
|
||||
#endif
|
||||
#else
|
||||
#warning config.h not found. Using defaults from config.example.h
|
||||
#include "config.example.h"
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020-2021 Chris Harlow, Harald Barth, David Cutting,
|
||||
@@ -49,11 +47,6 @@
|
||||
*/
|
||||
|
||||
#include "DCCEX.h"
|
||||
|
||||
#ifdef CPU_TYPE_ERROR
|
||||
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH THE ARCHITECTURES LISTED IN defines.h
|
||||
#endif
|
||||
|
||||
#ifdef WIFI_WARNING
|
||||
#warning You have defined that you want WiFi but your hardware has not enough memory to do that, so WiFi DISABLED
|
||||
#endif
|
||||
@@ -83,28 +76,20 @@ void setup()
|
||||
// Responsibility 2: Start all the communications before the DCC engine
|
||||
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi
|
||||
// Start Ethernet if it exists
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
#if WIFI_ON
|
||||
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL);
|
||||
#endif // WIFI_ON
|
||||
#else
|
||||
// ESP32 needs wifi on always
|
||||
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL);
|
||||
#endif // ARDUINO_ARCH_ESP32
|
||||
|
||||
#if ETHERNET_ON
|
||||
EthernetInterface::setup();
|
||||
#endif // ETHERNET_ON
|
||||
|
||||
// Initialise HAL layer before reading EEprom or setting up MotorDrivers
|
||||
IODevice::begin();
|
||||
|
||||
// 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
|
||||
TrackManager::Setup(MOTOR_SHIELD_TYPE);
|
||||
DCC::begin(MOTOR_SHIELD_TYPE);
|
||||
|
||||
// Start RMFT aka EX-RAIL (ignored if no automnation)
|
||||
RMFT::begin();
|
||||
@@ -113,16 +98,17 @@ void setup()
|
||||
// Invoke any DCC++EX commands in the form "SETUP("xxxx");"" found in optional file mySetup.h.
|
||||
// This can be used to create turnouts, outputs, sensors etc. through the normal text commands.
|
||||
#if __has_include ( "mySetup.h")
|
||||
#define SETUP(cmd) DCCEXParser::parse(F(cmd))
|
||||
#include "mySetup.h"
|
||||
#undef SETUP
|
||||
#define SETUP(cmd) DCCEXParser::parse(F(cmd))
|
||||
#include "mySetup.h"
|
||||
#undef SETUP
|
||||
#endif
|
||||
|
||||
#if defined(LCN_SERIAL)
|
||||
LCN_SERIAL.begin(115200);
|
||||
LCN::init(LCN_SERIAL);
|
||||
#endif
|
||||
LCD(3, F("Ready"));
|
||||
|
||||
LCD(3,F("Ready"));
|
||||
CommandDistributor::broadcastPower();
|
||||
}
|
||||
|
||||
@@ -138,15 +124,9 @@ void loop()
|
||||
SerialManager::loop();
|
||||
|
||||
// Responsibility 3: Optionally handle any incoming WiFi traffic
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
#if WIFI_ON
|
||||
WifiInterface::loop();
|
||||
#endif //WIFI_ON
|
||||
#else //ARDUINO_ARCH_ESP32
|
||||
#ifndef WIFI_TASK_ON_CORE0
|
||||
WifiESP::loop();
|
||||
#endif
|
||||
#endif //ARDUINO_ARCH_ESP32
|
||||
#if ETHERNET_ON
|
||||
EthernetInterface::loop();
|
||||
#endif
|
||||
@@ -167,7 +147,7 @@ void loop()
|
||||
// Report any decrease in memory (will automatically trigger on first call)
|
||||
static int ramLowWatermark = __INT_MAX__; // replaced on first loop
|
||||
|
||||
int freeNow = DCCTimer::getMinimumFreeMemory();
|
||||
int freeNow = minimumFreeMemory();
|
||||
if (freeNow < ramLowWatermark) {
|
||||
ramLowWatermark = freeNow;
|
||||
LCD(3,F("Free RAM=%5db"), ramLowWatermark);
|
||||
|
500
DCC.cpp
500
DCC.cpp
@@ -8,7 +8,7 @@
|
||||
* © 2020-2021 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of DCC-EX
|
||||
* This file is part of Asbelos DCC API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -35,8 +35,6 @@
|
||||
#include "IODevice.h"
|
||||
#include "EXRAIL2.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "TrackManager.h"
|
||||
#include "DCCTimer.h"
|
||||
|
||||
// This module is responsible for converting API calls into
|
||||
// messages to be sent to the waveform generator.
|
||||
@@ -58,26 +56,36 @@ const byte FN_GROUP_4=0x08;
|
||||
const byte FN_GROUP_5=0x10;
|
||||
|
||||
FSH* DCC::shieldName=NULL;
|
||||
byte DCC::joinRelay=UNUSED_PIN;
|
||||
byte DCC::globalSpeedsteps=128;
|
||||
|
||||
void DCC::begin(const FSH * motorShieldName) {
|
||||
void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver) {
|
||||
shieldName=(FSH *)motorShieldName;
|
||||
StringFormatter::send(&USB_SERIAL,F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
|
||||
StringFormatter::send(Serial,F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
|
||||
|
||||
// Initialise HAL layer before reading EEprom.
|
||||
IODevice::begin();
|
||||
|
||||
#ifndef DISABLE_EEPROM
|
||||
// Load stuff from EEprom
|
||||
(void)EEPROM; // tell compiler not to warn this is unused
|
||||
EEStore::init();
|
||||
#endif
|
||||
#ifndef ARDUINO_ARCH_ESP32 /* On ESP32 started in TrackManager::setTrackMode() */
|
||||
DCCWaveform::begin();
|
||||
#endif
|
||||
|
||||
DCCWaveform::begin(mainDriver,progDriver);
|
||||
}
|
||||
|
||||
void DCC::setJoinRelayPin(byte joinRelayPin) {
|
||||
joinRelay=joinRelayPin;
|
||||
if (joinRelay!=UNUSED_PIN) {
|
||||
pinMode(joinRelay,OUTPUT);
|
||||
digitalWrite(joinRelay,LOW); // LOW is relay disengaged
|
||||
}
|
||||
}
|
||||
|
||||
void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) {
|
||||
byte speedCode = (tSpeed & 0x7F) + tDirection * 128;
|
||||
setThrottle2(cab, speedCode);
|
||||
TrackManager::setDCSignal(cab,speedCode); // in case this is a dcc track on this addr
|
||||
// retain speed for loco reminders
|
||||
updateLocoReminder(cab, speedCode );
|
||||
}
|
||||
@@ -137,25 +145,12 @@ void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
|
||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
|
||||
}
|
||||
|
||||
// returns speed steps 0 to 127 (1 == emergency stop)
|
||||
// or -1 on "loco not found"
|
||||
int8_t DCC::getThrottleSpeed(int cab) {
|
||||
uint8_t DCC::getThrottleSpeed(int cab) {
|
||||
int reg=lookupSpeedTable(cab);
|
||||
if (reg<0) return -1;
|
||||
return speedTable[reg].speedCode & 0x7F;
|
||||
}
|
||||
|
||||
// returns speed code byte
|
||||
// or 128 (speed 0, dir forward) on "loco not found".
|
||||
uint8_t DCC::getThrottleSpeedByte(int cab) {
|
||||
int reg=lookupSpeedTable(cab);
|
||||
if (reg<0)
|
||||
return 128;
|
||||
return speedTable[reg].speedCode;
|
||||
}
|
||||
|
||||
// returns direction on loco
|
||||
// or true/forward on "loco not found"
|
||||
bool DCC::getThrottleDirection(int cab) {
|
||||
int reg=lookupSpeedTable(cab);
|
||||
if (reg<0) return true;
|
||||
@@ -163,9 +158,8 @@ bool DCC::getThrottleDirection(int cab) {
|
||||
}
|
||||
|
||||
// Set function to value on or off
|
||||
bool DCC::setFn( int cab, int16_t functionNumber, bool on) {
|
||||
if (cab<=0 ) return false;
|
||||
if (functionNumber < 0) return false;
|
||||
void DCC::setFn( int cab, int16_t functionNumber, bool on) {
|
||||
if (cab<=0 ) return;
|
||||
|
||||
if (functionNumber>28) {
|
||||
//non reminding advanced binary bit set
|
||||
@@ -184,11 +178,11 @@ bool DCC::setFn( int cab, int16_t functionNumber, bool on) {
|
||||
b[nB++] = functionNumber >>7 ; // high order bits
|
||||
}
|
||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
int reg = lookupSpeedTable(cab);
|
||||
if (reg<0) return false;
|
||||
if (reg<0) return;
|
||||
|
||||
// Take care of functions:
|
||||
// Set state of function
|
||||
@@ -203,7 +197,6 @@ bool DCC::setFn( int cab, int16_t functionNumber, bool on) {
|
||||
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
||||
CommandDistributor::broadcastLoco(reg);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Flip function state
|
||||
@@ -244,39 +237,24 @@ uint32_t DCC::getFunctionMap(int cab) {
|
||||
return (reg<0)?0:speedTable[reg].functions;
|
||||
}
|
||||
|
||||
void DCC::setAccessory(int address, byte port, bool gate, byte onoff /*= 2*/) {
|
||||
// onoff is tristate:
|
||||
// 0 => send off packet
|
||||
// 1 => send on packet
|
||||
// >1 => send both on and off packets.
|
||||
|
||||
// An accessory has an address, 4 ports and 2 gates (coils) each. That's how
|
||||
// the initial decoders were orgnized and that influenced how the DCC
|
||||
// standard was made.
|
||||
void DCC::setAccessory(int address, byte number, bool activate) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DCC::setAccessory(%d,%d,%d)"), address, port, gate);
|
||||
DIAG(F("DCC::setAccessory(%d,%d,%d)"), address, number, activate);
|
||||
#endif
|
||||
// use masks to detect wrong values and do nothing
|
||||
if(address != (address & 511))
|
||||
return;
|
||||
if(port != (port & 3))
|
||||
if(number != (number & 3))
|
||||
return;
|
||||
byte b[2];
|
||||
|
||||
// first byte is of the form 10AAAAAA, where AAAAAA represent 6 least signifcant bits of accessory address
|
||||
// second byte is of the form 1AAACPPG, where C is 1 for on, PP the ports 0 to 3 and G the gate (coil).
|
||||
b[0] = address % 64 + 128;
|
||||
b[1] = ((((address / 64) % 8) << 4) + (port % 4 << 1) + gate % 2) ^ 0xF8;
|
||||
if (onoff != 0) {
|
||||
DCCWaveform::mainTrack.schedulePacket(b, 2, 3); // Repeat on packet three times
|
||||
b[0] = address % 64 + 128; // first byte is of the form 10AAAAAA, where AAAAAA represent 6 least signifcant bits of accessory address
|
||||
b[1] = ((((address / 64) % 8) << 4) + (number % 4 << 1) + activate % 2) ^ 0xF8; // second byte is of the form 1AAACDDD, where C should be 1, and the least significant D represent activate/deactivate
|
||||
|
||||
DCCWaveform::mainTrack.schedulePacket(b, 2, 4); // Repeat the packet four times
|
||||
#if defined(EXRAIL_ACTIVE)
|
||||
RMFT2::activateEvent(address<<2|port,gate);
|
||||
RMFT2::activateEvent(address<<2|number,activate);
|
||||
#endif
|
||||
}
|
||||
if (onoff != 1) {
|
||||
b[1] &= ~0x08; // set C to 0
|
||||
DCCWaveform::mainTrack.schedulePacket(b, 2, 3); // Repeat off packet three times
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@@ -318,6 +296,14 @@ void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) {
|
||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
|
||||
}
|
||||
|
||||
void DCC::setProgTrackSyncMain(bool on) {
|
||||
if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,on?HIGH:LOW);
|
||||
DCCWaveform::progTrackSyncMain=on;
|
||||
}
|
||||
void DCC::setProgTrackBoost(bool on) {
|
||||
DCCWaveform::progTrackBoosted=on;
|
||||
}
|
||||
|
||||
FSH* DCC::getMotorShieldName() {
|
||||
return shieldName;
|
||||
}
|
||||
@@ -327,14 +313,14 @@ const ackOp FLASH WRITE_BIT0_PROG[] = {
|
||||
W0,WACK,
|
||||
V0, WACK, // validate bit is 0
|
||||
ITC1, // if acked, callback(1)
|
||||
CALLFAIL // callback (-1)
|
||||
FAIL // callback (-1)
|
||||
};
|
||||
const ackOp FLASH WRITE_BIT1_PROG[] = {
|
||||
BASELINE,
|
||||
W1,WACK,
|
||||
V1, WACK, // validate bit is 1
|
||||
ITC1, // if acked, callback(1)
|
||||
CALLFAIL // callback (-1)
|
||||
FAIL // callback (-1)
|
||||
};
|
||||
|
||||
const ackOp FLASH VERIFY_BIT0_PROG[] = {
|
||||
@@ -343,7 +329,7 @@ const ackOp FLASH VERIFY_BIT0_PROG[] = {
|
||||
ITC0, // if acked, callback(0)
|
||||
V1, WACK, // validate bit is 1
|
||||
ITC1,
|
||||
CALLFAIL // callback (-1)
|
||||
FAIL // callback (-1)
|
||||
};
|
||||
const ackOp FLASH VERIFY_BIT1_PROG[] = {
|
||||
BASELINE,
|
||||
@@ -351,7 +337,7 @@ const ackOp FLASH VERIFY_BIT1_PROG[] = {
|
||||
ITC1, // if acked, callback(1)
|
||||
V0, WACK,
|
||||
ITC0,
|
||||
CALLFAIL // callback (-1)
|
||||
FAIL // callback (-1)
|
||||
};
|
||||
|
||||
const ackOp FLASH READ_BIT_PROG[] = {
|
||||
@@ -360,7 +346,7 @@ const ackOp FLASH READ_BIT_PROG[] = {
|
||||
ITC1, // if acked, callback(1)
|
||||
V0, WACK, // validate bit is zero
|
||||
ITC0, // if acked callback 0
|
||||
CALLFAIL // bit not readable
|
||||
FAIL // bit not readable
|
||||
};
|
||||
|
||||
const ackOp FLASH WRITE_BYTE_PROG[] = {
|
||||
@@ -368,7 +354,7 @@ const ackOp FLASH WRITE_BYTE_PROG[] = {
|
||||
WB,WACK,ITC1, // Write and callback(1) if ACK
|
||||
// handle decoders that dont ack a write
|
||||
VB,WACK,ITC1, // validate byte and callback(1) if correct
|
||||
CALLFAIL // callback (-1)
|
||||
FAIL // callback (-1)
|
||||
};
|
||||
|
||||
const ackOp FLASH VERIFY_BYTE_PROG[] = {
|
||||
@@ -394,7 +380,7 @@ const ackOp FLASH VERIFY_BYTE_PROG[] = {
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
VB, WACK, ITCBV, // verify merged byte and return it if acked ok - with retry report
|
||||
CALLFAIL };
|
||||
FAIL };
|
||||
|
||||
|
||||
const ackOp FLASH READ_CV_PROG[] = {
|
||||
@@ -417,7 +403,7 @@ const ackOp FLASH READ_CV_PROG[] = {
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
VB, WACK, ITCB, // verify merged byte and return it if acked ok
|
||||
CALLFAIL }; // verification failed
|
||||
FAIL }; // verification failed
|
||||
|
||||
|
||||
const ackOp FLASH LOCO_ID_PROG[] = {
|
||||
@@ -483,7 +469,7 @@ const ackOp FLASH LOCO_ID_PROG[] = {
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
VB, WACK, ITCB, // verify merged byte and callback
|
||||
CALLFAIL
|
||||
FAIL
|
||||
};
|
||||
|
||||
const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
|
||||
@@ -498,9 +484,9 @@ const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
|
||||
V0,WACK,NAKFAIL,
|
||||
SETCV, (ackOp)1,
|
||||
SETBYTEL, // low byte of word
|
||||
WB,WACK,ITC1, // If ACK, we are done - callback(1) means Ok
|
||||
VB,WACK,ITC1, // Some decoders do not ack and need verify
|
||||
CALLFAIL
|
||||
WB,WACK, // some decoders don't ACK writes
|
||||
VB,WACK,ITCB,
|
||||
FAIL
|
||||
};
|
||||
|
||||
const ackOp FLASH LONG_LOCO_ID_PROG[] = {
|
||||
@@ -516,51 +502,47 @@ const ackOp FLASH LONG_LOCO_ID_PROG[] = {
|
||||
V1,WACK,NAKFAIL,
|
||||
// Store high byte of address in cv 17
|
||||
SETCV, (ackOp)17,
|
||||
SETBYTEH, // high byte of word
|
||||
WB,WACK, // do write
|
||||
ITSKIP, // if ACK, jump to SKIPTARGET
|
||||
VB,WACK, // try verify instead
|
||||
ITSKIP, // if ACK, jump to SKIPTARGET
|
||||
CALLFAIL, // if still here, fail
|
||||
SKIPTARGET,
|
||||
SETBYTEH, // high byte of word
|
||||
WB,WACK,
|
||||
VB,WACK,NAKFAIL,
|
||||
// store
|
||||
SETCV, (ackOp)18,
|
||||
SETBYTEL, // low byte of word
|
||||
WB,WACK,ITC1, // If ACK, we are done - callback(1) means Ok
|
||||
VB,WACK,ITC1, // Some decoders do not ack and need verify
|
||||
CALLFAIL
|
||||
WB,WACK,
|
||||
VB,WACK,ITC1, // callback(1) means Ok
|
||||
FAIL
|
||||
};
|
||||
|
||||
void DCC::writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) {
|
||||
DCCACK::Setup(cv, byteValue, WRITE_BYTE_PROG, callback);
|
||||
ackManagerSetup(cv, byteValue, WRITE_BYTE_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::writeCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) {
|
||||
if (bitNum >= 8) callback(-1);
|
||||
else DCCACK::Setup(cv, bitNum, bitValue?WRITE_BIT1_PROG:WRITE_BIT0_PROG, callback);
|
||||
else ackManagerSetup(cv, bitNum, bitValue?WRITE_BIT1_PROG:WRITE_BIT0_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::verifyCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) {
|
||||
DCCACK::Setup(cv, byteValue, VERIFY_BYTE_PROG, callback);
|
||||
ackManagerSetup(cv, byteValue, VERIFY_BYTE_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::verifyCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) {
|
||||
if (bitNum >= 8) callback(-1);
|
||||
else DCCACK::Setup(cv, bitNum, bitValue?VERIFY_BIT1_PROG:VERIFY_BIT0_PROG, callback);
|
||||
else ackManagerSetup(cv, bitNum, bitValue?VERIFY_BIT1_PROG:VERIFY_BIT0_PROG, callback);
|
||||
}
|
||||
|
||||
|
||||
void DCC::readCVBit(int16_t cv, byte bitNum, ACK_CALLBACK callback) {
|
||||
if (bitNum >= 8) callback(-1);
|
||||
else DCCACK::Setup(cv, bitNum,READ_BIT_PROG, callback);
|
||||
else ackManagerSetup(cv, bitNum,READ_BIT_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::readCV(int16_t cv, ACK_CALLBACK callback) {
|
||||
DCCACK::Setup(cv, 0,READ_CV_PROG, callback);
|
||||
ackManagerSetup(cv, 0,READ_CV_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::getLocoId(ACK_CALLBACK callback) {
|
||||
DCCACK::Setup(0,0, LOCO_ID_PROG, callback);
|
||||
ackManagerSetup(0,0, LOCO_ID_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::setLocoId(int id,ACK_CALLBACK callback) {
|
||||
@@ -569,9 +551,9 @@ void DCC::setLocoId(int id,ACK_CALLBACK callback) {
|
||||
return;
|
||||
}
|
||||
if (id<=HIGHEST_SHORT_ADDR)
|
||||
DCCACK::Setup(id, SHORT_LOCO_ID_PROG, callback);
|
||||
ackManagerSetup(id, SHORT_LOCO_ID_PROG, callback);
|
||||
else
|
||||
DCCACK::Setup(id | 0xc000,LONG_LOCO_ID_PROG, callback);
|
||||
ackManagerSetup(id | 0xc000,LONG_LOCO_ID_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
|
||||
@@ -588,22 +570,26 @@ void DCC::forgetAllLocos() { // removes all speed reminders
|
||||
byte DCC::loopStatus=0;
|
||||
|
||||
void DCC::loop() {
|
||||
TrackManager::loop(); // power overload checks
|
||||
DCCWaveform::loop(ackManagerProg!=NULL); // power overload checks
|
||||
ackManagerLoop(); // maintain prog track ack manager
|
||||
issueReminders();
|
||||
}
|
||||
|
||||
void DCC::issueReminders() {
|
||||
// if the main track transmitter still has a pending packet, skip this time around.
|
||||
if ( DCCWaveform::mainTrack.getPacketPending()) return;
|
||||
// Move to next loco slot. If occupied, send a reminder.
|
||||
int reg = lastLocoReminder+1;
|
||||
if (reg > highestUsedReg) reg = 0; // Go to start of table
|
||||
if (speedTable[reg].loco > 0) {
|
||||
// have found loco to remind
|
||||
if (issueReminder(reg))
|
||||
lastLocoReminder = reg;
|
||||
} else
|
||||
lastLocoReminder = reg;
|
||||
if ( DCCWaveform::mainTrack.packetPending) return;
|
||||
|
||||
// This loop searches for a loco in the speed table starting at nextLoco and cycling back around
|
||||
for (int reg=0;reg<MAX_LOCOS;reg++) {
|
||||
int slot=reg+nextLoco;
|
||||
if (slot>=MAX_LOCOS) slot-=MAX_LOCOS;
|
||||
if (speedTable[slot].loco > 0) {
|
||||
// have found the next loco to remind
|
||||
// issueReminder will return true if this loco is completed (ie speed and functions)
|
||||
if (issueReminder(slot)) nextLoco=slot+1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DCC::issueReminder(int reg) {
|
||||
@@ -661,7 +647,7 @@ byte DCC::cv2(int cv) {
|
||||
return lowByte(cv);
|
||||
}
|
||||
|
||||
int DCC::lookupSpeedTable(int locoId, bool autoCreate) {
|
||||
int DCC::lookupSpeedTable(int locoId) {
|
||||
// determine speed reg for this loco
|
||||
int firstEmpty = MAX_LOCOS;
|
||||
int reg;
|
||||
@@ -669,9 +655,6 @@ int DCC::lookupSpeedTable(int locoId, bool autoCreate) {
|
||||
if (speedTable[reg].loco == locoId) break;
|
||||
if (speedTable[reg].loco == 0 && firstEmpty == MAX_LOCOS) firstEmpty = reg;
|
||||
}
|
||||
|
||||
// return -1 if not found and not auto creating
|
||||
if (reg== MAX_LOCOS && !autoCreate) return -1;
|
||||
if (reg == MAX_LOCOS) reg = firstEmpty;
|
||||
if (reg >= MAX_LOCOS) {
|
||||
DIAG(F("Too many locos"));
|
||||
@@ -683,7 +666,6 @@ int DCC::lookupSpeedTable(int locoId, bool autoCreate) {
|
||||
speedTable[reg].groupFlags=0;
|
||||
speedTable[reg].functions=0;
|
||||
}
|
||||
if (reg > highestUsedReg) highestUsedReg = reg;
|
||||
return reg;
|
||||
}
|
||||
|
||||
@@ -691,7 +673,7 @@ void DCC::updateLocoReminder(int loco, byte speedCode) {
|
||||
|
||||
if (loco==0) {
|
||||
// broadcast stop/estop but dont change direction
|
||||
for (int reg = 0; reg < highestUsedReg; reg++) {
|
||||
for (int reg = 0; reg < MAX_LOCOS; reg++) {
|
||||
if (speedTable[reg].loco==0) continue;
|
||||
byte newspeed=(speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f);
|
||||
if (speedTable[reg].speedCode != newspeed) {
|
||||
@@ -711,14 +693,326 @@ void DCC::updateLocoReminder(int loco, byte speedCode) {
|
||||
}
|
||||
|
||||
DCC::LOCO DCC::speedTable[MAX_LOCOS];
|
||||
int DCC::lastLocoReminder = 0;
|
||||
int DCC::highestUsedReg = 0;
|
||||
int DCC::nextLoco = 0;
|
||||
|
||||
//ACK MANAGER
|
||||
ackOp const * DCC::ackManagerProg;
|
||||
ackOp const * DCC::ackManagerProgStart;
|
||||
byte DCC::ackManagerByte;
|
||||
byte DCC::ackManagerByteVerify;
|
||||
byte DCC::ackManagerStash;
|
||||
int DCC::ackManagerWord;
|
||||
byte DCC::ackManagerRetry;
|
||||
byte DCC::ackRetry = 2;
|
||||
int16_t DCC::ackRetrySum;
|
||||
int16_t DCC::ackRetryPSum;
|
||||
int DCC::ackManagerCv;
|
||||
byte DCC::ackManagerBitNum;
|
||||
bool DCC::ackReceived;
|
||||
bool DCC::ackManagerRejoin;
|
||||
|
||||
CALLBACK_STATE DCC::callbackState=READY;
|
||||
|
||||
ACK_CALLBACK DCC::ackManagerCallback;
|
||||
|
||||
void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback) {
|
||||
if (!DCCWaveform::progTrack.canMeasureCurrent()) {
|
||||
callback(-2);
|
||||
return;
|
||||
}
|
||||
|
||||
ackManagerRejoin=DCCWaveform::progTrackSyncMain;
|
||||
if (ackManagerRejoin ) {
|
||||
// Change from JOIN must zero resets packet.
|
||||
setProgTrackSyncMain(false);
|
||||
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
||||
}
|
||||
|
||||
DCCWaveform::progTrack.autoPowerOff=false;
|
||||
if (DCCWaveform::progTrack.getPowerMode() == POWERMODE::OFF) {
|
||||
DCCWaveform::progTrack.autoPowerOff=true; // power off afterwards
|
||||
if (Diag::ACK) DIAG(F("Auto Prog power on"));
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
if (MotorDriver::commonFaultPin)
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
||||
}
|
||||
|
||||
ackManagerCv = cv;
|
||||
ackManagerProg = program;
|
||||
ackManagerProgStart = program;
|
||||
ackManagerRetry = ackRetry;
|
||||
ackManagerByte = byteValueOrBitnum;
|
||||
ackManagerByteVerify = byteValueOrBitnum;
|
||||
ackManagerBitNum=byteValueOrBitnum;
|
||||
ackManagerCallback = callback;
|
||||
}
|
||||
|
||||
void DCC::ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback) {
|
||||
ackManagerWord=wordval;
|
||||
ackManagerSetup(0, 0, program, callback);
|
||||
}
|
||||
|
||||
const byte RESET_MIN=8; // tuning of reset counter before sending message
|
||||
|
||||
// checkRessets return true if the caller should yield back to loop and try later.
|
||||
bool DCC::checkResets(uint8_t numResets) {
|
||||
return DCCWaveform::progTrack.sentResetsSincePacket < numResets;
|
||||
}
|
||||
|
||||
void DCC::ackManagerLoop() {
|
||||
while (ackManagerProg) {
|
||||
byte opcode=GETFLASH(ackManagerProg);
|
||||
|
||||
// breaks from this switch will step to next prog entry
|
||||
// returns from this switch will stay on same entry
|
||||
// (typically waiting for a reset counter or ACK waiting, or when all finished.)
|
||||
switch (opcode) {
|
||||
case BASELINE:
|
||||
if (DCCWaveform::progTrack.getPowerMode()==POWERMODE::OVERLOAD) return;
|
||||
if (checkResets(DCCWaveform::progTrack.autoPowerOff || ackManagerRejoin ? 20 : 3)) return;
|
||||
DCCWaveform::progTrack.setAckBaseline();
|
||||
callbackState=READY;
|
||||
break;
|
||||
case W0: // write 0 bit
|
||||
case W1: // write 1 bit
|
||||
{
|
||||
if (checkResets(RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("W%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum);
|
||||
byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum;
|
||||
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
DCCWaveform::progTrack.setAckPending();
|
||||
callbackState=AFTER_WRITE;
|
||||
}
|
||||
break;
|
||||
|
||||
case WB: // write byte
|
||||
{
|
||||
if (checkResets( RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("WB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
||||
byte message[] = {cv1(WRITE_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
DCCWaveform::progTrack.setAckPending();
|
||||
callbackState=AFTER_WRITE;
|
||||
}
|
||||
break;
|
||||
|
||||
case VB: // Issue validate Byte packet
|
||||
{
|
||||
if (checkResets( RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("VB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
||||
byte message[] = { cv1(VERIFY_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
DCCWaveform::progTrack.setAckPending();
|
||||
}
|
||||
break;
|
||||
|
||||
case V0:
|
||||
case V1: // Issue validate bit=0 or bit=1 packet
|
||||
{
|
||||
if (checkResets(RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("V%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum);
|
||||
byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum;
|
||||
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
DCCWaveform::progTrack.setAckPending();
|
||||
}
|
||||
break;
|
||||
|
||||
case WACK: // wait for ack (or absence of ack)
|
||||
{
|
||||
byte ackState=2; // keep polling
|
||||
|
||||
ackState=DCCWaveform::progTrack.getAck();
|
||||
if (ackState==2) return; // keep polling
|
||||
ackReceived=ackState==1;
|
||||
break; // we have a genuine ACK result
|
||||
}
|
||||
case ITC0:
|
||||
case ITC1: // If True Callback(0 or 1) (if prevous WACK got an ACK)
|
||||
if (ackReceived) {
|
||||
callback(opcode==ITC0?0:1);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ITCB: // If True callback(byte)
|
||||
if (ackReceived) {
|
||||
callback(ackManagerByte);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ITCBV: // If True callback(byte) - Verify
|
||||
if (ackReceived) {
|
||||
if (ackManagerByte == ackManagerByteVerify) {
|
||||
ackRetrySum ++;
|
||||
LCD(1, F("v %d %d Sum=%d"), ackManagerCv, ackManagerByte, ackRetrySum);
|
||||
}
|
||||
callback(ackManagerByte);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ITCB7: // If True callback(byte & 0x7F)
|
||||
if (ackReceived) {
|
||||
callback(ackManagerByte & 0x7F);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case NAKFAIL: // If nack callback(-1)
|
||||
if (!ackReceived) {
|
||||
callback(-1);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case FAIL: // callback(-1)
|
||||
callback(-1);
|
||||
return;
|
||||
|
||||
case BIV: // ackManagerByte initial value
|
||||
ackManagerByte = ackManagerByteVerify;
|
||||
break;
|
||||
|
||||
case STARTMERGE:
|
||||
ackManagerBitNum=7;
|
||||
ackManagerByte=0;
|
||||
break;
|
||||
|
||||
case MERGE: // Merge previous Validate zero wack response with byte value and update bit number (use for reading CV bytes)
|
||||
ackManagerByte <<= 1;
|
||||
// ackReceived means bit is zero.
|
||||
if (!ackReceived) ackManagerByte |= 1;
|
||||
ackManagerBitNum--;
|
||||
break;
|
||||
|
||||
case SETBIT:
|
||||
ackManagerProg++;
|
||||
ackManagerBitNum=GETFLASH(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETCV:
|
||||
ackManagerProg++;
|
||||
ackManagerCv=GETFLASH(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETBYTE:
|
||||
ackManagerProg++;
|
||||
ackManagerByte=GETFLASH(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETBYTEH:
|
||||
ackManagerByte=highByte(ackManagerWord);
|
||||
break;
|
||||
|
||||
case SETBYTEL:
|
||||
ackManagerByte=lowByte(ackManagerWord);
|
||||
break;
|
||||
|
||||
case STASHLOCOID:
|
||||
ackManagerStash=ackManagerByte; // stash value from CV17
|
||||
break;
|
||||
|
||||
case COMBINELOCOID:
|
||||
// ackManagerStash is cv17, ackManagerByte is CV 18
|
||||
callback( LONG_ADDR_MARKER | ( ackManagerByte + ((ackManagerStash - 192) << 8)));
|
||||
return;
|
||||
|
||||
case ITSKIP:
|
||||
if (!ackReceived) break;
|
||||
// SKIP opcodes until SKIPTARGET found
|
||||
while (opcode!=SKIPTARGET) {
|
||||
ackManagerProg++;
|
||||
opcode=GETFLASH(ackManagerProg);
|
||||
}
|
||||
break;
|
||||
case SKIPTARGET:
|
||||
break;
|
||||
default:
|
||||
DIAG(F("!! ackOp %d FAULT!!"),opcode);
|
||||
callback( -1);
|
||||
return;
|
||||
|
||||
} // end of switch
|
||||
ackManagerProg++;
|
||||
}
|
||||
}
|
||||
|
||||
void DCC::callback(int value) {
|
||||
// check for automatic retry
|
||||
if (value == -1 && ackManagerRetry > 0) {
|
||||
ackRetrySum ++;
|
||||
LCD(0, F("Retry %d %d Sum=%d"), ackManagerCv, ackManagerRetry, ackRetrySum);
|
||||
ackManagerRetry --;
|
||||
ackManagerProg = ackManagerProgStart;
|
||||
return;
|
||||
}
|
||||
|
||||
static unsigned long callbackStart;
|
||||
// We are about to leave programming mode
|
||||
// Rule 1: If we have written to a decoder we must maintain power for 100mS
|
||||
// Rule 2: If we are re-joining the main track we must power off for 30mS
|
||||
|
||||
switch (callbackState) {
|
||||
case AFTER_WRITE: // first attempt to callback after a write operation
|
||||
if (!ackManagerRejoin && !DCCWaveform::progTrack.autoPowerOff) {
|
||||
callbackState=READY;
|
||||
break;
|
||||
} // lines 906-910 added. avoid wait after write. use 1 PROG
|
||||
callbackStart=millis();
|
||||
callbackState=WAITING_100;
|
||||
if (Diag::ACK) DIAG(F("Stable 100mS"));
|
||||
break;
|
||||
|
||||
case WAITING_100: // waiting for 100mS
|
||||
if (millis()-callbackStart < 100) break;
|
||||
// stable after power maintained for 100mS
|
||||
|
||||
// If we are going to power off anyway, it doesnt matter
|
||||
// but if we will keep the power on, we must off it for 30mS
|
||||
if (DCCWaveform::progTrack.autoPowerOff) callbackState=READY;
|
||||
else { // Need to cycle power off and on
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
||||
callbackStart=millis();
|
||||
callbackState=WAITING_30;
|
||||
if (Diag::ACK) DIAG(F("OFF 30mS"));
|
||||
}
|
||||
break;
|
||||
|
||||
case WAITING_30: // waiting for 30mS with power off
|
||||
if (millis()-callbackStart < 30) break;
|
||||
//power has been off for 30mS
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
callbackState=READY;
|
||||
break;
|
||||
|
||||
case READY: // ready after read, or write after power delay and off period.
|
||||
// power off if we powered it on
|
||||
if (DCCWaveform::progTrack.autoPowerOff) {
|
||||
if (Diag::ACK) DIAG(F("Auto Prog power off"));
|
||||
DCCWaveform::progTrack.doAutoPowerOff();
|
||||
if (MotorDriver::commonFaultPin)
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||
}
|
||||
// Restore <1 JOIN> to state before BASELINE
|
||||
if (ackManagerRejoin) {
|
||||
setProgTrackSyncMain(true);
|
||||
if (Diag::ACK) DIAG(F("Auto JOIN"));
|
||||
}
|
||||
|
||||
ackManagerProg=NULL; // no more steps to execute
|
||||
if (Diag::ACK) DIAG(F("Callback(%d)"),value);
|
||||
(ackManagerCallback)( value);
|
||||
}
|
||||
}
|
||||
|
||||
void DCC::displayCabList(Print * stream) {
|
||||
|
||||
int used=0;
|
||||
for (int reg = 0; reg <= highestUsedReg; reg++) {
|
||||
for (int reg = 0; reg < MAX_LOCOS; reg++) {
|
||||
if (speedTable[reg].loco>0) {
|
||||
used ++;
|
||||
StringFormatter::send(stream,F("cab=%d, speed=%d, dir=%c \n"),
|
||||
|
131
DCC.h
131
DCC.h
@@ -36,40 +36,83 @@
|
||||
#error short addr greater than 127 does not make sense
|
||||
#endif
|
||||
#endif
|
||||
#include "DCCACK.h"
|
||||
const uint16_t LONG_ADDR_MARKER = 0x4000;
|
||||
|
||||
typedef void (*ACK_CALLBACK)(int16_t result);
|
||||
|
||||
enum ackOp : byte
|
||||
{ // 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)
|
||||
ITCBV, // If True callback(byte) - end of Verify Byte
|
||||
ITCB7, // If True callback(byte &0x7F)
|
||||
NAKFAIL, // if false callback(-1)
|
||||
FAIL, // callback(-1)
|
||||
BIV, // Set ackManagerByte to initial value for Verify retry
|
||||
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
|
||||
SETBYTE, // sets current byte to next prog byte
|
||||
SETBYTEH, // sets current byte to word high byte
|
||||
SETBYTEL, // sets current byte to word low 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 CALLBACK_STATE : byte {
|
||||
AFTER_WRITE, // Start callback sequence after something was written to the decoder
|
||||
WAITING_100, // Waiting for 100mS of stable power
|
||||
WAITING_30, // waiting to 30ms of power off gap.
|
||||
READY, // Ready to complete callback
|
||||
};
|
||||
|
||||
|
||||
// Allocations with memory implications..!
|
||||
// Base system takes approx 900 bytes + 8 per loco. Turnouts, Sensors etc are dynamically created
|
||||
#if defined(HAS_ENOUGH_MEMORY)
|
||||
const byte MAX_LOCOS = 50;
|
||||
#else
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
const byte MAX_LOCOS = 20;
|
||||
#elif defined(ARDUINO_AVR_NANO)
|
||||
const byte MAX_LOCOS = 30;
|
||||
#else
|
||||
const byte MAX_LOCOS = 50;
|
||||
#endif
|
||||
|
||||
class DCC
|
||||
{
|
||||
public:
|
||||
static void begin(const FSH * motorShieldName);
|
||||
static void begin(const FSH * motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver);
|
||||
static void setJoinRelayPin(byte joinRelayPin);
|
||||
static void loop();
|
||||
|
||||
// Public DCC API functions
|
||||
static void setThrottle(uint16_t cab, uint8_t tSpeed, bool tDirection);
|
||||
static int8_t getThrottleSpeed(int cab);
|
||||
static uint8_t getThrottleSpeedByte(int cab);
|
||||
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 bool setFn(int cab, int16_t functionNumber, bool on);
|
||||
static void setFn(int cab, int16_t functionNumber, bool on);
|
||||
static void changeFn(int cab, int16_t functionNumber);
|
||||
static int getFn(int cab, int16_t functionNumber);
|
||||
static uint32_t getFunctionMap(int cab);
|
||||
static void updateGroupflags(byte &flags, int16_t functionNumber);
|
||||
static void setAccessory(int address, byte port, bool gate, byte onoff = 2);
|
||||
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(int16_t cv, ACK_CALLBACK callback);
|
||||
static void readCVBit(int16_t cv, byte bitNum, ACK_CALLBACK callback); // -1 for error
|
||||
@@ -85,11 +128,18 @@ public:
|
||||
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 FSH *getMotorShieldName();
|
||||
static inline void setGlobalSpeedsteps(byte s) {
|
||||
globalSpeedsteps = s;
|
||||
};
|
||||
|
||||
static inline int16_t setAckRetry(byte retry) {
|
||||
ackRetry = retry;
|
||||
ackRetryPSum = ackRetrySum;
|
||||
ackRetrySum = 0; // reset running total
|
||||
return ackRetryPSum;
|
||||
};
|
||||
|
||||
struct LOCO
|
||||
{
|
||||
int loco;
|
||||
@@ -98,24 +148,46 @@ public:
|
||||
unsigned long functions;
|
||||
};
|
||||
static LOCO speedTable[MAX_LOCOS];
|
||||
static int lookupSpeedTable(int locoId, bool autoCreate=true);
|
||||
static byte cv1(byte opcode, int cv);
|
||||
static byte cv2(int cv);
|
||||
|
||||
private:
|
||||
static byte joinRelay;
|
||||
static byte loopStatus;
|
||||
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 bool issueReminder(int reg);
|
||||
static int lastLocoReminder;
|
||||
static int highestUsedReg;
|
||||
static int nextLoco;
|
||||
static FSH *shieldName;
|
||||
static byte globalSpeedsteps;
|
||||
|
||||
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 ackOp const *ackManagerProgStart;
|
||||
static byte ackManagerByte;
|
||||
static byte ackManagerByteVerify;
|
||||
static byte ackManagerBitNum;
|
||||
static int ackManagerCv;
|
||||
static byte ackManagerRetry;
|
||||
static byte ackRetry;
|
||||
static int16_t ackRetrySum;
|
||||
static int16_t ackRetryPSum;
|
||||
static int ackManagerWord;
|
||||
static byte ackManagerStash;
|
||||
static bool ackReceived;
|
||||
static bool ackManagerRejoin;
|
||||
static ACK_CALLBACK ackManagerCallback;
|
||||
static CALLBACK_STATE callbackState;
|
||||
static void ackManagerSetup(int cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback);
|
||||
static void ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback);
|
||||
static void ackManagerLoop();
|
||||
static bool checkResets( uint8_t numResets);
|
||||
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;
|
||||
@@ -130,4 +202,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"
|
||||
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#define ARDUINO_TYPE "MEGAAVR"
|
||||
#elif defined(ARDUINO_TEENSY32)
|
||||
#define ARDUINO_TYPE "TEENSY32"
|
||||
#elif defined(ARDUINO_TEENSY35)
|
||||
#define ARDUINO_TYPE "TEENSY35"
|
||||
#elif defined(ARDUINO_TEENSY36)
|
||||
#define ARDUINO_TYPE "TEENSY36"
|
||||
#elif defined(ARDUINO_TEENSY40)
|
||||
#define ARDUINO_TYPE "TEENSY40"
|
||||
#elif defined(ARDUINO_TEENSY41)
|
||||
#define ARDUINO_TYPE "TEENSY41"
|
||||
#else
|
||||
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
||||
|
469
DCCACK.cpp
469
DCCACK.cpp
@@ -1,469 +0,0 @@
|
||||
/*
|
||||
* © 2021 M Steve Todd
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2021 Harald Barth
|
||||
* © 2020-2022 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "DCCACK.h"
|
||||
#include "DIAG.h"
|
||||
#include "DCC.h"
|
||||
#include "DCCWaveform.h"
|
||||
#include "TrackManager.h"
|
||||
|
||||
unsigned int DCCACK::minAckPulseDuration = 2000; // micros
|
||||
unsigned int DCCACK::maxAckPulseDuration = 20000; // micros
|
||||
|
||||
MotorDriver * DCCACK::progDriver=NULL;
|
||||
ackOp const * DCCACK::ackManagerProg;
|
||||
ackOp const * DCCACK::ackManagerProgStart;
|
||||
byte DCCACK::ackManagerByte;
|
||||
byte DCCACK::ackManagerByteVerify;
|
||||
byte DCCACK::ackManagerStash;
|
||||
int DCCACK::ackManagerWord;
|
||||
byte DCCACK::ackManagerRetry;
|
||||
byte DCCACK::ackRetry = 2;
|
||||
int16_t DCCACK::ackRetrySum;
|
||||
int16_t DCCACK::ackRetryPSum;
|
||||
int DCCACK::ackManagerCv;
|
||||
byte DCCACK::ackManagerBitNum;
|
||||
bool DCCACK::ackReceived;
|
||||
bool DCCACK::ackManagerRejoin;
|
||||
volatile uint8_t DCCACK::numAckGaps=0;
|
||||
volatile uint8_t DCCACK::numAckSamples=0;
|
||||
uint8_t DCCACK::trailingEdgeCounter=0;
|
||||
|
||||
|
||||
unsigned int DCCACK::ackPulseDuration; // micros
|
||||
unsigned long DCCACK::ackPulseStart; // micros
|
||||
volatile bool DCCACK::ackDetected;
|
||||
unsigned long DCCACK::ackCheckStart; // millis
|
||||
volatile bool DCCACK::ackPending;
|
||||
bool DCCACK::autoPowerOff;
|
||||
int DCCACK::ackThreshold;
|
||||
int DCCACK::ackLimitmA = 50;
|
||||
int DCCACK::ackMaxCurrent;
|
||||
unsigned int DCCACK::ackCheckDuration; // millis
|
||||
|
||||
|
||||
CALLBACK_STATE DCCACK::callbackState=READY;
|
||||
|
||||
ACK_CALLBACK DCCACK::ackManagerCallback;
|
||||
|
||||
void DCCACK::Setup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback) {
|
||||
ackManagerRejoin=TrackManager::isJoined();
|
||||
if (ackManagerRejoin) {
|
||||
// Change from JOIN must zero resets packet.
|
||||
TrackManager::setJoin(false);
|
||||
DCCWaveform::progTrack.clearResets();
|
||||
}
|
||||
|
||||
progDriver=TrackManager::getProgDriver();
|
||||
if (progDriver==NULL) {
|
||||
TrackManager::setJoin(ackManagerRejoin);
|
||||
callback(-3); // we dont have a prog track!
|
||||
return;
|
||||
}
|
||||
if (!progDriver->canMeasureCurrent()) {
|
||||
TrackManager::setJoin(ackManagerRejoin);
|
||||
callback(-2); // our prog track cant measure current
|
||||
return;
|
||||
}
|
||||
|
||||
autoPowerOff=false;
|
||||
if (progDriver->getPower() == POWERMODE::OFF) {
|
||||
autoPowerOff=true; // power off afterwards
|
||||
if (Diag::ACK) DIAG(F("Auto Prog power on"));
|
||||
progDriver->setPower(POWERMODE::ON);
|
||||
|
||||
/* TODO !!! in MotorDriver surely!
|
||||
if (MotorDriver::commonFaultPin)
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||
DCCWaveform::progTrack.clearResets();
|
||||
**/
|
||||
}
|
||||
|
||||
|
||||
ackManagerCv = cv;
|
||||
ackManagerProg = program;
|
||||
ackManagerProgStart = program;
|
||||
ackManagerRetry = ackRetry;
|
||||
ackManagerByte = byteValueOrBitnum;
|
||||
ackManagerByteVerify = byteValueOrBitnum;
|
||||
ackManagerBitNum=byteValueOrBitnum;
|
||||
ackManagerCallback = callback;
|
||||
}
|
||||
|
||||
void DCCACK::Setup(int wordval, ackOp const program[], ACK_CALLBACK callback) {
|
||||
ackManagerWord=wordval;
|
||||
Setup(0, 0, program, callback);
|
||||
}
|
||||
|
||||
const byte RESET_MIN=8; // tuning of reset counter before sending message
|
||||
|
||||
// checkRessets return true if the caller should yield back to loop and try later.
|
||||
bool DCCACK::checkResets(uint8_t numResets) {
|
||||
return DCCWaveform::progTrack.getResets() < numResets;
|
||||
}
|
||||
// Operations applicable to PROG track ONLY.
|
||||
// (yes I know I could have subclassed the main track but...)
|
||||
|
||||
void DCCACK::setAckBaseline() {
|
||||
int baseline=progDriver->getCurrentRaw();
|
||||
ackThreshold= baseline + progDriver->mA2raw(ackLimitmA);
|
||||
if (Diag::ACK) DIAG(F("ACK baseline=%d/%dmA Threshold=%d/%dmA Duration between %uus and %uus"),
|
||||
baseline,progDriver->raw2mA(baseline),
|
||||
ackThreshold,progDriver->raw2mA(ackThreshold),
|
||||
minAckPulseDuration, maxAckPulseDuration);
|
||||
}
|
||||
|
||||
void DCCACK::setAckPending() {
|
||||
ackMaxCurrent=0;
|
||||
ackPulseStart=0;
|
||||
ackPulseDuration=0;
|
||||
ackDetected=false;
|
||||
ackCheckStart=millis();
|
||||
numAckSamples=0;
|
||||
numAckGaps=0;
|
||||
ackPending=true; // interrupt routines will now take note
|
||||
}
|
||||
|
||||
byte DCCACK::getAck() {
|
||||
if (ackPending) return (2); // still waiting
|
||||
if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%uuS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
|
||||
ackMaxCurrent,progDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps);
|
||||
if (ackDetected) return (1); // Yes we had an ack
|
||||
return(0); // pending set off but not detected means no ACK.
|
||||
}
|
||||
|
||||
|
||||
void DCCACK::loop() {
|
||||
while (ackManagerProg) {
|
||||
byte opcode=GETFLASH(ackManagerProg);
|
||||
|
||||
// breaks from this switch will step to next prog entry
|
||||
// returns from this switch will stay on same entry
|
||||
// (typically waiting for a reset counter or ACK waiting, or when all finished.)
|
||||
switch (opcode) {
|
||||
case BASELINE:
|
||||
if (progDriver->getPower()==POWERMODE::OVERLOAD) return;
|
||||
if (checkResets(autoPowerOff || ackManagerRejoin ? 20 : 3)) return;
|
||||
setAckBaseline();
|
||||
callbackState=AFTER_READ;
|
||||
break;
|
||||
case W0: // write 0 bit
|
||||
case W1: // write 1 bit
|
||||
{
|
||||
if (checkResets(RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("W%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum);
|
||||
byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum;
|
||||
byte message[] = {DCC::cv1(BIT_MANIPULATE, ackManagerCv), DCC::cv2(ackManagerCv), instruction };
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
setAckPending();
|
||||
callbackState=AFTER_WRITE;
|
||||
}
|
||||
break;
|
||||
|
||||
case WB: // write byte
|
||||
{
|
||||
if (checkResets( RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("WB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
||||
byte message[] = {DCC::cv1(WRITE_BYTE, ackManagerCv), DCC::cv2(ackManagerCv), ackManagerByte};
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
setAckPending();
|
||||
callbackState=AFTER_WRITE;
|
||||
}
|
||||
break;
|
||||
|
||||
case VB: // Issue validate Byte packet
|
||||
{
|
||||
if (checkResets( RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("VB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
||||
byte message[] = { DCC::cv1(VERIFY_BYTE, ackManagerCv), DCC::cv2(ackManagerCv), ackManagerByte};
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
setAckPending();
|
||||
}
|
||||
break;
|
||||
|
||||
case V0:
|
||||
case V1: // Issue validate bit=0 or bit=1 packet
|
||||
{
|
||||
if (checkResets(RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("V%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum);
|
||||
byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum;
|
||||
byte message[] = {DCC::cv1(BIT_MANIPULATE, ackManagerCv), DCC::cv2(ackManagerCv), instruction };
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
setAckPending();
|
||||
}
|
||||
break;
|
||||
|
||||
case WACK: // wait for ack (or absence of ack)
|
||||
{
|
||||
byte ackState=2; // keep polling
|
||||
|
||||
ackState=getAck();
|
||||
if (ackState==2) return; // keep polling
|
||||
ackReceived=ackState==1;
|
||||
break; // we have a genuine ACK result
|
||||
}
|
||||
case ITC0:
|
||||
case ITC1: // If True Callback(0 or 1) (if prevous WACK got an ACK)
|
||||
if (ackReceived) {
|
||||
callback(opcode==ITC0?0:1);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ITCB: // If True callback(byte)
|
||||
if (ackReceived) {
|
||||
callback(ackManagerByte);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ITCBV: // If True callback(byte) - Verify
|
||||
if (ackReceived) {
|
||||
if (ackManagerByte == ackManagerByteVerify) {
|
||||
ackRetrySum ++;
|
||||
LCD(1, F("v %d %d Sum=%d"), ackManagerCv, ackManagerByte, ackRetrySum);
|
||||
}
|
||||
callback(ackManagerByte);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ITCB7: // If True callback(byte & 0x7F)
|
||||
if (ackReceived) {
|
||||
callback(ackManagerByte & 0x7F);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case NAKFAIL: // If nack callback(-1)
|
||||
if (!ackReceived) {
|
||||
callback(-1);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case CALLFAIL: // callback(-1)
|
||||
callback(-1);
|
||||
return;
|
||||
|
||||
case BIV: // ackManagerByte initial value
|
||||
ackManagerByte = ackManagerByteVerify;
|
||||
break;
|
||||
|
||||
case STARTMERGE:
|
||||
ackManagerBitNum=7;
|
||||
ackManagerByte=0;
|
||||
break;
|
||||
|
||||
case MERGE: // Merge previous Validate zero wack response with byte value and update bit number (use for reading CV bytes)
|
||||
ackManagerByte <<= 1;
|
||||
// ackReceived means bit is zero.
|
||||
if (!ackReceived) ackManagerByte |= 1;
|
||||
ackManagerBitNum--;
|
||||
break;
|
||||
|
||||
case SETBIT:
|
||||
ackManagerProg++;
|
||||
ackManagerBitNum=GETFLASH(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETCV:
|
||||
ackManagerProg++;
|
||||
ackManagerCv=GETFLASH(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETBYTE:
|
||||
ackManagerProg++;
|
||||
ackManagerByte=GETFLASH(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETBYTEH:
|
||||
ackManagerByte=highByte(ackManagerWord);
|
||||
break;
|
||||
|
||||
case SETBYTEL:
|
||||
ackManagerByte=lowByte(ackManagerWord);
|
||||
break;
|
||||
|
||||
case STASHLOCOID:
|
||||
ackManagerStash=ackManagerByte; // stash value from CV17
|
||||
break;
|
||||
|
||||
case COMBINELOCOID:
|
||||
// ackManagerStash is cv17, ackManagerByte is CV 18
|
||||
callback( LONG_ADDR_MARKER | ( ackManagerByte + ((ackManagerStash - 192) << 8)));
|
||||
return;
|
||||
|
||||
case ITSKIP:
|
||||
if (!ackReceived) break;
|
||||
// SKIP opcodes until SKIPTARGET found
|
||||
while (opcode!=SKIPTARGET) {
|
||||
ackManagerProg++;
|
||||
opcode=GETFLASH(ackManagerProg);
|
||||
}
|
||||
break;
|
||||
case SKIPTARGET:
|
||||
break;
|
||||
default:
|
||||
DIAG(F("!! ackOp %d FAULT!!"),opcode);
|
||||
callback( -1);
|
||||
return;
|
||||
|
||||
} // end of switch
|
||||
ackManagerProg++;
|
||||
}
|
||||
}
|
||||
|
||||
void DCCACK::callback(int value) {
|
||||
// check for automatic retry
|
||||
if (value == -1 && ackManagerRetry > 0) {
|
||||
ackRetrySum ++;
|
||||
LCD(0, F("Retry %d %d Sum=%d"), ackManagerCv, ackManagerRetry, ackRetrySum);
|
||||
ackManagerRetry --;
|
||||
ackManagerProg = ackManagerProgStart;
|
||||
return;
|
||||
}
|
||||
|
||||
static unsigned long callbackStart;
|
||||
// We are about to leave programming mode
|
||||
// Rule 1: If we have written to a decoder we must maintain power for 100mS
|
||||
// Rule 2: If we are re-joining the main track we must power off for 30mS
|
||||
|
||||
switch (callbackState) {
|
||||
case AFTER_READ:
|
||||
if (ackManagerRejoin && autoPowerOff) {
|
||||
progDriver->setPower(POWERMODE::OFF);
|
||||
callbackStart=millis();
|
||||
callbackState=WAITING_30;
|
||||
if (Diag::ACK) DIAG(F("OFF 30mS"));
|
||||
} else {
|
||||
callbackState=READY;
|
||||
}
|
||||
break;
|
||||
|
||||
case AFTER_WRITE: // first attempt to callback after a write operation
|
||||
if (!ackManagerRejoin && !autoPowerOff) {
|
||||
callbackState=READY;
|
||||
break;
|
||||
} // lines 906-910 added. avoid wait after write. use 1 PROG
|
||||
callbackStart=millis();
|
||||
callbackState=WAITING_100;
|
||||
if (Diag::ACK) DIAG(F("Stable 100mS"));
|
||||
break;
|
||||
|
||||
case WAITING_100: // waiting for 100mS
|
||||
if (millis()-callbackStart < 100) break;
|
||||
// stable after power maintained for 100mS
|
||||
|
||||
// If we are going to power off anyway, it doesnt matter
|
||||
// but if we will keep the power on, we must off it for 30mS
|
||||
if (autoPowerOff) callbackState=READY;
|
||||
else { // Need to cycle power off and on
|
||||
progDriver->setPower(POWERMODE::OFF);
|
||||
callbackStart=millis();
|
||||
callbackState=WAITING_30;
|
||||
if (Diag::ACK) DIAG(F("OFF 30mS"));
|
||||
}
|
||||
break;
|
||||
|
||||
case WAITING_30: // waiting for 30mS with power off
|
||||
if (millis()-callbackStart < 30) break;
|
||||
//power has been off for 30mS
|
||||
progDriver->setPower(POWERMODE::ON);
|
||||
callbackState=READY;
|
||||
break;
|
||||
|
||||
case READY: // ready after read, or write after power delay and off period.
|
||||
// power off if we powered it on
|
||||
if (autoPowerOff) {
|
||||
if (Diag::ACK) DIAG(F("Auto Prog power off"));
|
||||
progDriver->setPower(POWERMODE::OFF);
|
||||
/* TODO
|
||||
if (MotorDriver::commonFaultPin)
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||
**/
|
||||
}
|
||||
// Restore <1 JOIN> to state before BASELINE
|
||||
if (ackManagerRejoin) {
|
||||
TrackManager::setJoin(true);
|
||||
if (Diag::ACK) DIAG(F("Auto JOIN"));
|
||||
}
|
||||
|
||||
ackManagerProg=NULL; // no more steps to execute
|
||||
if (Diag::ACK) DIAG(F("Callback(%d)"),value);
|
||||
(ackManagerCallback)( value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DCCACK::checkAck(byte sentResetsSincePacket) {
|
||||
if (!ackPending) return;
|
||||
// This function operates in interrupt() time so must be fast and can't DIAG
|
||||
if (sentResetsSincePacket > 6) { //ACK timeout
|
||||
ackCheckDuration=millis()-ackCheckStart;
|
||||
ackPending = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int current=progDriver->getCurrentRaw(true); // true means "from interrupt"
|
||||
numAckSamples++;
|
||||
if (current > ackMaxCurrent) ackMaxCurrent=current;
|
||||
// An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba)
|
||||
|
||||
if (current>ackThreshold) {
|
||||
if (trailingEdgeCounter > 0) {
|
||||
numAckGaps++;
|
||||
trailingEdgeCounter = 0;
|
||||
}
|
||||
if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected
|
||||
return;
|
||||
}
|
||||
|
||||
// not in pulse
|
||||
if (ackPulseStart==0) return; // keep waiting for leading edge
|
||||
|
||||
// if we reach to this point, we have
|
||||
// detected trailing edge of pulse
|
||||
if (trailingEdgeCounter == 0) {
|
||||
ackPulseDuration=micros()-ackPulseStart;
|
||||
}
|
||||
|
||||
// but we do not trust it yet and return (which will force another
|
||||
// measurement) and first the third time around with low current
|
||||
// the ack detection will be finalized.
|
||||
if (trailingEdgeCounter < 2) {
|
||||
trailingEdgeCounter++;
|
||||
return;
|
||||
}
|
||||
trailingEdgeCounter = 0;
|
||||
|
||||
if (ackPulseDuration>=minAckPulseDuration && ackPulseDuration<=maxAckPulseDuration) {
|
||||
ackCheckDuration=millis()-ackCheckStart;
|
||||
ackDetected=true;
|
||||
ackPending=false;
|
||||
DCCWaveform::progTrack.clearRepeats(); // shortcut remaining repeat packets
|
||||
return; // we have a genuine ACK result
|
||||
}
|
||||
ackPulseStart=0; // We have detected a too-short or too-long pulse so ignore and wait for next leading edge
|
||||
}
|
||||
|
156
DCCACK.h
156
DCCACK.h
@@ -1,156 +0,0 @@
|
||||
/*
|
||||
* © 2021 M Steve Todd
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2021 Harald Barth
|
||||
* © 2020-2022 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef DCCACK_h
|
||||
#define DCCACK_h
|
||||
|
||||
#include "MotorDriver.h"
|
||||
|
||||
typedef void (*ACK_CALLBACK)(int16_t result);
|
||||
|
||||
enum ackOp : byte
|
||||
{ // 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)
|
||||
ITCBV, // If True callback(byte) - end of Verify Byte
|
||||
ITCB7, // If True callback(byte &0x7F)
|
||||
NAKFAIL, // if false callback(-1)
|
||||
CALLFAIL, // callback(-1)
|
||||
BIV, // Set ackManagerByte to initial value for Verify retry
|
||||
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
|
||||
SETBYTE, // sets current byte to next prog byte
|
||||
SETBYTEH, // sets current byte to word high byte
|
||||
SETBYTEL, // sets current byte to word low 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 CALLBACK_STATE : byte {
|
||||
|
||||
AFTER_READ, // Start callback sequence after something was read from the decoder
|
||||
AFTER_WRITE, // Start callback sequence after something was written to the decoder
|
||||
WAITING_100, // Waiting for 100mS of stable power
|
||||
WAITING_30, // waiting to 30ms of power off gap.
|
||||
READY, // Ready to complete callback
|
||||
};
|
||||
|
||||
|
||||
|
||||
class DCCACK {
|
||||
public:
|
||||
static byte getAck(); //prog track only 0=NACK, 1=ACK 2=keep waiting
|
||||
static void checkAck(byte sentResetsSincePacket); // Interrupt time ack checker
|
||||
static inline void setAckLimit(int mA) {
|
||||
ackLimitmA = mA;
|
||||
}
|
||||
static inline void setMinAckPulseDuration(unsigned int i) {
|
||||
minAckPulseDuration = i;
|
||||
}
|
||||
static inline void setMaxAckPulseDuration(unsigned int i) {
|
||||
maxAckPulseDuration = i;
|
||||
}
|
||||
|
||||
static void Setup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback);
|
||||
static void Setup(int wordval, ackOp const program[], ACK_CALLBACK callback);
|
||||
static void loop();
|
||||
static bool isActive() { return ackManagerProg!=NULL;}
|
||||
static inline int16_t setAckRetry(byte retry) {
|
||||
ackRetry = retry;
|
||||
ackRetryPSum = ackRetrySum;
|
||||
ackRetrySum = 0; // reset running total
|
||||
return ackRetryPSum;
|
||||
};
|
||||
|
||||
|
||||
private:
|
||||
static const byte SET_SPEED = 0x3f;
|
||||
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 void setAckBaseline();
|
||||
static void setAckPending();
|
||||
static void callback(int value);
|
||||
|
||||
static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable)
|
||||
|
||||
// ACK management (Prog track only)
|
||||
static void checkAck();
|
||||
static bool checkResets(uint8_t numResets);
|
||||
|
||||
static volatile bool ackPending;
|
||||
static volatile bool ackDetected;
|
||||
static int ackThreshold;
|
||||
static int ackLimitmA;
|
||||
static int ackMaxCurrent;
|
||||
static unsigned long ackCheckStart; // millis
|
||||
static unsigned int ackCheckDuration; // millis
|
||||
|
||||
static unsigned int ackPulseDuration; // micros
|
||||
static unsigned long ackPulseStart; // micros
|
||||
|
||||
static unsigned int minAckPulseDuration ; // micros
|
||||
static unsigned int maxAckPulseDuration ; // micros
|
||||
static MotorDriver* progDriver;
|
||||
static volatile uint8_t numAckGaps;
|
||||
static volatile uint8_t numAckSamples;
|
||||
static uint8_t trailingEdgeCounter;
|
||||
static ackOp const * ackManagerProg;
|
||||
static ackOp const * ackManagerProgStart;
|
||||
static byte ackManagerByte;
|
||||
static byte ackManagerByteVerify;
|
||||
static byte ackManagerStash;
|
||||
static int ackManagerWord;
|
||||
static byte ackManagerRetry;
|
||||
static byte ackRetry;
|
||||
static int16_t ackRetrySum;
|
||||
static int16_t ackRetryPSum;
|
||||
static int ackManagerCv;
|
||||
static byte ackManagerBitNum;
|
||||
static bool ackReceived;
|
||||
static bool ackManagerRejoin;
|
||||
static bool autoPowerOff;
|
||||
static CALLBACK_STATE callbackState;
|
||||
static ACK_CALLBACK ackManagerCallback;
|
||||
|
||||
|
||||
};
|
||||
#endif
|
7
DCCEX.h
7
DCCEX.h
@@ -32,23 +32,18 @@
|
||||
#include "DCCEXParser.h"
|
||||
#include "SerialManager.h"
|
||||
#include "version.h"
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
#include "WifiInterface.h"
|
||||
#else
|
||||
#include "WifiESP32.h"
|
||||
#endif
|
||||
#if ETHERNET_ON == true
|
||||
#include "EthernetInterface.h"
|
||||
#endif
|
||||
#include "LCD_Implementation.h"
|
||||
#include "LCN.h"
|
||||
#include "freeMemory.h"
|
||||
#include "IODevice.h"
|
||||
#include "Turnouts.h"
|
||||
#include "Sensors.h"
|
||||
#include "Outputs.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "TrackManager.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "EXRAIL.h"
|
||||
|
||||
#endif
|
||||
|
341
DCCEXParser.cpp
341
DCCEXParser.cpp
@@ -1,5 +1,4 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021 Mike S
|
||||
* © 2021 Herb Morton
|
||||
@@ -31,25 +30,24 @@
|
||||
#include "Turnouts.h"
|
||||
#include "Outputs.h"
|
||||
#include "Sensors.h"
|
||||
#include "freeMemory.h"
|
||||
#include "GITHUB_SHA.h"
|
||||
#include "version.h"
|
||||
#include "defines.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "EEStore.h"
|
||||
#include "DIAG.h"
|
||||
#include "TrackManager.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "EXRAIL2.h"
|
||||
|
||||
// This macro can't be created easily as a portable function because the
|
||||
// flashlist requires a far pointer for high flash access.
|
||||
#define SENDFLASHLIST(stream,flashList) \
|
||||
for (int16_t i=0;;i+=sizeof(flashList[0])) { \
|
||||
int16_t value=GETHIGHFLASHW(flashList,i); \
|
||||
if (value==0) break; \
|
||||
StringFormatter::send(stream,F(" %d"),value); \
|
||||
}
|
||||
#include <avr/wdt.h>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Figure out if we have enough memory for advanced features
|
||||
//
|
||||
#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO)
|
||||
// nope
|
||||
#else
|
||||
#define HAS_ENOUGH_MEMORY
|
||||
#endif
|
||||
|
||||
// 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
|
||||
@@ -75,13 +73,9 @@ const int16_t HASH_KEYWORD_RETRY = 25704;
|
||||
const int16_t HASH_KEYWORD_SPEED28 = -17064;
|
||||
const int16_t HASH_KEYWORD_SPEED128 = 25816;
|
||||
const int16_t HASH_KEYWORD_SERVO=27709;
|
||||
const int16_t HASH_KEYWORD_TT=2688;
|
||||
const int16_t HASH_KEYWORD_VPIN=-415;
|
||||
const int16_t HASH_KEYWORD_A='A';
|
||||
const int16_t HASH_KEYWORD_C='C';
|
||||
const int16_t HASH_KEYWORD_R='R';
|
||||
const int16_t HASH_KEYWORD_T='T';
|
||||
const int16_t HASH_KEYWORD_X='X';
|
||||
const int16_t HASH_KEYWORD_C=67;
|
||||
const int16_t HASH_KEYWORD_T=84;
|
||||
const int16_t HASH_KEYWORD_LCN = 15137;
|
||||
const int16_t HASH_KEYWORD_HAL = 10853;
|
||||
const int16_t HASH_KEYWORD_SHOW = -21309;
|
||||
@@ -97,7 +91,7 @@ Print *DCCEXParser::stashStream = NULL;
|
||||
RingStream *DCCEXParser::stashRingStream = NULL;
|
||||
byte DCCEXParser::stashTarget=0;
|
||||
|
||||
// This is a JMRI command parser.
|
||||
// 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.
|
||||
@@ -151,7 +145,7 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte
|
||||
runningValue = 16 * runningValue + (hot - 'A' + 10);
|
||||
break;
|
||||
}
|
||||
if (hot=='_' || (hot >= 'A' && hot <= 'Z'))
|
||||
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
|
||||
@@ -168,12 +162,9 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte
|
||||
return parameterCount;
|
||||
}
|
||||
|
||||
extern __attribute__((weak)) void myFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
||||
FILTER_CALLBACK DCCEXParser::filterCallback = myFilter;
|
||||
FILTER_CALLBACK DCCEXParser::filterCallback = 0;
|
||||
FILTER_CALLBACK DCCEXParser::filterRMFTCallback = 0;
|
||||
AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0;
|
||||
|
||||
// deprecated
|
||||
void DCCEXParser::setFilter(FILTER_CALLBACK filter)
|
||||
{
|
||||
filterCallback = filter;
|
||||
@@ -193,26 +184,12 @@ void DCCEXParser::parse(const FSH * cmd) {
|
||||
int size=strlen_P((char *)cmd)+1;
|
||||
char buffer[size];
|
||||
strcpy_P(buffer,(char *)cmd);
|
||||
parse(&USB_SERIAL,(byte *)buffer,NULL);
|
||||
parse(&Serial,(byte *)buffer,NULL);
|
||||
}
|
||||
|
||||
// See documentation on DCC class for info on this section
|
||||
|
||||
void DCCEXParser::parse(Print *stream, byte *com, RingStream *ringStream) {
|
||||
// This function can get stings of the form "<C OMM AND>" or "C OMM AND"
|
||||
// found is true first after the leading "<" has been passed
|
||||
bool found = (com[0] != '<');
|
||||
for (byte *c=com; c[0] != '\0'; c++) {
|
||||
if (found) {
|
||||
parseOne(stream, c, ringStream);
|
||||
found=false;
|
||||
}
|
||||
if (c[0] == '<')
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
{
|
||||
#ifndef DISABLE_EEPROM
|
||||
(void)EEPROM; // tell compiler not to warn this is unused
|
||||
@@ -237,23 +214,10 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
return; // filterCallback asked us to ignore
|
||||
case 't': // THROTTLE <t [REGISTER] CAB SPEED DIRECTION>
|
||||
{
|
||||
if (params==1) { // <t cab> display state
|
||||
|
||||
int16_t slot=DCC::lookupSpeedTable(p[0],false);
|
||||
if (slot>=0) {
|
||||
DCC::LOCO * sp=&DCC::speedTable[slot];
|
||||
StringFormatter::send(stream,F("<l %d %d %d %l>\n"),
|
||||
sp->loco,slot,sp->speedCode,sp->functions);
|
||||
}
|
||||
else // send dummy state speed 0 fwd no functions.
|
||||
StringFormatter::send(stream,F("<l %d -1 128 0>\n"),p[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
int16_t cab;
|
||||
int16_t tspeed;
|
||||
int16_t direction;
|
||||
|
||||
|
||||
if (params == 4)
|
||||
{ // <t REGISTER CAB SPEED DIRECTION>
|
||||
cab = p[1];
|
||||
@@ -295,44 +259,33 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
return;
|
||||
break;
|
||||
|
||||
case 'a': // ACCESSORY <a ADDRESS SUBADDRESS ACTIVATE [ONOFF]> or <a LINEARADDRESS ACTIVATE>
|
||||
case 'a': // ACCESSORY <a ADDRESS SUBADDRESS ACTIVATE> or <a LINEARADDRESS ACTIVATE>
|
||||
{
|
||||
int address;
|
||||
byte subaddress;
|
||||
byte activep;
|
||||
byte onoff;
|
||||
if (params==2) { // <a LINEARADDRESS ACTIVATE>
|
||||
address=(p[0] - 1) / 4 + 1;
|
||||
subaddress=(p[0] - 1) % 4;
|
||||
activep=1;
|
||||
onoff=2; // send both
|
||||
activep=1;
|
||||
}
|
||||
else if (params==3) { // <a ADDRESS SUBADDRESS ACTIVATE>
|
||||
address=p[0];
|
||||
subaddress=p[1];
|
||||
activep=2;
|
||||
onoff=2; // send both
|
||||
}
|
||||
else if (params==4) { // <a ADDRESS SUBADDRESS ACTIVATE ONOFF>
|
||||
address=p[0];
|
||||
subaddress=p[1];
|
||||
activep=2;
|
||||
if ((p[3] < 0) || (p[3] > 1)) // invalid onoff 0|1
|
||||
break;
|
||||
onoff=p[3];
|
||||
activep=2;
|
||||
}
|
||||
else break; // invalid no of parameters
|
||||
|
||||
if (
|
||||
((address & 0x01FF) != address) // invalid address (limit 9 bits)
|
||||
|| ((subaddress & 0x03) != subaddress) // invalid subaddress (limit 2 bits)
|
||||
|| (p[activep] > 1) || (p[activep] < 0) // invalid activate 0|1
|
||||
) break;
|
||||
((address & 0x01FF) != address) // invalid address (limit 9 bits )
|
||||
|| ((subaddress & 0x03) != subaddress) // invalid subaddress (limit 2 bits )
|
||||
|| ((p[activep] & 0x01) != p[activep]) // invalid activate 0|1
|
||||
) break;
|
||||
// Honour the configuration option (config.h) which allows the <a> command to be reversed
|
||||
#ifdef DCC_ACCESSORY_COMMAND_REVERSE
|
||||
DCC::setAccessory(address, subaddress,p[activep]==0,onoff);
|
||||
DCC::setAccessory(address, subaddress,p[activep]==0);
|
||||
#else
|
||||
DCC::setAccessory(address, subaddress,p[activep]==1,onoff);
|
||||
DCC::setAccessory(address, subaddress,p[activep]==1);
|
||||
#endif
|
||||
}
|
||||
return;
|
||||
@@ -364,8 +317,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
case 'P': // WRITE TRANSPARENT DCC PACKET PROG <P REG X1 ... X9>
|
||||
// NOTE: this command was parsed in HEX instead of decimal
|
||||
params--; // drop REG
|
||||
if (params<1) break;
|
||||
if (params > MAX_PACKET_SIZE) break;
|
||||
if (params<1) break;
|
||||
{
|
||||
byte packet[params];
|
||||
for (int i=0;i<params;i++) {
|
||||
@@ -381,9 +333,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
break;
|
||||
if (params == 1) // <W id> Write new loco id (clearing consist and managing short/long)
|
||||
DCC::setLocoId(p[0],callback_Wloco);
|
||||
else if (params == 4) // WRITE CV ON PROG <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
|
||||
DCC::writeCVByte(p[0], p[1], callback_W4);
|
||||
else // WRITE CV ON PROG <W CV VALUE>
|
||||
else // WRITE CV ON PROG <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
|
||||
DCC::writeCVByte(p[0], p[1], callback_W);
|
||||
return;
|
||||
|
||||
@@ -411,13 +361,6 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
return;
|
||||
|
||||
case 'R': // READ CV ON PROG
|
||||
if (params == 1)
|
||||
{ // <R CV> -- uses verify callback
|
||||
if (!stashCallback(stream, p, ringStream))
|
||||
break;
|
||||
DCC::verifyCVByte(p[0], 0, callback_Vbyte);
|
||||
return;
|
||||
}
|
||||
if (params == 3)
|
||||
{ // <R CV CALLBACKNUM CALLBACKSUB>
|
||||
if (!stashCallback(stream, p, ringStream))
|
||||
@@ -458,9 +401,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
}
|
||||
else break; // will reply <X>
|
||||
}
|
||||
if (main) TrackManager::setMainPower(POWERMODE::ON);
|
||||
if (prog) TrackManager::setProgPower(POWERMODE::ON);
|
||||
TrackManager::setJoin(join);
|
||||
if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||
if (prog) DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
DCC::setProgTrackSyncMain(join);
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
@@ -485,12 +428,12 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
else break; // will reply <X>
|
||||
}
|
||||
|
||||
if (main) TrackManager::setMainPower(POWERMODE::OFF);
|
||||
if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||
if (prog) {
|
||||
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
|
||||
TrackManager::setProgPower(POWERMODE::OFF);
|
||||
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
||||
}
|
||||
TrackManager::setJoin(false);
|
||||
DCC::setProgTrackSyncMain(false);
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
@@ -501,14 +444,18 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
return;
|
||||
|
||||
case 'c': // SEND METER RESPONSES <c>
|
||||
// No longer supported because of multiple tracks <c MeterName value C/V unit min max res warn>
|
||||
break;
|
||||
// <c MeterName value C/V unit min max res warn>
|
||||
StringFormatter::send(stream, F("<c CurrentMAIN %d C Milli 0 %d 1 %d>\n"), DCCWaveform::mainTrack.getCurrentmA(),
|
||||
DCCWaveform::mainTrack.getMaxmA(), DCCWaveform::mainTrack.getTripmA());
|
||||
StringFormatter::send(stream, F("<a %d>\n"), DCCWaveform::mainTrack.get1024Current()); //'a' message deprecated, remove once JMRI 4.22 is available
|
||||
return;
|
||||
|
||||
case 'Q': // SENSORS <Q>
|
||||
Sensor::printAll(stream);
|
||||
return;
|
||||
|
||||
case 's': // <s>
|
||||
StringFormatter::send(stream, F("<p%d>\n"), DCCWaveform::mainTrack.getPowerMode() == POWERMODE::ON);
|
||||
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||
Turnout::printAll(stream); //send all Turnout states
|
||||
Output::printAll(stream); //send all Output states
|
||||
@@ -536,11 +483,6 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
return;
|
||||
return;
|
||||
|
||||
case '=': // <= Track manager control >
|
||||
if (TrackManager::parseJ(stream, params, p))
|
||||
return;
|
||||
break;
|
||||
|
||||
case '#': // NUMBER OF LOCOSLOTS <#>
|
||||
StringFormatter::send(stream, F("<# %d>\n"), MAX_LOCOS);
|
||||
return;
|
||||
@@ -555,82 +497,17 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
if(params!=3) break;
|
||||
if (Diag::CMD)
|
||||
DIAG(F("Setting loco %d F%d %S"), p[0], p[1], p[2] ? F("ON") : F("OFF"));
|
||||
if (DCC::setFn(p[0], p[1], p[2] == 1)) return;
|
||||
break;
|
||||
DCC::setFn(p[0], p[1], p[2] == 1);
|
||||
return;
|
||||
|
||||
#if WIFI_ON
|
||||
case '+': // Complex Wifi interface command (not usual parse)
|
||||
if (atCommandCallback && !ringStream) {
|
||||
TrackManager::setPower(POWERMODE::OFF);
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
||||
atCommandCallback((HardwareSerial *)stream,com);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
case 'J' : // throttle info access
|
||||
{
|
||||
if ((params<1) | (params>2)) break; // <J>
|
||||
int16_t id=(params==2)?p[1]:0;
|
||||
switch(p[0]) {
|
||||
case HASH_KEYWORD_A: // <JA> returns automations/routes
|
||||
StringFormatter::send(stream, F("<jA"));
|
||||
if (params==1) {// <JA>
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
SENDFLASHLIST(stream,RMFT2::routeIdList)
|
||||
SENDFLASHLIST(stream,RMFT2::automationIdList)
|
||||
#endif
|
||||
}
|
||||
else { // <JA id>
|
||||
StringFormatter::send(stream,F(" %d %c \"%S\""),
|
||||
id,
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
RMFT2::getRouteType(id), // A/R
|
||||
RMFT2::getRouteDescription(id)
|
||||
#else
|
||||
'X',F("")
|
||||
#endif
|
||||
);
|
||||
}
|
||||
StringFormatter::send(stream, F(">\n"));
|
||||
return;
|
||||
case HASH_KEYWORD_R: // <JR> returns rosters
|
||||
StringFormatter::send(stream, F("<jR"));
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
if (params==1) {
|
||||
SENDFLASHLIST(stream,RMFT2::rosterIdList)
|
||||
}
|
||||
else StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
|
||||
id, RMFT2::getRosterName(id), RMFT2::getRosterFunctions(id));
|
||||
#endif
|
||||
StringFormatter::send(stream, F(">\n"));
|
||||
return;
|
||||
case HASH_KEYWORD_T: // <JT> returns turnout list
|
||||
StringFormatter::send(stream, F("<jT"));
|
||||
if (params==1) { // <JT>
|
||||
for ( Turnout * t=Turnout::first(); t; t=t->next()) {
|
||||
if (t->isHidden()) continue;
|
||||
StringFormatter::send(stream, F(" %d"),t->getId());
|
||||
}
|
||||
}
|
||||
else { // <JT id>
|
||||
Turnout * t=Turnout::get(id);
|
||||
if (!t || t->isHidden()) StringFormatter::send(stream, F(" %d X"),id);
|
||||
else StringFormatter::send(stream, F(" %d %c \"%S\""),
|
||||
id,t->isThrown()?'T':'C',
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
RMFT2::getTurnoutDescription(id)
|
||||
#else
|
||||
F("")
|
||||
#endif
|
||||
);
|
||||
}
|
||||
StringFormatter::send(stream, F(">\n"));
|
||||
return;
|
||||
default: break;
|
||||
} // switch(p[1])
|
||||
break; // case J
|
||||
}
|
||||
|
||||
default: //anything else will diagnose and drop out to <X>
|
||||
DIAG(F("Opcode=%c params=%d"), opcode, params);
|
||||
@@ -692,39 +569,43 @@ bool DCCEXParser::parseZ(Print *stream, int16_t params, int16_t p[])
|
||||
//===================================
|
||||
bool DCCEXParser::parsef(Print *stream, int16_t params, int16_t p[])
|
||||
{
|
||||
// JMRI sends this info in DCC message format but it's not exactly
|
||||
// convenient for other processing
|
||||
if (params == 2) {
|
||||
byte instructionField = p[1] & 0xE0; // 1110 0000
|
||||
if (instructionField == 0x80) { // 1000 0000 Function group 1
|
||||
// Shuffle bits from order F0 F4 F3 F2 F1 to F4 F3 F2 F1 F0
|
||||
byte normalized = (p[1] << 1 & 0x1e) | (p[1] >> 4 & 0x01);
|
||||
return (funcmap(p[0], normalized, 0, 4));
|
||||
} else if (instructionField == 0xA0) { // 1010 0000 Function group 2
|
||||
if (p[1] & 0x10) // 0001 0000 Bit selects F5toF8 / F9toF12
|
||||
return (funcmap(p[0], p[1], 5, 8));
|
||||
else
|
||||
return (funcmap(p[0], p[1], 9, 12));
|
||||
}
|
||||
}
|
||||
if (params == 3) {
|
||||
if (p[1] == 222) {
|
||||
return (funcmap(p[0], p[2], 13, 20));
|
||||
} else if (p[1] == 223) {
|
||||
return (funcmap(p[0], p[2], 21, 28));
|
||||
}
|
||||
}
|
||||
(void)stream; // NO RESPONSE
|
||||
return false;
|
||||
// JMRI sends this info in DCC message format but it's not exactly
|
||||
// convenient for other processing
|
||||
if (params == 2)
|
||||
{
|
||||
byte instructionField = p[1] & 0xE0; // 1110 0000
|
||||
if (instructionField == 0x80) // 1000 0000 Function group 1
|
||||
{
|
||||
// Shuffle bits from order F0 F4 F3 F2 F1 to F4 F3 F2 F1 F0
|
||||
byte normalized = (p[1] << 1 & 0x1e) | (p[1] >> 4 & 0x01);
|
||||
funcmap(p[0], normalized, 0, 4);
|
||||
}
|
||||
else if (instructionField == 0xA0) // 1010 0000 Function group 2
|
||||
{
|
||||
if (p[1] & 0x10) // 0001 0000 Bit selects F5toF8 / F9toF12
|
||||
funcmap(p[0], p[1], 5, 8);
|
||||
else
|
||||
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::funcmap(int16_t cab, byte value, byte fstart, byte fstop)
|
||||
void DCCEXParser::funcmap(int16_t cab, byte value, byte fstart, byte fstop)
|
||||
{
|
||||
for (int16_t i = fstart; i <= fstop; i++) {
|
||||
if (! DCC::setFn(cab, i, value & 1)) return false;
|
||||
value >>= 1;
|
||||
}
|
||||
return true;
|
||||
for (int16_t i = fstart; i <= fstop; i++)
|
||||
{
|
||||
DCC::setFn(cab, i, value & 1);
|
||||
value >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
//===================================
|
||||
@@ -733,7 +614,15 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
|
||||
switch (params)
|
||||
{
|
||||
case 0: // <T> list turnout definitions
|
||||
return Turnout::printAll(stream); // will <X> if none found
|
||||
{
|
||||
bool gotOne = false;
|
||||
for (Turnout *tt = Turnout::first(); tt != NULL; tt = tt->next())
|
||||
{
|
||||
gotOne = true;
|
||||
tt->print(stream);
|
||||
}
|
||||
return gotOne; // will <X> if none found
|
||||
}
|
||||
|
||||
case 1: // <T id> delete turnout
|
||||
if (!Turnout::remove(p[0]))
|
||||
@@ -754,19 +643,12 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
|
||||
case HASH_KEYWORD_T:
|
||||
state= false;
|
||||
break;
|
||||
case HASH_KEYWORD_X:
|
||||
{
|
||||
Turnout *tt = Turnout::get(p[0]);
|
||||
if (tt) {
|
||||
tt->print(stream);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
default: // Invalid parameter
|
||||
return false;
|
||||
default:
|
||||
return false; // Invalid parameter
|
||||
}
|
||||
if (!Turnout::setClosed(p[0], state)) return false;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -849,23 +731,23 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_RAM: // <D RAM>
|
||||
StringFormatter::send(stream, F("Free memory=%d\n"), DCCTimer::getMinimumFreeMemory());
|
||||
StringFormatter::send(stream, F("Free memory=%d\n"), minimumFreeMemory());
|
||||
break;
|
||||
|
||||
case HASH_KEYWORD_ACK: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value>
|
||||
if (params >= 3) {
|
||||
if (p[1] == HASH_KEYWORD_LIMIT) {
|
||||
DCCACK::setAckLimit(p[2]);
|
||||
DCCWaveform::progTrack.setAckLimit(p[2]);
|
||||
LCD(1, F("Ack Limit=%dmA"), p[2]); // <D ACK LIMIT 42>
|
||||
} else if (p[1] == HASH_KEYWORD_MIN) {
|
||||
DCCACK::setMinAckPulseDuration(p[2]);
|
||||
DCCWaveform::progTrack.setMinAckPulseDuration(p[2]);
|
||||
LCD(0, F("Ack Min=%uus"), p[2]); // <D ACK MIN 1500>
|
||||
} else if (p[1] == HASH_KEYWORD_MAX) {
|
||||
DCCACK::setMaxAckPulseDuration(p[2]);
|
||||
DCCWaveform::progTrack.setMaxAckPulseDuration(p[2]);
|
||||
LCD(0, F("Ack Max=%uus"), p[2]); // <D ACK MAX 9000>
|
||||
} else if (p[1] == HASH_KEYWORD_RETRY) {
|
||||
if (p[2] >255) p[2]=3;
|
||||
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // <D ACK RETRY 2>
|
||||
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCC::setAckRetry(p[2])); // <D ACK RETRY 2>
|
||||
}
|
||||
} else {
|
||||
StringFormatter::send(stream, F("Ack diag %S\n"), onOff ? F("on") : F("off"));
|
||||
@@ -896,13 +778,15 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
#endif
|
||||
|
||||
case HASH_KEYWORD_PROGBOOST:
|
||||
TrackManager::progTrackBoosted=true;
|
||||
return true;
|
||||
DCC::setProgTrackBoost(true);
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_RESET:
|
||||
DCCTimer::reset();
|
||||
break; // and <X> if we didnt restart
|
||||
|
||||
{
|
||||
wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms
|
||||
delay(50); // wait for the prescaller time to expire
|
||||
break; // and <X> if we didnt restart
|
||||
}
|
||||
|
||||
#ifndef DISABLE_EEPROM
|
||||
case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries>
|
||||
@@ -937,10 +821,6 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
break;
|
||||
#endif
|
||||
|
||||
case HASH_KEYWORD_TT: // <D TT vpin steps activity>
|
||||
IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
|
||||
break;
|
||||
|
||||
default: // invalid/unknown
|
||||
break;
|
||||
}
|
||||
@@ -976,14 +856,7 @@ void DCCEXParser::commitAsyncReplyStream() {
|
||||
void DCCEXParser::callback_W(int16_t result)
|
||||
{
|
||||
StringFormatter::send(getAsyncReplyStream(),
|
||||
F("<r %d %d>\n"), stashP[0], result == 1 ? stashP[1] : -1);
|
||||
commitAsyncReplyStream();
|
||||
}
|
||||
|
||||
void DCCEXParser::callback_W4(int16_t result)
|
||||
{
|
||||
StringFormatter::send(getAsyncReplyStream(),
|
||||
F("<r%d|%d|%d %d>\n"), stashP[2], stashP[3], stashP[0], result == 1 ? stashP[1] : -1);
|
||||
F("<r%d|%d|%d %d>\n"), stashP[2], stashP[3], stashP[0], result == 1 ? stashP[1] : -1);
|
||||
commitAsyncReplyStream();
|
||||
}
|
||||
|
||||
|
@@ -33,7 +33,6 @@ struct DCCEXParser
|
||||
|
||||
static void parse(Print * stream, byte * command, RingStream * ringStream);
|
||||
static void parse(const FSH * cmd);
|
||||
static void parseOne(Print * stream, byte * command, RingStream * ringStream);
|
||||
static void setFilter(FILTER_CALLBACK filter);
|
||||
static void setRMFTFilter(FILTER_CALLBACK filter);
|
||||
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
|
||||
@@ -61,7 +60,6 @@ struct DCCEXParser
|
||||
static int16_t stashP[MAX_COMMAND_PARAMS];
|
||||
static bool stashCallback(Print * stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream);
|
||||
static void callback_W(int16_t result);
|
||||
static void callback_W4(int16_t result);
|
||||
static void callback_B(int16_t result);
|
||||
static void callback_R(int16_t result);
|
||||
static void callback_Rloco(int16_t result);
|
||||
@@ -71,8 +69,7 @@ struct DCCEXParser
|
||||
static FILTER_CALLBACK filterCallback;
|
||||
static FILTER_CALLBACK filterRMFTCallback;
|
||||
static AT_COMMAND_CALLBACK atCommandCallback;
|
||||
static bool funcmap(int16_t cab, byte value, byte fstart, byte fstop);
|
||||
static void sendFlashList(Print * stream,const int16_t flashList[]);
|
||||
static void funcmap(int16_t cab, byte value, byte fstart, byte fstop);
|
||||
|
||||
};
|
||||
|
||||
|
235
DCCRMT.cpp
235
DCCRMT.cpp
@@ -1,235 +0,0 @@
|
||||
/*
|
||||
* © 2021-2022, Harald Barth.
|
||||
*
|
||||
* This file is part of DCC-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#include "defines.h"
|
||||
#include "DIAG.h"
|
||||
#include "DCCRMT.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "DCCWaveform.h" // for MAX_PACKET_SIZE
|
||||
#include "soc/gpio_sig_map.h"
|
||||
|
||||
// Number of bits resulting out of X bytes of DCC payload data
|
||||
// Each byte has one bit extra and at the end we have one EOF marker
|
||||
#define DATA_LEN(X) ((X)*9+1)
|
||||
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,2,0)
|
||||
#error wrong IDF version
|
||||
#endif
|
||||
|
||||
void setDCCBit1(rmt_item32_t* item) {
|
||||
item->level0 = 1;
|
||||
item->duration0 = DCC_1_HALFPERIOD;
|
||||
item->level1 = 0;
|
||||
item->duration1 = DCC_1_HALFPERIOD;
|
||||
}
|
||||
|
||||
void setDCCBit0(rmt_item32_t* item) {
|
||||
item->level0 = 1;
|
||||
item->duration0 = DCC_0_HALFPERIOD;
|
||||
item->level1 = 0;
|
||||
item->duration1 = DCC_0_HALFPERIOD;
|
||||
}
|
||||
|
||||
// special long zero to trigger scope
|
||||
void setDCCBit0Long(rmt_item32_t* item) {
|
||||
item->level0 = 1;
|
||||
item->duration0 = DCC_0_HALFPERIOD + DCC_0_HALFPERIOD/10;
|
||||
item->level1 = 0;
|
||||
item->duration1 = DCC_0_HALFPERIOD + DCC_0_HALFPERIOD/10;
|
||||
}
|
||||
|
||||
void setEOT(rmt_item32_t* item) {
|
||||
item->val = 0;
|
||||
}
|
||||
|
||||
// This is an array that contains the this pointers
|
||||
// to all uses channel objects. This is used to determine
|
||||
// which of the channels was triggering the ISR as there
|
||||
// is only ONE common ISR routine for all channels.
|
||||
RMTChannel *channelHandle[8] = { 0 };
|
||||
|
||||
void IRAM_ATTR interrupt(rmt_channel_t channel, void *t) {
|
||||
RMTChannel *tt = channelHandle[channel];
|
||||
if (tt) tt->RMTinterrupt();
|
||||
if (channel == 0)
|
||||
DCCTimer::updateMinimumFreeMemoryISR(0);
|
||||
}
|
||||
|
||||
RMTChannel::RMTChannel(pinpair pins, bool isMain) {
|
||||
byte ch;
|
||||
byte plen;
|
||||
if (isMain) {
|
||||
ch = 0;
|
||||
plen = PREAMBLE_BITS_MAIN;
|
||||
} else {
|
||||
ch = 2;
|
||||
plen = PREAMBLE_BITS_PROG;
|
||||
}
|
||||
|
||||
// preamble
|
||||
preambleLen = plen+2; // plen 1 bits, one 0 bit and one EOF marker
|
||||
preamble = (rmt_item32_t*)malloc(preambleLen*sizeof(rmt_item32_t));
|
||||
for (byte n=0; n<plen; n++)
|
||||
setDCCBit1(preamble + n); // preamble bits
|
||||
#ifdef SCOPE
|
||||
setDCCBit0Long(preamble + plen); // start of packet 0 bit long version
|
||||
#else
|
||||
setDCCBit0(preamble + plen); // start of packet 0 bit normal version
|
||||
#endif
|
||||
setEOT(preamble + plen + 1); // EOT marker
|
||||
|
||||
// idle
|
||||
idleLen = 28;
|
||||
idle = (rmt_item32_t*)malloc(idleLen*sizeof(rmt_item32_t));
|
||||
if (isMain) {
|
||||
for (byte n=0; n<8; n++) // 0 to 7
|
||||
setDCCBit1(idle + n);
|
||||
for (byte n=8; n<18; n++) // 8, 9 to 16, 17
|
||||
setDCCBit0(idle + n);
|
||||
for (byte n=18; n<26; n++) // 18 to 25
|
||||
setDCCBit1(idle + n);
|
||||
} else {
|
||||
for (byte n=0; n<26; n++) // all zero
|
||||
setDCCBit0(idle + n);
|
||||
}
|
||||
setDCCBit1(idle + 26); // end bit
|
||||
setEOT(idle + 27); // EOT marker
|
||||
|
||||
// data: max packet size today is 5 + checksum
|
||||
maxDataLen = DATA_LEN(MAX_PACKET_SIZE+1); // plus checksum
|
||||
data = (rmt_item32_t*)malloc(maxDataLen*sizeof(rmt_item32_t));
|
||||
|
||||
rmt_config_t config;
|
||||
// Configure the RMT channel for TX
|
||||
bzero(&config, sizeof(rmt_config_t));
|
||||
config.rmt_mode = RMT_MODE_TX;
|
||||
config.channel = channel = (rmt_channel_t)ch;
|
||||
config.clk_div = RMT_CLOCK_DIVIDER;
|
||||
config.gpio_num = (gpio_num_t)pins.pin;
|
||||
config.mem_block_num = 2; // With longest DCC packet 11 inc checksum (future expansion)
|
||||
// number of bits needed is 22preamble + start +
|
||||
// 11*9 + extrazero + EOT = 124
|
||||
// 2 mem block of 64 RMT items should be enough
|
||||
|
||||
ESP_ERROR_CHECK(rmt_config(&config));
|
||||
addPin(pins.invpin, true);
|
||||
/*
|
||||
// test: config another gpio pin
|
||||
gpio_num_t gpioNum = (gpio_num_t)(pin-1);
|
||||
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpioNum], PIN_FUNC_GPIO);
|
||||
gpio_set_direction(gpioNum, GPIO_MODE_OUTPUT);
|
||||
gpio_matrix_out(gpioNum, RMT_SIG_OUT0_IDX, 0, 0);
|
||||
*/
|
||||
|
||||
// NOTE: ESP_INTR_FLAG_IRAM is *NOT* included in this bitmask
|
||||
ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, ESP_INTR_FLAG_LOWMED|ESP_INTR_FLAG_SHARED));
|
||||
|
||||
// DIAG(F("Register interrupt on core %d"), xPortGetCoreID());
|
||||
|
||||
ESP_ERROR_CHECK(rmt_set_tx_loop_mode(channel, true));
|
||||
channelHandle[channel] = this; // used by interrupt
|
||||
rmt_register_tx_end_callback(interrupt, 0);
|
||||
rmt_set_tx_intr_en(channel, true);
|
||||
|
||||
DIAG(F("Channel %d DCC signal for %s start"), config.channel, isMain ? "MAIN" : "PROG");
|
||||
|
||||
// send one bit to kickstart the signal, remaining data will come from the
|
||||
// packet queue. We intentionally do not wait for the RMT TX complete here.
|
||||
//rmt_write_items(channel, preamble, preambleLen, false);
|
||||
RMTprefill();
|
||||
dataReady = false;
|
||||
}
|
||||
|
||||
void RMTChannel::RMTprefill() {
|
||||
rmt_fill_tx_items(channel, preamble, preambleLen, 0);
|
||||
rmt_fill_tx_items(channel, idle, idleLen, preambleLen-1);
|
||||
}
|
||||
|
||||
const byte transmitMask[] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
|
||||
|
||||
int RMTChannel::RMTfillData(const byte buffer[], byte byteCount, byte repeatCount=0) {
|
||||
//int RMTChannel::RMTfillData(dccPacket packet) {
|
||||
// dataReady: Signals to then interrupt routine. It is set when
|
||||
// we have data in the channel buffer which can be copied out
|
||||
// to the HW. dataRepeat on the other hand signals back to
|
||||
// the caller of this function if the data has been sent enough
|
||||
// times (0 to 3 means 1 to 4 times in total).
|
||||
if (dataRepeat > 0) // we have still old work to do
|
||||
return dataRepeat;
|
||||
if (dataReady == true) // the packet is not copied out yet
|
||||
return 1000;
|
||||
if (DATA_LEN(byteCount) > maxDataLen) { // this would overun our allocated memory for data
|
||||
DIAG(F("Can not convert DCC bytes # %d to DCC bits %d, buffer too small"), byteCount, maxDataLen);
|
||||
return -1; // something very broken, can not convert packet
|
||||
}
|
||||
|
||||
// convert bytes to RMT stream of "bits"
|
||||
byte bitcounter = 0;
|
||||
for(byte n=0; n<byteCount; n++) {
|
||||
for(byte bit=0; bit<8; bit++) {
|
||||
if (buffer[n] & transmitMask[bit])
|
||||
setDCCBit1(data + bitcounter++);
|
||||
else
|
||||
setDCCBit0(data + bitcounter++);
|
||||
}
|
||||
setDCCBit0(data + bitcounter++); // zero at end of each byte
|
||||
}
|
||||
setDCCBit1(data + bitcounter-1); // overwrite previous zero bit with one bit
|
||||
setEOT(data + bitcounter++); // EOT marker
|
||||
dataLen = bitcounter;
|
||||
dataReady = true;
|
||||
dataRepeat = repeatCount+1; // repeatCount of 0 means send once
|
||||
return 0;
|
||||
}
|
||||
|
||||
void IRAM_ATTR RMTChannel::RMTinterrupt() {
|
||||
//no rmt_tx_start(channel,true) as we run in loop mode
|
||||
//preamble is always loaded at beginning of buffer
|
||||
packetCounter++;
|
||||
if (!dataReady && dataRepeat == 0) { // we did run empty
|
||||
rmt_fill_tx_items(channel, idle, idleLen, preambleLen-1);
|
||||
return; // nothing to do about that
|
||||
}
|
||||
|
||||
// take care of incoming data
|
||||
if (dataReady) { // if we have new data, fill while preamble is running
|
||||
rmt_fill_tx_items(channel, data, dataLen, preambleLen-1);
|
||||
dataReady = false;
|
||||
}
|
||||
if (dataRepeat > 0) // if a repeat count was specified, work on that
|
||||
dataRepeat--;
|
||||
}
|
||||
|
||||
bool RMTChannel::addPin(byte pin, bool inverted) {
|
||||
if (pin == UNUSED_PIN)
|
||||
return true;
|
||||
gpio_num_t gpioNum = (gpio_num_t)(pin);
|
||||
esp_err_t err;
|
||||
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpioNum], PIN_FUNC_GPIO);
|
||||
err = gpio_set_direction(gpioNum, GPIO_MODE_OUTPUT);
|
||||
if (err != ESP_OK) return false;
|
||||
gpio_matrix_out(gpioNum, RMT_SIG_OUT0_IDX+channel, inverted, 0);
|
||||
if (err != ESP_OK) return false;
|
||||
return true;
|
||||
}
|
||||
bool RMTChannel::addPin(pinpair pins) {
|
||||
return addPin(pins.pin) && addPin(pins.invpin, true);
|
||||
}
|
||||
#endif //ESP32
|
66
DCCRMT.h
66
DCCRMT.h
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* © 2021-2022, Harald Barth.
|
||||
*
|
||||
* This file is part of DCC-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include "driver/rmt.h"
|
||||
#include "soc/rmt_reg.h"
|
||||
#include "soc/rmt_struct.h"
|
||||
#include "MotorDriver.h" // for class pinpair
|
||||
|
||||
// make calculations easy and set up for microseconds
|
||||
#define RMT_CLOCK_DIVIDER 80
|
||||
#define DCC_1_HALFPERIOD 58 //4640 // 1 / 80000000 * 4640 = 58us
|
||||
#define DCC_0_HALFPERIOD 100 //8000
|
||||
|
||||
class RMTChannel {
|
||||
public:
|
||||
RMTChannel(pinpair pins, bool isMain);
|
||||
bool addPin(byte pin, bool inverted=0);
|
||||
bool addPin(pinpair pins);
|
||||
void IRAM_ATTR RMTinterrupt();
|
||||
void RMTprefill();
|
||||
//int RMTfillData(dccPacket packet);
|
||||
int RMTfillData(const byte buffer[], byte byteCount, byte repeatCount);
|
||||
inline bool busy() {
|
||||
if (dataRepeat > 0) // we have still old work to do
|
||||
return true;
|
||||
return dataReady;
|
||||
};
|
||||
inline uint32_t packetCount() { return packetCounter; };
|
||||
|
||||
private:
|
||||
|
||||
rmt_channel_t channel;
|
||||
// 3 types of data to send, preamble and then idle or data
|
||||
// if this is prog track, idle will contain reset instead
|
||||
rmt_item32_t *idle;
|
||||
byte idleLen;
|
||||
rmt_item32_t *preamble;
|
||||
byte preambleLen;
|
||||
rmt_item32_t *data;
|
||||
byte dataLen;
|
||||
byte maxDataLen;
|
||||
uint32_t packetCounter = 0;
|
||||
// flags
|
||||
volatile bool dataReady = false; // do we have real data available or send idle
|
||||
volatile byte dataRepeat = 0;
|
||||
};
|
||||
#endif //ESP32
|
218
DCCTimer.cpp
Normal file
218
DCCTimer.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* © 2021 Mike S
|
||||
* © 2021 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* © 2021 Chris Harlow
|
||||
* © 2021 David Cutting
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of Asbelos DCC API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
/* This timer class is used to manage the single timer required to handle the DCC waveform.
|
||||
* All timer access comes through this class so that it can be compiled for
|
||||
* various hardware CPU types.
|
||||
*
|
||||
* DCCEX works on a single timer interrupt at a regular 58uS interval.
|
||||
* The DCCWaveform class generates the signals to the motor shield
|
||||
* based on this timer.
|
||||
*
|
||||
* If the motor drivers are BOTH configured to use the correct 2 pins for the architecture,
|
||||
* (see isPWMPin() function. )
|
||||
* then this allows us to use a hardware driven pin switching arrangement which is
|
||||
* achieved by setting the duty cycle of the NEXT clock interrupt to 0% or 100% depending on
|
||||
* the required pin state. (see setPWM())
|
||||
* This is more accurate than the software interrupt but at the expense of
|
||||
* limiting the choice of available pins.
|
||||
* Fortunately, a standard motor shield on a Mega uses pins that qualify for PWM...
|
||||
* Other shields may be jumpered to PWM pins or run directly using the software interrupt.
|
||||
*
|
||||
* Because the PWM-based waveform is effectively set half a cycle after the software version,
|
||||
* it is not acceptable to drive the two tracks on different methiods or it would cause
|
||||
* problems for <1 JOIN> etc.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "DCCTimer.h"
|
||||
const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle
|
||||
const long CLOCK_CYCLES=(F_CPU / 1000000 * DCC_SIGNAL_TIME) >>1;
|
||||
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
|
||||
#ifdef ARDUINO_ARCH_MEGAAVR
|
||||
// Arduino unoWifi Rev2 and nanoEvery architectire
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
noInterrupts();
|
||||
ADC0.CTRLC = (ADC0.CTRLC & 0b00110000) | 0b01000011; // speed up analogRead sample time
|
||||
TCB0.CTRLB = TCB_CNTMODE_INT_gc & ~TCB_CCMPEN_bm; // timer compare mode with output disabled
|
||||
TCB0.CTRLA = TCB_CLKSEL_CLKDIV2_gc; // 8 MHz ~ 0.125 us
|
||||
TCB0.CCMP = CLOCK_CYCLES -1; // 1 tick less for timer reset
|
||||
TCB0.INTFLAGS = TCB_CAPT_bm; // clear interrupt request flag
|
||||
TCB0.INTCTRL = TCB_CAPT_bm; // Enable the interrupt
|
||||
TCB0.CNT = 0;
|
||||
TCB0.CTRLA |= TCB_ENABLE_bm; // start
|
||||
interrupts();
|
||||
}
|
||||
|
||||
// ISR called by timer interrupt every 58uS
|
||||
ISR(TCB0_INT_vect){
|
||||
TCB0.INTFLAGS = TCB_CAPT_bm;
|
||||
interruptHandler();
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
(void) pin;
|
||||
return false; // TODO what are the relevant pins?
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
(void) pin;
|
||||
(void) high;
|
||||
// TODO what are the relevant pins?
|
||||
}
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
memcpy(mac,(void *) &SIGROW.SERNUM0,6); // serial number
|
||||
mac[0] &= 0xFE;
|
||||
mac[0] |= 0x02;
|
||||
}
|
||||
|
||||
#elif defined(TEENSYDUINO)
|
||||
IntervalTimer myDCCTimer;
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
|
||||
myDCCTimer.begin(interruptHandler, DCC_SIGNAL_TIME);
|
||||
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
//Teensy: digitalPinHasPWM, todo
|
||||
(void) pin;
|
||||
return false; // TODO what are the relevant pins?
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
// TODO what are the relevant pins?
|
||||
(void) pin;
|
||||
(void) high;
|
||||
}
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
#if defined(__IMXRT1062__) //Teensy 4.0 and Teensy 4.1
|
||||
uint32_t m1 = HW_OCOTP_MAC1;
|
||||
uint32_t m2 = HW_OCOTP_MAC0;
|
||||
mac[0] = m1 >> 8;
|
||||
mac[1] = m1 >> 0;
|
||||
mac[2] = m2 >> 24;
|
||||
mac[3] = m2 >> 16;
|
||||
mac[4] = m2 >> 8;
|
||||
mac[5] = m2 >> 0;
|
||||
#else
|
||||
read_mac(mac);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(__IMXRT1062__)
|
||||
void DCCTimer::read_mac(byte mac[6]) {
|
||||
read(0xe,mac,0);
|
||||
read(0xf,mac,3);
|
||||
}
|
||||
|
||||
// http://forum.pjrc.com/threads/91-teensy-3-MAC-address
|
||||
void DCCTimer::read(uint8_t word, uint8_t *mac, uint8_t offset) {
|
||||
FTFL_FCCOB0 = 0x41; // Selects the READONCE command
|
||||
FTFL_FCCOB1 = word; // read the given word of read once area
|
||||
|
||||
// launch command and wait until complete
|
||||
FTFL_FSTAT = FTFL_FSTAT_CCIF;
|
||||
while(!(FTFL_FSTAT & FTFL_FSTAT_CCIF));
|
||||
|
||||
*(mac+offset) = FTFL_FCCOB5; // collect only the top three bytes,
|
||||
*(mac+offset+1) = FTFL_FCCOB6; // in the right orientation (big endian).
|
||||
*(mac+offset+2) = FTFL_FCCOB7; // Skip FTFL_FCCOB4 as it's always 0.
|
||||
}
|
||||
#endif
|
||||
|
||||
#else
|
||||
// Arduino nano, uno, mega etc
|
||||
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
|
||||
#define TIMER1_A_PIN 11
|
||||
#define TIMER1_B_PIN 12
|
||||
#define TIMER1_C_PIN 13
|
||||
#else
|
||||
#define TIMER1_A_PIN 9
|
||||
#define TIMER1_B_PIN 10
|
||||
#endif
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
noInterrupts();
|
||||
ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time
|
||||
TCCR1A = 0;
|
||||
ICR1 = CLOCK_CYCLES;
|
||||
TCNT1 = 0;
|
||||
TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1
|
||||
TIMSK1 = _BV(TOIE1); // Enable Software interrupt
|
||||
interrupts();
|
||||
}
|
||||
|
||||
// ISR called by timer interrupt every 58uS
|
||||
ISR(TIMER1_OVF_vect){ interruptHandler(); }
|
||||
|
||||
// Alternative pin manipulation via PWM control.
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
return pin==TIMER1_A_PIN
|
||||
|| pin==TIMER1_B_PIN
|
||||
#ifdef TIMER1_C_PIN
|
||||
|| pin==TIMER1_C_PIN
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
if (pin==TIMER1_A_PIN) {
|
||||
TCCR1A |= _BV(COM1A1);
|
||||
OCR1A= high?1024:0;
|
||||
}
|
||||
else if (pin==TIMER1_B_PIN) {
|
||||
TCCR1A |= _BV(COM1B1);
|
||||
OCR1B= high?1024:0;
|
||||
}
|
||||
#ifdef TIMER1_C_PIN
|
||||
else if (pin==TIMER1_C_PIN) {
|
||||
TCCR1A |= _BV(COM1C1);
|
||||
OCR1C= high?1024:0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#include <avr/boot.h>
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
for (byte i=0; i<6; i++) {
|
||||
mac[i]=boot_signature_byte_get(0x0E + i);
|
||||
}
|
||||
mac[0] &= 0xFE;
|
||||
mac[0] |= 0x02;
|
||||
|
||||
}
|
||||
|
||||
#endif
|
98
DCCTimer.h
98
DCCTimer.h
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021-2022 Harald Barth
|
||||
* © 2021 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -21,34 +20,6 @@
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* There are several different implementations of this class which the compiler will select
|
||||
according to the hardware.
|
||||
*/
|
||||
|
||||
/* This timer class is used to manage the single timer required to handle the DCC waveform.
|
||||
* All timer access comes through this class so that it can be compiled for
|
||||
* various hardware CPU types.
|
||||
*
|
||||
* DCCEX works on a single timer interrupt at a regular 58uS interval.
|
||||
* The DCCWaveform class generates the signals to the motor shield
|
||||
* based on this timer.
|
||||
*
|
||||
* If the motor drivers are BOTH configured to use the correct 2 pins for the architecture,
|
||||
* (see isPWMPin() function. )
|
||||
* then this allows us to use a hardware driven pin switching arrangement which is
|
||||
* achieved by setting the duty cycle of the NEXT clock interrupt to 0% or 100% depending on
|
||||
* the required pin state. (see setPWM())
|
||||
* This is more accurate than the software interrupt but at the expense of
|
||||
* limiting the choice of available pins.
|
||||
* Fortunately, a standard motor shield on a Mega uses pins that qualify for PWM...
|
||||
* Other shields may be jumpered to PWM pins or run directly using the software interrupt.
|
||||
*
|
||||
* Because the PWM-based waveform is effectively set half a cycle after the software version,
|
||||
* it is not acceptable to drive the two tracks on different methiods or it would cause
|
||||
* problems for <1 JOIN> etc.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DCCTimer_h
|
||||
#define DCCTimer_h
|
||||
#include "Arduino.h"
|
||||
@@ -61,70 +32,11 @@ class DCCTimer {
|
||||
static void getSimulatedMacAddress(byte mac[6]);
|
||||
static bool isPWMPin(byte pin);
|
||||
static void setPWM(byte pin, bool high);
|
||||
static void clearPWM();
|
||||
// Update low ram level. Allow for extra bytes to be specified
|
||||
// by estimation or inspection, that may be used by other
|
||||
// called subroutines. Must be called with interrupts disabled.
|
||||
//
|
||||
// Although __brkval may go up and down as heap memory is allocated
|
||||
// and freed, this function records only the worst case encountered.
|
||||
// So even if all of the heap is freed, the reported minimum free
|
||||
// memory will not increase.
|
||||
//
|
||||
static void inline updateMinimumFreeMemoryISR(unsigned char extraBytes=0)
|
||||
__attribute__((always_inline)) {
|
||||
int spare = freeMemory()-extraBytes;
|
||||
if (spare < 0) spare = 0;
|
||||
if (spare < minimum_free_memory) minimum_free_memory = spare;
|
||||
};
|
||||
|
||||
static int getMinimumFreeMemory();
|
||||
static void reset();
|
||||
|
||||
private:
|
||||
static int freeMemory();
|
||||
static volatile int minimum_free_memory;
|
||||
static const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle
|
||||
#if defined(ARDUINO_ARCH_STM32) // TODO: PMA temporary hack - assumes 100Mhz F_CPU as STM32 can change frequency
|
||||
static const long CLOCK_CYCLES=(100000000L / 1000000 * DCC_SIGNAL_TIME) >>1;
|
||||
#else
|
||||
static const long CLOCK_CYCLES=(F_CPU / 1000000 * DCC_SIGNAL_TIME) >>1;
|
||||
#if (defined(TEENSYDUINO) && !defined(__IMXRT1062__))
|
||||
static void read_mac(byte mac[6]);
|
||||
static void read(uint8_t word, uint8_t *mac, uint8_t offset);
|
||||
#endif
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
// Class ADCee implements caching of the ADC value for platforms which
|
||||
// have a too slow ADC read to wait for. On these platforms the ADC is
|
||||
// scanned continiously in the background from an ISR. On such
|
||||
// architectures that use the analog read during DCC waveform with
|
||||
// specially configured ADC, for example AVR, init must be called
|
||||
// PRIOR to the start of the waveform. It returns the current value so
|
||||
// that an offset can be initialized.
|
||||
class ADCee {
|
||||
public:
|
||||
// init does add the pin to the list of scanned pins (if this
|
||||
// platform's implementation scans pins) and returns the first
|
||||
// read value. It is called before the regular scan is started.
|
||||
static int init(uint8_t pin);
|
||||
// read does read the pin value from the scanned cache or directly
|
||||
// if this is a platform that does not scan. fromISR is a hint if
|
||||
// it was called from ISR because for some implementations that
|
||||
// makes a difference.
|
||||
static int read(uint8_t pin, bool fromISR=false);
|
||||
// returns possible max value that the ADC can return
|
||||
static int16_t ADCmax();
|
||||
private:
|
||||
// On platforms that scan, it is called from waveform ISR
|
||||
// only on a regular basis.
|
||||
static void scan();
|
||||
// begin is called for any setup that must be done before
|
||||
// scan can be called.
|
||||
static void begin();
|
||||
// bit array of used pins (max 16)
|
||||
static uint16_t usedpins;
|
||||
// cached analog values (malloc:ed to actual number of ADC channels)
|
||||
static int *analogvals;
|
||||
// friend so that we can call scan() and begin()
|
||||
friend class DCCWaveform;
|
||||
};
|
||||
#endif
|
||||
|
239
DCCTimerAVR.cpp
239
DCCTimerAVR.cpp
@@ -1,239 +0,0 @@
|
||||
/*
|
||||
* © 2021 Mike S
|
||||
* © 2021-2022 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* © 2021 Chris Harlow
|
||||
* © 2021 David Cutting
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of Asbelos DCC API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// ATTENTION: this file only compiles on a UNO or MEGA
|
||||
// Please refer to DCCTimer.h for general comments about how this class works
|
||||
// This is to avoid repetition and duplication.
|
||||
#ifdef ARDUINO_ARCH_AVR
|
||||
#include <avr/boot.h>
|
||||
#include <avr/wdt.h>
|
||||
#include "DCCTimer.h"
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
|
||||
// Arduino nano, uno, mega etc
|
||||
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
|
||||
#define TIMER1_A_PIN 11
|
||||
#define TIMER1_B_PIN 12
|
||||
#define TIMER1_C_PIN 13
|
||||
#else
|
||||
#define TIMER1_A_PIN 9
|
||||
#define TIMER1_B_PIN 10
|
||||
#endif
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
noInterrupts();
|
||||
TCCR1A = 0;
|
||||
ICR1 = CLOCK_CYCLES;
|
||||
TCNT1 = 0;
|
||||
TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1
|
||||
TIMSK1 = _BV(TOIE1); // Enable Software interrupt
|
||||
interrupts();
|
||||
}
|
||||
|
||||
// ISR called by timer interrupt every 58uS
|
||||
ISR(TIMER1_OVF_vect){ interruptHandler(); }
|
||||
|
||||
// Alternative pin manipulation via PWM control.
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
return pin==TIMER1_A_PIN
|
||||
|| pin==TIMER1_B_PIN
|
||||
#ifdef TIMER1_C_PIN
|
||||
|| pin==TIMER1_C_PIN
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
if (pin==TIMER1_A_PIN) {
|
||||
TCCR1A |= _BV(COM1A1);
|
||||
OCR1A= high?1024:0;
|
||||
}
|
||||
else if (pin==TIMER1_B_PIN) {
|
||||
TCCR1A |= _BV(COM1B1);
|
||||
OCR1B= high?1024:0;
|
||||
}
|
||||
#ifdef TIMER1_C_PIN
|
||||
else if (pin==TIMER1_C_PIN) {
|
||||
TCCR1A |= _BV(COM1C1);
|
||||
OCR1C= high?1024:0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void DCCTimer::clearPWM() {
|
||||
TCCR1A= 0;
|
||||
}
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
for (byte i=0; i<6; i++) {
|
||||
// take the fist 3 and last 3 of the serial.
|
||||
// the first 5 of 8 are at 0x0E to 0x013
|
||||
// the last 3 of 8 are at 0x15 to 0x017
|
||||
mac[i]=boot_signature_byte_get(0x0E + i + (i>2? 4 : 0));
|
||||
}
|
||||
mac[0] &= 0xFE;
|
||||
mac[0] |= 0x02;
|
||||
}
|
||||
|
||||
|
||||
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
|
||||
|
||||
// Return low memory value...
|
||||
int DCCTimer::getMinimumFreeMemory() {
|
||||
noInterrupts(); // Disable interrupts to get volatile value
|
||||
int retval = minimum_free_memory;
|
||||
interrupts();
|
||||
return retval;
|
||||
}
|
||||
|
||||
extern char *__brkval;
|
||||
extern char *__malloc_heap_start;
|
||||
|
||||
int DCCTimer::freeMemory() {
|
||||
char top;
|
||||
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
|
||||
}
|
||||
|
||||
void DCCTimer::reset() {
|
||||
wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms
|
||||
delay(50); // wait for the prescaller time to expire
|
||||
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
#define NUM_ADC_INPUTS 15
|
||||
#else
|
||||
#define NUM_ADC_INPUTS 7
|
||||
#endif
|
||||
uint16_t ADCee::usedpins = 0;
|
||||
int * ADCee::analogvals = NULL;
|
||||
bool ADCusesHighPort = false;
|
||||
|
||||
/*
|
||||
* Register a new pin to be scanned
|
||||
* Returns current reading of pin and
|
||||
* stores that as well
|
||||
*/
|
||||
int ADCee::init(uint8_t pin) {
|
||||
uint8_t id = pin - A0;
|
||||
if (id > NUM_ADC_INPUTS)
|
||||
return -1023;
|
||||
if (id > 7)
|
||||
ADCusesHighPort = true;
|
||||
pinMode(pin, INPUT);
|
||||
int value = analogRead(pin);
|
||||
if (analogvals == NULL)
|
||||
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
|
||||
analogvals[id] = value;
|
||||
usedpins |= (1<<id);
|
||||
return value;
|
||||
}
|
||||
int16_t ADCee::ADCmax() {
|
||||
return 1023;
|
||||
}
|
||||
/*
|
||||
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
|
||||
*/
|
||||
int ADCee::read(uint8_t pin, bool fromISR) {
|
||||
(void)fromISR; // AVR does ignore this arg
|
||||
uint8_t id = pin - A0;
|
||||
if ((usedpins & (1<<id) ) == 0)
|
||||
return -1023;
|
||||
// we do not need to check (analogvals == NULL)
|
||||
// because usedpins would still be 0 in that case
|
||||
return analogvals[id];
|
||||
}
|
||||
/*
|
||||
* Scan function that is called from interrupt
|
||||
*/
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
void ADCee::scan() {
|
||||
static byte id = 0; // id and mask are the same thing but it is faster to
|
||||
static uint16_t mask = 1; // increment and shift instead to calculate mask from id
|
||||
static bool waiting = false;
|
||||
|
||||
if (waiting) {
|
||||
// look if we have a result
|
||||
byte low, high;
|
||||
if (bit_is_set(ADCSRA, ADSC))
|
||||
return; // no result, continue to wait
|
||||
// found value
|
||||
low = ADCL; //must read low before high
|
||||
high = ADCH;
|
||||
bitSet(ADCSRA, ADIF);
|
||||
analogvals[id] = (high << 8) | low;
|
||||
// advance at least one track
|
||||
// for scope debug TrackManager::track[1]->setBrake(0);
|
||||
waiting = false;
|
||||
id++;
|
||||
mask = mask << 1;
|
||||
if (id == NUM_ADC_INPUTS+1) {
|
||||
id = 0;
|
||||
mask = 1;
|
||||
}
|
||||
}
|
||||
if (!waiting) {
|
||||
if (usedpins == 0) // otherwise we would loop forever
|
||||
return;
|
||||
// look for a valid track to sample or until we are around
|
||||
while (true) {
|
||||
if (mask & usedpins) {
|
||||
// start new ADC aquire on id
|
||||
#if defined(ADCSRB) && defined(MUX5)
|
||||
if (ADCusesHighPort) { // if we ever have started to use high pins)
|
||||
if (id > 7) // if we use a high ADC pin
|
||||
bitSet(ADCSRB, MUX5); // set MUX5 bit
|
||||
else
|
||||
bitClear(ADCSRB, MUX5);
|
||||
}
|
||||
#endif
|
||||
ADMUX=(1<<REFS0)|(id & 0x07); //select AVCC as reference and set MUX
|
||||
bitSet(ADCSRA,ADSC); // start conversion
|
||||
// for scope debug TrackManager::track[1]->setBrake(1);
|
||||
waiting = true;
|
||||
return;
|
||||
}
|
||||
id++;
|
||||
mask = mask << 1;
|
||||
if (id == NUM_ADC_INPUTS+1) {
|
||||
id = 0;
|
||||
mask = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
|
||||
void ADCee::begin() {
|
||||
noInterrupts();
|
||||
// ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time
|
||||
// Set up ADC for free running mode
|
||||
ADMUX=(1<<REFS0); //select AVCC as reference. We set MUX later
|
||||
ADCSRA = (1<<ADEN)|(1 << ADPS2); // ADPS2 means divisor 32 and 16Mhz/32=500kHz.
|
||||
//bitSet(ADCSRA, ADSC); //do not start the ADC yet. Done when we have set the MUX
|
||||
interrupts();
|
||||
}
|
||||
#endif
|
178
DCCTimerESP.cpp
178
DCCTimerESP.cpp
@@ -1,178 +0,0 @@
|
||||
/*
|
||||
* © 2020-2022 Harald Barth
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// ATTENTION: this file only compiles on an ESP8266 and ESP32
|
||||
// On ESP32 we do not even use the functions but they are here for completeness sake
|
||||
// Please refer to DCCTimer.h for general comments about how this class works
|
||||
// This is to avoid repetition and duplication.
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
|
||||
#include "DCCTimer.h"
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
timer1_disable();
|
||||
|
||||
// There seem to be differnt ways to attach interrupt handler
|
||||
// ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
|
||||
// ETS_FRC_TIMER1_NMI_INTR_ATTACH(interruptHandler);
|
||||
// Let us choose the one from the API
|
||||
timer1_attachInterrupt(interruptHandler);
|
||||
|
||||
// not exactly sure of order:
|
||||
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_LOOP);
|
||||
timer1_write(CLOCK_CYCLES);
|
||||
}
|
||||
// We do not support to use PWM to make the Waveform on ESP
|
||||
bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) {
|
||||
return false;
|
||||
}
|
||||
void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) {
|
||||
}
|
||||
void IRAM_ATTR DCCTimer::clearPWM() {
|
||||
}
|
||||
|
||||
// Fake this as it should not be used
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
mac[0] = 0xFE;
|
||||
mac[1] = 0xBE;
|
||||
mac[2] = 0xEF;
|
||||
mac[3] = 0xC0;
|
||||
mac[4] = 0xFF;
|
||||
mac[5] = 0xEE;
|
||||
}
|
||||
|
||||
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
|
||||
|
||||
// Return low memory value...
|
||||
int DCCTimer::getMinimumFreeMemory() {
|
||||
noInterrupts(); // Disable interrupts to get volatile value
|
||||
int retval = minimum_free_memory;
|
||||
interrupts();
|
||||
return retval;
|
||||
}
|
||||
|
||||
int DCCTimer::freeMemory() {
|
||||
return ESP.getFreeHeap();
|
||||
}
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <driver/adc.h>
|
||||
#include <soc/sens_reg.h>
|
||||
#include <soc/sens_struct.h>
|
||||
#undef ADC_INPUT_MAX_VALUE
|
||||
#define ADC_INPUT_MAX_VALUE 4095 // 12 bit ADC
|
||||
#define pinToADC1Channel(X) (adc1_channel_t)(((X) > 35) ? (X)-36 : (X)-28)
|
||||
|
||||
int IRAM_ATTR local_adc1_get_raw(int channel) {
|
||||
uint16_t adc_value;
|
||||
SENS.sar_meas_start1.sar1_en_pad = (1 << channel); // only one channel is selected
|
||||
while (SENS.sar_slave_addr1.meas_status != 0);
|
||||
SENS.sar_meas_start1.meas1_start_sar = 0;
|
||||
SENS.sar_meas_start1.meas1_start_sar = 1;
|
||||
while (SENS.sar_meas_start1.meas1_done_sar == 0);
|
||||
adc_value = SENS.sar_meas_start1.meas1_data_sar;
|
||||
return adc_value;
|
||||
}
|
||||
|
||||
#include "DCCTimer.h"
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
|
||||
// https://www.visualmicro.com/page/Timer-Interrupts-Explained.aspx
|
||||
|
||||
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
// This should not be called on ESP32 so disable it
|
||||
return;
|
||||
interruptHandler = callback;
|
||||
hw_timer_t *timer = NULL;
|
||||
timer = timerBegin(0, 2, true); // prescaler can be 2 to 65536 so choose 2
|
||||
timerAttachInterrupt(timer, interruptHandler, true);
|
||||
timerAlarmWrite(timer, CLOCK_CYCLES / 6, true); // divide by prescaler*3 (Clockbase is 80Mhz and not F_CPU 240Mhz)
|
||||
timerAlarmEnable(timer);
|
||||
}
|
||||
|
||||
// We do not support to use PWM to make the Waveform on ESP
|
||||
bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) {
|
||||
return false;
|
||||
}
|
||||
void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) {
|
||||
}
|
||||
void IRAM_ATTR DCCTimer::clearPWM() {
|
||||
}
|
||||
|
||||
// Fake this as it should not be used
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
mac[0] = 0xFE;
|
||||
mac[1] = 0xBE;
|
||||
mac[2] = 0xEF;
|
||||
mac[3] = 0xC0;
|
||||
mac[4] = 0xFF;
|
||||
mac[5] = 0xEE;
|
||||
}
|
||||
|
||||
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
|
||||
|
||||
// Return low memory value...
|
||||
int DCCTimer::getMinimumFreeMemory() {
|
||||
noInterrupts(); // Disable interrupts to get volatile value
|
||||
int retval = minimum_free_memory;
|
||||
interrupts();
|
||||
return retval;
|
||||
}
|
||||
|
||||
int DCCTimer::freeMemory() {
|
||||
return ESP.getFreeHeap();
|
||||
}
|
||||
|
||||
void DCCTimer::reset() {
|
||||
ESP.restart();
|
||||
}
|
||||
int ADCee::init(uint8_t pin) {
|
||||
pinMode(pin, ANALOG);
|
||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||
adc1_config_channel_atten(pinToADC1Channel(pin),ADC_ATTEN_DB_11);
|
||||
return adc1_get_raw(pinToADC1Channel(pin));
|
||||
}
|
||||
int16_t ADCee::ADCmax() {
|
||||
return 4095;
|
||||
}
|
||||
/*
|
||||
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
|
||||
*/
|
||||
int ADCee::read(uint8_t pin, bool fromISR) {
|
||||
return local_adc1_get_raw(pinToADC1Channel(pin));
|
||||
}
|
||||
/*
|
||||
* Scan function that is called from interrupt
|
||||
*/
|
||||
void ADCee::scan() {
|
||||
}
|
||||
|
||||
void ADCee::begin() {
|
||||
}
|
||||
|
||||
#endif //ESP32
|
||||
|
@@ -1,155 +0,0 @@
|
||||
/*
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* © 2021 Chris Harlow
|
||||
* © 2021 David Cutting
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of Asbelos DCC API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
/* This timer class is used to manage the single timer required to handle the DCC waveform.
|
||||
* All timer access comes through this class so that it can be compiled for
|
||||
* various hardware CPU types.
|
||||
*
|
||||
* DCCEX works on a single timer interrupt at a regular 58uS interval.
|
||||
* The DCCWaveform class generates the signals to the motor shield
|
||||
* based on this timer.
|
||||
*
|
||||
* If the motor drivers are BOTH configured to use the correct 2 pins for the architecture,
|
||||
* (see isPWMPin() function. )
|
||||
* then this allows us to use a hardware driven pin switching arrangement which is
|
||||
* achieved by setting the duty cycle of the NEXT clock interrupt to 0% or 100% depending on
|
||||
* the required pin state. (see setPWM())
|
||||
* This is more accurate than the software interrupt but at the expense of
|
||||
* limiting the choice of available pins.
|
||||
* Fortunately, a standard motor shield on a Mega uses pins that qualify for PWM...
|
||||
* Other shields may be jumpered to PWM pins or run directly using the software interrupt.
|
||||
*
|
||||
* Because the PWM-based waveform is effectively set half a cycle after the software version,
|
||||
* it is not acceptable to drive the two tracks on different methiods or it would cause
|
||||
* problems for <1 JOIN> etc.
|
||||
*
|
||||
*/
|
||||
|
||||
// ATTENTION: this file only compiles on a UnoWifiRev3 or NanoEvery
|
||||
// Please refer to DCCTimer.h for general comments about how this class works
|
||||
// This is to avoid repetition and duplication.
|
||||
#ifdef ARDUINO_ARCH_MEGAAVR
|
||||
|
||||
#include "DCCTimer.h"
|
||||
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
extern char *__brkval;
|
||||
extern char *__malloc_heap_start;
|
||||
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
noInterrupts();
|
||||
ADC0.CTRLC = (ADC0.CTRLC & 0b00110000) | 0b01000011; // speed up analogRead sample time
|
||||
TCB0.CTRLB = TCB_CNTMODE_INT_gc & ~TCB_CCMPEN_bm; // timer compare mode with output disabled
|
||||
TCB0.CTRLA = TCB_CLKSEL_CLKDIV2_gc; // 8 MHz ~ 0.125 us
|
||||
TCB0.CCMP = CLOCK_CYCLES -1; // 1 tick less for timer reset
|
||||
TCB0.INTFLAGS = TCB_CAPT_bm; // clear interrupt request flag
|
||||
TCB0.INTCTRL = TCB_CAPT_bm; // Enable the interrupt
|
||||
TCB0.CNT = 0;
|
||||
TCB0.CTRLA |= TCB_ENABLE_bm; // start
|
||||
interrupts();
|
||||
}
|
||||
|
||||
// ISR called by timer interrupt every 58uS
|
||||
ISR(TCB0_INT_vect){
|
||||
TCB0.INTFLAGS = TCB_CAPT_bm; // Clear interrupt request flag
|
||||
interruptHandler();
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
(void) pin;
|
||||
return false; // TODO what are the relevant pins?
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
(void) pin;
|
||||
(void) high;
|
||||
// TODO what are the relevant pins?
|
||||
}
|
||||
|
||||
void DCCTimer::clearPWM() {
|
||||
// Do nothing unless we implent HA
|
||||
}
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
memcpy(mac,(void *) &SIGROW.SERNUM0,6); // serial number
|
||||
mac[0] &= 0xFE;
|
||||
mac[0] |= 0x02;
|
||||
}
|
||||
|
||||
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
|
||||
|
||||
// Return low memory value...
|
||||
int DCCTimer::getMinimumFreeMemory() {
|
||||
noInterrupts(); // Disable interrupts to get volatile value
|
||||
int retval = minimum_free_memory;
|
||||
interrupts();
|
||||
return retval;
|
||||
}
|
||||
|
||||
extern char *__brkval;
|
||||
extern char *__malloc_heap_start;
|
||||
|
||||
int DCCTimer::freeMemory() {
|
||||
char top;
|
||||
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
|
||||
}
|
||||
|
||||
void DCCTimer::reset() {
|
||||
CPU_CCP=0xD8;
|
||||
WDT.CTRLA=0x4;
|
||||
while(true){}
|
||||
}
|
||||
|
||||
int16_t ADCee::ADCmax() {
|
||||
return 4095;
|
||||
}
|
||||
|
||||
int ADCee::init(uint8_t pin) {
|
||||
return analogRead(pin);
|
||||
}
|
||||
/*
|
||||
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
|
||||
*/
|
||||
int ADCee::read(uint8_t pin, bool fromISR) {
|
||||
int current;
|
||||
if (!fromISR) noInterrupts();
|
||||
current = analogRead(pin);
|
||||
if (!fromISR) interrupts();
|
||||
return current;
|
||||
}
|
||||
/*
|
||||
* Scan function that is called from interrupt
|
||||
*/
|
||||
void ADCee::scan() {
|
||||
}
|
||||
|
||||
void ADCee::begin() {
|
||||
noInterrupts();
|
||||
interrupts();
|
||||
}
|
||||
#endif
|
292
DCCTimerSAMD.cpp
292
DCCTimerSAMD.cpp
@@ -1,292 +0,0 @@
|
||||
/*
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021-2022 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* © 2021 Chris Harlow
|
||||
* © 2021 David Cutting
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of Asbelos DCC API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// ATTENTION: this file only compiles on a SAMD21 based board
|
||||
// Please refer to DCCTimer.h for general comments about how this class works
|
||||
// This is to avoid repetition and duplication.
|
||||
#ifdef ARDUINO_ARCH_SAMD
|
||||
|
||||
#include "DCCTimer.h"
|
||||
#include <wiring_private.h>
|
||||
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
noInterrupts();
|
||||
// Timer setup - setup clock sources first
|
||||
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide 48MHz by 1
|
||||
GCLK_GENDIV_ID(4); // Apply to GCLK4
|
||||
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
|
||||
|
||||
REG_GCLK_GENCTRL = GCLK_GENCTRL_GENEN | // Enable GCLK
|
||||
GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source
|
||||
GCLK_GENCTRL_ID(4); // Select GCLK4
|
||||
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
|
||||
|
||||
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable generic clock
|
||||
4 << GCLK_CLKCTRL_GEN_Pos | // Apply to GCLK4
|
||||
GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed GCLK to TCC0/1
|
||||
while (GCLK->STATUS.bit.SYNCBUSY);
|
||||
|
||||
// Assume we're using TCC0... as we're bit-bashing the DCC waveform output pins anyway
|
||||
// for "normal accuracy" DCC waveform generation. For high accuracy we're going to need
|
||||
// to a good deal more. The TCC waveform output pins are mux'd on the SAMD, and output
|
||||
// pins for each TCC are only available on certain pins
|
||||
TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM; // Select NPWM as waveform
|
||||
while (TCC0->SYNCBUSY.bit.WAVE); // Wait for sync
|
||||
|
||||
// Set the frequency
|
||||
TCC0->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV1_Val);
|
||||
TCC0->PER.reg = CLOCK_CYCLES * 2;
|
||||
while (TCC0->SYNCBUSY.bit.PER);
|
||||
|
||||
// Start the timer
|
||||
TCC0->CTRLA.bit.ENABLE = 1;
|
||||
while (TCC0->SYNCBUSY.bit.ENABLE);
|
||||
|
||||
// Set the interrupt condition, priority and enable it in the NVIC
|
||||
TCC0->INTENSET.reg = TCC_INTENSET_OVF; // Only interrupt on overflow
|
||||
int USBprio = NVIC_GetPriority((IRQn_Type) USB_IRQn); // Fetch the USB priority
|
||||
NVIC_SetPriority((IRQn_Type)TCC0_IRQn, USBprio); // Match the USB priority
|
||||
// NVIC_SetPriority((IRQn_Type)TCC0_IRQn, 0); // Make this highest priority
|
||||
NVIC_EnableIRQ((IRQn_Type)TCC0_IRQn); // Enable the interrupt
|
||||
interrupts();
|
||||
}
|
||||
|
||||
// Timer IRQ handlers replace the dummy handlers (in cortex_handlers)
|
||||
// copied from rf24 branch
|
||||
void TCC0_Handler() {
|
||||
if(TCC0->INTFLAG.bit.OVF) {
|
||||
TCC0->INTFLAG.bit.OVF = 1; // writing a 1 clears the flag
|
||||
interruptHandler();
|
||||
}
|
||||
}
|
||||
|
||||
void TCC1_Handler() {
|
||||
if(TCC1->INTFLAG.bit.OVF) {
|
||||
TCC1->INTFLAG.bit.OVF = 1; // writing a 1 clears the flag
|
||||
interruptHandler();
|
||||
}
|
||||
}
|
||||
|
||||
void TCC2_Handler() {
|
||||
if(TCC2->INTFLAG.bit.OVF) {
|
||||
TCC2->INTFLAG.bit.OVF = 1; // writing a 1 clears the flag
|
||||
interruptHandler();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
//TODO: SAMD whilst this call to digitalPinHasPWM will reveal which pins can do PWM,
|
||||
// there's no support yet for High Accuracy, so for now return false
|
||||
// return digitalPinHasPWM(pin);
|
||||
return false;
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
// TODO: High Accuracy mode is not supported as yet, and may never need to be
|
||||
(void) pin;
|
||||
(void) high;
|
||||
}
|
||||
|
||||
void DCCTimer::clearPWM() {
|
||||
return;
|
||||
}
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
volatile uint32_t *serno1 = (volatile uint32_t *)0x0080A00C;
|
||||
volatile uint32_t *serno2 = (volatile uint32_t *)0x0080A040;
|
||||
// volatile uint32_t *serno3 = (volatile uint32_t *)0x0080A044;
|
||||
// volatile uint32_t *serno4 = (volatile uint32_t *)0x0080A048;
|
||||
|
||||
volatile uint32_t m1 = *serno1;
|
||||
volatile uint32_t m2 = *serno2;
|
||||
mac[0] = m1 >> 8;
|
||||
mac[1] = m1 >> 0;
|
||||
mac[2] = m2 >> 24;
|
||||
mac[3] = m2 >> 16;
|
||||
mac[4] = m2 >> 8;
|
||||
mac[5] = m2 >> 0;
|
||||
}
|
||||
|
||||
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
|
||||
|
||||
// Return low memory value...
|
||||
int DCCTimer::getMinimumFreeMemory() {
|
||||
noInterrupts(); // Disable interrupts to get volatile value
|
||||
int retval = freeMemory();
|
||||
interrupts();
|
||||
return retval;
|
||||
}
|
||||
|
||||
extern "C" char* sbrk(int incr);
|
||||
|
||||
int DCCTimer::freeMemory() {
|
||||
char top;
|
||||
return (int)(&top - reinterpret_cast<char *>(sbrk(0)));
|
||||
}
|
||||
|
||||
void DCCTimer::reset() {
|
||||
__disable_irq();
|
||||
NVIC_SystemReset();
|
||||
while(true) {};
|
||||
}
|
||||
|
||||
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
|
||||
|
||||
uint16_t ADCee::usedpins = 0;
|
||||
int * ADCee::analogvals = NULL;
|
||||
|
||||
int ADCee::init(uint8_t pin) {
|
||||
uint id = pin - A0;
|
||||
int value = 0;
|
||||
|
||||
if (id > NUM_ADC_INPUTS)
|
||||
return -1023;
|
||||
|
||||
// Dummy read using Arduino library
|
||||
analogReadResolution(12);
|
||||
value = analogRead(pin);
|
||||
|
||||
// Reconfigure ADC
|
||||
ADC->CTRLA.bit.ENABLE = 0; // disable ADC
|
||||
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
|
||||
|
||||
ADC->CTRLB.reg &= 0b1111100011001111; // mask PRESCALER and RESSEL bits
|
||||
ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 16
|
||||
ADC_CTRLB_RESSEL_12BIT; // Result 12 bits, 10 bits possible
|
||||
ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time
|
||||
ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0
|
||||
ADC->SAMPCTRL.reg = 0x00ul; // sampling Time Length = 0
|
||||
ADC->CTRLA.bit.ENABLE = 1; // enable ADC
|
||||
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
|
||||
|
||||
// Permanently configure SAMD IO MUX for that pin
|
||||
pinPeripheral(pin, PIO_ANALOG);
|
||||
ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[pin].ulADCChannelNumber; // Selection for the positive ADC input
|
||||
|
||||
// Start conversion
|
||||
ADC->SWTRIG.bit.START = 1;
|
||||
|
||||
// Wait for the conversion to be ready
|
||||
while (ADC->INTFLAG.bit.RESRDY == 0); // Waiting for conversion to complete
|
||||
|
||||
// Read the value
|
||||
value = ADC->RESULT.reg;
|
||||
|
||||
if (analogvals == NULL)
|
||||
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
|
||||
analogvals[id] = value;
|
||||
usedpins |= (1<<id);
|
||||
|
||||
return value;
|
||||
}
|
||||
int16_t ADCee::ADCmax() {
|
||||
return 4095;
|
||||
}
|
||||
/*
|
||||
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
|
||||
*/
|
||||
int ADCee::read(uint8_t pin, bool fromISR) {
|
||||
uint8_t id = pin - A0;
|
||||
if ((usedpins & (1<<id) ) == 0)
|
||||
return -1023;
|
||||
// we do not need to check (analogvals == NULL)
|
||||
// because usedpins would still be 0 in that case
|
||||
return analogvals[id];
|
||||
}
|
||||
/*
|
||||
* Scan function that is called from interrupt
|
||||
*/
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
void ADCee::scan() {
|
||||
static uint id = 0; // id and mask are the same thing but it is faster to
|
||||
static uint16_t mask = 1; // increment and shift instead to calculate mask from id
|
||||
static bool waiting = false;
|
||||
|
||||
if (waiting) {
|
||||
// look if we have a result
|
||||
if (ADC->INTFLAG.bit.RESRDY == 0)
|
||||
return; // no result, continue to wait
|
||||
// found value
|
||||
analogvals[id] = ADC->RESULT.reg;
|
||||
// advance at least one track
|
||||
// for scope debug TrackManager::track[1]->setBrake(0);
|
||||
waiting = false;
|
||||
id++;
|
||||
mask = mask << 1;
|
||||
if (id == NUM_ADC_INPUTS+1) {
|
||||
id = 0;
|
||||
mask = 1;
|
||||
}
|
||||
}
|
||||
if (!waiting) {
|
||||
if (usedpins == 0) // otherwise we would loop forever
|
||||
return;
|
||||
// look for a valid track to sample or until we are around
|
||||
while (true) {
|
||||
if (mask & usedpins) {
|
||||
// start new ADC aquire on id
|
||||
ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[id + A0].ulADCChannelNumber; // Selection for the positive ADC input
|
||||
// Start conversion
|
||||
ADC->SWTRIG.bit.START = 1;
|
||||
// for scope debug TrackManager::track[1]->setBrake(1);
|
||||
waiting = true;
|
||||
return;
|
||||
}
|
||||
id++;
|
||||
mask = mask << 1;
|
||||
if (id == NUM_ADC_INPUTS+1) {
|
||||
id = 0;
|
||||
mask = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
|
||||
void ADCee::begin() {
|
||||
noInterrupts();
|
||||
// Set up ADC to do faster reads... default for Arduino Zero platform configs is 436uS,
|
||||
// and we need sub-58uS. This code sets it to a read speed of around 5-6uS, and enables
|
||||
// 12-bit mode
|
||||
// Reconfigure ADC
|
||||
ADC->CTRLA.bit.ENABLE = 0; // disable ADC
|
||||
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
|
||||
|
||||
ADC->CTRLB.reg &= 0b1111100011001111; // mask PRESCALER and RESSEL bits
|
||||
ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 16
|
||||
ADC_CTRLB_RESSEL_12BIT; // Result 12 bits, 10 bits possible
|
||||
ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time
|
||||
ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0
|
||||
ADC->SAMPCTRL.reg = 0x00ul; // sampling Time Length = 0
|
||||
ADC->CTRLA.bit.ENABLE = 1; // enable ADC
|
||||
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
|
||||
interrupts();
|
||||
}
|
||||
#endif
|
@@ -1,157 +0,0 @@
|
||||
/*
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* © 2021 Chris Harlow
|
||||
* © 2021 David Cutting
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of Asbelos DCC API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// ATTENTION: this file only compiles on a STM32 based boards
|
||||
// Please refer to DCCTimer.h for general comments about how this class works
|
||||
// This is to avoid repetition and duplication.
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
|
||||
#include "DCCTimer.h"
|
||||
|
||||
#if defined(ARDUINO_NUCLEO_F411RE)
|
||||
// STM32F411RE doesn't have Serial1 defined by default
|
||||
HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE
|
||||
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
|
||||
// via the debugger on the Nucleo-64 STM32F411RE. It is therefore unavailable
|
||||
// for other DCC-EX uses like WiFi, DFPlayer, etc.
|
||||
// Let's define Serial6 as an additional serial port (the only other option for the F411RE)
|
||||
HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE
|
||||
#elif defined(ARDUINO_BLAH_F412ZG) || defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||
// Nucleo-144 boards don't have Serial1 defined by default
|
||||
HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE
|
||||
#else
|
||||
#warning Serial1 not defined
|
||||
#endif
|
||||
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
// Let's use STM32's timer #11 until disabused of this notion
|
||||
// Timer #11 is used for "servo" library, but as DCC-EX is not using
|
||||
// this libary, we should be free and clear.
|
||||
HardwareTimer timer(TIM11);
|
||||
|
||||
// Timer IRQ handler
|
||||
void Timer11_Handler() {
|
||||
interruptHandler();
|
||||
}
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
noInterrupts();
|
||||
|
||||
// adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES);
|
||||
timer.pause();
|
||||
timer.setPrescaleFactor(1);
|
||||
// timer.setOverflow(CLOCK_CYCLES * 2);
|
||||
timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
|
||||
timer.attachInterrupt(Timer11_Handler);
|
||||
timer.refresh();
|
||||
timer.resume();
|
||||
|
||||
interrupts();
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
//TODO: SAMD whilst this call to digitalPinHasPWM will reveal which pins can do PWM,
|
||||
// there's no support yet for High Accuracy, so for now return false
|
||||
// return digitalPinHasPWM(pin);
|
||||
return false;
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
// TODO: High Accuracy mode is not supported as yet, and may never need to be
|
||||
(void) pin;
|
||||
(void) high;
|
||||
}
|
||||
|
||||
void DCCTimer::clearPWM() {
|
||||
return;
|
||||
}
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
volatile uint32_t *serno1 = (volatile uint32_t *)0x1FFF7A10;
|
||||
volatile uint32_t *serno2 = (volatile uint32_t *)0x1FFF7A14;
|
||||
volatile uint32_t *serno3 = (volatile uint32_t *)0x1FFF7A18;
|
||||
|
||||
volatile uint32_t m1 = *serno1;
|
||||
volatile uint32_t m2 = *serno2;
|
||||
mac[0] = m1 >> 8;
|
||||
mac[1] = m1 >> 0;
|
||||
mac[2] = m2 >> 24;
|
||||
mac[3] = m2 >> 16;
|
||||
mac[4] = m2 >> 8;
|
||||
mac[5] = m2 >> 0;
|
||||
}
|
||||
|
||||
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
|
||||
|
||||
// Return low memory value...
|
||||
int DCCTimer::getMinimumFreeMemory() {
|
||||
noInterrupts(); // Disable interrupts to get volatile value
|
||||
int retval = freeMemory();
|
||||
interrupts();
|
||||
return retval;
|
||||
}
|
||||
|
||||
extern "C" char* sbrk(int incr);
|
||||
|
||||
int DCCTimer::freeMemory() {
|
||||
char top;
|
||||
return (int)(&top - reinterpret_cast<char *>(sbrk(0)));
|
||||
}
|
||||
|
||||
void DCCTimer::reset() {
|
||||
__disable_irq();
|
||||
NVIC_SystemReset();
|
||||
while(true) {};
|
||||
}
|
||||
|
||||
int16_t ADCee::ADCmax() {
|
||||
return 4095;
|
||||
}
|
||||
|
||||
int ADCee::init(uint8_t pin) {
|
||||
return analogRead(pin);
|
||||
}
|
||||
/*
|
||||
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
|
||||
*/
|
||||
int ADCee::read(uint8_t pin, bool fromISR) {
|
||||
int current;
|
||||
if (!fromISR) noInterrupts();
|
||||
current = analogRead(pin);
|
||||
if (!fromISR) interrupts();
|
||||
return current;
|
||||
}
|
||||
/*
|
||||
* Scan function that is called from interrupt
|
||||
*/
|
||||
void ADCee::scan() {
|
||||
}
|
||||
|
||||
void ADCee::begin() {
|
||||
noInterrupts();
|
||||
interrupts();
|
||||
}
|
||||
#endif
|
@@ -1,171 +0,0 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* © 2021 Chris Harlow
|
||||
* © 2021 David Cutting
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of Asbelos DCC API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// ATTENTION: this file only compiles on a TEENSY
|
||||
// Please refer to DCCTimer.h for general comments about how this class works
|
||||
// This is to avoid repetition and duplication.
|
||||
#ifdef TEENSYDUINO
|
||||
|
||||
#include "DCCTimer.h"
|
||||
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
|
||||
IntervalTimer myDCCTimer;
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
myDCCTimer.begin(interruptHandler, DCC_SIGNAL_TIME);
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
//Teensy: digitalPinHasPWM, todo
|
||||
(void) pin;
|
||||
return false; // TODO what are the relevant pins?
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
// TODO what are the relevant pins?
|
||||
(void) pin;
|
||||
(void) high;
|
||||
}
|
||||
|
||||
void DCCTimer::clearPWM() {
|
||||
// Do nothing unless we implent HA
|
||||
}
|
||||
|
||||
#if defined(__IMXRT1062__) //Teensy 4.0 and Teensy 4.1
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
uint32_t m1 = HW_OCOTP_MAC1;
|
||||
uint32_t m2 = HW_OCOTP_MAC0;
|
||||
mac[0] = m1 >> 8;
|
||||
mac[1] = m1 >> 0;
|
||||
mac[2] = m2 >> 24;
|
||||
mac[3] = m2 >> 16;
|
||||
mac[4] = m2 >> 8;
|
||||
mac[5] = m2 >> 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// http://forum.pjrc.com/threads/91-teensy-3-MAC-address
|
||||
void teensyRead(uint8_t word, uint8_t *mac, uint8_t offset) {
|
||||
FTFL_FCCOB0 = 0x41; // Selects the READONCE command
|
||||
FTFL_FCCOB1 = word; // read the given word of read once area
|
||||
|
||||
// launch command and wait until complete
|
||||
FTFL_FSTAT = FTFL_FSTAT_CCIF;
|
||||
while(!(FTFL_FSTAT & FTFL_FSTAT_CCIF));
|
||||
|
||||
*(mac+offset) = FTFL_FCCOB5; // collect only the top three bytes,
|
||||
*(mac+offset+1) = FTFL_FCCOB6; // in the right orientation (big endian).
|
||||
*(mac+offset+2) = FTFL_FCCOB7; // Skip FTFL_FCCOB4 as it's always 0.
|
||||
}
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
teensyRead(0xe,mac,0);
|
||||
teensyRead(0xf,mac,3);
|
||||
}
|
||||
#endif
|
||||
|
||||
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
|
||||
|
||||
// Return low memory value...
|
||||
int DCCTimer::getMinimumFreeMemory() {
|
||||
noInterrupts(); // Disable interrupts to get volatile value
|
||||
int retval = freeMemory();
|
||||
interrupts();
|
||||
return retval;
|
||||
}
|
||||
|
||||
extern "C" char* sbrk(int incr);
|
||||
|
||||
#if !defined(__IMXRT1062__)
|
||||
int DCCTimer::freeMemory() {
|
||||
char top;
|
||||
return &top - reinterpret_cast<char*>(sbrk(0));
|
||||
}
|
||||
|
||||
#else
|
||||
#if defined(ARDUINO_TEENSY40)
|
||||
static const unsigned DTCM_START = 0x20000000UL;
|
||||
static const unsigned OCRAM_START = 0x20200000UL;
|
||||
static const unsigned OCRAM_SIZE = 512;
|
||||
static const unsigned FLASH_SIZE = 1984;
|
||||
#elif defined(ARDUINO_TEENSY41)
|
||||
static const unsigned DTCM_START = 0x20000000UL;
|
||||
static const unsigned OCRAM_START = 0x20200000UL;
|
||||
static const unsigned OCRAM_SIZE = 512;
|
||||
static const unsigned FLASH_SIZE = 7936;
|
||||
#if TEENSYDUINO>151
|
||||
extern "C" uint8_t external_psram_size;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
int DCCTimer::freeMemory() {
|
||||
extern unsigned long _ebss;
|
||||
extern unsigned long _sdata;
|
||||
extern unsigned long _estack;
|
||||
const unsigned DTCM_START = 0x20000000UL;
|
||||
unsigned dtcm = (unsigned)&_estack - DTCM_START;
|
||||
unsigned stackinuse = (unsigned) &_estack - (unsigned) __builtin_frame_address(0);
|
||||
unsigned varsinuse = (unsigned)&_ebss - (unsigned)&_sdata;
|
||||
unsigned freemem = dtcm - (stackinuse + varsinuse);
|
||||
return freemem;
|
||||
}
|
||||
|
||||
#endif
|
||||
void DCCTimer::reset() {
|
||||
// found at https://forum.pjrc.com/threads/59935-Reboot-Teensy-programmatically
|
||||
SCB_AIRCR = 0x05FA0004;
|
||||
}
|
||||
|
||||
int16_t ADCee::ADCmax() {
|
||||
return 4095;
|
||||
}
|
||||
|
||||
int ADCee::init(uint8_t pin) {
|
||||
return analogRead(pin);
|
||||
}
|
||||
/*
|
||||
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
|
||||
*/
|
||||
int ADCee::read(uint8_t pin, bool fromISR) {
|
||||
int current;
|
||||
if (!fromISR) noInterrupts();
|
||||
current = analogRead(pin);
|
||||
if (!fromISR) interrupts();
|
||||
return current;
|
||||
}
|
||||
/*
|
||||
* Scan function that is called from interrupt
|
||||
*/
|
||||
void ADCee::scan() {
|
||||
}
|
||||
|
||||
void ADCee::begin() {
|
||||
noInterrupts();
|
||||
interrupts();
|
||||
}
|
||||
#endif
|
338
DCCWaveform.cpp
338
DCCWaveform.cpp
@@ -2,7 +2,7 @@
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2022 Harald Barth
|
||||
* © 2020-2021 Harald Barth
|
||||
* © 2020-2021 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -21,53 +21,43 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
// This code is replaced entirely on an ESP32
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "DCCWaveform.h"
|
||||
#include "TrackManager.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "DCCACK.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
#include "freeMemory.h"
|
||||
|
||||
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
|
||||
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
||||
|
||||
bool DCCWaveform::progTrackSyncMain=false;
|
||||
bool DCCWaveform::progTrackBoosted=false;
|
||||
int DCCWaveform::progTripValue=0;
|
||||
volatile uint8_t DCCWaveform::numAckGaps=0;
|
||||
volatile uint8_t DCCWaveform::numAckSamples=0;
|
||||
uint8_t DCCWaveform::trailingEdgeCounter=0;
|
||||
|
||||
// This bitmask has 9 entries as each byte is trasmitted as a zero + 8 bits.
|
||||
const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
|
||||
|
||||
const byte idlePacket[] = {0xFF, 0x00, 0xFF};
|
||||
const byte resetPacket[] = {0x00, 0x00, 0x00};
|
||||
|
||||
|
||||
// For each state of the wave nextState=stateTransform[currentState]
|
||||
const WAVE_STATE stateTransform[]={
|
||||
/* WAVE_START -> */ WAVE_PENDING,
|
||||
/* WAVE_MID_1 -> */ WAVE_START,
|
||||
/* WAVE_HIGH_0 -> */ WAVE_MID_0,
|
||||
/* WAVE_MID_0 -> */ WAVE_LOW_0,
|
||||
/* WAVE_LOW_0 -> */ WAVE_START,
|
||||
/* WAVE_PENDING (should not happen) -> */ WAVE_PENDING};
|
||||
|
||||
// For each state of the wave, signal pin is HIGH or LOW
|
||||
const bool signalTransform[]={
|
||||
/* WAVE_START -> */ HIGH,
|
||||
/* WAVE_MID_1 -> */ LOW,
|
||||
/* WAVE_HIGH_0 -> */ HIGH,
|
||||
/* WAVE_MID_0 -> */ LOW,
|
||||
/* WAVE_LOW_0 -> */ LOW,
|
||||
/* WAVE_PENDING (should not happen) -> */ LOW};
|
||||
|
||||
void DCCWaveform::begin() {
|
||||
ADCee::begin();
|
||||
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) {
|
||||
mainTrack.motorDriver=mainDriver;
|
||||
progTrack.motorDriver=progDriver;
|
||||
progTripValue = progDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once hence static
|
||||
mainTrack.setPowerMode(POWERMODE::OFF);
|
||||
progTrack.setPowerMode(POWERMODE::OFF);
|
||||
// Fault pin config for odd motor boards (example pololu)
|
||||
MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin())
|
||||
&& (mainDriver->getFaultPin() != UNUSED_PIN));
|
||||
// Only use PWM if both pins are PWM capable. Otherwise JOIN does not work
|
||||
MotorDriver::usePWM= mainDriver->isPWMCapable() && progDriver->isPWMCapable();
|
||||
DIAG(F("Signal pin config: %S accuracy waveform"),
|
||||
MotorDriver::usePWM ? F("high") : F("normal") );
|
||||
DCCTimer::begin(DCCWaveform::interruptHandler);
|
||||
}
|
||||
|
||||
void DCCWaveform::loop() {
|
||||
// empty placemarker in case ESP32 needs something here
|
||||
void DCCWaveform::loop(bool ackManagerActive) {
|
||||
mainTrack.checkPowerOverload(false);
|
||||
progTrack.checkPowerOverload(ackManagerActive);
|
||||
}
|
||||
|
||||
#pragma GCC push_options
|
||||
@@ -76,26 +66,24 @@ void DCCWaveform::interruptHandler() {
|
||||
// call the timer edge sensitive actions for progtrack and maintrack
|
||||
// member functions would be cleaner but have more overhead
|
||||
byte sigMain=signalTransform[mainTrack.state];
|
||||
byte sigProg=TrackManager::progTrackSyncMain? sigMain : signalTransform[progTrack.state];
|
||||
byte sigProg=progTrackSyncMain? sigMain : signalTransform[progTrack.state];
|
||||
|
||||
// Set the signal state for both tracks
|
||||
TrackManager::setDCCSignal(sigMain);
|
||||
TrackManager::setPROGSignal(sigProg);
|
||||
|
||||
// Refresh the values in the ADCee object buffering the values of the ADC HW
|
||||
ADCee::scan();
|
||||
|
||||
mainTrack.motorDriver->setSignal(sigMain);
|
||||
progTrack.motorDriver->setSignal(sigProg);
|
||||
|
||||
// Move on in the state engine
|
||||
mainTrack.state=stateTransform[mainTrack.state];
|
||||
progTrack.state=stateTransform[progTrack.state];
|
||||
|
||||
|
||||
// WAVE_PENDING means we dont yet know what the next bit is
|
||||
if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2();
|
||||
if (progTrack.state==WAVE_PENDING) progTrack.interrupt2();
|
||||
else DCCACK::checkAck(progTrack.getResets());
|
||||
else if (progTrack.ackPending) progTrack.checkAck();
|
||||
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
#pragma GCC push_options
|
||||
|
||||
// An instance of this class handles the DCC transmissions for one track. (main or prog)
|
||||
// Interrupts are marshalled via the statics.
|
||||
@@ -103,6 +91,9 @@ void DCCWaveform::interruptHandler() {
|
||||
// When the current buffer is exhausted, either the pending buffer (if there is one waiting) or an idle buffer.
|
||||
|
||||
|
||||
// This bitmask has 9 entries as each byte is trasmitted as a zero + 8 bits.
|
||||
const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
|
||||
|
||||
|
||||
DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
||||
isMainTrack = isMain;
|
||||
@@ -114,10 +105,105 @@ DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
||||
requiredPreambles = preambleBits+1;
|
||||
bytes_sent = 0;
|
||||
bits_sent = 0;
|
||||
sampleDelay = 0;
|
||||
lastSampleTaken = millis();
|
||||
ackPending=false;
|
||||
}
|
||||
|
||||
POWERMODE DCCWaveform::getPowerMode() {
|
||||
return powerMode;
|
||||
}
|
||||
|
||||
void DCCWaveform::setPowerMode(POWERMODE mode) {
|
||||
powerMode = mode;
|
||||
bool ison = (mode == POWERMODE::ON);
|
||||
motorDriver->setPower( ison);
|
||||
sentResetsSincePacket=0;
|
||||
}
|
||||
|
||||
|
||||
void DCCWaveform::checkPowerOverload(bool ackManagerActive) {
|
||||
if (millis() - lastSampleTaken < sampleDelay) return;
|
||||
lastSampleTaken = millis();
|
||||
int tripValue= motorDriver->getRawCurrentTripValue();
|
||||
if (!isMainTrack && !ackManagerActive && !progTrackSyncMain && !progTrackBoosted)
|
||||
tripValue=progTripValue;
|
||||
|
||||
// Trackname for diag messages later
|
||||
const FSH*trackname = isMainTrack ? F("MAIN") : F("PROG");
|
||||
switch (powerMode) {
|
||||
case POWERMODE::OFF:
|
||||
sampleDelay = POWER_SAMPLE_OFF_WAIT;
|
||||
break;
|
||||
case POWERMODE::ON:
|
||||
// Check current
|
||||
lastCurrent=motorDriver->getCurrentRaw();
|
||||
if (lastCurrent < 0) {
|
||||
// We have a fault pin condition to take care of
|
||||
lastCurrent = -lastCurrent;
|
||||
setPowerMode(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again
|
||||
if (MotorDriver::commonFaultPin) {
|
||||
if (lastCurrent <= tripValue) {
|
||||
setPowerMode(POWERMODE::ON); // maybe other track
|
||||
}
|
||||
// Write this after the fact as we want to turn on as fast as possible
|
||||
// because we don't know which output actually triggered the fault pin
|
||||
DIAG(F("COMMON FAULT PIN ACTIVE - TOGGLED POWER on %S"), trackname);
|
||||
} else {
|
||||
DIAG(F("%S FAULT PIN ACTIVE - OVERLOAD"), trackname);
|
||||
if (lastCurrent < tripValue) {
|
||||
lastCurrent = tripValue; // exaggerate
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastCurrent < tripValue) {
|
||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||
if(power_good_counter<100)
|
||||
power_good_counter++;
|
||||
else
|
||||
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->raw2mA(lastCurrent);
|
||||
unsigned int maxmA=motorDriver->raw2mA(tripValue);
|
||||
power_good_counter=0;
|
||||
sampleDelay = power_sample_overload_wait;
|
||||
DIAG(F("%S TRACK POWER OVERLOAD current=%d max=%d offtime=%d"), trackname, mA, maxmA, sampleDelay);
|
||||
if (power_sample_overload_wait >= 10000)
|
||||
power_sample_overload_wait = 10000;
|
||||
else
|
||||
power_sample_overload_wait *= 2;
|
||||
}
|
||||
break;
|
||||
case POWERMODE::OVERLOAD:
|
||||
// Try setting it back on after the OVERLOAD_WAIT
|
||||
setPowerMode(POWERMODE::ON);
|
||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||
// Debug code....
|
||||
DIAG(F("%S TRACK POWER RESET delay=%d"), trackname, sampleDelay);
|
||||
break;
|
||||
default:
|
||||
sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning.
|
||||
}
|
||||
}
|
||||
// For each state of the wave nextState=stateTransform[currentState]
|
||||
const WAVE_STATE DCCWaveform::stateTransform[]={
|
||||
/* WAVE_START -> */ WAVE_PENDING,
|
||||
/* WAVE_MID_1 -> */ WAVE_START,
|
||||
/* WAVE_HIGH_0 -> */ WAVE_MID_0,
|
||||
/* WAVE_MID_0 -> */ WAVE_LOW_0,
|
||||
/* WAVE_LOW_0 -> */ WAVE_START,
|
||||
/* WAVE_PENDING (should not happen) -> */ WAVE_PENDING};
|
||||
|
||||
// For each state of the wave, signal pin is HIGH or LOW
|
||||
const bool DCCWaveform::signalTransform[]={
|
||||
/* WAVE_START -> */ HIGH,
|
||||
/* WAVE_MID_1 -> */ LOW,
|
||||
/* WAVE_HIGH_0 -> */ HIGH,
|
||||
/* WAVE_MID_0 -> */ LOW,
|
||||
/* WAVE_LOW_0 -> */ LOW,
|
||||
/* WAVE_PENDING (should not happen) -> */ LOW};
|
||||
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
void DCCWaveform::interrupt2() {
|
||||
@@ -130,7 +216,7 @@ void DCCWaveform::interrupt2() {
|
||||
remainingPreambles--;
|
||||
// Update free memory diagnostic as we don't have anything else to do this time.
|
||||
// Allow for checkAck and its called functions using 22 bytes more.
|
||||
DCCTimer::updateMinimumFreeMemoryISR(22);
|
||||
updateMinimumFreeMemory(22);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -163,20 +249,21 @@ void DCCWaveform::interrupt2() {
|
||||
transmitLength = pendingLength;
|
||||
transmitRepeats = pendingRepeats;
|
||||
packetPending = false;
|
||||
clearResets();
|
||||
sentResetsSincePacket=0;
|
||||
}
|
||||
else {
|
||||
// Fortunately reset and idle packets are the same length
|
||||
memcpy( transmitPacket, isMainTrack ? idlePacket : resetPacket, sizeof(idlePacket));
|
||||
transmitLength = sizeof(idlePacket);
|
||||
transmitRepeats = 0;
|
||||
if (getResets() < 250) sentResetsSincePacket++; // only place to increment (private!)
|
||||
if (sentResetsSincePacket<250) sentResetsSincePacket++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
|
||||
|
||||
// Wait until there is no packet pending, then make this pending
|
||||
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
|
||||
if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum
|
||||
@@ -192,90 +279,91 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
|
||||
pendingLength = byteCount + 1;
|
||||
pendingRepeats = repeats;
|
||||
packetPending = true;
|
||||
clearResets();
|
||||
sentResetsSincePacket=0;
|
||||
}
|
||||
bool DCCWaveform::getPacketPending() {
|
||||
return packetPending;
|
||||
|
||||
// Operations applicable to PROG track ONLY.
|
||||
// (yes I know I could have subclassed the main track but...)
|
||||
|
||||
void DCCWaveform::setAckBaseline() {
|
||||
if (isMainTrack) return;
|
||||
int baseline=motorDriver->getCurrentRaw();
|
||||
ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA);
|
||||
if (Diag::ACK) DIAG(F("ACK baseline=%d/%dmA Threshold=%d/%dmA Duration between %uus and %uus"),
|
||||
baseline,motorDriver->raw2mA(baseline),
|
||||
ackThreshold,motorDriver->raw2mA(ackThreshold),
|
||||
minAckPulseDuration, maxAckPulseDuration);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include "DCCWaveform.h"
|
||||
#include "DCCACK.h"
|
||||
|
||||
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
|
||||
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
||||
RMTChannel *DCCWaveform::rmtMainChannel = NULL;
|
||||
RMTChannel *DCCWaveform::rmtProgChannel = NULL;
|
||||
|
||||
DCCWaveform::DCCWaveform(byte preambleBits, bool isMain) {
|
||||
isMainTrack = isMain;
|
||||
requiredPreambles = preambleBits;
|
||||
void DCCWaveform::setAckPending() {
|
||||
if (isMainTrack) return;
|
||||
ackMaxCurrent=0;
|
||||
ackPulseStart=0;
|
||||
ackPulseDuration=0;
|
||||
ackDetected=false;
|
||||
ackCheckStart=millis();
|
||||
numAckSamples=0;
|
||||
numAckGaps=0;
|
||||
ackPending=true; // interrupt routines will now take note
|
||||
}
|
||||
void DCCWaveform::begin() {
|
||||
for(const auto& md: TrackManager::getMainDrivers()) {
|
||||
pinpair p = md->getSignalPin();
|
||||
if(rmtMainChannel) {
|
||||
//DIAG(F("added pins %d %d to MAIN channel"), p.pin, p.invpin);
|
||||
rmtMainChannel->addPin(p); // add pin to existing main channel
|
||||
} else {
|
||||
//DIAG(F("new MAIN channel with pins %d %d"), p.pin, p.invpin);
|
||||
rmtMainChannel = new RMTChannel(p, true); /* create new main channel */
|
||||
|
||||
byte DCCWaveform::getAck() {
|
||||
if (ackPending) return (2); // still waiting
|
||||
if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%uuS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
|
||||
ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps);
|
||||
if (ackDetected) return (1); // Yes we had an ack
|
||||
return(0); // pending set off but not detected means no ACK.
|
||||
}
|
||||
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
void DCCWaveform::checkAck() {
|
||||
// This function operates in interrupt() time so must be fast and can't DIAG
|
||||
if (sentResetsSincePacket > 6) { //ACK timeout
|
||||
ackCheckDuration=millis()-ackCheckStart;
|
||||
ackPending = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
MotorDriver *md = TrackManager::getProgDriver();
|
||||
if (md) {
|
||||
pinpair p = md->getSignalPin();
|
||||
if (rmtProgChannel) {
|
||||
//DIAG(F("added pins %d %d to PROG channel"), p.pin, p.invpin);
|
||||
rmtProgChannel->addPin(p); // add pin to existing prog channel
|
||||
} else {
|
||||
//DIAG(F("new PROGchannel with pins %d %d"), p.pin, p.invpin);
|
||||
rmtProgChannel = new RMTChannel(p, false);
|
||||
|
||||
int current=motorDriver->getCurrentRaw();
|
||||
numAckSamples++;
|
||||
if (current > ackMaxCurrent) ackMaxCurrent=current;
|
||||
// An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba)
|
||||
|
||||
if (current>ackThreshold) {
|
||||
if (trailingEdgeCounter > 0) {
|
||||
numAckGaps++;
|
||||
trailingEdgeCounter = 0;
|
||||
}
|
||||
if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected
|
||||
return;
|
||||
}
|
||||
|
||||
// not in pulse
|
||||
if (ackPulseStart==0) return; // keep waiting for leading edge
|
||||
|
||||
// if we reach to this point, we have
|
||||
// detected trailing edge of pulse
|
||||
if (trailingEdgeCounter == 0) {
|
||||
ackPulseDuration=micros()-ackPulseStart;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
|
||||
if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum
|
||||
|
||||
byte checksum = 0;
|
||||
for (byte b = 0; b < byteCount; b++) {
|
||||
checksum ^= buffer[b];
|
||||
pendingPacket[b] = buffer[b];
|
||||
}
|
||||
// buffer is MAX_PACKET_SIZE but pendingPacket is one bigger
|
||||
pendingPacket[byteCount] = checksum;
|
||||
pendingLength = byteCount + 1;
|
||||
pendingRepeats = repeats;
|
||||
// The resets will be zero not only now but as well repeats packets into the future
|
||||
clearResets(repeats+1);
|
||||
{
|
||||
int ret;
|
||||
do {
|
||||
if(isMainTrack) {
|
||||
if (rmtMainChannel != NULL)
|
||||
ret = rmtMainChannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats);
|
||||
} else {
|
||||
if (rmtProgChannel != NULL)
|
||||
ret = rmtProgChannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats);
|
||||
}
|
||||
} while(ret > 0);
|
||||
}
|
||||
}
|
||||
// but we do not trust it yet and return (which will force another
|
||||
// measurement) and first the third time around with low current
|
||||
// the ack detection will be finalized.
|
||||
if (trailingEdgeCounter < 2) {
|
||||
trailingEdgeCounter++;
|
||||
return;
|
||||
}
|
||||
trailingEdgeCounter = 0;
|
||||
|
||||
bool DCCWaveform::getPacketPending() {
|
||||
if(isMainTrack) {
|
||||
if (rmtMainChannel == NULL)
|
||||
return true;
|
||||
return rmtMainChannel->busy();
|
||||
} else {
|
||||
if (rmtProgChannel == NULL)
|
||||
return true;
|
||||
return rmtProgChannel->busy();
|
||||
}
|
||||
if (ackPulseDuration>=minAckPulseDuration && ackPulseDuration<=maxAckPulseDuration) {
|
||||
ackCheckDuration=millis()-ackCheckStart;
|
||||
ackDetected=true;
|
||||
ackPending=false;
|
||||
transmitRepeats=0; // shortcut remaining repeat packets
|
||||
return; // we have a genuine ACK result
|
||||
}
|
||||
ackPulseStart=0; // We have detected a too-short or too-long pulse so ignore and wait for next leading edge
|
||||
}
|
||||
void IRAM_ATTR DCCWaveform::loop() {
|
||||
DCCACK::checkAck(progTrack.getResets());
|
||||
}
|
||||
#endif
|
||||
#pragma GCC pop_options
|
||||
|
150
DCCWaveform.h
150
DCCWaveform.h
@@ -25,70 +25,107 @@
|
||||
#define DCCWaveform_h
|
||||
|
||||
#include "MotorDriver.h"
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include "DCCRMT.h"
|
||||
#include "TrackManager.h"
|
||||
#endif
|
||||
|
||||
|
||||
// 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;
|
||||
|
||||
// Number of preamble bits.
|
||||
const int PREAMBLE_BITS_MAIN = 16;
|
||||
const int PREAMBLE_BITS_PROG = 22;
|
||||
const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum.
|
||||
|
||||
|
||||
// The WAVE_STATE enum is deliberately numbered because a change of order would be catastrophic
|
||||
// to the transform array.
|
||||
enum WAVE_STATE : byte {WAVE_START=0,WAVE_MID_1=1,WAVE_HIGH_0=2,WAVE_MID_0=3,WAVE_LOW_0=4,WAVE_PENDING=5};
|
||||
|
||||
|
||||
// NOTE: static functions are used for the overall controller, then
|
||||
// one instance is created for each track.
|
||||
|
||||
|
||||
enum class POWERMODE : byte { OFF, ON, OVERLOAD };
|
||||
|
||||
const byte idlePacket[] = {0xFF, 0x00, 0xFF};
|
||||
const byte resetPacket[] = {0x00, 0x00, 0x00};
|
||||
|
||||
class DCCWaveform {
|
||||
public:
|
||||
DCCWaveform( byte preambleBits, bool isMain);
|
||||
static void begin();
|
||||
static void loop();
|
||||
static void begin(MotorDriver * mainDriver, MotorDriver * progDriver);
|
||||
static void loop(bool ackManagerActive);
|
||||
static DCCWaveform mainTrack;
|
||||
static DCCWaveform progTrack;
|
||||
inline void clearRepeats() { transmitRepeats=0; }
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
inline void clearResets() { sentResetsSincePacket=0; }
|
||||
inline byte getResets() { return sentResetsSincePacket; }
|
||||
#else
|
||||
// extrafudge is added when we know that the resets will first come extrafudge packets in the future
|
||||
inline void clearResets(byte extrafudge=0) {
|
||||
if ((isMainTrack ? rmtMainChannel : rmtProgChannel) == NULL) return;
|
||||
resetPacketBase = isMainTrack ? rmtMainChannel->packetCount() : rmtProgChannel->packetCount();
|
||||
resetPacketBase += extrafudge;
|
||||
};
|
||||
inline byte getResets() {
|
||||
if ((isMainTrack ? rmtMainChannel : rmtProgChannel) == NULL) return 0;
|
||||
uint32_t packetcount = isMainTrack ?
|
||||
rmtMainChannel->packetCount() : rmtProgChannel->packetCount();
|
||||
uint32_t count = packetcount - resetPacketBase; // Beware of unsigned interger arithmetic.
|
||||
if (count > UINT32_MAX/2) // we are in the extrafudge area
|
||||
return 0;
|
||||
if (count > 255) // cap to 255
|
||||
return 255;
|
||||
return count; // all special cases handled above
|
||||
};
|
||||
#endif
|
||||
|
||||
void beginTrack();
|
||||
void setPowerMode(POWERMODE);
|
||||
POWERMODE getPowerMode();
|
||||
void checkPowerOverload(bool ackManagerActive);
|
||||
inline int get1024Current() {
|
||||
if (powerMode == POWERMODE::ON)
|
||||
return (int)(lastCurrent*(long int)1024/motorDriver->getRawCurrentTripValue());
|
||||
return 0;
|
||||
}
|
||||
inline int getCurrentmA() {
|
||||
if (powerMode == POWERMODE::ON)
|
||||
return motorDriver->raw2mA(lastCurrent);
|
||||
return 0;
|
||||
}
|
||||
inline int getMaxmA() {
|
||||
if (maxmA == 0) { //only calculate this for first request, it doesn't change
|
||||
maxmA = motorDriver->raw2mA(motorDriver->getRawCurrentTripValue()); //TODO: replace with actual max value or calc
|
||||
}
|
||||
return maxmA;
|
||||
}
|
||||
inline int getTripmA() {
|
||||
if (tripmA == 0) { //only calculate this for first request, it doesn't change
|
||||
tripmA = motorDriver->raw2mA(motorDriver->getRawCurrentTripValue());
|
||||
}
|
||||
return tripmA;
|
||||
}
|
||||
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
|
||||
bool getPacketPending();
|
||||
|
||||
private:
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
volatile bool packetPending;
|
||||
volatile byte sentResetsSincePacket;
|
||||
#else
|
||||
volatile uint32_t resetPacketBase;
|
||||
#endif
|
||||
volatile bool autoPowerOff=false;
|
||||
void setAckBaseline(); //prog track only
|
||||
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);
|
||||
autoPowerOff=false;
|
||||
}
|
||||
};
|
||||
inline bool canMeasureCurrent() {
|
||||
return motorDriver->canMeasureCurrent();
|
||||
};
|
||||
inline void setAckLimit(int mA) {
|
||||
ackLimitmA = mA;
|
||||
}
|
||||
inline void setMinAckPulseDuration(unsigned int i) {
|
||||
minAckPulseDuration = i;
|
||||
}
|
||||
inline void setMaxAckPulseDuration(unsigned int i) {
|
||||
maxAckPulseDuration = i;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// For each state of the wave nextState=stateTransform[currentState]
|
||||
static const WAVE_STATE stateTransform[6];
|
||||
|
||||
// For each state of the wave, signal pin is HIGH or LOW
|
||||
static const bool signalTransform[6];
|
||||
|
||||
static void interruptHandler();
|
||||
void interrupt2();
|
||||
void checkAck();
|
||||
|
||||
bool isMainTrack;
|
||||
MotorDriver* motorDriver;
|
||||
// Transmission controller
|
||||
byte transmitPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
|
||||
byte transmitLength;
|
||||
@@ -101,9 +138,38 @@ class DCCWaveform {
|
||||
byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
|
||||
byte pendingLength;
|
||||
byte pendingRepeats;
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
static RMTChannel *rmtMainChannel;
|
||||
static RMTChannel *rmtProgChannel;
|
||||
#endif
|
||||
int lastCurrent;
|
||||
static int progTripValue;
|
||||
int maxmA;
|
||||
int tripmA;
|
||||
|
||||
// current sampling
|
||||
POWERMODE powerMode;
|
||||
unsigned long lastSampleTaken;
|
||||
unsigned int sampleDelay;
|
||||
// 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;
|
||||
|
||||
// ACK management (Prog track only)
|
||||
volatile bool ackPending;
|
||||
volatile bool ackDetected;
|
||||
int ackThreshold;
|
||||
int ackLimitmA = 60;
|
||||
int ackMaxCurrent;
|
||||
unsigned long ackCheckStart; // millis
|
||||
unsigned int ackCheckDuration; // millis
|
||||
|
||||
unsigned int ackPulseDuration; // micros
|
||||
unsigned long ackPulseStart; // micros
|
||||
|
||||
unsigned int minAckPulseDuration = 4000; // micros
|
||||
unsigned int maxAckPulseDuration = 8500; // micros
|
||||
|
||||
volatile static uint8_t numAckGaps;
|
||||
volatile static uint8_t numAckSamples;
|
||||
static uint8_t trailingEdgeCounter;
|
||||
};
|
||||
#endif
|
||||
|
10
EEStore.cpp
10
EEStore.cpp
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2022 Harald Barth
|
||||
* © 2020-2021 Harald Barth
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2013-2016 Gregg E. Berman
|
||||
* All rights reserved.
|
||||
@@ -31,12 +31,12 @@
|
||||
#include "Sensors.h"
|
||||
#include "Turnouts.h"
|
||||
|
||||
#if defined(ARDUINO_ARCH_SAMC)
|
||||
#if defined(ARDUINO_ARCH_SAMD)
|
||||
ExternalEEPROM EEPROM;
|
||||
#endif
|
||||
|
||||
void EEStore::init() {
|
||||
#if defined(ARDUINO_ARCH_SAMC)
|
||||
#if defined(ARDUINO_ARCH_SAMD)
|
||||
EEPROM.begin(0x50); // Address for Microchip 24-series EEPROM with all three
|
||||
// A pins grounded (0b1010000 = 0x50)
|
||||
#endif
|
||||
@@ -49,7 +49,7 @@ void EEStore::init() {
|
||||
if (strncmp(eeStore->data.id, EESTORE_ID, sizeof(EESTORE_ID)) != 0) {
|
||||
// if not, create blank eeStore structure (no
|
||||
// turnouts, no sensors) and save it back to EEPROM
|
||||
strncpy(eeStore->data.id, EESTORE_ID, sizeof(EESTORE_ID)+0);
|
||||
strncpy(eeStore->data.id, EESTORE_ID, sizeof(EESTORE_ID));
|
||||
eeStore->data.nTurnouts = 0;
|
||||
eeStore->data.nSensors = 0;
|
||||
eeStore->data.nOutputs = 0;
|
||||
@@ -98,7 +98,7 @@ int EEStore::pointer() { return (eeAddress); }
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void EEStore::dump(int num) {
|
||||
byte b = 0;
|
||||
byte b;
|
||||
DIAG(F("Addr 0x char"));
|
||||
for (int n = 0; n < num; n++) {
|
||||
EEPROM.get(n, b);
|
||||
|
@@ -26,7 +26,7 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#if defined(ARDUINO_ARCH_SAMC)
|
||||
#if defined(ARDUINO_ARCH_SAMD)
|
||||
#include <SparkFun_External_EEPROM.h>
|
||||
extern ExternalEEPROM EEPROM;
|
||||
#else
|
||||
|
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* © 2022 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <Arduino.h>
|
||||
#include "ESP32-fixes.h"
|
||||
|
||||
#include "esp32-hal.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
|
||||
#ifdef SOC_LEDC_SUPPORT_HS_MODE
|
||||
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM<<1)
|
||||
#else
|
||||
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM)
|
||||
#endif
|
||||
|
||||
static int8_t pin_to_channel[SOC_GPIO_PIN_COUNT] = { 0 };
|
||||
static int cnt_channel = LEDC_CHANNELS;
|
||||
|
||||
void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency) {
|
||||
if (pin < SOC_GPIO_PIN_COUNT) {
|
||||
if (pin_to_channel[pin] != 0) {
|
||||
ledcSetup(pin_to_channel[pin], frequency, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DCCEXanalogWrite(uint8_t pin, int value) {
|
||||
if (pin < SOC_GPIO_PIN_COUNT) {
|
||||
if (pin_to_channel[pin] == 0) {
|
||||
if (!cnt_channel) {
|
||||
log_e("No more PWM channels available! All %u already used", LEDC_CHANNELS);
|
||||
return;
|
||||
}
|
||||
pin_to_channel[pin] = --cnt_channel;
|
||||
ledcAttachPin(pin, cnt_channel);
|
||||
ledcSetup(cnt_channel, 1000, 8);
|
||||
} else {
|
||||
ledcAttachPin(pin, pin_to_channel[pin]);
|
||||
}
|
||||
ledcWrite(pin_to_channel[pin], value);
|
||||
}
|
||||
}
|
||||
#endif
|
702
EXRAIL2.cpp
702
EXRAIL2.cpp
File diff suppressed because it is too large
Load Diff
95
EXRAIL2.h
95
EXRAIL2.h
@@ -22,7 +22,6 @@
|
||||
#define EXRAIL2_H
|
||||
#include "FSH.h"
|
||||
#include "IODevice.h"
|
||||
#include "Turnouts.h"
|
||||
|
||||
// The following are the operation codes (or instructions) for a kind of virtual machine.
|
||||
// Each instruction is normally 3 bytes long with an operation code followed by a parameter.
|
||||
@@ -34,56 +33,33 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION,
|
||||
OPCODE_RESERVE,OPCODE_FREE,
|
||||
OPCODE_AT,OPCODE_AFTER,OPCODE_AUTOSTART,
|
||||
OPCODE_ATGTE,OPCODE_ATLT,
|
||||
OPCODE_ATTIMEOUT1,OPCODE_ATTIMEOUT2,
|
||||
OPCODE_ATTIMEOUT1,OPCODE_ATTIMEOUT2,OPCODE_IFTIMEOUT,
|
||||
OPCODE_LATCH,OPCODE_UNLATCH,OPCODE_SET,OPCODE_RESET,
|
||||
OPCODE_ENDIF,OPCODE_ELSE,
|
||||
OPCODE_IF,OPCODE_IFNOT,OPCODE_ENDIF,OPCODE_IFRANDOM,OPCODE_IFRESERVE,
|
||||
OPCODE_IFCLOSED, OPCODE_IFTHROWN,OPCODE_ELSE,
|
||||
OPCODE_DELAY,OPCODE_DELAYMINS,OPCODE_DELAYMS,OPCODE_RANDWAIT,
|
||||
OPCODE_FON,OPCODE_FOFF,OPCODE_XFON,OPCODE_XFOFF,
|
||||
OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE,
|
||||
OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR,
|
||||
OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN,
|
||||
OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,OPCODE_POM,
|
||||
OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,OPCODE_FORGET,
|
||||
OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,
|
||||
OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,OPCODE_POWERON,
|
||||
OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT,
|
||||
OPCODE_PRINT,OPCODE_DCCACTIVATE,
|
||||
OPCODE_ONACTIVATE,OPCODE_ONDEACTIVATE,
|
||||
OPCODE_ROSTER,OPCODE_KILLALL,
|
||||
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,
|
||||
OPCODE_ENDTASK,OPCODE_ENDEXRAIL,
|
||||
OPCODE_SET_TRACK,
|
||||
OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN,
|
||||
|
||||
// OPcodes below this point are skip-nesting IF operations
|
||||
// placed here so that they may be skipped as a group
|
||||
// see skipIfBlock()
|
||||
IF_TYPE_OPCODES, // do not move this...
|
||||
OPCODE_IFRED,OPCODE_IFAMBER,OPCODE_IFGREEN,
|
||||
OPCODE_IFGTE,OPCODE_IFLT,
|
||||
OPCODE_IFTIMEOUT,
|
||||
OPCODE_IF,OPCODE_IFNOT,
|
||||
OPCODE_IFRANDOM,OPCODE_IFRESERVE,
|
||||
OPCODE_IFCLOSED,OPCODE_IFTHROWN
|
||||
OPCODE_ONACTIVATE,OPCODE_ONDEACTIVATE,OPCODE_IFGTE,OPCODE_IFLT,
|
||||
OPCODE_ROSTER,
|
||||
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,OPCODE_ENDTASK,OPCODE_ENDEXRAIL
|
||||
};
|
||||
|
||||
enum thrunger: byte {
|
||||
thrunge_print, thrunge_broadcast, thrunge_serial,thrunge_parse,
|
||||
thrunge_serial1, thrunge_serial2, thrunge_serial3,
|
||||
thrunge_serial4, thrunge_serial5, thrunge_serial6,
|
||||
thrunge_lcd, thrunge_lcn};
|
||||
|
||||
|
||||
|
||||
// Flag bits for status of hardware and TPL
|
||||
static const byte SECTION_FLAG = 0x80;
|
||||
static const byte LATCH_FLAG = 0x40;
|
||||
static const byte TASK_FLAG = 0x20;
|
||||
static const byte SPARE_FLAG = 0x10;
|
||||
static const byte SIGNAL_MASK = 0x0C;
|
||||
static const byte SIGNAL_RED = 0x08;
|
||||
static const byte SIGNAL_AMBER = 0x0C;
|
||||
static const byte SIGNAL_GREEN = 0x04;
|
||||
static const byte LATCH_FLAG = 0x40;
|
||||
static const byte TASK_FLAG = 0x20;
|
||||
static const byte SPARE_FLAG = 0x10;
|
||||
static const byte COUNTER_MASK= 0x0F;
|
||||
|
||||
static const byte MAX_STACK_DEPTH=4;
|
||||
|
||||
@@ -110,39 +86,25 @@ class LookList {
|
||||
RMFT2(int route, uint16_t cab);
|
||||
~RMFT2();
|
||||
static void readLocoCallback(int16_t cv);
|
||||
static void emitWithrottleRouteList(Print* stream);
|
||||
static void createNewTask(int route, uint16_t cab);
|
||||
static void turnoutEvent(int16_t id, bool closed);
|
||||
static void activateEvent(int16_t addr, bool active);
|
||||
static const int16_t SERVO_SIGNAL_FLAG=0x4000;
|
||||
static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000;
|
||||
static const int16_t DCC_SIGNAL_FLAG=0x1000;
|
||||
static const int16_t SIGNAL_ID_MASK=0x0FFF;
|
||||
// Throttle Info Access functions built by exrail macros
|
||||
static const byte rosterNameCount;
|
||||
static const int16_t HIGHFLASH routeIdList[];
|
||||
static const int16_t HIGHFLASH automationIdList[];
|
||||
static const int16_t HIGHFLASH rosterIdList[];
|
||||
static const FSH * getRouteDescription(int16_t id);
|
||||
static char getRouteType(int16_t id);
|
||||
static const FSH * getTurnoutDescription(int16_t id);
|
||||
static const FSH * getRosterName(int16_t id);
|
||||
static const FSH * getRosterFunctions(int16_t id);
|
||||
|
||||
static void emitTurnoutDescription(Print* stream,int16_t id);
|
||||
static const byte rosterNameCount;
|
||||
static void emitWithrottleRoster(Print * stream);
|
||||
static const FSH * getRosterFunctions(int16_t cabid);
|
||||
private:
|
||||
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
||||
static bool parseSlash(Print * stream, byte & paramCount, int16_t p[]) ;
|
||||
static void streamFlags(Print* stream);
|
||||
static bool setFlag(VPIN id,byte onMask, byte OffMask=0);
|
||||
static void setFlag(VPIN id,byte onMask, byte OffMask=0);
|
||||
static bool getFlag(VPIN id,byte mask);
|
||||
static int16_t progtrackLocoId;
|
||||
static void doSignal(int16_t id,char rag);
|
||||
static bool isSignal(int16_t id,char rag);
|
||||
static int16_t getSignalSlot(int16_t id);
|
||||
static void setTurnoutHiddenState(Turnout * t);
|
||||
static LookList* LookListLoader(OPCODE op1,
|
||||
OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL);
|
||||
static void handleEvent(const FSH* reason,LookList* handlers, int16_t id);
|
||||
static uint16_t getOperand(int progCounter,byte n);
|
||||
static void doSignal(VPIN id,bool red, bool amber, bool green);
|
||||
static void emitRouteDescription(Print * stream, char type, int id, const FSH * description);
|
||||
static void emitWithrottleDescriptions(Print * stream);
|
||||
|
||||
static RMFT2 * loopTask;
|
||||
static RMFT2 * pausingTask;
|
||||
void delayMe(long millisecs);
|
||||
@@ -154,22 +116,18 @@ private:
|
||||
void kill(const FSH * reason=NULL,int operand=0);
|
||||
void printMessage(uint16_t id); // Built by RMFTMacros.h
|
||||
void printMessage2(const FSH * msg);
|
||||
void thrungeString(uint32_t strfar, thrunger mode, byte id=0);
|
||||
uint16_t getOperand(byte n);
|
||||
|
||||
|
||||
static bool diag;
|
||||
static const HIGHFLASH byte RouteCode[];
|
||||
static const HIGHFLASH int16_t SignalDefinitions[];
|
||||
static const FLASH byte RouteCode[];
|
||||
static const FLASH int16_t SignalDefinitions[];
|
||||
static byte flags[MAX_FLAGS];
|
||||
static LookList * sequenceLookup;
|
||||
static LookList * onThrowLookup;
|
||||
static LookList * onCloseLookup;
|
||||
static LookList * onActivateLookup;
|
||||
static LookList * onDeactivateLookup;
|
||||
static LookList * onRedLookup;
|
||||
static LookList * onAmberLookup;
|
||||
static LookList * onGreenLookup;
|
||||
|
||||
|
||||
// Local variables - exist for each instance/task
|
||||
RMFT2 *next; // loop chain
|
||||
int progCounter; // Byte offset of next route opcode in ROUTES table
|
||||
@@ -186,7 +144,8 @@ private:
|
||||
bool forward;
|
||||
bool invert;
|
||||
byte speedo;
|
||||
int onEventStartPosition;
|
||||
int16_t onTurnoutId;
|
||||
int16_t onActivateAddr;
|
||||
byte stackDepth;
|
||||
int callStack[MAX_STACK_DEPTH];
|
||||
};
|
||||
|
@@ -29,15 +29,12 @@
|
||||
#undef ALIAS
|
||||
#undef AMBER
|
||||
#undef AT
|
||||
#undef ATGTE
|
||||
#undef ATLT
|
||||
#undef ATTIMEOUT
|
||||
#undef AUTOMATION
|
||||
#undef AUTOSTART
|
||||
#undef BROADCAST
|
||||
#undef CALL
|
||||
#undef CLOSE
|
||||
#undef DCC_SIGNAL
|
||||
#undef DEACTIVATE
|
||||
#undef DEACTIVATEL
|
||||
#undef DELAY
|
||||
@@ -55,40 +52,29 @@
|
||||
#undef FOFF
|
||||
#undef FOLLOW
|
||||
#undef FON
|
||||
#undef FORGET
|
||||
#undef FREE
|
||||
#undef FWD
|
||||
#undef GREEN
|
||||
#undef HAL
|
||||
#undef IF
|
||||
#undef IFAMBER
|
||||
#undef IFCLOSED
|
||||
#undef IFGREEN
|
||||
#undef IFGTE
|
||||
#undef IFLT
|
||||
#undef IFNOT
|
||||
#undef IFRANDOM
|
||||
#undef IFRED
|
||||
#undef IFRESERVE
|
||||
#undef IFTHROWN
|
||||
#undef IFTIMEOUT
|
||||
#undef INVERT_DIRECTION
|
||||
#undef JOIN
|
||||
#undef KILLALL
|
||||
#undef LATCH
|
||||
#undef LCD
|
||||
#undef LCN
|
||||
#undef MOVETT
|
||||
#undef ONACTIVATE
|
||||
#undef ONACTIVATEL
|
||||
#undef ONAMBER
|
||||
#undef ONDEACTIVATE
|
||||
#undef ONDEACTIVATEL
|
||||
#undef ONCLOSE
|
||||
#undef ONGREEN
|
||||
#undef ONRED
|
||||
#undef ONTHROW
|
||||
#undef PARSE
|
||||
#undef PAUSE
|
||||
#undef PIN_TURNOUT
|
||||
#undef PRINT
|
||||
@@ -110,18 +96,12 @@
|
||||
#undef SERIAL1
|
||||
#undef SERIAL2
|
||||
#undef SERIAL3
|
||||
#undef SERIAL4
|
||||
#undef SERIAL5
|
||||
#undef SERIAL6
|
||||
#undef SERVO
|
||||
#undef SERVO2
|
||||
#undef SERVO_TURNOUT
|
||||
#undef SERVO_SIGNAL
|
||||
#undef SET
|
||||
#undef SET_TRACK
|
||||
#undef SETLOCO
|
||||
#undef SIGNAL
|
||||
#undef SIGNALH
|
||||
#undef SPEED
|
||||
#undef START
|
||||
#undef STOP
|
||||
@@ -129,8 +109,6 @@
|
||||
#undef TURNOUT
|
||||
#undef UNJOIN
|
||||
#undef UNLATCH
|
||||
#undef VIRTUAL_SIGNAL
|
||||
#undef VIRTUAL_TURNOUT
|
||||
#undef WAITFOR
|
||||
#undef XFOFF
|
||||
#undef XFON
|
||||
@@ -139,18 +117,15 @@
|
||||
#define ACTIVATE(addr,subaddr)
|
||||
#define ACTIVATEL(addr)
|
||||
#define AFTER(sensor_id)
|
||||
#define ALIAS(name,value...)
|
||||
#define ALIAS(name,value)
|
||||
#define AMBER(signal_id)
|
||||
#define AT(sensor_id)
|
||||
#define ATGTE(sensor_id,value)
|
||||
#define ATLT(sensor_id,value)
|
||||
#define ATTIMEOUT(sensor_id,timeout_ms)
|
||||
#define AUTOMATION(id,description)
|
||||
#define AUTOMATION(id, description)
|
||||
#define AUTOSTART
|
||||
#define BROADCAST(msg)
|
||||
#define CALL(route)
|
||||
#define CLOSE(id)
|
||||
#define DCC_SIGNAL(id,add,subaddr)
|
||||
#define DEACTIVATE(addr,subaddr)
|
||||
#define DEACTIVATEL(addr)
|
||||
#define DELAY(mindelay)
|
||||
@@ -167,44 +142,33 @@
|
||||
#define FADE(pin,value,ms)
|
||||
#define FOFF(func)
|
||||
#define FOLLOW(route)
|
||||
#define FON(func)
|
||||
#define FORGET
|
||||
#define FON(func)
|
||||
#define FREE(blockid)
|
||||
#define FWD(speed)
|
||||
#define GREEN(signal_id)
|
||||
#define HAL(haltype,params...)
|
||||
#define IF(sensor_id)
|
||||
#define IFAMBER(signal_id)
|
||||
#define IFCLOSED(turnout_id)
|
||||
#define IFGREEN(signal_id)
|
||||
#define IFGTE(sensor_id,value)
|
||||
#define IFLT(sensor_id,value)
|
||||
#define IFNOT(sensor_id)
|
||||
#define IFRANDOM(percent)
|
||||
#define IFRED(signal_id)
|
||||
#define IFTHROWN(turnout_id)
|
||||
#define IFRESERVE(block)
|
||||
#define IFTIMEOUT
|
||||
#define INVERT_DIRECTION
|
||||
#define JOIN
|
||||
#define KILLALL
|
||||
#define LATCH(sensor_id)
|
||||
#define LCD(row,msg)
|
||||
#define LCN(msg)
|
||||
#define MOVETT(id,steps,activity)
|
||||
#define ONACTIVATE(addr,subaddr)
|
||||
#define ONACTIVATEL(linear)
|
||||
#define ONAMBER(signal_id)
|
||||
#define ONDEACTIVATE(addr,subaddr)
|
||||
#define ONDEACTIVATEL(linear)
|
||||
#define ONCLOSE(turnout_id)
|
||||
#define ONGREEN(signal_id)
|
||||
#define ONRED(signal_id)
|
||||
#define ONTHROW(turnout_id)
|
||||
#define PAUSE
|
||||
#define PIN_TURNOUT(id,pin,description...)
|
||||
#define PRINT(msg)
|
||||
#define PARSE(msg)
|
||||
#define POM(cv,value)
|
||||
#define POWEROFF
|
||||
#define POWERON
|
||||
@@ -215,7 +179,7 @@
|
||||
#define RESUME
|
||||
#define RETURN
|
||||
#define REV(speed)
|
||||
#define ROUTE(id,description)
|
||||
#define ROUTE(id, description)
|
||||
#define ROSTER(cab,name,funcmap...)
|
||||
#define SENDLOCO(cab,route)
|
||||
#define SEQUENCE(id)
|
||||
@@ -223,18 +187,12 @@
|
||||
#define SERIAL1(msg)
|
||||
#define SERIAL2(msg)
|
||||
#define SERIAL3(msg)
|
||||
#define SERIAL4(msg)
|
||||
#define SERIAL5(msg)
|
||||
#define SERIAL6(msg)
|
||||
#define SERVO(id,position,profile)
|
||||
#define SERVO2(id,position,duration)
|
||||
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)
|
||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...)
|
||||
#define SET(pin)
|
||||
#define SET_TRACK(track,mode)
|
||||
#define SETLOCO(loco)
|
||||
#define SIGNAL(redpin,amberpin,greenpin)
|
||||
#define SIGNALH(redpin,amberpin,greenpin)
|
||||
#define SPEED(speed)
|
||||
#define START(route)
|
||||
#define STOP
|
||||
@@ -242,8 +200,6 @@
|
||||
#define TURNOUT(id,addr,subaddr,description...)
|
||||
#define UNJOIN
|
||||
#define UNLATCH(sensor_id)
|
||||
#define VIRTUAL_SIGNAL(id)
|
||||
#define VIRTUAL_TURNOUT(id,description...)
|
||||
#define WAITFOR(pin)
|
||||
#define XFOFF(cab,func)
|
||||
#define XFON(cab,func)
|
||||
|
195
EXRAILMacros.h
195
EXRAILMacros.h
@@ -49,182 +49,109 @@
|
||||
|
||||
// CAUTION: The macros below are multiple passed over myAutomation.h
|
||||
|
||||
|
||||
// helper macro for turnout descriptions, creates NULL for missing description
|
||||
#define O_DESC(id, desc) case id: return ("" desc)[0]?F("" desc):NULL;
|
||||
// helper macro for turnout description as HIDDEN
|
||||
#define HIDDEN "\x01"
|
||||
|
||||
// Pass 1 Implements aliases
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ALIAS
|
||||
#define ALIAS(name,value...) const int name= 1##value##0 ==10 ? -__COUNTER__ : value##0/10;
|
||||
#define ALIAS(name,value) const int name=value;
|
||||
#include "myAutomation.h"
|
||||
|
||||
// Pass 1h Implements HAL macro by creating exrailHalSetup function
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef HAL
|
||||
#define HAL(haltype,params...) haltype::create(params);
|
||||
void exrailHalSetup() {
|
||||
#include "myAutomation.h"
|
||||
}
|
||||
|
||||
// Pass 2 create throttle route list
|
||||
// Pass 2 convert descriptions to withrottle format emitter function
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ROUTE
|
||||
#define ROUTE(id, description) id,
|
||||
const int16_t HIGHFLASH RMFT2::routeIdList[]= {
|
||||
#include "myAutomation.h"
|
||||
0};
|
||||
// Pass 2a create throttle automation list
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#define ROUTE(id, description) emitRouteDescription(stream,'R',id,F(description));
|
||||
#undef AUTOMATION
|
||||
#define AUTOMATION(id, description) id,
|
||||
const int16_t HIGHFLASH RMFT2::automationIdList[]= {
|
||||
#define AUTOMATION(id, description) emitRouteDescription(stream,'A',id,F(description));
|
||||
void RMFT2::emitWithrottleDescriptions(Print * stream) {
|
||||
(void)stream;
|
||||
#include "myAutomation.h"
|
||||
0};
|
||||
|
||||
// Pass 3 Create route descriptions:
|
||||
#undef ROUTE
|
||||
#define ROUTE(id, description) case id: return F(description);
|
||||
#undef AUTOMATION
|
||||
#define AUTOMATION(id, description) case id: return F(description);
|
||||
const FSH * RMFT2::getRouteDescription(int16_t id) {
|
||||
switch(id) {
|
||||
#include "myAutomation.h"
|
||||
default: break;
|
||||
}
|
||||
return F("");
|
||||
}
|
||||
|
||||
// Pass 4... Create Text sending functions
|
||||
// Pass 3... Create Text sending functions
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
const int StringMacroTracker1=__COUNTER__;
|
||||
#define THRUNGE(msg,mode) \
|
||||
case (__COUNTER__ - StringMacroTracker1) : {\
|
||||
static const char HIGHFLASH thrunge[]=msg;\
|
||||
strfar=(uint32_t)GETFARPTR(thrunge);\
|
||||
tmode=mode;\
|
||||
break;\
|
||||
}
|
||||
#undef BROADCAST
|
||||
#define BROADCAST(msg) THRUNGE(msg,thrunge_broadcast)
|
||||
#undef PARSE
|
||||
#define PARSE(msg) THRUNGE(msg,thrunge_parse)
|
||||
#define BROADCAST(msg) case (__COUNTER__ - StringMacroTracker1) : CommandDistributor::broadcastText(F(msg));break;
|
||||
#undef PRINT
|
||||
#define PRINT(msg) THRUNGE(msg,thrunge_print)
|
||||
#define PRINT(msg) case (__COUNTER__ - StringMacroTracker1) : printMessage2(F(msg));break;
|
||||
#undef LCN
|
||||
#define LCN(msg) THRUNGE(msg,thrunge_lcn)
|
||||
#define LCN(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&LCN_SERIAL,F(msg));break;
|
||||
#undef SERIAL
|
||||
#define SERIAL(msg) THRUNGE(msg,thrunge_serial)
|
||||
#define SERIAL(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial,F(msg));break;
|
||||
#undef SERIAL1
|
||||
#define SERIAL1(msg) THRUNGE(msg,thrunge_serial1)
|
||||
#define SERIAL1(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial1,F(msg));break;
|
||||
#undef SERIAL2
|
||||
#define SERIAL2(msg) THRUNGE(msg,thrunge_serial2)
|
||||
#define SERIAL2(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial2,F(msg));break;
|
||||
#undef SERIAL3
|
||||
#define SERIAL3(msg) THRUNGE(msg,thrunge_serial3)
|
||||
#undef SERIAL4
|
||||
#define SERIAL4(msg) THRUNGE(msg,thrunge_serial4)
|
||||
#undef SERIAL5
|
||||
#define SERIAL5(msg) THRUNGE(msg,thrunge_serial5)
|
||||
#undef SERIAL6
|
||||
#define SERIAL6(msg) THRUNGE(msg,thrunge_serial6)
|
||||
#define SERIAL3(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial3,F(msg));break;
|
||||
#undef LCD
|
||||
#define LCD(id,msg) \
|
||||
case (__COUNTER__ - StringMacroTracker1) : {\
|
||||
static const char HIGHFLASH thrunge[]=msg;\
|
||||
strfar=(uint32_t)GETFARPTR(thrunge);\
|
||||
tmode=thrunge_lcd; \
|
||||
lcdid=id;\
|
||||
break;\
|
||||
}
|
||||
#define LCD(id,msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::lcd(id,F(msg));break;
|
||||
|
||||
void RMFT2::printMessage(uint16_t id) {
|
||||
thrunger tmode;
|
||||
uint32_t strfar=0;
|
||||
byte lcdid=0;
|
||||
switch(id) {
|
||||
#include "myAutomation.h"
|
||||
default: break ;
|
||||
}
|
||||
if (strfar) thrungeString(strfar,tmode,lcdid);
|
||||
}
|
||||
|
||||
|
||||
// Pass 5: Turnout descriptions (optional)
|
||||
// Pass 4: Turnout descriptions (optional)
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef TURNOUT
|
||||
#define TURNOUT(id,addr,subaddr,description...) O_DESC(id,description)
|
||||
#define TURNOUT(id,addr,subaddr,description...) case id: desc=F("" description); break;
|
||||
#undef PIN_TURNOUT
|
||||
#define PIN_TURNOUT(id,pin,description...) O_DESC(id,description)
|
||||
#define PIN_TURNOUT(id,pin,description...) case id: desc=F("" description); break;
|
||||
#undef SERVO_TURNOUT
|
||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) O_DESC(id,description)
|
||||
#undef VIRTUAL_TURNOUT
|
||||
#define VIRTUAL_TURNOUT(id,description...) O_DESC(id,description)
|
||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) case id: desc=F("" description); break;
|
||||
|
||||
const FSH * RMFT2::getTurnoutDescription(int16_t turnoutid) {
|
||||
void RMFT2::emitTurnoutDescription(Print* stream,int16_t turnoutid) {
|
||||
const FSH * desc=F("");
|
||||
switch (turnoutid) {
|
||||
#include "myAutomation.h"
|
||||
default:break;
|
||||
default: break;
|
||||
}
|
||||
return NULL;
|
||||
if (GETFLASH(desc)=='\0') desc=F("%d");
|
||||
StringFormatter::send(stream,desc,turnoutid);
|
||||
}
|
||||
|
||||
// Pass 6: Roster IDs (count)
|
||||
// Pass 5: Roster names (count)
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ROSTER
|
||||
#define ROSTER(cabid,name,funcmap...) +1
|
||||
|
||||
const byte RMFT2::rosterNameCount=0
|
||||
#include "myAutomation.h"
|
||||
;
|
||||
|
||||
// Pass 6: Roster IDs
|
||||
#include "myAutomation.h"
|
||||
;
|
||||
|
||||
// Pass 6: Roster names emitter
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ROSTER
|
||||
#define ROSTER(cabid,name,funcmap...) cabid,
|
||||
const int16_t HIGHFLASH RMFT2::rosterIdList[]={
|
||||
#include "myAutomation.h"
|
||||
0};
|
||||
#define ROSTER(cabid,name,funcmap...) StringFormatter::send(stream,(FSH *)format,F(name),cabid,cabid<128?'S':'L');
|
||||
void RMFT2::emitWithrottleRoster(Print * stream) {
|
||||
static const char format[] FLASH ="]\\[%S}|{%d}|{%c";
|
||||
(void)format;
|
||||
StringFormatter::send(stream,F("RL%d"), rosterNameCount);
|
||||
#include "myAutomation.h"
|
||||
stream->write('\n');
|
||||
}
|
||||
|
||||
// Pass 7: Roster names getter
|
||||
// Pass 7: functions getter
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ROSTER
|
||||
#define ROSTER(cabid,name,funcmap...) case cabid: return F(name);
|
||||
const FSH * RMFT2::getRosterName(int16_t id) {
|
||||
switch(id) {
|
||||
#include "myAutomation.h"
|
||||
default: break;
|
||||
}
|
||||
return F("");
|
||||
}
|
||||
|
||||
// Pass to get roster functions
|
||||
#undef ROSTER
|
||||
#define ROSTER(cabid,name,funcmap...) case cabid: return F("" funcmap);
|
||||
const FSH * RMFT2::getRosterFunctions(int16_t id) {
|
||||
switch(id) {
|
||||
const FSH * RMFT2::getRosterFunctions(int16_t cabid) {
|
||||
switch(cabid) {
|
||||
#include "myAutomation.h"
|
||||
default: break;
|
||||
}
|
||||
return F("");
|
||||
}
|
||||
default: return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 8 Signal definitions
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef SIGNAL
|
||||
#define SIGNAL(redpin,amberpin,greenpin) redpin,redpin,amberpin,greenpin,
|
||||
#undef SIGNALH
|
||||
#define SIGNALH(redpin,amberpin,greenpin) redpin | RMFT2::ACTIVE_HIGH_SIGNAL_FLAG,redpin,amberpin,greenpin,
|
||||
#undef SERVO_SIGNAL
|
||||
#define SERVO_SIGNAL(vpin,redval,amberval,greenval) vpin | RMFT2::SERVO_SIGNAL_FLAG,redval,amberval,greenval,
|
||||
#undef DCC_SIGNAL
|
||||
#define DCC_SIGNAL(id,addr,subaddr) id | RMFT2::DCC_SIGNAL_FLAG,addr,subaddr,0,
|
||||
#undef VIRTUAL_SIGNAL
|
||||
#define VIRTUAL_SIGNAL(id) id,0,0,0,
|
||||
|
||||
const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define SIGNAL(redpin,amberpin,greenpin) redpin,amberpin,greenpin,
|
||||
const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#include "myAutomation.h"
|
||||
0,0,0,0 };
|
||||
0,0,0 };
|
||||
|
||||
// Last Pass : create main routes table
|
||||
// Only undef the macros, not dummy them.
|
||||
@@ -239,11 +166,9 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define ACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1 | 1),
|
||||
#define ACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1 | 1),
|
||||
#define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id),
|
||||
#define ALIAS(name,value...)
|
||||
#define ALIAS(name,value)
|
||||
#define AMBER(signal_id) OPCODE_AMBER,V(signal_id),
|
||||
#define AT(sensor_id) OPCODE_AT,V(sensor_id),
|
||||
#define ATGTE(sensor_id,value) OPCODE_ATGTE,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define ATLT(sensor_id,value) OPCODE_ATLT,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define ATTIMEOUT(sensor_id,timeout) OPCODE_ATTIMEOUT1,0,0,OPCODE_ATTIMEOUT2,V(sensor_id),OPCODE_PAD,V(timeout/100L),
|
||||
#define AUTOMATION(id, description) OPCODE_AUTOMATION, V(id),
|
||||
#define AUTOSTART OPCODE_AUTOSTART,0,0,
|
||||
@@ -255,7 +180,6 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define DELAY(ms) ms<30000?OPCODE_DELAYMS:OPCODE_DELAY,V(ms/(ms<30000?1L:100L)),
|
||||
#define DELAYMINS(mindelay) OPCODE_DELAYMINS,V(mindelay),
|
||||
#define DELAYRANDOM(mindelay,maxdelay) DELAY(mindelay) OPCODE_RANDWAIT,V((maxdelay-mindelay)/100L),
|
||||
#define DCC_SIGNAL(id,add,subaddr)
|
||||
#define DONE OPCODE_ENDTASK,0,0,
|
||||
#define DRIVE(analogpin) OPCODE_DRIVE,V(analogpin),
|
||||
#define ELSE OPCODE_ELSE,0,0,
|
||||
@@ -268,46 +192,35 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define FOFF(func) OPCODE_FOFF,V(func),
|
||||
#define FOLLOW(route) OPCODE_FOLLOW,V(route),
|
||||
#define FON(func) OPCODE_FON,V(func),
|
||||
#define FORGET OPCODE_FORGET,0,0,
|
||||
#define FREE(blockid) OPCODE_FREE,V(blockid),
|
||||
#define FWD(speed) OPCODE_FWD,V(speed),
|
||||
#define GREEN(signal_id) OPCODE_GREEN,V(signal_id),
|
||||
#define HAL(haltype,params...)
|
||||
#define IF(sensor_id) OPCODE_IF,V(sensor_id),
|
||||
#define IFAMBER(signal_id) OPCODE_IFAMBER,V(signal_id),
|
||||
#define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id),
|
||||
#define IFGREEN(signal_id) OPCODE_IFGREEN,V(signal_id),
|
||||
#define IFGTE(sensor_id,value) OPCODE_IFGTE,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define IFLT(sensor_id,value) OPCODE_IFLT,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define IFNOT(sensor_id) OPCODE_IFNOT,V(sensor_id),
|
||||
#define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent),
|
||||
#define IFRED(signal_id) OPCODE_IFRED,V(signal_id),
|
||||
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
|
||||
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),
|
||||
#define IFTIMEOUT OPCODE_IFTIMEOUT,0,0,
|
||||
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0,
|
||||
#define JOIN OPCODE_JOIN,0,0,
|
||||
#define KILLALL OPCODE_KILLALL,0,0,
|
||||
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
|
||||
#define LCD(id,msg) PRINT(msg)
|
||||
#define LCN(msg) PRINT(msg)
|
||||
#define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0),
|
||||
#define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr),
|
||||
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
|
||||
#define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id),
|
||||
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
|
||||
#define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr),
|
||||
#define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3),
|
||||
#define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id),
|
||||
#define ONRED(signal_id) OPCODE_ONRED,V(signal_id),
|
||||
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
|
||||
#define PAUSE OPCODE_PAUSE,0,0,
|
||||
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
|
||||
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
|
||||
#define POWEROFF OPCODE_POWEROFF,0,0,
|
||||
#define POWERON OPCODE_POWERON,0,0,
|
||||
#define POWERON OPCODE_POWERON,0,0,
|
||||
#define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2),
|
||||
#define PARSE(msg) PRINT(msg)
|
||||
#define READ_LOCO OPCODE_READ_LOCO1,0,0,OPCODE_READ_LOCO2,0,0,
|
||||
#define RED(signal_id) OPCODE_RED,V(signal_id),
|
||||
#define RESERVE(blockid) OPCODE_RESERVE,V(blockid),
|
||||
@@ -323,18 +236,12 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define SERIAL1(msg) PRINT(msg)
|
||||
#define SERIAL2(msg) PRINT(msg)
|
||||
#define SERIAL3(msg) PRINT(msg)
|
||||
#define SERIAL4(msg) PRINT(msg)
|
||||
#define SERIAL5(msg) PRINT(msg)
|
||||
#define SERIAL6(msg) PRINT(msg)
|
||||
#define SERVO(id,position,profile) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::profile),OPCODE_PAD,V(0),
|
||||
#define SERVO2(id,position,ms) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::Instant),OPCODE_PAD,V(ms/100L),
|
||||
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)
|
||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) OPCODE_SERVOTURNOUT,V(id),OPCODE_PAD,V(pin),OPCODE_PAD,V(activeAngle),OPCODE_PAD,V(inactiveAngle),OPCODE_PAD,V(PCA9685::ProfileType::profile),
|
||||
#define SET(pin) OPCODE_SET,V(pin),
|
||||
#define SET_TRACK(track,mode) OPCODE_SET_TRACK,V(TRACK_MODE_##mode <<8 | TRACK_NUMBER_##track),
|
||||
#define SETLOCO(loco) OPCODE_SETLOCO,V(loco),
|
||||
#define SIGNAL(redpin,amberpin,greenpin)
|
||||
#define SIGNALH(redpin,amberpin,greenpin)
|
||||
#define SPEED(speed) OPCODE_SPEED,V(speed),
|
||||
#define START(route) OPCODE_START,V(route),
|
||||
#define STOP OPCODE_SPEED,V(0),
|
||||
@@ -342,15 +249,13 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define TURNOUT(id,addr,subaddr,description...) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr),
|
||||
#define UNJOIN OPCODE_UNJOIN,0,0,
|
||||
#define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id),
|
||||
#define VIRTUAL_SIGNAL(id)
|
||||
#define VIRTUAL_TURNOUT(id,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(0),
|
||||
#define WAITFOR(pin) OPCODE_WAITFOR,V(pin),
|
||||
#define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func),
|
||||
#define XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func),
|
||||
|
||||
// Build RouteCode
|
||||
const int StringMacroTracker2=__COUNTER__;
|
||||
const HIGHFLASH byte RMFT2::RouteCode[] = {
|
||||
const FLASH byte RMFT2::RouteCode[] = {
|
||||
#include "myAutomation.h"
|
||||
OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 };
|
||||
|
||||
|
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* © 2022 Bruno Sanches
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2022 Harald Barth
|
||||
* © 2020-2021 Harald Barth
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2020 Gregor Baues
|
||||
* All rights reserved.
|
||||
@@ -27,7 +26,6 @@
|
||||
#include "EthernetInterface.h"
|
||||
#include "DIAG.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "WiThrottle.h"
|
||||
#include "DCCTimer.h"
|
||||
|
||||
EthernetInterface * EthernetInterface::singleton=NULL;
|
||||
@@ -37,13 +35,8 @@ EthernetInterface * EthernetInterface::singleton=NULL;
|
||||
*/
|
||||
void EthernetInterface::setup()
|
||||
{
|
||||
if (singleton!=NULL) {
|
||||
DIAG(F("Prog Error!"));
|
||||
return;
|
||||
}
|
||||
if ((singleton=new EthernetInterface()))
|
||||
return;
|
||||
DIAG(F("Ethernet not initialized"));
|
||||
singleton=new EthernetInterface();
|
||||
if (!singleton->connected) singleton=NULL;
|
||||
};
|
||||
|
||||
|
||||
@@ -68,33 +61,37 @@ EthernetInterface::EthernetInterface()
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
|
||||
DIAG(F("Ethernet shield not found or W5100"));
|
||||
DIAG(F("begin OK."));
|
||||
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
|
||||
DIAG(F("Ethernet shield not found"));
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned long startmilli = millis();
|
||||
while ((millis() - startmilli) < 5500) { // Loop to give time to check for cable connection
|
||||
while ((millis() - startmilli) < 5500) // Loop to give time to check for cable connection
|
||||
{
|
||||
if (Ethernet.linkStatus() == LinkON)
|
||||
break;
|
||||
DIAG(F("Ethernet waiting for link (1sec) "));
|
||||
delay(1000);
|
||||
}
|
||||
// now we either do have link of we have a W5100
|
||||
// where we do not know if we have link. That's
|
||||
// the reason to now run checkLink.
|
||||
// CheckLinks sets up outboundRing if it does
|
||||
// not exist yet as well.
|
||||
checkLink();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cleanup any resources
|
||||
*
|
||||
* @return none
|
||||
*/
|
||||
EthernetInterface::~EthernetInterface() {
|
||||
delete server;
|
||||
delete outboundRing;
|
||||
if (Ethernet.linkStatus() == LinkOFF) {
|
||||
DIAG(F("Ethernet cable not connected"));
|
||||
return;
|
||||
}
|
||||
|
||||
connected=true;
|
||||
|
||||
IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address
|
||||
|
||||
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT
|
||||
server->begin();
|
||||
|
||||
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
|
||||
LCD(5,F("Port:%d"), IP_PORT);
|
||||
|
||||
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,73 +100,33 @@ EthernetInterface::~EthernetInterface() {
|
||||
*/
|
||||
void EthernetInterface::loop()
|
||||
{
|
||||
if (!singleton || (!singleton->checkLink()))
|
||||
return;
|
||||
if (!singleton) return;
|
||||
|
||||
switch (Ethernet.maintain()) {
|
||||
switch (Ethernet.maintain())
|
||||
{
|
||||
case 1:
|
||||
//renewed fail
|
||||
DIAG(F("Ethernet Error: renewed fail"));
|
||||
singleton=NULL;
|
||||
return;
|
||||
|
||||
case 3:
|
||||
//rebind fail
|
||||
DIAG(F("Ethernet Error: rebind fail"));
|
||||
singleton=NULL;
|
||||
return;
|
||||
|
||||
default:
|
||||
//nothing happened
|
||||
break;
|
||||
}
|
||||
|
||||
singleton->loop2();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks ethernet link cable status and detects when it connects / disconnects
|
||||
*
|
||||
* @return true when cable is connected, false otherwise
|
||||
*/
|
||||
bool EthernetInterface::checkLink() {
|
||||
if (Ethernet.linkStatus() != LinkOFF) { // check for not linkOFF instead of linkON as the W5100 does return LinkUnknown
|
||||
//if we are not connected yet, setup a new server
|
||||
if(!connected) {
|
||||
DIAG(F("Ethernet cable connected"));
|
||||
connected=true;
|
||||
#ifdef IP_ADDRESS
|
||||
setLocalIP(IP_ADDRESS); // for static IP, set it again
|
||||
#endif
|
||||
IPAddress ip = Ethernet.localIP(); // look what IP was obtained (dynamic or static)
|
||||
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT
|
||||
server->begin();
|
||||
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
|
||||
LCD(5,F("Port:%d"), IP_PORT);
|
||||
// only create a outboundRing it none exists, this may happen if the cable
|
||||
// gets disconnected and connected again
|
||||
if(!outboundRing)
|
||||
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
|
||||
}
|
||||
return true;
|
||||
} else { // connected
|
||||
DIAG(F("Ethernet cable disconnected"));
|
||||
connected=false;
|
||||
//clean up any client
|
||||
for (byte socket = 0; socket < MAX_SOCK_NUM; socket++) {
|
||||
if(clients[socket].connected())
|
||||
clients[socket].stop();
|
||||
}
|
||||
// tear down server
|
||||
delete server;
|
||||
server = nullptr;
|
||||
LCD(4,F("IP: None"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void EthernetInterface::loop2() {
|
||||
if (!outboundRing) { // no idea to call loop2() if we can't handle outgoing data in it
|
||||
if (Diag::ETHERNET) DIAG(F("No outboundRing"));
|
||||
return;
|
||||
}
|
||||
void EthernetInterface::loop2()
|
||||
{
|
||||
// get client from the server
|
||||
EthernetClient client = server->accept();
|
||||
|
||||
@@ -205,7 +162,9 @@ void EthernetInterface::loop2() {
|
||||
buffer[count] = '\0'; // terminate the string properly
|
||||
if (Diag::ETHERNET) DIAG(F(",count=%d:%e"), socket,buffer);
|
||||
// execute with data going directly back
|
||||
outboundRing->mark(socket);
|
||||
CommandDistributor::parse(socket,buffer,outboundRing);
|
||||
outboundRing->commit();
|
||||
return; // limit the amount of processing that takes place within 1 loop() cycle.
|
||||
}
|
||||
}
|
||||
@@ -219,14 +178,10 @@ void EthernetInterface::loop2() {
|
||||
if (Diag::ETHERNET) DIAG(F("Ethernet: disconnect %d "), socket);
|
||||
}
|
||||
}
|
||||
|
||||
WiThrottle::loop(outboundRing);
|
||||
|
||||
// handle at most 1 outbound transmission
|
||||
int socketOut=outboundRing->read();
|
||||
if (socketOut >= MAX_SOCK_NUM) {
|
||||
DIAG(F("Ethernet outboundRing socket=%d error"), socketOut);
|
||||
} else if (socketOut >= 0) {
|
||||
if (socketOut>=0) {
|
||||
int count=outboundRing->count();
|
||||
if (Diag::ETHERNET) DIAG(F("Ethernet reply socket=%d, count=:%d"), socketOut,count);
|
||||
for(;count>0;count--) clients[socketOut].write(outboundRing->read());
|
||||
|
@@ -31,7 +31,7 @@
|
||||
#include "defines.h"
|
||||
#include "DCCEXParser.h"
|
||||
#include <Arduino.h>
|
||||
//#include <avr/pgmspace.h>
|
||||
#include <avr/pgmspace.h>
|
||||
#if defined (ARDUINO_TEENSY41)
|
||||
#include <NativeEthernet.h> //TEENSY Ethernet Treiber
|
||||
#include <NativeEthernetUdp.h>
|
||||
@@ -56,16 +56,15 @@ class EthernetInterface {
|
||||
static void loop();
|
||||
|
||||
private:
|
||||
static EthernetInterface * singleton;
|
||||
bool connected;
|
||||
EthernetInterface();
|
||||
~EthernetInterface();
|
||||
void loop2();
|
||||
bool checkLink();
|
||||
EthernetServer * server = NULL;
|
||||
static EthernetInterface * singleton;
|
||||
bool connected;
|
||||
EthernetInterface();
|
||||
void loop2();
|
||||
EthernetServer * server;
|
||||
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
|
||||
uint8_t buffer[MAX_ETH_BUFFER+1]; // buffer used by TCP for the recv
|
||||
RingStream * outboundRing = NULL;
|
||||
RingStream * outboundRing;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
48
FSH.h
48
FSH.h
@@ -1,5 +1,4 @@
|
||||
/*
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
@@ -34,51 +33,24 @@
|
||||
* PROGMEM use FLASH instead
|
||||
* pgm_read_byte_near use GETFLASH instead.
|
||||
* pgm_read_word_near use GETFLASHW instead.
|
||||
*
|
||||
* Also:
|
||||
* HIGHFLASH - PROGMEM forced to end of link so needs far pointers.
|
||||
* GETHIGHFLASH,GETHIGHFLASHW to access them
|
||||
*
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#ifdef ARDUINO_ARCH_AVR
|
||||
// AVR devices have flash memory mapped differently
|
||||
// progmem can be accessed by _near functions or _far
|
||||
typedef __FlashStringHelper FSH;
|
||||
#define FLASH PROGMEM
|
||||
#define GETFLASH(addr) pgm_read_byte_near(addr)
|
||||
|
||||
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
// AVR_MEGA memory deliberately placed at end of link may need _far functions
|
||||
#define HIGHFLASH __attribute__((section(".fini2")))
|
||||
#define GETFARPTR(data) pgm_get_far_address(data)
|
||||
#define GETHIGHFLASH(data,offset) pgm_read_byte_far(GETFARPTR(data)+offset)
|
||||
#define GETHIGHFLASHW(data,offset) pgm_read_word_far(GETFARPTR(data)+offset)
|
||||
#else
|
||||
// AVR_UNO/NANO runtime does not support _far functions so just use _near equivalent
|
||||
// as there is no progmem above 32kb anyway.
|
||||
#define HIGHFLASH PROGMEM
|
||||
#define GETFARPTR(data) ((uint32_t)(data))
|
||||
#define GETHIGHFLASH(data,offset) pgm_read_byte_near(GETFARPTR(data)+(offset))
|
||||
#define GETHIGHFLASHW(data,offset) pgm_read_word_near(GETFARPTR(data)+(offset))
|
||||
#endif
|
||||
|
||||
#else
|
||||
// Non-AVR Flat-memory devices have no need of this support so can be remapped to normal memory access
|
||||
#if defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#ifdef F
|
||||
#undef F
|
||||
#endif
|
||||
#ifdef FLASH
|
||||
#undef FLASH
|
||||
#endif
|
||||
#define F(str) (str)
|
||||
typedef char FSH;
|
||||
#define GETFLASH(addr) (*(const unsigned char *)(addr))
|
||||
#define GETFLASHW(addr) (*(const unsigned short *)(addr))
|
||||
#define FLASH
|
||||
#define HIGHFLASH
|
||||
#define GETFARPTR(data) ((uint32_t)(data))
|
||||
#define GETFLASH(addr) (*(const byte *)(addr))
|
||||
#define GETHIGHFLASH(data,offset) (*(const byte *)(GETFARPTR(data)+offset))
|
||||
#define GETHIGHFLASHW(data,offset) (*(const uint16_t *)(GETFARPTR(data)+offset))
|
||||
#define strlen_P strlen
|
||||
#define strcpy_P strcpy
|
||||
#else
|
||||
typedef __FlashStringHelper FSH;
|
||||
#define GETFLASH(addr) pgm_read_byte_near(addr)
|
||||
#define GETFLASHW(addr) pgm_read_word_near(addr)
|
||||
#define FLASH PROGMEM
|
||||
#endif
|
||||
#endif
|
||||
|
@@ -1 +1 @@
|
||||
#define GITHUB_SHA "devel-202212051450Z"
|
||||
#define GITHUB_SHA "a26d988"
|
||||
|
@@ -1,7 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021, Neil McKechnie
|
||||
* All rights reserved.
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
@@ -32,9 +30,6 @@
|
||||
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#include "I2CManager_NonBlocking.h"
|
||||
#include "I2CManager_Mega4809.h" // NanoEvery/UnoWifi
|
||||
#elif defined(ARDUINO_ARCH_SAMD)
|
||||
#include "I2CManager_NonBlocking.h"
|
||||
#include "I2CManager_SAMD.h" // SAMD21 for now... SAMD51 as well later
|
||||
#else
|
||||
#define I2C_USE_WIRE
|
||||
#include "I2CManager_Wire.h" // Other platforms
|
||||
|
@@ -1,5 +1,4 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -111,10 +110,10 @@
|
||||
*
|
||||
*/
|
||||
|
||||
// Add following line to config.h to enable Wire library instead of native I2C drivers
|
||||
// Uncomment following line to enable Wire library instead of native I2C drivers
|
||||
//#define I2C_USE_WIRE
|
||||
|
||||
// Add following line to config.h to disable the use of interrupts by the native I2C drivers.
|
||||
// Uncomment following line to disable the use of interrupts by the native I2C drivers.
|
||||
//#define I2C_NO_INTERRUPTS
|
||||
|
||||
// Default to use interrupts within the native I2C drivers.
|
||||
@@ -231,11 +230,7 @@ public:
|
||||
private:
|
||||
bool _beginCompleted = false;
|
||||
bool _clockSpeedFixed = false;
|
||||
#if defined(__arm__)
|
||||
uint32_t _clockSpeed = 32000000L; // 3.2MHz max on SAMD and STM32
|
||||
#else
|
||||
uint32_t _clockSpeed = 400000L; // 400kHz max on Arduino.
|
||||
#endif
|
||||
|
||||
// Finish off request block by waiting for completion and posting status.
|
||||
uint8_t finishRB(I2CRB *rb, uint8_t status);
|
||||
|
@@ -72,7 +72,7 @@ void I2CManagerClass::I2C_sendStart() {
|
||||
bytesToReceive = currentRequest->readLen;
|
||||
|
||||
// If anything to send, initiate write. Otherwise initiate read.
|
||||
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend))
|
||||
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) & !bytesToSend))
|
||||
TWI0.MADDR = (currentRequest->i2cAddress << 1) | 1;
|
||||
else
|
||||
TWI0.MADDR = (currentRequest->i2cAddress << 1) | 0;
|
||||
|
@@ -1,7 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021, Neil McKechnie
|
||||
* All rights reserved.
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
@@ -25,46 +23,7 @@
|
||||
#include <Arduino.h>
|
||||
#include "I2CManager.h"
|
||||
#if defined(I2C_USE_INTERRUPTS)
|
||||
// atomic.h isn't available on SAMD, and likely others too...
|
||||
#if defined(__AVR__)
|
||||
#include <util/atomic.h>
|
||||
#elif defined(__arm__)
|
||||
// Helper assembly language functions
|
||||
static __inline__ uint8_t my_iSeiRetVal(void)
|
||||
{
|
||||
__asm__ __volatile__ ("cpsie i" ::);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static __inline__ uint8_t my_iCliRetVal(void)
|
||||
{
|
||||
__asm__ __volatile__ ("cpsid i" ::);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static __inline__ void my_iRestore(const uint32_t *__s)
|
||||
{
|
||||
uint32_t res = *__s;
|
||||
__asm__ __volatile__ ("MSR primask, %0" : : "r" (res) );
|
||||
}
|
||||
|
||||
static __inline__ uint32_t my_iGetIReg( void )
|
||||
{
|
||||
uint32_t reg;
|
||||
__asm__ __volatile__ ("MRS %0, primask" : "=r" (reg) );
|
||||
return reg;
|
||||
}
|
||||
// Macros for atomic isolation
|
||||
#define MY_ATOMIC_RESTORESTATE uint32_t _sa_saved \
|
||||
__attribute__((__cleanup__(my_iRestore))) = my_iGetIReg()
|
||||
|
||||
#define ATOMIC() \
|
||||
for ( MY_ATOMIC_RESTORESTATE, _done = my_iCliRetVal(); \
|
||||
_done; _done = 0 )
|
||||
|
||||
#define ATOMIC_BLOCK(x) ATOMIC()
|
||||
#define ATOMIC_RESTORESTATE
|
||||
#endif
|
||||
#else
|
||||
#define ATOMIC_BLOCK(x)
|
||||
#define ATOMIC_RESTORESTATE
|
||||
|
@@ -1,244 +0,0 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021, Neil McKechnie
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef I2CMANAGER_SAMD_H
|
||||
#define I2CMANAGER_SAMD_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "I2CManager.h"
|
||||
|
||||
//#include <avr/io.h>
|
||||
//#include <avr/interrupt.h>
|
||||
#include <wiring_private.h>
|
||||
|
||||
/***************************************************************************
|
||||
* Interrupt handler.
|
||||
* IRQ handler for SERCOM3 which is the default I2C definition for Arduino Zero
|
||||
* compatible variants such as the Sparkfun SAMD21 Dev Breakout etc.
|
||||
* Later we may wish to allow use of an alternate I2C bus, or more than one I2C
|
||||
* bus on the SAMD architecture
|
||||
***************************************************************************/
|
||||
#if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_SAMD_ZERO)
|
||||
void SERCOM3_Handler() {
|
||||
I2CManagerClass::handleInterrupt();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Assume SERCOM3 for now - default I2C bus on Arduino Zero and variants of same
|
||||
Sercom *s = SERCOM3;
|
||||
|
||||
/***************************************************************************
|
||||
* Set I2C clock speed register.
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
|
||||
|
||||
// Calculate a rise time appropriate to the requested bus speed
|
||||
int t_rise;
|
||||
if (i2cClockSpeed < 200000L) {
|
||||
i2cClockSpeed = 100000L;
|
||||
t_rise = 1000;
|
||||
} else if (i2cClockSpeed < 800000L) {
|
||||
i2cClockSpeed = 400000L;
|
||||
t_rise = 300;
|
||||
} else if (i2cClockSpeed < 1200000L) {
|
||||
i2cClockSpeed = 1000000L;
|
||||
t_rise = 120;
|
||||
} else {
|
||||
i2cClockSpeed = 100000L;
|
||||
t_rise = 1000;
|
||||
}
|
||||
|
||||
// Disable the I2C master mode and wait for sync
|
||||
s->I2CM.CTRLA.bit.ENABLE = 0 ;
|
||||
while (s->I2CM.SYNCBUSY.bit.ENABLE != 0);
|
||||
|
||||
// Calculate baudrate - using a rise time appropriate for the speed
|
||||
s->I2CM.BAUD.bit.BAUD = SystemCoreClock / (2 * i2cClockSpeed) - 5 - (((SystemCoreClock / 1000000) * t_rise) / (2 * 1000));
|
||||
|
||||
// Enable the I2C master mode and wait for sync
|
||||
s->I2CM.CTRLA.bit.ENABLE = 1 ;
|
||||
while (s->I2CM.SYNCBUSY.bit.ENABLE != 0);
|
||||
|
||||
// Setting bus idle mode and wait for sync
|
||||
s->I2CM.STATUS.bit.BUSSTATE = 1 ;
|
||||
while (s->I2CM.SYNCBUSY.bit.SYSOP != 0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Initialise I2C registers.
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_init()
|
||||
{
|
||||
//Setting clock
|
||||
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(GCM_SERCOM3_CORE) | // Generic Clock 0 (SERCOM3)
|
||||
GCLK_CLKCTRL_GEN_GCLK0 | // Generic Clock Generator 0 is source
|
||||
GCLK_CLKCTRL_CLKEN ;
|
||||
|
||||
/* Wait for peripheral clock synchronization */
|
||||
while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY );
|
||||
|
||||
// Software reset the SERCOM
|
||||
s->I2CM.CTRLA.bit.SWRST = 1;
|
||||
|
||||
//Wait both bits Software Reset from CTRLA and SYNCBUSY are equal to 0
|
||||
while(s->I2CM.CTRLA.bit.SWRST || s->I2CM.SYNCBUSY.bit.SWRST);
|
||||
|
||||
// Set master mode and enable SCL Clock Stretch mode (stretch after ACK bit)
|
||||
s->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_MODE( I2C_MASTER_OPERATION )/* |
|
||||
SERCOM_I2CM_CTRLA_SCLSM*/ ;
|
||||
|
||||
// Enable Smart mode and Quick Command
|
||||
s->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN | SERCOM_I2CM_CTRLB_QCEN;
|
||||
|
||||
#if defined(I2C_USE_INTERRUPTS)
|
||||
// Setting NVIC
|
||||
NVIC_EnableIRQ(SERCOM3_IRQn);
|
||||
NVIC_SetPriority (SERCOM3_IRQn, SERCOM_NVIC_PRIORITY); // Match default SERCOM priorities
|
||||
// NVIC_SetPriority (SERCOM3_IRQn, 0); // Set highest priority
|
||||
|
||||
// Enable all interrupts
|
||||
s->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_MB | SERCOM_I2CM_INTENSET_SB | SERCOM_I2CM_INTENSET_ERROR;
|
||||
#endif
|
||||
|
||||
// Calculate baudrate and set default rate for now
|
||||
s->I2CM.BAUD.bit.BAUD = SystemCoreClock / ( 2 * I2C_FREQ) - 7 / (2 * 1000);
|
||||
|
||||
// Enable the I2C master mode and wait for sync
|
||||
s->I2CM.CTRLA.bit.ENABLE = 1 ;
|
||||
while (s->I2CM.SYNCBUSY.bit.ENABLE != 0);
|
||||
|
||||
// Setting bus idle mode and wait for sync
|
||||
s->I2CM.STATUS.bit.BUSSTATE = 1 ;
|
||||
while (s->I2CM.SYNCBUSY.bit.SYSOP != 0);
|
||||
|
||||
// Set SDA/SCL pins as outputs and enable pullups, at present we assume these are
|
||||
// the default ones for SERCOM3 (see assumption above)
|
||||
pinPeripheral(PIN_WIRE_SDA, g_APinDescription[PIN_WIRE_SDA].ulPinType);
|
||||
pinPeripheral(PIN_WIRE_SCL, g_APinDescription[PIN_WIRE_SCL].ulPinType);
|
||||
|
||||
// Enable the SCL and SDA pins on the sercom: includes increased driver strength,
|
||||
// pull-up resistors and pin multiplexer
|
||||
PORT->Group[g_APinDescription[PIN_WIRE_SCL].ulPort].PINCFG[g_APinDescription[PIN_WIRE_SCL].ulPin].reg =
|
||||
PORT_PINCFG_DRVSTR | PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN;
|
||||
PORT->Group[g_APinDescription[PIN_WIRE_SDA].ulPort].PINCFG[g_APinDescription[PIN_WIRE_SDA].ulPin].reg =
|
||||
PORT_PINCFG_DRVSTR | PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Initiate a start bit for transmission.
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_sendStart() {
|
||||
bytesToSend = currentRequest->writeLen;
|
||||
bytesToReceive = currentRequest->readLen;
|
||||
|
||||
// We may have initiated a stop bit before this without waiting for it.
|
||||
// Wait for stop bit to be sent before sending start.
|
||||
while (s->I2CM.STATUS.bit.BUSSTATE == 0x2);
|
||||
|
||||
// If anything to send, initiate write. Otherwise initiate read.
|
||||
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend))
|
||||
{
|
||||
// Send start and address with read/write flag or'd in
|
||||
s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1;
|
||||
}
|
||||
else {
|
||||
// Wait while the I2C bus is BUSY
|
||||
while (s->I2CM.STATUS.bit.BUSSTATE != 0x1);
|
||||
s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1ul) | 0;
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Initiate a stop bit for transmission (does not interrupt)
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_sendStop() {
|
||||
s->I2CM.CTRLB.bit.CMD = 3; // Stop condition
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Close I2C down
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_close() {
|
||||
I2C_sendStop();
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Main state machine for I2C, called from interrupt handler or,
|
||||
* if I2C_USE_INTERRUPTS isn't defined, from the I2CManagerClass::loop() function
|
||||
* (and therefore, indirectly, from I2CRB::wait() and I2CRB::isBusy()).
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_handleInterrupt() {
|
||||
|
||||
if (s->I2CM.STATUS.bit.ARBLOST) {
|
||||
// Arbitration lost, restart
|
||||
I2C_sendStart(); // Reinitiate request
|
||||
} else if (s->I2CM.STATUS.bit.BUSERR) {
|
||||
// Bus error
|
||||
state = I2C_STATUS_BUS_ERROR;
|
||||
} else if (s->I2CM.INTFLAG.bit.MB) {
|
||||
// Master write completed
|
||||
if (s->I2CM.STATUS.bit.RXNACK) {
|
||||
// Nacked, send stop.
|
||||
I2C_sendStop();
|
||||
state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
|
||||
} else if (bytesToSend) {
|
||||
// Acked, so send next byte
|
||||
if (currentRequest->operation == OPERATION_SEND_P)
|
||||
s->I2CM.DATA.bit.DATA = GETFLASH(currentRequest->writeBuffer + (txCount++));
|
||||
else
|
||||
s->I2CM.DATA.bit.DATA = currentRequest->writeBuffer[txCount++];
|
||||
bytesToSend--;
|
||||
} else if (bytesToReceive) {
|
||||
// Last sent byte acked and no more to send. Send repeated start, address and read bit.
|
||||
s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1;
|
||||
} else {
|
||||
// No more data to send/receive. Initiate a STOP condition.
|
||||
I2C_sendStop();
|
||||
state = I2C_STATUS_OK; // Done
|
||||
}
|
||||
} else if (s->I2CM.INTFLAG.bit.SB) {
|
||||
// Master read completed without errors
|
||||
if (bytesToReceive) {
|
||||
currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte
|
||||
bytesToReceive--;
|
||||
} else {
|
||||
// Buffer full, issue nack/stop
|
||||
s->I2CM.CTRLB.bit.ACKACT = 1;
|
||||
I2C_sendStop();
|
||||
state = I2C_STATUS_OK;
|
||||
}
|
||||
if (bytesToReceive) {
|
||||
// PMA - I think Smart Mode means we have nothing to do...
|
||||
// More bytes to receive, issue ack and start another read
|
||||
}
|
||||
else
|
||||
{
|
||||
// Transaction finished, issue NACK and STOP.
|
||||
s->I2CM.CTRLB.bit.ACKACT = 1;
|
||||
I2C_sendStop();
|
||||
state = I2C_STATUS_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* I2CMANAGER_SAMD_H */
|
@@ -94,24 +94,22 @@ uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t rea
|
||||
/***************************************************************************
|
||||
* Function to queue a request block and initiate operations.
|
||||
*
|
||||
* For the Wire version, this executes synchronously.
|
||||
* The read/write/write_P functions return I2C_STATUS_OK always, and the
|
||||
* completion status of the operation is in the request block, as for
|
||||
* the non-blocking version.
|
||||
* For the Wire version, this executes synchronously, but the status is
|
||||
* returned in the I2CRB as for the asynchronous version.
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::queueRequest(I2CRB *req) {
|
||||
switch (req->operation) {
|
||||
case OPERATION_READ:
|
||||
read(req->i2cAddress, req->readBuffer, req->readLen, NULL, 0, req);
|
||||
req->status = read(req->i2cAddress, req->readBuffer, req->readLen, NULL, 0, req);
|
||||
break;
|
||||
case OPERATION_SEND:
|
||||
write(req->i2cAddress, req->writeBuffer, req->writeLen, req);
|
||||
req->status = write(req->i2cAddress, req->writeBuffer, req->writeLen, req);
|
||||
break;
|
||||
case OPERATION_SEND_P:
|
||||
write_P(req->i2cAddress, req->writeBuffer, req->writeLen, req);
|
||||
req->status = write_P(req->i2cAddress, req->writeBuffer, req->writeLen, req);
|
||||
break;
|
||||
case OPERATION_REQUEST:
|
||||
read(req->i2cAddress, req->readBuffer, req->readLen, req->writeBuffer, req->writeLen, req);
|
||||
req->status = read(req->i2cAddress, req->readBuffer, req->readLen, req->writeBuffer, req->writeLen, req);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
109
IODevice.cpp
109
IODevice.cpp
@@ -25,7 +25,6 @@
|
||||
#include "DIAG.h"
|
||||
#include "FSH.h"
|
||||
#include "IO_MCP23017.h"
|
||||
#include "DCCTimer.h"
|
||||
|
||||
#if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#define USE_FAST_IO
|
||||
@@ -33,7 +32,7 @@
|
||||
|
||||
// Link to halSetup function. If not defined, the function reference will be NULL.
|
||||
extern __attribute__((weak)) void halSetup();
|
||||
extern __attribute__((weak)) void exrailHalSetup();
|
||||
extern __attribute__((weak)) void mySetup(); // Deprecated function name, output warning if it's declared
|
||||
|
||||
//==================================================================================================================
|
||||
// Static methods
|
||||
@@ -48,26 +47,12 @@ extern __attribute__((weak)) void exrailHalSetup();
|
||||
// Create any standard device instances that may be required, such as the Arduino pins
|
||||
// and PCA9685.
|
||||
void IODevice::begin() {
|
||||
// Call user's halSetup() function (if defined in the build in myHal.cpp).
|
||||
// The contents will depend on the user's system hardware configuration.
|
||||
// The myHal.cpp file is a standard C++ module so has access to all of the DCC++EX APIs.
|
||||
|
||||
// This is done first so that the following defaults will detect an overlap and not
|
||||
// create something that conflicts with the users vpin definitions.
|
||||
if (halSetup)
|
||||
halSetup();
|
||||
|
||||
// include any HAL devices defined in exrail.
|
||||
if (exrailHalSetup)
|
||||
exrailHalSetup();
|
||||
|
||||
// Initialise the IO subsystem defaults
|
||||
// Initialise the IO subsystem
|
||||
ArduinoPins::create(2, NUM_DIGITAL_PINS-2); // Reserve pins for direct access
|
||||
// Predefine two PCA9685 modules 0x40-0x41
|
||||
// Allocates 32 pins 100-131
|
||||
PCA9685::create(100, 16, 0x40);
|
||||
PCA9685::create(116, 16, 0x41);
|
||||
|
||||
// Predefine two MCP23017 module 0x20/0x21
|
||||
// Allocates 32 pins 164-195
|
||||
MCP23017::create(164, 16, 0x20);
|
||||
@@ -78,6 +63,16 @@ void IODevice::begin() {
|
||||
dev->_begin();
|
||||
}
|
||||
_initPhase = false;
|
||||
|
||||
// Check for presence of deprecated mySetup() function, and output warning.
|
||||
if (mySetup)
|
||||
DIAG(F("WARNING: mySetup() function should be renamed to halSetup()"));
|
||||
|
||||
// Call user's halSetup() function (if defined in the build in myHal.cpp).
|
||||
// The contents will depend on the user's system hardware configuration.
|
||||
// The myHal.cpp file is a standard C++ module so has access to all of the DCC++EX APIs.
|
||||
if (halSetup)
|
||||
halSetup();
|
||||
}
|
||||
|
||||
// Overarching static loop() method for the IODevice subsystem. Works through the
|
||||
@@ -196,17 +191,7 @@ int IODevice::readAnalogue(VPIN vpin) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("IODevice::readAnalogue(): Vpin %d not found!"), (int)vpin);
|
||||
#endif
|
||||
return -1023;
|
||||
}
|
||||
int IODevice::configureAnalogIn(VPIN vpin) {
|
||||
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
|
||||
if (dev->owns(vpin))
|
||||
return dev->_configureAnalogIn(vpin);
|
||||
}
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("IODevice::configureAnalogIn(): Vpin %d not found!"), (int)vpin);
|
||||
#endif
|
||||
return -1023;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write value to virtual pin(s). If multiple devices are allocated the same pin
|
||||
@@ -289,36 +274,7 @@ IODevice *IODevice::findDevice(VPIN vpin) {
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Private helper function to check for vpin overlap. Run during setup only.
|
||||
// returns true if pins DONT overlap with existing device
|
||||
bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, uint8_t i2cAddress) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("Check no overlap %d %d 0x%x"), firstPin,nPins,i2cAddress);
|
||||
#endif
|
||||
VPIN lastPin=firstPin+nPins-1;
|
||||
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
|
||||
|
||||
// check for pin range overlaps (verbose but compiler will fix that)
|
||||
VPIN firstDevPin=dev->_firstVpin;
|
||||
VPIN lastDevPin=firstDevPin+dev->_nPins-1;
|
||||
bool noOverlap= firstPin>lastDevPin || lastPin<firstDevPin;
|
||||
if (!noOverlap) {
|
||||
DIAG(F("WARNING HAL Overlap definition of pins %d to %d ignored."),
|
||||
firstPin, lastPin);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for overlapping I2C address
|
||||
if (i2cAddress && dev->_I2CAddress==i2cAddress) {
|
||||
DIAG(F("WARNING HAL Overlap. i2c Addr 0x%x ignored."),i2cAddress);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true; // no overlaps... OK to go on with constructor
|
||||
}
|
||||
|
||||
|
||||
//==================================================================================================================
|
||||
// Static data
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
@@ -372,10 +328,11 @@ int IODevice::read(VPIN vpin) {
|
||||
return !digitalRead(vpin); // Return inverted state (5v=0, 0v=1)
|
||||
}
|
||||
int IODevice::readAnalogue(VPIN vpin) {
|
||||
return ADCee::read(vpin);
|
||||
}
|
||||
int IODevice::configureAnalogIn(VPIN vpin) {
|
||||
return ADCee::init(vpin);
|
||||
pinMode(vpin, INPUT);
|
||||
noInterrupts();
|
||||
int value = analogRead(vpin);
|
||||
interrupts();
|
||||
return value;
|
||||
}
|
||||
void IODevice::loop() {}
|
||||
void IODevice::DumpAll() {
|
||||
@@ -477,18 +434,7 @@ int ArduinoPins::_read(VPIN vpin) {
|
||||
|
||||
// Device-specific readAnalogue function (analogue input)
|
||||
int ArduinoPins::_readAnalogue(VPIN vpin) {
|
||||
if (vpin > 255) return -1023;
|
||||
uint8_t pin = vpin;
|
||||
int value = ADCee::read(pin);
|
||||
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("Arduino Read Pin:%d Value:%d"), pin, value);
|
||||
#endif
|
||||
return value;
|
||||
}
|
||||
int ArduinoPins::_configureAnalogIn(VPIN vpin) {
|
||||
if (vpin > 255) return -1023;
|
||||
uint8_t pin = vpin;
|
||||
int pin = vpin;
|
||||
uint8_t mask = 1 << ((pin-_firstVpin) % 8);
|
||||
uint8_t index = (pin-_firstVpin) / 8;
|
||||
if (_pinModes[index] & mask) {
|
||||
@@ -500,9 +446,22 @@ int ArduinoPins::_configureAnalogIn(VPIN vpin) {
|
||||
else
|
||||
pinMode(pin, INPUT);
|
||||
}
|
||||
int value = ADCee::init(pin);
|
||||
|
||||
// Since AnalogRead is also called from interrupt code, disable interrupts
|
||||
// while we're using it. There's only one ADC shared by all analogue inputs
|
||||
// on the Arduino, so we don't want interruptions.
|
||||
//******************************************************************************
|
||||
// NOTE: If the HAL is running on a computer without the DCC signal generator,
|
||||
// then interrupts needn't be disabled. Also, the DCC signal generator puts
|
||||
// the ADC into fast mode, so if it isn't present, analogueRead calls will be much
|
||||
// slower!!
|
||||
//******************************************************************************
|
||||
noInterrupts();
|
||||
int value = analogRead(pin);
|
||||
interrupts();
|
||||
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("configureAnalogIn Pin:%d Value:%d"), pin, value);
|
||||
DIAG(F("Arduino Read Pin:%d Value:%d"), pin, value);
|
||||
#endif
|
||||
return value;
|
||||
}
|
||||
|
67
IODevice.h
67
IODevice.h
@@ -143,7 +143,6 @@ public:
|
||||
|
||||
// read invokes the IODevice instance's _readAnalogue method.
|
||||
static int readAnalogue(VPIN vpin);
|
||||
static int configureAnalogIn(VPIN vpin);
|
||||
|
||||
// loop invokes the IODevice instance's _loop method.
|
||||
static void loop();
|
||||
@@ -161,8 +160,6 @@ public:
|
||||
// once the GPIO port concerned has been read.
|
||||
void setGPIOInterruptPin(int16_t pinNumber);
|
||||
|
||||
// Method to check if pins will overlap before creating new device.
|
||||
static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0);
|
||||
|
||||
protected:
|
||||
|
||||
@@ -171,7 +168,6 @@ protected:
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_nextEntryTime = 0;
|
||||
_I2CAddress=0;
|
||||
}
|
||||
|
||||
// Method to perform initialisation of the device (optionally implemented within device class)
|
||||
@@ -204,10 +200,6 @@ protected:
|
||||
(void)vpin;
|
||||
return 0;
|
||||
};
|
||||
virtual int _configureAnalogIn(VPIN vpin) {
|
||||
(void)vpin;
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Method to perform updates on an ongoing basis (optionally implemented within device class)
|
||||
virtual void _loop(unsigned long currentMicros) {
|
||||
@@ -228,14 +220,14 @@ protected:
|
||||
// Common object fields.
|
||||
VPIN _firstVpin;
|
||||
int _nPins;
|
||||
uint8_t _I2CAddress;
|
||||
|
||||
// Flag whether the device supports callbacks.
|
||||
bool _hasCallback = false;
|
||||
|
||||
// Pin number of interrupt pin for GPIO extender devices. The extender module will pull this
|
||||
// pin low if an input changes state.
|
||||
int16_t _gpioInterruptPin = -1;
|
||||
|
||||
|
||||
// Static support function for subclass creation
|
||||
static void addDevice(IODevice *newDevice);
|
||||
|
||||
@@ -247,6 +239,7 @@ private:
|
||||
bool owns(VPIN vpin);
|
||||
// Method to find device handling Vpin
|
||||
static IODevice *findDevice(VPIN vpin);
|
||||
|
||||
IODevice *_nextDevice = 0;
|
||||
unsigned long _nextEntryTime;
|
||||
static IODevice *_firstDevice;
|
||||
@@ -264,6 +257,8 @@ private:
|
||||
class PCA9685 : public IODevice {
|
||||
public:
|
||||
static void create(VPIN vpin, int nPins, uint8_t I2CAddress);
|
||||
// Constructor
|
||||
PCA9685(VPIN vpin, int nPins, uint8_t I2CAddress);
|
||||
enum ProfileType : uint8_t {
|
||||
Instant = 0, // Moves immediately between positions (if duration not specified)
|
||||
UseDuration = 0, // Use specified duration
|
||||
@@ -275,8 +270,6 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
// Constructor
|
||||
PCA9685(VPIN vpin, int nPins, uint8_t I2CAddress);
|
||||
// Device-specific initialisation
|
||||
void _begin() override;
|
||||
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override;
|
||||
@@ -288,7 +281,8 @@ private:
|
||||
void updatePosition(uint8_t pin);
|
||||
void writeDevice(uint8_t pin, int value);
|
||||
void _display() override;
|
||||
|
||||
|
||||
uint8_t _I2CAddress; // 0x40-0x43 possible
|
||||
|
||||
struct ServoData {
|
||||
uint16_t activePosition : 12; // Config parameter
|
||||
@@ -323,10 +317,10 @@ private:
|
||||
class DCCAccessoryDecoder: public IODevice {
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, int DCCAddress, int DCCSubaddress);
|
||||
|
||||
private:
|
||||
// Constructor
|
||||
DCCAccessoryDecoder(VPIN firstVpin, int nPins, int DCCAddress, int DCCSubaddress);
|
||||
|
||||
private:
|
||||
// Device-specific write function.
|
||||
void _begin() override;
|
||||
void _write(VPIN vpin, int value) override;
|
||||
@@ -346,13 +340,13 @@ public:
|
||||
addDevice(new ArduinoPins(firstVpin, nPins));
|
||||
}
|
||||
|
||||
// Constructor
|
||||
ArduinoPins(VPIN firstVpin, int nPins);
|
||||
|
||||
static void fastWriteDigital(uint8_t pin, uint8_t value);
|
||||
static bool fastReadDigital(uint8_t pin);
|
||||
|
||||
private:
|
||||
// Constructor
|
||||
ArduinoPins(VPIN firstVpin, int nPins);
|
||||
|
||||
// Device-specific pin configuration
|
||||
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override;
|
||||
// Device-specific write function.
|
||||
@@ -360,7 +354,6 @@ private:
|
||||
// Device-specific read functions.
|
||||
int _read(VPIN vpin) override;
|
||||
int _readAnalogue(VPIN vpin) override;
|
||||
int _configureAnalogIn(VPIN vpin) override;
|
||||
void _display() override;
|
||||
|
||||
|
||||
@@ -369,44 +362,10 @@ private:
|
||||
uint8_t *_pinInUse;
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* IODevice subclass for EX-Turntable.
|
||||
*/
|
||||
|
||||
class EXTurntable : public IODevice {
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, uint8_t I2CAddress);
|
||||
// Constructor
|
||||
EXTurntable(VPIN firstVpin, int nPins, uint8_t I2CAddress);
|
||||
enum ActivityNumber : uint8_t {
|
||||
Turn = 0, // Rotate turntable, maintain phase
|
||||
Turn_PInvert = 1, // Rotate turntable, invert phase
|
||||
Home = 2, // Initiate homing
|
||||
Calibrate = 3, // Initiate calibration sequence
|
||||
LED_On = 4, // Turn LED on
|
||||
LED_Slow = 5, // Set LED to a slow blink
|
||||
LED_Fast = 6, // Set LED to a fast blink
|
||||
LED_Off = 7, // Turn LED off
|
||||
Acc_On = 8, // Turn accessory pin on
|
||||
Acc_Off = 9, // Turn accessory pin off
|
||||
};
|
||||
|
||||
private:
|
||||
// Device-specific write function.
|
||||
void _begin() override;
|
||||
void _loop(unsigned long currentMicros) override;
|
||||
int _read(VPIN vpin) override;
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) override;
|
||||
void _display() override;
|
||||
uint8_t _stepperStatus;
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "IO_MCP23008.h"
|
||||
#include "IO_MCP23017.h"
|
||||
#include "IO_PCF8574.h"
|
||||
#include "IO_duinoNodes.h"
|
||||
|
||||
#endif // iodevice_h
|
||||
#endif // iodevice_h
|
@@ -59,10 +59,6 @@
|
||||
**********************************************************************************************/
|
||||
class ADS111x: public IODevice {
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress) {
|
||||
if (checkNoOverlap(firstVpin,nPins,i2cAddress)) new ADS111x(firstVpin, nPins, i2cAddress);
|
||||
}
|
||||
private:
|
||||
ADS111x(VPIN firstVpin, int nPins, uint8_t i2cAddress) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = min(nPins,4);
|
||||
@@ -72,6 +68,10 @@ private:
|
||||
_value[i] = -1;
|
||||
addDevice(this);
|
||||
}
|
||||
static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress) {
|
||||
new ADS111x(firstVpin, nPins, i2cAddress);
|
||||
}
|
||||
private:
|
||||
void _begin() {
|
||||
// Initialise ADS device
|
||||
if (I2CManager.exists(_i2cAddress)) {
|
||||
|
@@ -26,8 +26,8 @@
|
||||
#define ADDRESS(packedaddr) ((packedaddr) >> 2)
|
||||
#define SUBADDRESS(packedaddr) ((packedaddr) % 4)
|
||||
|
||||
void DCCAccessoryDecoder::create(VPIN firstVpin, int nPins, int DCCAddress, int DCCSubaddress) {
|
||||
if (checkNoOverlap(firstVpin,nPins)) new DCCAccessoryDecoder(firstVpin, nPins, DCCAddress, DCCSubaddress);
|
||||
void DCCAccessoryDecoder::create(VPIN vpin, int nPins, int DCCAddress, int DCCSubaddress) {
|
||||
new DCCAccessoryDecoder(vpin, nPins, DCCAddress, DCCSubaddress);
|
||||
}
|
||||
|
||||
// Constructors
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* © 2022, Neil McKechnie. All rights reserved.
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
*
|
||||
@@ -36,31 +36,24 @@
|
||||
* In mySetup function within mySetup.cpp:
|
||||
* DFPlayer::create(3500, 5, Serial1);
|
||||
*
|
||||
* Writing an analogue value 1-2999 to the first pin (3500) will play the numbered file from the SD card;
|
||||
* Writing an analogue value 0 to the first pin (3500) will stop the file playing;
|
||||
* Writing an analogue value 0-30 to the second pin (3501) will set the volume;
|
||||
* Writing a digital value of 1 to a pin will play the file corresponding to that pin, e.g.
|
||||
the first file will be played by setting pin 3500, the second by setting pin 3501 etc.;
|
||||
* Writing a digital value of 0 to any pin will stop the player;
|
||||
* Writing an analogue value 0-2999 to the first pin will select a numbered file from the SD card;
|
||||
* Writing an analogue value 0-30 to the second pin will set the volume of the output;
|
||||
* Writing a digital value to the first pin will play or stop the file;
|
||||
* Reading a digital value from any pin will return true(1) if the player is playing, false(0) otherwise.
|
||||
*
|
||||
* From EX-RAIL, the following commands may be used:
|
||||
* SET(3500) -- starts playing the first file (file 1) on the SD card
|
||||
* SET(3501) -- starts playing the second file (file 2) on the SD card
|
||||
* SET(3500) -- starts playing the first file on the SD card
|
||||
* SET(3501) -- starts playing the second file on the SD card
|
||||
* etc.
|
||||
* RESET(3500) -- stops all playing on the player
|
||||
* WAITFOR(3500) -- wait for the file currently being played by the player to complete
|
||||
* SERVO(3500,2,Instant) -- plays file 2 at current volume
|
||||
* SERVO(3501,20,Instant) -- Sets the volume to 20
|
||||
* SERVO(3500,23,0) -- plays file 23 at current volume
|
||||
* SERVO(3500,23,30) -- plays file 23 at volume 30 (maximum)
|
||||
* SERVO(3501,20,0) -- Sets the volume to 20
|
||||
*
|
||||
* NB The DFPlayer's serial lines are not 5V safe, so connecting the Arduino TX directly
|
||||
* to the DFPlayer's RX terminal will cause lots of noise over the speaker, or worse.
|
||||
* A 1k resistor in series with the module's RX terminal will alleviate this.
|
||||
*
|
||||
* Files on the SD card are numbered according to their order in the directory on the
|
||||
* card (as listed by the DIR command in Windows). This may not match the order of the files
|
||||
* as displayed by Windows File Manager, which sorts the file names. It is suggested that
|
||||
* files be copied into an empty SDcard in the desired order, one at a time.
|
||||
*/
|
||||
|
||||
#ifndef IO_DFPlayer_h
|
||||
@@ -75,26 +68,7 @@ private:
|
||||
uint8_t _inputIndex = 0;
|
||||
unsigned long _commandSendTime; // Allows timeout processing
|
||||
|
||||
// When two commands are sent in quick succession, the device sometimes
|
||||
// fails to execute one. A delay is required between successive commands.
|
||||
// This could be implemented by buffering commands and outputting them
|
||||
// from the loop() function, but it would somewhat complicate the
|
||||
// driver. A simpler solution is to output a number of NUL pad characters
|
||||
// between successive command strings if there isn't sufficient elapsed time
|
||||
// between them. At 9600 baud, each pad character takes approximately
|
||||
// 1ms to complete. Experiments indicate that the minimum number of pads
|
||||
// for reliable operation is 17. This gives 17.7ms between the end of one
|
||||
// command and the beginning of the next, or 28ms between successive commands
|
||||
// being completed. I've allowed 20 characters, which is almost 21ms.
|
||||
const int numPadCharacters = 20; // Number of pad characters between commands
|
||||
|
||||
public:
|
||||
|
||||
static void create(VPIN firstVpin, int nPins, HardwareSerial &serial) {
|
||||
if (checkNoOverlap(firstVpin,nPins)) new DFPlayer(firstVpin, nPins, serial);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Constructor
|
||||
DFPlayer(VPIN firstVpin, int nPins, HardwareSerial &serial) :
|
||||
IODevice(firstVpin, nPins),
|
||||
@@ -103,10 +77,13 @@ protected:
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
static void create(VPIN firstVpin, int nPins, HardwareSerial &serial) {
|
||||
new DFPlayer(firstVpin, nPins, serial);
|
||||
}
|
||||
|
||||
protected:
|
||||
void _begin() override {
|
||||
_serial->begin(9600, SERIAL_8N1); // 9600baud, no parity, 1 stop bit
|
||||
// Flush any data in input queue
|
||||
while (_serial->available()) _serial->read();
|
||||
_serial->begin(9600);
|
||||
_deviceState = DEVSTATE_INITIALISING;
|
||||
|
||||
// Send a query to the device to see if it responds
|
||||
@@ -116,10 +93,10 @@ protected:
|
||||
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
// Check for incoming data on _serial, and update busy flag accordingly.
|
||||
// Expected message is in the form "7E FF 06 3D xx xx xx xx xx EF"
|
||||
// Expected message is in the form "7F FF 06 3D xx xx xx xx xx EF"
|
||||
while (_serial->available()) {
|
||||
int c = _serial->read();
|
||||
if (c == 0x7E && _inputIndex == 0)
|
||||
if (c == 0x7E)
|
||||
_inputIndex = 1;
|
||||
else if ((c==0xFF && _inputIndex==1)
|
||||
|| (c==0x3D && _inputIndex==3)
|
||||
@@ -146,8 +123,8 @@ protected:
|
||||
} else
|
||||
_inputIndex = 0; // Unrecognised character sequence, start again!
|
||||
}
|
||||
// Check if the initial prompt to device has timed out. Allow 5 seconds
|
||||
if (_deviceState == DEVSTATE_INITIALISING && currentMicros - _commandSendTime > 5000000UL) {
|
||||
// Check if the initial prompt to device has timed out. Allow 1 second
|
||||
if (_deviceState == DEVSTATE_INITIALISING && currentMicros - _commandSendTime > 1000000UL) {
|
||||
DIAG(F("DFPlayer device not responding on serial port"));
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
@@ -183,7 +160,7 @@ protected:
|
||||
uint8_t pin = vpin - _firstVpin;
|
||||
|
||||
// Validate parameter.
|
||||
volume = min((uint8_t)30,volume);
|
||||
volume = min(30,volume);
|
||||
|
||||
if (pin == 0) {
|
||||
// Play track
|
||||
@@ -240,7 +217,6 @@ private:
|
||||
|
||||
void sendPacket(uint8_t command, uint16_t arg = 0)
|
||||
{
|
||||
unsigned long currentMillis = millis();
|
||||
uint8_t out[] = { 0x7E,
|
||||
0xFF,
|
||||
06,
|
||||
@@ -254,19 +230,7 @@ private:
|
||||
|
||||
setChecksum(out);
|
||||
|
||||
// Check how long since the last command was sent.
|
||||
// Each character takes approx 1ms at 9600 baud
|
||||
unsigned long minimumGap = numPadCharacters + sizeof(out);
|
||||
if (currentMillis - _commandSendTime < minimumGap) {
|
||||
// Output some pad characters to add an
|
||||
// artificial delay between commands
|
||||
for (int i=0; i<numPadCharacters; i++)
|
||||
_serial->write(0);
|
||||
}
|
||||
|
||||
// Now output the command
|
||||
_serial->write(out, sizeof(out));
|
||||
_commandSendTime = currentMillis;
|
||||
}
|
||||
|
||||
uint16_t calcChecksum(uint8_t* packet)
|
||||
|
121
IO_EXTurntable.h
121
IO_EXTurntable.h
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* © 2021, Peter Cole. All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The IO_EXTurntable device driver is used to control a turntable via an Arduino with a stepper motor over I2C.
|
||||
*
|
||||
* The EX-Turntable code lives in a separate repo (https://github.com/DCC-EX/Turntable-EX) and contains the stepper motor logic.
|
||||
*
|
||||
* This device driver sends a step position to Turntable-EX to indicate the step position to move to using either of these commands:
|
||||
* <D TT vpin steps activity> in the serial console
|
||||
* MOVETT(vpin, steps, activity) in EX-RAIL
|
||||
* Refer to the documentation for further information including the valid activities.
|
||||
*/
|
||||
|
||||
#ifndef IO_EXTurntable_h
|
||||
#define IO_EXTurntable_h
|
||||
|
||||
#include "IODevice.h"
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
void EXTurntable::create(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
|
||||
new EXTurntable(firstVpin, nPins, I2CAddress);
|
||||
}
|
||||
|
||||
// Constructor
|
||||
EXTurntable::EXTurntable(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_I2CAddress = I2CAddress;
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
// Initialisation of TurntableEX
|
||||
void EXTurntable::_begin() {
|
||||
I2CManager.begin();
|
||||
I2CManager.setClock(1000000);
|
||||
if (I2CManager.exists(_I2CAddress)) {
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
} else {
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
// Processing loop to obtain status of stepper
|
||||
// 0 = finished moving and in correct position
|
||||
// 1 = still moving
|
||||
void EXTurntable::_loop(unsigned long currentMicros) {
|
||||
uint8_t readBuffer[1];
|
||||
I2CManager.read(_I2CAddress, readBuffer, 1);
|
||||
_stepperStatus = readBuffer[0];
|
||||
// DIAG(F("Turntable-EX returned status: %d"), _stepperStatus);
|
||||
delayUntil(currentMicros + 500000); // Wait 500ms before checking again, turntables turn slowly
|
||||
}
|
||||
|
||||
// Read returns status as obtained in our loop.
|
||||
// Return false if our status value is invalid.
|
||||
int EXTurntable::_read(VPIN vpin) {
|
||||
if (_deviceState == DEVSTATE_FAILED) return 0;
|
||||
// DIAG(F("_read status: %d"), _stepperStatus);
|
||||
if (_stepperStatus > 1) {
|
||||
return false;
|
||||
} else {
|
||||
return _stepperStatus;
|
||||
}
|
||||
}
|
||||
|
||||
// writeAnalogue to send the steps and activity to Turntable-EX.
|
||||
// Sends 3 bytes containing the MSB and LSB of the step count, and activity.
|
||||
// value contains the steps, bit shifted to MSB + LSB.
|
||||
// activity contains the activity flag as per this list:
|
||||
//
|
||||
// Turn = 0, // Rotate turntable, maintain phase
|
||||
// Turn_PInvert = 1, // Rotate turntable, invert phase
|
||||
// Home = 2, // Initiate homing
|
||||
// Calibrate = 3, // Initiate calibration sequence
|
||||
// LED_On = 4, // Turn LED on
|
||||
// LED_Slow = 5, // Set LED to a slow blink
|
||||
// LED_Fast = 6, // Set LED to a fast blink
|
||||
// LED_Off = 7, // Turn LED off
|
||||
// Acc_On = 8, // Turn accessory pin on
|
||||
// Acc_Off = 9 // Turn accessory pin off
|
||||
void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
uint8_t stepsMSB = value >> 8;
|
||||
uint8_t stepsLSB = value & 0xFF;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("TurntableEX WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"),
|
||||
vpin, value, activity, duration);
|
||||
DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"),
|
||||
_I2CAddress, stepsMSB, stepsLSB, activity);
|
||||
#endif
|
||||
_stepperStatus = 1; // Tell the device driver Turntable-EX is busy
|
||||
I2CManager.write(_I2CAddress, 3, stepsMSB, stepsLSB, activity);
|
||||
}
|
||||
|
||||
// Display Turnetable-EX device driver info.
|
||||
void EXTurntable::_display() {
|
||||
DIAG(F("TurntableEX I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin,
|
||||
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
#endif
|
@@ -36,7 +36,7 @@ IO_ExampleSerial::IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *se
|
||||
|
||||
// Static create method for one module.
|
||||
void IO_ExampleSerial::create(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud) {
|
||||
if (checkNoOverlap(firstVpin,nPins)) new IO_ExampleSerial(firstVpin, nPins, serial, baud);
|
||||
new IO_ExampleSerial(firstVpin, nPins, serial, baud);
|
||||
}
|
||||
|
||||
// Device-specific initialisation
|
||||
|
@@ -36,10 +36,10 @@
|
||||
|
||||
class IO_ExampleSerial : public IODevice {
|
||||
public:
|
||||
IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud);
|
||||
static void create(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud);
|
||||
|
||||
protected:
|
||||
IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud);
|
||||
void _begin() override;
|
||||
void _loop(unsigned long currentMicros) override;
|
||||
void _write(VPIN vpin, int value) override;
|
||||
|
@@ -47,7 +47,7 @@ protected:
|
||||
void _loop(unsigned long currentMicros) override;
|
||||
|
||||
// Data fields
|
||||
|
||||
uint8_t _I2CAddress;
|
||||
// Allocate enough space for all input pins
|
||||
T _portInputState;
|
||||
T _portOutputState;
|
||||
@@ -69,10 +69,6 @@ protected:
|
||||
|
||||
I2CRB requestBlock;
|
||||
FSH *_deviceName;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
// workaround: Has somehow no min function for all types
|
||||
static inline T min(T a, int b) { return a < b ? a : b; };
|
||||
#endif
|
||||
};
|
||||
|
||||
// Because class GPIOBase is a template, the implementation (below) must be contained within the same
|
||||
@@ -250,4 +246,4 @@ int GPIOBase<T>::_read(VPIN vpin) {
|
||||
return (_portInputState & mask) ? 0 : 1; // Invert state (5v=0, 0v=1)
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
21
IO_HCSR04.h
21
IO_HCSR04.h
@@ -73,14 +73,6 @@ private:
|
||||
const uint16_t factor = 58; // ms/cm
|
||||
|
||||
public:
|
||||
|
||||
// Static create function provides alternative way to create object
|
||||
static void create(VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) {
|
||||
if (checkNoOverlap(vpin))
|
||||
new HCSR04(vpin, trigPin, echoPin, onThreshold, offThreshold);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Constructor perfroms static initialisation of the device object
|
||||
HCSR04 (VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) {
|
||||
_firstVpin = vpin;
|
||||
@@ -91,7 +83,14 @@ protected:
|
||||
_offThreshold = offThreshold;
|
||||
addDevice(this);
|
||||
}
|
||||
// _begin function called to perform dynamic initialisation of the device
|
||||
|
||||
// Static create function provides alternative way to create object
|
||||
static void create(VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) {
|
||||
new HCSR04(vpin, trigPin, echoPin, onThreshold, offThreshold);
|
||||
}
|
||||
|
||||
protected:
|
||||
// _begin function called to perform dynamic initialisation of the device
|
||||
void _begin() override {
|
||||
pinMode(_trigPin, OUTPUT);
|
||||
pinMode(_echoPin, INPUT);
|
||||
@@ -138,7 +137,7 @@ private:
|
||||
//
|
||||
void read_HCSR04device() {
|
||||
// uint16 enough to time up to 65ms
|
||||
uint16_t startTime, waitTime = 0, currentTime, maxTime;
|
||||
uint16_t startTime, waitTime, currentTime, maxTime;
|
||||
|
||||
// If receive pin is still set on from previous call, abort the read.
|
||||
if (ArduinoPins::fastReadDigital(_echoPin))
|
||||
@@ -186,4 +185,4 @@ private:
|
||||
|
||||
};
|
||||
|
||||
#endif //IO_HCSR04_H
|
||||
#endif //IO_HCSR04_H
|
@@ -1,5 +1,4 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
@@ -26,19 +25,19 @@
|
||||
class MCP23008 : public GPIOBase<uint8_t> {
|
||||
public:
|
||||
static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) {
|
||||
if (checkNoOverlap(firstVpin, nPins,I2CAddress)) new MCP23008(firstVpin, nPins, I2CAddress, interruptPin);
|
||||
new MCP23008(firstVpin, nPins, I2CAddress, interruptPin);
|
||||
}
|
||||
|
||||
private:
|
||||
// Constructor
|
||||
MCP23008(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1)
|
||||
: GPIOBase<uint8_t>((FSH *)F("MCP23008"), firstVpin, min(nPins, (uint8_t)8), I2CAddress, interruptPin) {
|
||||
: GPIOBase<uint8_t>((FSH *)F("MCP23008"), firstVpin, min(nPins, 8), I2CAddress, interruptPin) {
|
||||
|
||||
requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
|
||||
outputBuffer, sizeof(outputBuffer));
|
||||
outputBuffer[0] = REG_GPIO;
|
||||
}
|
||||
|
||||
private:
|
||||
void _writeGpioPort() override {
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPIO, _portOutputState);
|
||||
}
|
||||
|
@@ -31,10 +31,9 @@
|
||||
class MCP23017 : public GPIOBase<uint16_t> {
|
||||
public:
|
||||
static void create(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1) {
|
||||
if (checkNoOverlap(vpin, nPins, I2CAddress)) new MCP23017(vpin, min(nPins,16), I2CAddress, interruptPin);
|
||||
new MCP23017(vpin, min(nPins,16), I2CAddress, interruptPin);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// Constructor
|
||||
MCP23017(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1)
|
||||
: GPIOBase<uint16_t>((FSH *)F("MCP23017"), vpin, nPins, I2CAddress, interruptPin)
|
||||
@@ -43,6 +42,8 @@ private:
|
||||
outputBuffer, sizeof(outputBuffer));
|
||||
outputBuffer[0] = REG_GPIOA;
|
||||
}
|
||||
|
||||
private:
|
||||
void _writeGpioPort() override {
|
||||
I2CManager.write(_I2CAddress, 3, REG_GPIOA, _portOutputState, _portOutputState>>8);
|
||||
}
|
||||
|
112
IO_PCA9555.h
Normal file
112
IO_PCA9555.h
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef io_pca9555_h
|
||||
#define io_pca9555_h
|
||||
|
||||
#include "IO_GPIOBase.h"
|
||||
#include "FSH.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* IODevice subclass for PCA9555 16-bit I/O expander (NXP & Texas Instruments).
|
||||
*/
|
||||
|
||||
class PCA9555 : public GPIOBase<uint16_t> {
|
||||
public:
|
||||
static void create(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1) {
|
||||
new PCA9555(vpin, min(nPins,16), I2CAddress, interruptPin);
|
||||
}
|
||||
|
||||
// Constructor
|
||||
PCA9555(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1)
|
||||
: GPIOBase<uint16_t>((FSH *)F("PCA9555"), vpin, nPins, I2CAddress, interruptPin)
|
||||
{
|
||||
requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
|
||||
outputBuffer, sizeof(outputBuffer));
|
||||
outputBuffer[0] = REG_INPUT_P0;
|
||||
}
|
||||
|
||||
private:
|
||||
void _writeGpioPort() override {
|
||||
I2CManager.write(_I2CAddress, 3, REG_OUTPUT_P0, _portOutputState, _portOutputState>>8);
|
||||
}
|
||||
void _writePullups() override {
|
||||
// Do nothing, pull-ups are always in place for input ports
|
||||
// This function is here for HAL GPIOBase API compatibilitiy
|
||||
|
||||
}
|
||||
void _writePortModes() override {
|
||||
// Write 0 to REG_CONF_P0 & REG_CONF_P1 for in-use pins that are outputs, 1 for others.
|
||||
// PCA9555 & TCA9555, Interrupt is always enabled for raising and falling edge
|
||||
uint16_t temp = ~(_portMode & _portInUse);
|
||||
I2CManager.write(_I2CAddress, 3, REG_CONF_P0, temp, temp>>8);
|
||||
}
|
||||
void _readGpioPort(bool immediate) override {
|
||||
if (immediate) {
|
||||
uint8_t buffer[2];
|
||||
I2CManager.read(_I2CAddress, buffer, 2, 1, REG_INPUT_P0);
|
||||
_portInputState = ((uint16_t)buffer[1]<<8) | buffer[0];
|
||||
/* PCA9555 Int bug fix, from PCA9555 datasheet: "must change command byte to something besides 00h
|
||||
* after a Read operation to the PCA9555 device or before reading from
|
||||
* another device"
|
||||
* Recommended solution, read from REG_OUTPUT_P0, then do nothing with the received data
|
||||
* Issue not seen during testing, uncomment if needed
|
||||
*/
|
||||
//I2CManager.read(_I2CAddress, buffer, 2, 1, REG_OUTPUT_P0);
|
||||
} else {
|
||||
// Queue new request
|
||||
requestBlock.wait(); // Wait for preceding operation to complete
|
||||
// Issue new request to read GPIO register
|
||||
I2CManager.queueRequest(&requestBlock);
|
||||
}
|
||||
}
|
||||
// This function is invoked when an I/O operation on the requestBlock completes.
|
||||
void _processCompletion(uint8_t status) override {
|
||||
if (status == I2C_STATUS_OK)
|
||||
_portInputState = ((uint16_t)inputBuffer[1]<<8) | inputBuffer[0];
|
||||
else
|
||||
_portInputState = 0xffff;
|
||||
}
|
||||
|
||||
void _setupDevice() override {
|
||||
// HAL API calls
|
||||
_writePortModes();
|
||||
_writePullups();
|
||||
_writeGpioPort();
|
||||
}
|
||||
|
||||
uint8_t inputBuffer[2];
|
||||
uint8_t outputBuffer[1];
|
||||
|
||||
|
||||
enum {
|
||||
REG_INPUT_P0 = 0x00,
|
||||
REG_INPUT_P1 = 0x01,
|
||||
REG_OUTPUT_P0 = 0x02,
|
||||
REG_OUTPUT_P1 = 0x03,
|
||||
REG_POL_INV_P0 = 0x04,
|
||||
REG_POL_INV_P1 = 0x05,
|
||||
REG_CONF_P0 = 0x06,
|
||||
REG_CONF_P1 = 0x07,
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif
|
@@ -39,7 +39,7 @@ static void writeRegister(byte address, byte reg, byte value);
|
||||
|
||||
// Create device driver instance.
|
||||
void PCA9685::create(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
|
||||
if (checkNoOverlap(firstVpin, nPins,I2CAddress)) new PCA9685(firstVpin, nPins, I2CAddress);
|
||||
new PCA9685(firstVpin, nPins, I2CAddress);
|
||||
}
|
||||
|
||||
// Configure a port on the PCA9685.
|
||||
@@ -114,6 +114,7 @@ void PCA9685::_begin() {
|
||||
// Device-specific write function, invoked from IODevice::write().
|
||||
// For this function, the configured profile is used.
|
||||
void PCA9685::_write(VPIN vpin, int value) {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value);
|
||||
#endif
|
||||
@@ -124,10 +125,7 @@ void PCA9685::_write(VPIN vpin, int value) {
|
||||
if (s != NULL) {
|
||||
// Use configured parameters
|
||||
_writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile, s->duration);
|
||||
} else {
|
||||
/* simulate digital pin on PWM */
|
||||
_writeAnalogue(vpin, value ? 4095 : 0, Instant | NoPowerOff, 0);
|
||||
}
|
||||
} // else { /* ignorethe request */ }
|
||||
}
|
||||
|
||||
// Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue().
|
||||
@@ -141,11 +139,11 @@ void PCA9685::_write(VPIN vpin, int value) {
|
||||
// 4 (Bounce) Servo 'bounces' at extremes.
|
||||
//
|
||||
void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"),
|
||||
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
|
||||
#endif
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d"),
|
||||
vpin, value, profile, duration);
|
||||
#endif
|
||||
int pin = vpin - _firstVpin;
|
||||
if (value > 4095) value = 4095;
|
||||
else if (value < 0) value = 0;
|
||||
@@ -155,10 +153,10 @@ void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t dur
|
||||
// Servo pin not configured, so configure now using defaults
|
||||
s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1);
|
||||
if (s == NULL) return; // Check for memory allocation failure
|
||||
s->activePosition = 4095;
|
||||
s->activePosition = 0;
|
||||
s->inactivePosition = 0;
|
||||
s->currentPosition = value;
|
||||
s->profile = Instant | NoPowerOff; // Use instant profile (but not this time)
|
||||
s->profile = Instant; // Use instant profile (but not this time)
|
||||
}
|
||||
|
||||
// Animated profile. Initiate the appropriate action.
|
||||
|
@@ -1,5 +1,4 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
@@ -44,16 +43,16 @@
|
||||
class PCF8574 : public GPIOBase<uint8_t> {
|
||||
public:
|
||||
static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) {
|
||||
if (checkNoOverlap(firstVpin, nPins,I2CAddress)) new PCF8574(firstVpin, nPins, I2CAddress, interruptPin);
|
||||
new PCF8574(firstVpin, nPins, I2CAddress, interruptPin);
|
||||
}
|
||||
|
||||
private:
|
||||
PCF8574(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1)
|
||||
: GPIOBase<uint8_t>((FSH *)F("PCF8574"), firstVpin, min(nPins, (uint8_t)8), I2CAddress, interruptPin)
|
||||
: GPIOBase<uint8_t>((FSH *)F("PCF8574"), firstVpin, min(nPins, 8), I2CAddress, interruptPin)
|
||||
{
|
||||
requestBlock.setReadParams(_I2CAddress, inputBuffer, 1);
|
||||
}
|
||||
|
||||
private:
|
||||
// The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise.
|
||||
void _writeGpioPort() override {
|
||||
I2CManager.write(_I2CAddress, 1, _portOutputState | ~_portMode);
|
||||
|
13
IO_VL53L0X.h
13
IO_VL53L0X.h
@@ -127,13 +127,7 @@ private:
|
||||
};
|
||||
const uint8_t VL53L0X_I2C_DEFAULT_ADDRESS=0x29;
|
||||
|
||||
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) {
|
||||
if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new VL53L0X(firstVpin, nPins, i2cAddress, onThreshold, offThreshold, xshutPin);
|
||||
}
|
||||
|
||||
protected:
|
||||
public:
|
||||
VL53L0X(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = min(nPins, 3);
|
||||
@@ -144,6 +138,11 @@ protected:
|
||||
_value = 0;
|
||||
addDevice(this);
|
||||
}
|
||||
static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) {
|
||||
new VL53L0X(firstVpin, nPins, i2cAddress, onThreshold, offThreshold, xshutPin);
|
||||
}
|
||||
|
||||
protected:
|
||||
void _begin() override {
|
||||
if (_xshutPin == VPIN_NONE) {
|
||||
// Check if device is already responding on the nominated address.
|
||||
|
173
IO_duinoNodes.h
173
IO_duinoNodes.h
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
* © 2022, Chris Harlow. All rights reserved.
|
||||
* Based on original by: Robin Simonds, Beagle Bay Inc
|
||||
*
|
||||
* This file is part of DCC-EX API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef IO_duinoNodes_h
|
||||
#define IO_duinoNodes_h
|
||||
#include <Arduino.h>
|
||||
#include "defines.h"
|
||||
#include "IODevice.h"
|
||||
|
||||
#define PIN_MASK(bit) (0x80>>(bit%8))
|
||||
#define GET_BIT(x) (_pinValues[(x)/8] & PIN_MASK((x)) )
|
||||
#define SET_BIT(x) _pinValues[(x)/8] |= PIN_MASK((x))
|
||||
#define CLR_BIT(x) _pinValues[(x)/8] &= ~PIN_MASK((x))
|
||||
#define DIAG_IO
|
||||
|
||||
|
||||
|
||||
class IO_duinoNodes : public IODevice {
|
||||
|
||||
public:
|
||||
IO_duinoNodes(VPIN firstVpin, int nPins,
|
||||
byte clockPin, byte latchPin, byte dataPin,
|
||||
const byte* pinmap) :
|
||||
IODevice(firstVpin, nPins) {
|
||||
|
||||
_latchPin=latchPin;
|
||||
_clockPin=clockPin;
|
||||
_dataPin=dataPin;
|
||||
_pinMap=pinmap;
|
||||
_nShiftBytes=(nPins+7)/8; // rounded up to multiples of 8 bits
|
||||
_pinValues=(byte*) calloc(_nShiftBytes,1);
|
||||
// Connect to HAL so my _write, _read and _loop will be called as required.
|
||||
IODevice::addDevice(this);
|
||||
}
|
||||
|
||||
// Called by HAL to start handling this device
|
||||
void _begin() override {
|
||||
_deviceState = DEVSTATE_NORMAL;
|
||||
pinMode(_latchPin,OUTPUT);
|
||||
pinMode(_clockPin,OUTPUT);
|
||||
pinMode(_dataPin,_pinMap?INPUT_PULLUP:OUTPUT);
|
||||
_display();
|
||||
}
|
||||
|
||||
// loop called by HAL supervisor
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
if (_pinMap) _loopInput(currentMicros);
|
||||
else if (_xmitPending) _loopOutput();
|
||||
}
|
||||
|
||||
void _loopInput(unsigned long currentMicros) {
|
||||
|
||||
if (currentMicros-_prevMicros < POLL_MICROS) return; // Nothing to do
|
||||
_prevMicros=currentMicros;
|
||||
|
||||
//set latch to HIGH to freeze & store parallel data
|
||||
ArduinoPins::fastWriteDigital(_latchPin, HIGH);
|
||||
delayMicroseconds(1);
|
||||
//set latch to LOW to enable the data to be transmitted serially
|
||||
ArduinoPins::fastWriteDigital(_latchPin, LOW);
|
||||
|
||||
// stream in the bitmap using mapping order provided at constructor
|
||||
for (int xmitByte=0;xmitByte<_nShiftBytes; xmitByte++) {
|
||||
byte newByte=0;
|
||||
for (int xmitBit=0;xmitBit<8; xmitBit++) {
|
||||
ArduinoPins::fastWriteDigital(_clockPin, LOW);
|
||||
delayMicroseconds(1);
|
||||
bool data = ArduinoPins::fastReadDigital(_dataPin);
|
||||
byte map=_pinMap[xmitBit];
|
||||
if (data) newByte |= map;
|
||||
else newByte &= ~map;
|
||||
ArduinoPins::fastWriteDigital(_clockPin, HIGH);
|
||||
delayMicroseconds(1);
|
||||
}
|
||||
_pinValues[xmitByte]=newByte;
|
||||
// DIAG(F("DIN %x=%x"),xmitByte, newByte);
|
||||
}
|
||||
}
|
||||
|
||||
void _loopOutput() {
|
||||
// stream out the bitmap (highest pin first)
|
||||
_xmitPending=false;
|
||||
ArduinoPins::fastWriteDigital(_latchPin, LOW);
|
||||
for (int xmitBit=_nShiftBytes*8 -1; xmitBit>=0; xmitBit--) {
|
||||
ArduinoPins::fastWriteDigital(_dataPin,GET_BIT(xmitBit));
|
||||
ArduinoPins::fastWriteDigital(_clockPin,HIGH);
|
||||
ArduinoPins::fastWriteDigital(_clockPin,LOW);
|
||||
}
|
||||
ArduinoPins::fastWriteDigital(_latchPin, HIGH);
|
||||
}
|
||||
|
||||
int _read(VPIN vpin) override {
|
||||
int pin=vpin - _firstVpin;
|
||||
bool b=GET_BIT(pin);
|
||||
return b?1:0;
|
||||
}
|
||||
|
||||
void _write(VPIN vpin, int value) override {
|
||||
int pin = vpin - _firstVpin;
|
||||
bool oldval=GET_BIT(pin);
|
||||
bool newval=value!=0;
|
||||
if (newval==oldval) return; // no change
|
||||
if (newval) SET_BIT(pin);
|
||||
else CLR_BIT(pin);
|
||||
_xmitPending=true; // shift register will be sent on next _loop()
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("IO_duinoNodes %SPUT Configured on VPins:%d-%d shift=%d"),
|
||||
_pinMap?F("IN"):F("OUT"),
|
||||
(int)_firstVpin,
|
||||
(int)_firstVpin+_nPins-1, _nShiftBytes*8);
|
||||
}
|
||||
|
||||
private:
|
||||
static const unsigned long POLL_MICROS=100000; // 10 / S
|
||||
unsigned long _prevMicros;
|
||||
int _nShiftBytes=0;
|
||||
VPIN _latchPin,_clockPin,_dataPin;
|
||||
byte* _pinValues;
|
||||
bool _xmitPending; // Only relevant in output mode
|
||||
const byte* _pinMap; // NULL in output mode
|
||||
};
|
||||
|
||||
class IO_DNIN8 {
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
|
||||
{
|
||||
// input arrives as board pin 0,7,6,5,1,2,3,4
|
||||
static const byte pinmap[8]={0x80,0x01,0x02,0x04,0x40,0x20,0x10,0x08};
|
||||
if (IODevice::checkNoOverlap(firstVpin,nPins))
|
||||
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class IO_DNIN8K {
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
|
||||
{
|
||||
// input arrives as board pin 0, 1, 2, 3, 4, 5, 6, 7
|
||||
static const byte pinmap[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
|
||||
if (IODevice::checkNoOverlap(firstVpin,nPins))
|
||||
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap);
|
||||
}
|
||||
};
|
||||
|
||||
class IO_DNOU8 {
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
|
||||
{
|
||||
if (IODevice::checkNoOverlap(firstVpin,nPins))
|
||||
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,NULL);
|
||||
}
|
||||
|
||||
};
|
||||
#endif
|
6
LCN.cpp
6
LCN.cpp
@@ -50,11 +50,7 @@ void LCN::loop() {
|
||||
if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch);
|
||||
if (!Turnout::exists(id)) LCNTurnout::create(id);
|
||||
Turnout::setClosedStateOnly(id,ch=='t');
|
||||
id = 0;
|
||||
}
|
||||
else if (ch == 'y' || ch == 'Y') { // Turnout opcodes
|
||||
if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch);
|
||||
Turnout::setClosed(id,ch=='y');
|
||||
Turnout::turnoutlistHash++; // signals ED update of turnout data
|
||||
id = 0;
|
||||
}
|
||||
else if (ch == 'S' || ch == 's') {
|
||||
|
350
MotorDriver.cpp
350
MotorDriver.cpp
@@ -1,5 +1,4 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2022 Harald Barth
|
||||
@@ -23,53 +22,27 @@
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#include "MotorDriver.h"
|
||||
#include "DCCWaveform.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#include "ESP32-fixes.h"
|
||||
#endif
|
||||
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
|
||||
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
|
||||
#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH)
|
||||
#define isLOW(fastpin) (!isHIGH(fastpin))
|
||||
|
||||
bool MotorDriver::usePWM=false;
|
||||
bool MotorDriver::commonFaultPin=false;
|
||||
|
||||
volatile portreg_t shadowPORTA;
|
||||
volatile portreg_t shadowPORTB;
|
||||
volatile portreg_t shadowPORTC;
|
||||
|
||||
MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||
|
||||
MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||
byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin) {
|
||||
powerPin=power_pin;
|
||||
invertPower=power_pin < 0;
|
||||
if (invertPower) {
|
||||
powerPin = 0-power_pin;
|
||||
IODevice::write(powerPin,HIGH);// set to OUTPUT and off
|
||||
} else {
|
||||
powerPin = power_pin;
|
||||
IODevice::write(powerPin,LOW);// set to OUTPUT and off
|
||||
}
|
||||
getFastPin(F("POWER"),powerPin,fastPowerPin);
|
||||
pinMode(powerPin, OUTPUT);
|
||||
|
||||
signalPin=signal_pin;
|
||||
getFastPin(F("SIG"),signalPin,fastSignalPin);
|
||||
pinMode(signalPin, OUTPUT);
|
||||
|
||||
fastSignalPin.shadowinout = NULL;
|
||||
if (HAVE_PORTA(fastSignalPin.inout == &PORTA)) {
|
||||
DIAG(F("Found PORTA pin %d"),signalPin);
|
||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||
fastSignalPin.inout = &shadowPORTA;
|
||||
}
|
||||
if (HAVE_PORTB(fastSignalPin.inout == &PORTB)) {
|
||||
DIAG(F("Found PORTB pin %d"),signalPin);
|
||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||
fastSignalPin.inout = &shadowPORTB;
|
||||
}
|
||||
if (HAVE_PORTC(fastSignalPin.inout == &PORTC)) {
|
||||
DIAG(F("Found PORTC pin %d"),signalPin);
|
||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||
fastSignalPin.inout = &shadowPORTC;
|
||||
}
|
||||
|
||||
|
||||
signalPin2=signal_pin2;
|
||||
if (signalPin2!=UNUSED_PIN) {
|
||||
dualSignal=true;
|
||||
@@ -83,15 +56,15 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||
invertBrake=brake_pin < 0;
|
||||
brakePin=invertBrake ? 0-brake_pin : brake_pin;
|
||||
getFastPin(F("BRAKE"),brakePin,fastBrakePin);
|
||||
// if brake is used for railcom cutout we need to do PORTX register trick here as well
|
||||
pinMode(brakePin, OUTPUT);
|
||||
setBrake(true); // start with brake on in case we hace DC stuff going on
|
||||
setBrake(false);
|
||||
}
|
||||
else brakePin=UNUSED_PIN;
|
||||
|
||||
currentPin=current_pin;
|
||||
if (currentPin!=UNUSED_PIN) {
|
||||
senseOffset = ADCee::init(currentPin);
|
||||
pinMode(currentPin, INPUT);
|
||||
senseOffset=analogRead(currentPin); // value of sensor at zero current
|
||||
}
|
||||
|
||||
faultPin=fault_pin;
|
||||
@@ -100,40 +73,15 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||
pinMode(faultPin, INPUT);
|
||||
}
|
||||
|
||||
// This conversion performed at compile time so the remainder of the code never needs
|
||||
// float calculations or libraray code.
|
||||
senseFactorInternal=sense_factor * senseScale;
|
||||
senseFactor=sense_factor;
|
||||
tripMilliamps=trip_milliamps;
|
||||
rawCurrentTripValue=mA2raw(trip_milliamps);
|
||||
|
||||
if (rawCurrentTripValue + senseOffset > ADCee::ADCmax()) {
|
||||
// This would mean that the values obtained from the ADC never
|
||||
// can reach the trip value. So independent of the current, the
|
||||
// short circuit protection would never trip. So we adjust the
|
||||
// trip value so that it is tiggered when the ADC reports it's
|
||||
// maximum value instead.
|
||||
|
||||
// DIAG(F("Changing short detection value from %d to %d mA"),
|
||||
// raw2mA(rawCurrentTripValue), raw2mA(ADCee::ADCmax()-senseOffset));
|
||||
rawCurrentTripValue=ADCee::ADCmax()-senseOffset;
|
||||
}
|
||||
|
||||
rawCurrentTripValue=(int)(trip_milliamps / sense_factor);
|
||||
|
||||
if (currentPin==UNUSED_PIN)
|
||||
DIAG(F("** WARNING ** No current or short detection"));
|
||||
else {
|
||||
DIAG(F("CurrentPin=A%d, Offset=%d, TripValue=%d"),
|
||||
DIAG(F("MotorDriver ** WARNING ** No current or short detection"));
|
||||
else
|
||||
DIAG(F("MotorDriver currentPin=A%d, senseOffset=%d, rawCurrentTripValue(relative to offset)=%d"),
|
||||
currentPin-A0, senseOffset,rawCurrentTripValue);
|
||||
|
||||
// self testing diagnostic for the non-float converters... may be removed when happy
|
||||
// DIAG(F("senseFactorInternal=%d raw2mA(1000)=%d mA2Raw(1000)=%d"),
|
||||
// senseFactorInternal, raw2mA(1000),mA2raw(1000));
|
||||
}
|
||||
|
||||
// prepare values for current detection
|
||||
sampleDelay = 0;
|
||||
lastSampleTaken = millis();
|
||||
progTripValue = mA2raw(TRIP_CURRENT_PROG);
|
||||
|
||||
}
|
||||
|
||||
bool MotorDriver::isPWMCapable() {
|
||||
@@ -141,21 +89,15 @@ bool MotorDriver::isPWMCapable() {
|
||||
}
|
||||
|
||||
|
||||
void MotorDriver::setPower(POWERMODE mode) {
|
||||
bool on=mode==POWERMODE::ON;
|
||||
void MotorDriver::setPower(bool on) {
|
||||
if (on) {
|
||||
noInterrupts();
|
||||
IODevice::write(powerPin,invertPower ? LOW : HIGH);
|
||||
interrupts();
|
||||
if (isProgTrack)
|
||||
DCCWaveform::progTrack.clearResets();
|
||||
// toggle brake before turning power on - resets overcurrent error
|
||||
// on the Pololu board if brake is wired to ^D2.
|
||||
setBrake(true);
|
||||
setBrake(false);
|
||||
setHIGH(fastPowerPin);
|
||||
}
|
||||
else {
|
||||
noInterrupts();
|
||||
IODevice::write(powerPin,invertPower ? HIGH : LOW);
|
||||
interrupts();
|
||||
}
|
||||
powerMode=mode;
|
||||
else setLOW(fastPowerPin);
|
||||
}
|
||||
|
||||
// setBrake applies brake if on == true. So to get
|
||||
@@ -166,164 +108,80 @@ void MotorDriver::setPower(POWERMODE mode) {
|
||||
// (HIGH == release brake) and setBrake does
|
||||
// compensate for that.
|
||||
//
|
||||
void MotorDriver::setBrake(bool on, bool interruptContext) {
|
||||
void MotorDriver::setBrake(bool on) {
|
||||
if (brakePin == UNUSED_PIN) return;
|
||||
if (!interruptContext) {noInterrupts();}
|
||||
if (on ^ invertBrake)
|
||||
setHIGH(fastBrakePin);
|
||||
else
|
||||
setLOW(fastBrakePin);
|
||||
if (!interruptContext) {interrupts();}
|
||||
if (on ^ invertBrake) setHIGH(fastBrakePin);
|
||||
else setLOW(fastBrakePin);
|
||||
}
|
||||
|
||||
void MotorDriver::setSignal( bool high) {
|
||||
if (usePWM) {
|
||||
DCCTimer::setPWM(signalPin,high);
|
||||
}
|
||||
else {
|
||||
if (high) {
|
||||
setHIGH(fastSignalPin);
|
||||
if (dualSignal) setLOW(fastSignalPin2);
|
||||
}
|
||||
else {
|
||||
setLOW(fastSignalPin);
|
||||
if (dualSignal) setHIGH(fastSignalPin2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_TEENSY32) || defined(ARDUINO_TEENSY35)|| defined(ARDUINO_TEENSY36)
|
||||
volatile unsigned int overflow_count=0;
|
||||
#endif
|
||||
|
||||
bool MotorDriver::canMeasureCurrent() {
|
||||
return currentPin!=UNUSED_PIN;
|
||||
}
|
||||
/*
|
||||
* Return the current reading as pin reading 0 to 1023. If the fault
|
||||
* pin is activated return a negative current to show active fault pin.
|
||||
* As there is no -0, cheat a little and return -1 in that case.
|
||||
* As there is no -0, create a little and return -1 in that case.
|
||||
*
|
||||
* senseOffset handles the case where a shield returns values above or below
|
||||
* a central value depending on direction.
|
||||
*
|
||||
* Bool fromISR should be adjusted dependent how function is called
|
||||
*/
|
||||
int MotorDriver::getCurrentRaw(bool fromISR) {
|
||||
(void)fromISR;
|
||||
int MotorDriver::getCurrentRaw() {
|
||||
if (currentPin==UNUSED_PIN) return 0;
|
||||
int current;
|
||||
current = ADCee::read(currentPin, fromISR)-senseOffset;
|
||||
#if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41)
|
||||
bool irq = disableInterrupts();
|
||||
current = analogRead(currentPin)-senseOffset;
|
||||
enableInterrupts(irq);
|
||||
#else // Uno, Mega and all the TEENSY3* but not TEENSY4*
|
||||
unsigned char sreg_backup;
|
||||
sreg_backup = SREG; /* save interrupt enable/disable state */
|
||||
cli();
|
||||
current = analogRead(currentPin)-senseOffset;
|
||||
#if defined(ARDUINO_TEENSY32) || defined(ARDUINO_TEENSY35)|| defined(ARDUINO_TEENSY36)
|
||||
overflow_count = 0;
|
||||
#endif
|
||||
if (sreg_backup & 128) sei(); /* restore interrupt state */
|
||||
#endif // outer #
|
||||
if (current<0) current=0-current;
|
||||
if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && powerMode==POWERMODE::ON)
|
||||
if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && isHIGH(fastPowerPin))
|
||||
return (current == 0 ? -1 : -current);
|
||||
return current;
|
||||
|
||||
}
|
||||
|
||||
#ifdef ANALOG_READ_INTERRUPT
|
||||
/*
|
||||
* This should only be called in interrupt context
|
||||
* Copies current value from HW to cached value in
|
||||
* Motordriver.
|
||||
*/
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
bool MotorDriver::sampleCurrentFromHW() {
|
||||
byte low, high;
|
||||
//if (!bit_is_set(ADCSRA, ADIF))
|
||||
if (bit_is_set(ADCSRA, ADSC))
|
||||
return false;
|
||||
// if ((ADMUX & mask) != (currentPin - A0))
|
||||
// return false;
|
||||
low = ADCL; //must read low before high
|
||||
high = ADCH;
|
||||
bitSet(ADCSRA, ADIF);
|
||||
sampleCurrent = (high << 8) | low;
|
||||
sampleCurrentTimestamp = millis();
|
||||
return true;
|
||||
}
|
||||
void MotorDriver::startCurrentFromHW() {
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
const byte mask = 7;
|
||||
#else
|
||||
const byte mask = 31;
|
||||
#endif
|
||||
ADMUX=(1<<REFS0)|((currentPin-A0) & mask); //select AVCC as reference and set MUX
|
||||
bitSet(ADCSRA,ADSC); // start conversion
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
#endif //ANALOG_READ_INTERRUPT
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
uint16_t taurustones[28] = { 165, 175, 196, 220,
|
||||
247, 262, 294, 330,
|
||||
249, 392, 440, 494,
|
||||
523, 587, 659, 698,
|
||||
494, 440, 392, 249,
|
||||
330, 284, 262, 247,
|
||||
220, 196, 175, 165 };
|
||||
#endif
|
||||
void MotorDriver::setDCSignal(byte speedcode) {
|
||||
if (brakePin == UNUSED_PIN)
|
||||
return;
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz
|
||||
#endif
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz
|
||||
TCCR4B = (TCCR4B & B11111000) | B00000100; // same for timer 4 but maxcount and thus divisor differs
|
||||
#endif
|
||||
// spedcoode is a dcc speed & direction
|
||||
byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127
|
||||
byte tDir=speedcode & 0x80;
|
||||
byte brake;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
{
|
||||
int f = 131;
|
||||
if (tSpeed > 2) {
|
||||
if (tSpeed <= 58) {
|
||||
f = taurustones[ (tSpeed-2)/2 ] ;
|
||||
}
|
||||
}
|
||||
DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup
|
||||
}
|
||||
#endif
|
||||
if (tSpeed <= 1) brake = 255;
|
||||
else if (tSpeed >= 127) brake = 0;
|
||||
else brake = 2 * (128-tSpeed);
|
||||
if (invertBrake)
|
||||
brake=255-brake;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
DCCEXanalogWrite(brakePin,brake);
|
||||
#else
|
||||
analogWrite(brakePin,brake);
|
||||
#endif
|
||||
//DIAG(F("DCSignal %d"), speedcode);
|
||||
if (HAVE_PORTA(fastSignalPin.shadowinout == &PORTA)) {
|
||||
noInterrupts();
|
||||
HAVE_PORTA(shadowPORTA=PORTA);
|
||||
setSignal(tDir);
|
||||
HAVE_PORTA(PORTA=shadowPORTA);
|
||||
interrupts();
|
||||
} else if (HAVE_PORTB(fastSignalPin.shadowinout == &PORTB)) {
|
||||
noInterrupts();
|
||||
HAVE_PORTB(shadowPORTB=PORTB);
|
||||
setSignal(tDir);
|
||||
HAVE_PORTB(PORTB=shadowPORTB);
|
||||
interrupts();
|
||||
} else if (HAVE_PORTC(fastSignalPin.shadowinout == &PORTC)) {
|
||||
noInterrupts();
|
||||
HAVE_PORTC(shadowPORTC=PORTC);
|
||||
setSignal(tDir);
|
||||
HAVE_PORTC(PORTC=shadowPORTC);
|
||||
interrupts();
|
||||
} else {
|
||||
noInterrupts();
|
||||
setSignal(tDir);
|
||||
interrupts();
|
||||
}
|
||||
// IMPORTANT: This function can be called in Interrupt() time within the 56uS timer
|
||||
// The default analogRead takes ~100uS which is catastrphic
|
||||
// so DCCTimer has set the sample time to be much faster.
|
||||
}
|
||||
|
||||
unsigned int MotorDriver::raw2mA( int raw) {
|
||||
//DIAG(F("%d = %d * %d / %d"), (int32_t)raw * senseFactorInternal / senseScale, raw, senseFactorInternal, senseScale);
|
||||
return (int32_t)raw * senseFactorInternal / senseScale;
|
||||
return (unsigned int)(raw * senseFactor);
|
||||
}
|
||||
unsigned int MotorDriver::mA2raw( unsigned int mA) {
|
||||
//DIAG(F("%d = %d * %d / %d"), (int32_t)mA * senseScale / senseFactorInternal, mA, senseScale, senseFactorInternal);
|
||||
return (int32_t)mA * senseScale / senseFactorInternal;
|
||||
int MotorDriver::mA2raw( unsigned int mA) {
|
||||
return (int)(mA / senseFactor);
|
||||
}
|
||||
|
||||
void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & result) {
|
||||
// DIAG(F("MotorDriver %S Pin=%d,"),type,pin);
|
||||
(void) type; // avoid compiler warning if diag not used above.
|
||||
#if defined(ARDUINO_ARCH_SAMD)
|
||||
PortGroup *port = digitalPinToPort(pin);
|
||||
#elif defined(ARDUINO_ARCH_STM32)
|
||||
GPIO_TypeDef *port = digitalPinToPort(pin);
|
||||
#else
|
||||
(void) type; // avoid compiler warning if diag not used above.
|
||||
uint8_t port = digitalPinToPort(pin);
|
||||
#endif
|
||||
if (input)
|
||||
result.inout = portInputRegister(port);
|
||||
else
|
||||
@@ -332,65 +190,3 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res
|
||||
result.maskLOW = ~result.maskHIGH;
|
||||
// DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH);
|
||||
}
|
||||
|
||||
void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
|
||||
if (millis() - lastSampleTaken < sampleDelay) return;
|
||||
lastSampleTaken = millis();
|
||||
int tripValue= useProgLimit?progTripValue:getRawCurrentTripValue();
|
||||
|
||||
// Trackname for diag messages later
|
||||
switch (powerMode) {
|
||||
case POWERMODE::OFF:
|
||||
sampleDelay = POWER_SAMPLE_OFF_WAIT;
|
||||
break;
|
||||
case POWERMODE::ON:
|
||||
// Check current
|
||||
lastCurrent=getCurrentRaw();
|
||||
if (lastCurrent < 0) {
|
||||
// We have a fault pin condition to take care of
|
||||
lastCurrent = -lastCurrent;
|
||||
setPower(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again
|
||||
if (commonFaultPin) {
|
||||
if (lastCurrent < tripValue) {
|
||||
setPower(POWERMODE::ON); // maybe other track
|
||||
}
|
||||
// Write this after the fact as we want to turn on as fast as possible
|
||||
// because we don't know which output actually triggered the fault pin
|
||||
DIAG(F("COMMON FAULT PIN ACTIVE: POWERTOGGLE TRACK %c"), trackno + 'A');
|
||||
} else {
|
||||
DIAG(F("TRACK %c FAULT PIN ACTIVE - OVERLOAD"), trackno + 'A');
|
||||
if (lastCurrent < tripValue) {
|
||||
lastCurrent = tripValue; // exaggerate
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastCurrent < tripValue) {
|
||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||
if(power_good_counter<100)
|
||||
power_good_counter++;
|
||||
else
|
||||
if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT;
|
||||
} else {
|
||||
setPower(POWERMODE::OVERLOAD);
|
||||
unsigned int mA=raw2mA(lastCurrent);
|
||||
unsigned int maxmA=raw2mA(tripValue);
|
||||
power_good_counter=0;
|
||||
sampleDelay = power_sample_overload_wait;
|
||||
DIAG(F("TRACK %c POWER OVERLOAD %dmA (limit %dmA) shutdown for %dms"), trackno + 'A', mA, maxmA, sampleDelay);
|
||||
if (power_sample_overload_wait >= 10000)
|
||||
power_sample_overload_wait = 10000;
|
||||
else
|
||||
power_sample_overload_wait *= 2;
|
||||
}
|
||||
break;
|
||||
case POWERMODE::OVERLOAD:
|
||||
// Try setting it back on after the OVERLOAD_WAIT
|
||||
setPower(POWERMODE::ON);
|
||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||
// Debug code....
|
||||
DIAG(F("TRACK %c POWER RESTORE (check %dms)"), trackno + 'A', sampleDelay);
|
||||
break;
|
||||
default:
|
||||
sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning.
|
||||
}
|
||||
}
|
||||
|
212
MotorDriver.h
212
MotorDriver.h
@@ -1,12 +1,10 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020 Chris Harlow
|
||||
* © 2022 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
* This file is part of Asbelos DCC API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -24,52 +22,6 @@
|
||||
#ifndef MotorDriver_h
|
||||
#define MotorDriver_h
|
||||
#include "FSH.h"
|
||||
#include "IODevice.h"
|
||||
#include "DCCTimer.h"
|
||||
|
||||
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
|
||||
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
|
||||
#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH)
|
||||
#define isLOW(fastpin) (!isHIGH(fastpin))
|
||||
|
||||
#define TOKENPASTE(x, y) x ## y
|
||||
#define TOKENPASTE2(x, y) TOKENPASTE(x, y)
|
||||
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
#define HAVE_PORTA(X) X
|
||||
#define HAVE_PORTB(X) X
|
||||
#define HAVE_PORTC(X) X
|
||||
#endif
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
#define HAVE_PORTB(X) X
|
||||
#endif
|
||||
#if defined(ARDUINO_ARCH_SAMD)
|
||||
#define PORTA REG_PORT_OUT0
|
||||
#define HAVE_PORTA(X) X
|
||||
#define PORTB REG_PORT_OUT1
|
||||
#define HAVE_PORTB(X) X
|
||||
#endif
|
||||
#if defined(ARDUINO_ARCH_STM32)
|
||||
#define PORTA GPIOA->ODR
|
||||
#define HAVE_PORTA(X) X
|
||||
#define PORTB GPIOB->ODR
|
||||
#define HAVE_PORTB(X) X
|
||||
#define PORTC GPIOC->ODR
|
||||
#define HAVE_PORTC(X) X
|
||||
#endif
|
||||
|
||||
// if macros not defined as pass-through we define
|
||||
// them here as someting that is valid as a
|
||||
// statement and evaluates to false.
|
||||
#ifndef HAVE_PORTA
|
||||
#define HAVE_PORTA(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
||||
#endif
|
||||
#ifndef HAVE_PORTB
|
||||
#define HAVE_PORTB(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
||||
#endif
|
||||
#ifndef HAVE_PORTC
|
||||
#define HAVE_PORTC(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
||||
#endif
|
||||
|
||||
// Virtualised Motor shield 1-track hardware Interface
|
||||
|
||||
@@ -77,161 +29,63 @@
|
||||
#define UNUSED_PIN 127 // inside int8_t
|
||||
#endif
|
||||
|
||||
class pinpair {
|
||||
public:
|
||||
pinpair(byte p1, byte p2) {
|
||||
pin = p1;
|
||||
invpin = p2;
|
||||
};
|
||||
byte pin = UNUSED_PIN;
|
||||
byte invpin = UNUSED_PIN;
|
||||
};
|
||||
|
||||
#if defined(__IMXRT1062__) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32)
|
||||
typedef uint32_t portreg_t;
|
||||
#else
|
||||
typedef uint8_t portreg_t;
|
||||
#endif
|
||||
|
||||
#if defined(__IMXRT1062__)
|
||||
struct FASTPIN {
|
||||
volatile portreg_t *inout;
|
||||
portreg_t maskHIGH;
|
||||
portreg_t maskLOW;
|
||||
volatile portreg_t *shadowinout;
|
||||
volatile uint32_t *inout;
|
||||
uint32_t maskHIGH;
|
||||
uint32_t maskLOW;
|
||||
};
|
||||
// The port registers that are shadowing
|
||||
// the real port registers. These are
|
||||
// defined in Motordriver.cpp
|
||||
extern volatile portreg_t shadowPORTA;
|
||||
extern volatile portreg_t shadowPORTB;
|
||||
extern volatile portreg_t shadowPORTC;
|
||||
|
||||
enum class POWERMODE : byte { OFF, ON, OVERLOAD };
|
||||
#else
|
||||
struct FASTPIN {
|
||||
volatile uint8_t *inout;
|
||||
uint8_t maskHIGH;
|
||||
uint8_t maskLOW;
|
||||
};
|
||||
#endif
|
||||
|
||||
class MotorDriver {
|
||||
public:
|
||||
|
||||
MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||
MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||
byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
|
||||
void setPower( POWERMODE mode);
|
||||
POWERMODE getPower() { return powerMode;}
|
||||
// as the port registers can be shadowed to get syncronized DCC signals
|
||||
// we need to take care of that and we have to turn off interrupts if
|
||||
// we setSignal() or setBrake() or setPower() during that time as
|
||||
// otherwise the call from interrupt context can undo whatever we do
|
||||
// from outside interrupt
|
||||
void setBrake( bool on, bool interruptContext=false);
|
||||
__attribute__((always_inline)) inline void setSignal( bool high) {
|
||||
if (trackPWM) {
|
||||
DCCTimer::setPWM(signalPin,high);
|
||||
}
|
||||
else {
|
||||
if (high) {
|
||||
setHIGH(fastSignalPin);
|
||||
if (dualSignal) setLOW(fastSignalPin2);
|
||||
}
|
||||
else {
|
||||
setLOW(fastSignalPin);
|
||||
if (dualSignal) setHIGH(fastSignalPin2);
|
||||
}
|
||||
}
|
||||
};
|
||||
inline void enableSignal(bool on) {
|
||||
if (on)
|
||||
pinMode(signalPin, OUTPUT);
|
||||
else
|
||||
pinMode(signalPin, INPUT);
|
||||
};
|
||||
inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); };
|
||||
void setDCSignal(byte speedByte);
|
||||
inline void detachDCSignal() {
|
||||
#if defined(__arm__)
|
||||
pinMode(brakePin, OUTPUT);
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
ledcDetachPin(brakePin);
|
||||
#else
|
||||
setDCSignal(128);
|
||||
#endif
|
||||
};
|
||||
int getCurrentRaw(bool fromISR=false);
|
||||
unsigned int raw2mA( int raw);
|
||||
unsigned int mA2raw( unsigned int mA);
|
||||
inline bool brakeCanPWM() {
|
||||
#if defined(ARDUINO_ARCH_ESP32) || defined(__arm__)
|
||||
// TODO: on ARM we can use digitalPinHasPWM, and may wish/need to
|
||||
return true;
|
||||
#else
|
||||
#ifdef digitalPinToTimer
|
||||
return ((brakePin!=UNUSED_PIN) && (digitalPinToTimer(brakePin)));
|
||||
#else
|
||||
return (brakePin<14 && brakePin >1);
|
||||
#endif //digitalPinToTimer
|
||||
#endif //ESP32/ARM
|
||||
}
|
||||
virtual void setPower( bool on);
|
||||
virtual void setSignal( bool high);
|
||||
virtual void setBrake( bool on);
|
||||
virtual int getCurrentRaw();
|
||||
virtual unsigned int raw2mA( int raw);
|
||||
virtual int mA2raw( unsigned int mA);
|
||||
inline int getRawCurrentTripValue() {
|
||||
return rawCurrentTripValue;
|
||||
}
|
||||
bool isPWMCapable();
|
||||
bool canMeasureCurrent();
|
||||
bool trackPWM = false; // this track uses PWM timer to generate the DCC waveform
|
||||
static bool usePWM;
|
||||
static bool commonFaultPin; // This is a stupid motor shield which has only a common fault pin for both outputs
|
||||
inline byte getFaultPin() {
|
||||
return faultPin;
|
||||
}
|
||||
inline void makeProgTrack(bool on) { // let this output know it's a prog track.
|
||||
isProgTrack = on;
|
||||
}
|
||||
void checkPowerOverload(bool useProgLimit, byte trackno);
|
||||
#ifdef ANALOG_READ_INTERRUPT
|
||||
bool sampleCurrentFromHW();
|
||||
void startCurrentFromHW();
|
||||
#endif
|
||||
private:
|
||||
bool isProgTrack = false; // tells us if this is a prog track
|
||||
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
|
||||
void getFastPin(const FSH* type,int pin, FASTPIN & result) {
|
||||
getFastPin(type, pin, 0, result);
|
||||
}
|
||||
VPIN powerPin;
|
||||
byte signalPin, signalPin2, currentPin, faultPin, brakePin;
|
||||
FASTPIN fastSignalPin, fastSignalPin2, fastBrakePin,fastFaultPin;
|
||||
byte powerPin, signalPin, signalPin2, currentPin, faultPin, brakePin;
|
||||
FASTPIN fastPowerPin,fastSignalPin, fastSignalPin2, fastBrakePin,fastFaultPin;
|
||||
bool dualSignal; // true to use signalPin2
|
||||
bool invertBrake; // brake pin passed as negative means pin is inverted
|
||||
bool invertPower; // power pin passed as negative means pin is inverted
|
||||
|
||||
// Raw to milliamp conversion factors avoiding float data types.
|
||||
// Milliamps=rawADCreading * sensefactorInternal / senseScale
|
||||
//
|
||||
// senseScale is chosen as 256 to give enough scale for 2 decimal place
|
||||
// raw->mA conversion with an ultra fast optimised integer multiplication
|
||||
int senseFactorInternal; // set to senseFactor * senseScale
|
||||
static const int senseScale=256;
|
||||
float senseFactor;
|
||||
int senseOffset;
|
||||
unsigned int tripMilliamps;
|
||||
int rawCurrentTripValue;
|
||||
// current sampling
|
||||
POWERMODE powerMode;
|
||||
unsigned long lastSampleTaken;
|
||||
unsigned int sampleDelay;
|
||||
int progTripValue;
|
||||
int lastCurrent;
|
||||
#ifdef ANALOG_READ_INTERRUPT
|
||||
volatile unsigned long sampleCurrentTimestamp;
|
||||
volatile uint16_t sampleCurrent;
|
||||
#if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41)
|
||||
static bool disableInterrupts() {
|
||||
uint32_t primask;
|
||||
__asm__ volatile("mrs %0, primask\n" : "=r" (primask)::);
|
||||
__disable_irq();
|
||||
return (primask == 0) ? true : false;
|
||||
}
|
||||
static void enableInterrupts(bool doit) {
|
||||
if (doit) __enable_irq();
|
||||
}
|
||||
#endif
|
||||
int maxmA;
|
||||
int tripmA;
|
||||
|
||||
// Wait times for power management. Unit: milliseconds
|
||||
static const int POWER_SAMPLE_ON_WAIT = 100;
|
||||
static const int POWER_SAMPLE_OFF_WAIT = 1000;
|
||||
static const int POWER_SAMPLE_OVERLOAD_WAIT = 20;
|
||||
|
||||
// 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;
|
||||
|
||||
};
|
||||
#endif
|
||||
|
102
MotorDrivers.h
102
MotorDrivers.h
@@ -1,5 +1,4 @@
|
||||
/*
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2022 Harald Barth
|
||||
* (c) 2020 Chris Harlow. All rights reserved.
|
||||
@@ -39,56 +38,17 @@
|
||||
#define UNUSED_PIN 127 // inside int8_t
|
||||
#endif
|
||||
|
||||
// The MotorDriver definition is:
|
||||
//
|
||||
// MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin,
|
||||
// float senseFactor, unsigned int tripMilliamps, byte faultPin);
|
||||
//
|
||||
// power_pin: Turns the board on/off. Often called ENABLE or PWM on the shield
|
||||
// signal_pin: Where the DCC signal goes in. Often called DIR on the shield
|
||||
// signal_pin2: Inverse of signal_pin. A few shields need this as well, can be replace by hardware inverter
|
||||
// brake_pin: When tuned on, brake is set - output shortened (*)
|
||||
// current_pin: Current sense voltage pin from shield to ADC
|
||||
// senseFactor: Relation between volts on current_pin and actual output current
|
||||
// tripMilliamps: Short circuit trip limit in milliampere, max 32767 (32.767A)
|
||||
// faultPin: Some shields have a pin to to report a fault condition to the uCPU. High when fault occurs
|
||||
//
|
||||
// (*) If the brake_pin is negative that means the sense
|
||||
// If the brakePin is negative that means the sense
|
||||
// of the brake pin on the motor bridge is inverted
|
||||
// (HIGH == release brake)
|
||||
|
||||
// Arduino STANDARD Motor Shield, used on different architectures:
|
||||
|
||||
#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32)
|
||||
// Setup for SAMD21 Sparkfun DEV board using Arduino standard Motor Shield R3 (MUST be R3
|
||||
// for 3v3 compatibility!!) senseFactor for 3.3v systems is 1.95 as calculated when using
|
||||
// 10-bit A/D samples, and for 12-bit samples it's more like 0.488, but we probably need
|
||||
// to tweak both these
|
||||
//
|
||||
// Arduino standard Motor Shield
|
||||
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
|
||||
new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 0.488, 1500, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 0.488, 1500, UNUSED_PIN)
|
||||
#define SAMD_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD
|
||||
#define STM32_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD
|
||||
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
// STANDARD shield on an ESPDUINO-32 (ESP32 in Uno form factor). The shield must be eiter the
|
||||
// 3.3V compatible R3 version or it has to be modified to not supply more than 3.3V to the
|
||||
// analog inputs. Here we use analog inputs A4 and A5 as A0 and A1 are wired in a way so that
|
||||
// they are not useable at the same time as WiFi (what a bummer). The numbers below are the
|
||||
// actual GPIO numbers. In comments the numbers the pins have on an Uno.
|
||||
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
|
||||
new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 36/*A4*/, 0.70, 1500, UNUSED_PIN), \
|
||||
new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 39/*A5*/, 0.70, 1500, UNUSED_PIN)
|
||||
|
||||
#else
|
||||
// STANDARD shield on any Arduino Uno or Mega compatible with the original specification.
|
||||
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
|
||||
new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 2.99, 1500, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 1500, UNUSED_PIN)
|
||||
#define BRAKE_PWM_SWAPPED_MOTOR_SHIELD F("BPS_MOTOR_SHIELD"), \
|
||||
new MotorDriver(-9 , 12, UNUSED_PIN, -3, A0, 2.99, 1500, UNUSED_PIN), \
|
||||
new MotorDriver(-8 , 13, UNUSED_PIN,-11, A1, 2.99, 1500, UNUSED_PIN)
|
||||
#endif
|
||||
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, 2000, UNUSED_PIN)
|
||||
|
||||
// Pololu Motor Shield
|
||||
#define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \
|
||||
@@ -105,17 +65,6 @@
|
||||
// new MotorDriver(2, 8, UNUSED_PIN, -10, A1, 18, 3000, 12)
|
||||
// See Pololu dial_mc33926_shield_schematic.pdf and truth table on page 17 of the MC33926 data sheet.
|
||||
|
||||
// Pololu Dual TB9051FTG Motor Shield
|
||||
// This is the shield without modifications. Unfortunately the TB9051FTG driver chip on
|
||||
// the shield makes short delays when direction is switched. That means that the chip
|
||||
// can NOT provide a standard conformant DCC signal independent how hard we try. If your
|
||||
// Decoders tolerate that signal, use it by all mean but it is not recommended. Without
|
||||
// modifications it uses the following pins below which means no HA waveform and no
|
||||
// RailCom on an Arduino Mega 2560 but the DCC signal is broken anyway.
|
||||
#define POLOLU_TB9051FTG F("POLOLU_TB9051FTG"), \
|
||||
new MotorDriver(2, 7, UNUSED_PIN, -9, A0, 10, 2500, 6), \
|
||||
new MotorDriver(4, 8, UNUSED_PIN, -10, A1, 10, 2500, 12)
|
||||
|
||||
// Firebox Mk1
|
||||
#define FIREBOX_MK1 F("FIREBOX_MK1"), \
|
||||
new MotorDriver(3, 6, 7, UNUSED_PIN, A5, 9.766, 5500, UNUSED_PIN), \
|
||||
@@ -128,17 +77,17 @@
|
||||
|
||||
// FunduMoto Motor Shield
|
||||
#define FUNDUMOTO_SHIELD F("FUNDUMOTO_SHIELD"), \
|
||||
new MotorDriver(10, 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 1500, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 1500, UNUSED_PIN)
|
||||
new MotorDriver(10, 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||
|
||||
// IBT_2 Motor Board for Main and Arduino Motor Shield for Prog
|
||||
#define IBT_2_WITH_ARDUINO F("IBT_2_WITH_ARDUINO_SHIELD"), \
|
||||
new MotorDriver(4, 5, 6, UNUSED_PIN, A5, 41.54, 5000, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 1500, UNUSED_PIN)
|
||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||
// YFROBOT Motor Shield (V3.1)
|
||||
#define YFROBOT_MOTOR_SHIELD F("YFROBOT_MOTOR_SHIELD"), \
|
||||
new MotorDriver(5, 4, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 1500, UNUSED_PIN), \
|
||||
new MotorDriver(6, 7, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 1500, UNUSED_PIN)
|
||||
new MotorDriver(5, 4, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \
|
||||
new MotorDriver(6, 7, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||
|
||||
// Makeblock ORION UNO like sized board with integrated motor driver
|
||||
// This is like an Uno with H-bridge and RJ12 contacts instead of pin rows.
|
||||
@@ -155,34 +104,7 @@
|
||||
// to an NANO EVERY board. You have to make the connectons from the shield to the board
|
||||
// as in this example or adjust the values yourself.
|
||||
#define NANOEVERY_EXAMPLE F("NANOEVERY_EXAMPLE"), \
|
||||
new MotorDriver(5, 6, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 1500, UNUSED_PIN),\
|
||||
new MotorDriver(9, 10, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 1500, UNUSED_PIN)
|
||||
new MotorDriver(5, 6, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN),\
|
||||
new MotorDriver(9, 10, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||
|
||||
// This is an example how to stack two standard motor shields. The upper shield
|
||||
// needs pins 3 8 9 11 12 13 A0 A1 disconnected from the lower shield and
|
||||
// jumpered instead like this: 2-3 6-8 7-9 4-13 5-11 10-12 A0-A4 A1-A5
|
||||
// Pin assigment table:
|
||||
// 2 Enable C jumpered
|
||||
// 3 Enable A direct
|
||||
// 4 Dir D jumpered
|
||||
// 5 Enable D jumpered
|
||||
// 6 Brake D jumpered
|
||||
// 7 Brake C jumpered
|
||||
// 8 Brake B direct
|
||||
// 9 Brake A direct
|
||||
// 10 Dir C jumpered
|
||||
// 11 Enable B direct
|
||||
// 12 Dir A direct
|
||||
// 13 Dir B direct
|
||||
// A0 Sense A direct
|
||||
// A1 Sense B direct
|
||||
// A4 Sense C jumpered
|
||||
// A5 Sense D jumpered
|
||||
//
|
||||
#define STACKED_MOTOR_SHIELD F("STACKED_MOTOR_SHIELD"),\
|
||||
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 2.99, 1500, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 1500, UNUSED_PIN), \
|
||||
new MotorDriver( 2, 10, UNUSED_PIN, 7, A4, 2.99, 1500, UNUSED_PIN), \
|
||||
new MotorDriver( 5, 4, UNUSED_PIN, 6, A5, 2.99, 1500, UNUSED_PIN)
|
||||
//
|
||||
#endif
|
||||
|
@@ -1,75 +0,0 @@
|
||||
Throttle Assist updates for versiuon 4.?
|
||||
|
||||
Chris Harlow April 2022
|
||||
|
||||
There are a number of additional throttle information commands that have been implemented to assist throttle authors to obtain information from the Command Station in order to implement turnout, route/automation and roster features which are already found in the Withrottle implementations.
|
||||
These commands are new and not overlapped with the existing commands which are probabaly due to be obsoleted as they are over complex and unfit for purpose.
|
||||
|
||||
Turnouts:
|
||||
|
||||
The conventional turnout definition commands and the ```<H>``` responses do not contain information about the turnout description which may have been provided in an EXRAIL script. A turnout description is much more user friendly than T123 and having a list helps the throttle UI build a suitable set of buttons.
|
||||
|
||||
```<JT>``` command returns a list of turnout ids. The throttle should be uninterested in the turnout technology used but needs to know the ids it can throw/close and monitor the current state.
|
||||
e.g. response ```<jT 1 17 22 19>```
|
||||
|
||||
```<JT 17>`` requests info on turnout 17.
|
||||
e.g. response ```<jT 17 T "Coal yard exit">``` or ```<jT 17 C "Coal yard exit">```
|
||||
(T=thrown, C=closed)
|
||||
or ```<jT 17 C "">``` indicating turnout description not given.
|
||||
or ```<jT 17 X>``` indicating turnout unknown (or possibly hidden.)
|
||||
|
||||
Note: It is still the throttles responsibility to monitor the status broadcasts.
|
||||
(TBD I'm thinking that the existing broadcast is messy and needs cleaning up)
|
||||
However, I'm not keen on dynamically created/deleted turnouts so I have no intention of providing a command that indicates the turnout list has been updated since the throttle started.
|
||||
Also note that turnouts marked in EXRAIL with the HIDDEN keyword instead of a "description" will NOT show up in these commands.
|
||||
|
||||
|
||||
Automations/Routes
|
||||
|
||||
A throttle need to know which EXRAIL Automations and Routes it can show the user.
|
||||
|
||||
```<JA>``` Returns a list of Automations/Routes
|
||||
e.g. ```<jA 13 16 23>```
|
||||
Indicates route/automation ids.
|
||||
Information on each route needs to be obtained by
|
||||
```<JA 13>```
|
||||
returns e.g. ```<jA 13 R "description">``` for a route
|
||||
or ```<jA 13 A "description">``` for an automation.
|
||||
or ```<jA 13 X>``` for id not found
|
||||
|
||||
Whats the difference:
|
||||
A Route is just a call to an EXRAIL ROUTE, traditionally to set some turnouts or signals but can be used to perform any kind of EXRAIL function... but its not expecting to know the loco.
|
||||
Thus a route can be triggered by sending in for example ```</START 13>```.
|
||||
|
||||
An Automation is a handoff of the last accessed loco id to an EXRAIL AUTOMATION which would typically drive the loco away.
|
||||
Thus an Automation expects a start command with a cab id
|
||||
e.g. ```</START 13 3>```
|
||||
|
||||
|
||||
Roster Information:
|
||||
The ```<JR>``` command requests a list of cab ids from the roster.
|
||||
e.g. responding ```<jR 3 200 6336>```
|
||||
or <jR> for none.
|
||||
|
||||
Each Roster entry had a name and function map obtained by:
|
||||
```<JR 200>``` reply like ```<jR 200 "Thomas" "whistle/*bell/squeal/panic">
|
||||
|
||||
Refer to EXRAIL ROSTER command for function map format.
|
||||
|
||||
|
||||
Obtaining throttle status.
|
||||
```<t cabid>``` Requests a deliberate update on the cab speed/functions in the same format as the cab broadcast.
|
||||
```<l cabid slot speedbyte functionMap>```
|
||||
Note that a slot of -1 indicates that the cab is not in the reminders table and this comand will not reserve a slot until such time as the cab is throttled.
|
||||
|
||||
|
||||
COMMANDS TO AVOID
|
||||
|
||||
```<f cab func1 func2>``` Use ```<F cab function 1/0>```
|
||||
```<t slot cab speed dir>``` Just drop the slot number
|
||||
```<T commands>``` other than ```<T id 0/1>```
|
||||
```<s>```
|
||||
```<c>```
|
||||
|
||||
|
||||
|
@@ -1,139 +0,0 @@
|
||||
# DCC++EX Track Manager
|
||||
|
||||
Chris Harlow 2022/03/23
|
||||
|
||||
**If you are only interested in a standard setup using just a DCC track and PROG track, then you DO NOT need to read the rest of this document.**
|
||||
|
||||
What follows is for advanced users interested in managing power districts and/or running DC locomotives through DCC++EX.
|
||||
|
||||
## What is the Track Manager
|
||||
Track Manger (TM from now on) is an integral part of DCC++EX software that is responsible for:
|
||||
- Managing track power state.
|
||||
- Monitoring track overloads and shorts.
|
||||
- Routing the DCC main or prog track waveforms to the correct Motor Driver and thus track.
|
||||
- Managing the JOIN feature.
|
||||
- Intercepting throttle commands to locos running on DC tracks.
|
||||
- Handling user or EXRAIL commands to switch track status.
|
||||
|
||||
In the default scenario of a single DCC track and a PROG track, the TM behaves as for the previous versions of DCC++EX so if thats what you want, you dont need to mess with it.
|
||||
|
||||
The TM is able to handle up to 8 separate track domains. Each domain requires a hardware driver to supply track voltage. A typical motor driver shield supplies two tracks, which is what we have used in the past as main and prog.
|
||||
|
||||
Unlike the previous version of DCC++EX, where the shield channel A was always the DCC main and channel B was always the DCC prog track, TM allows :
|
||||
- None, any or all the tracks can be DCC Main.
|
||||
- None or ONE track may be DCC prog at any given time.
|
||||
- Any track may be powered on or off independently of the others.
|
||||
- Any track may be disconnected from the DCC signal and used as a DC track with a given loco address. (See DC discussion later)
|
||||
|
||||
With such flexibility comes responsibility... the potential for making mistakes means taking extra care with your configuration!
|
||||
|
||||
**NOTE** TM does NOT use "zero stretching" to control your DC motor. Instead, it uses true Pulse Width Modulation (PWM) to efficiently run your loco using the same method a decoder uses to control a DCC loco's motor. DC locos can even run better on TM than they can on a normal analog throttle, especially at low speed, since it is always applying the full track voltage, albeit in pulses of varying duration.
|
||||
|
||||
## Using the Track Manager (DCC)
|
||||
TM names the tracks A to H. In a default setup, you will normally have tracks A and B where A will default to be the DCC main signal and B will be the DCC prog.
|
||||
|
||||
There is a new user command `<=>` which is used to control the TM but the `<0>` and `<1>` commands operate as before.
|
||||
|
||||
- `<=>` lists the current track settings.
|
||||
In a default setup this will normally return
|
||||
```
|
||||
<=A DCC>
|
||||
<=B PROG>
|
||||
```
|
||||
- `<=t DCC>` sets track t (A..H) to use the DCC main track. For example `<=C DCC>` sets track C. All tracks that are set to DCC will receive the same DCC signal waveform.
|
||||
- `<=t PROG>` Sets track t (A..H) to be the one and only PROG track. Any previous PROG track is turned off.
|
||||
- `<=t OFF>` turns off the track t. It will not power on with `<1>` because it will not know what signal to send.
|
||||
|
||||
In an all-DCC environment it is unlikely that you will need to do anything other than setting any additional tracks (C...H) as DCC in your `mySetup.h` file.
|
||||
|
||||
Bear in mind that a track may actually be only connected to DCC accessories such as signals and turnouts... your layout, your choice.
|
||||
|
||||
Note that when setting a track to PROG or OFF, its power is switched off automatically. (The PROG track manages power on an as-needed basis under normal circumstances.
|
||||
When setting a track to MAIN (or DC, DCX see later) the power is applied according to the most recent `<1>` or `<0>` command as being the most compatible with previous versions.
|
||||
|
||||
## using the Track Manager (DC)
|
||||
|
||||
TM allows any or all of your tracks to be individually selected as a DC track which responds to throttle commands on any given loco address. So for example if track A is set to DC address 55, then any throttle commands to loco 55 will be transmitted as DC onto track A and thus a DC loco can be driven along that track. almost exactly as if it was DCC.
|
||||
Your throttle (JMRI, EX-Webthrottle, Withrottle, Engine Driver etc etc) do not know or care that this is a DC loco so nothing needs to change.
|
||||
|
||||
For a simple Command Station setup to run just two DC tracks instead of DCC, you only need to assign DC addresses to tracks A and B. If you want DCC on track A and DC on track B, you just need to set track B to a suitable DC address.
|
||||
|
||||
The command to set a track to a DC address is as follows
|
||||
- `<=t DC a>` Sets track t (A..H) to use loco address a. e.g. <=A DC 3>
|
||||
|
||||
A simple 2 separate loop DC track, wired the traditional way in opposite directions, may be set like this to use loco addresses 1 and 2.
|
||||
```
|
||||
<=A DC 1>
|
||||
<=A DC 2>
|
||||
```
|
||||
|
||||
### Crossing between DC tracks
|
||||
|
||||
There are some slightly mind-bending issues to be addressed, especially if you want to be able to cross between two separate DC tracks or use your layout in DCC or DC mode. This is because the control of DC loco direction is relative to the TRACK and not the LOCO. (you turn a DC loco round on the track and it continues in the same geographical direction. You turn a DCC loco around and it continues to go forwards or backwards in the opposite geographical direction.)
|
||||
|
||||
Generally DC tracks are wired so that two mainline tracks are in opposite direction which makes operation easy BUT crossovers between tracks will cause shorts unless you have very complex switching arrangements.
|
||||
This is generally incompatible with DCC wiring which expects to be able to cross between tracks with impunity because they are all wired with the same polarity.
|
||||
|
||||
To get over this issue TM allows the polarity of a DC track to be swapped so that tracks wired for DCC may be switched to DC with a polarity chosen at run time according to your operations. So, for example, you may have two loops with a crossing between them. Normally you need them in opposite directions, but when you need to drive over the crossing, you need to switch one or other track so that they are at the same polarity.
|
||||
(This is a good case for using EXRAIL to help)
|
||||
|
||||
The command `<=t DCX a>` will set track t (A..H) to be DC but with reversed polarity compared with a track set to DC.
|
||||
|
||||
Its perfectly OK to cross between DC tracks by setting them to the same loco address and making sure you get the polarity right!
|
||||
|
||||
## Connecting Hardware
|
||||
Each track requires hardware to control it
|
||||
- Power on/off
|
||||
- Polarity (direction, signal etc)
|
||||
- Brake (shorts tracks together)
|
||||
- Current (analog reading)
|
||||
|
||||
The standard motor shields provide this for two separate tracks and are predictable and easy to use. However STACKING shields is not a viable way of adding more tracks because it prevents the software from gaining access to the individual track pins. Similarly, wiring all the signal pins together for example, will give you a shared DCC signal but it will eliminate any possibility of switching the track purpose at run time. So, you are going to have to understand enough to wire track drivers to various pins if you wish to extend beyond 2 tracks and take advantage of TM.
|
||||
|
||||
You will also need to consider the implications of differing electronic implementations that would cause unexpected issues when a loco moves between tracks. We know this works fine for a typical shield because we use `<1 JOIN>` quite happily but this may be different if you mix hardware types..... (NOT MY PROBLEM !)
|
||||
|
||||
The easiest way to consider the wiring is to treat each track individually (either as a separate driver or as half of a shield).
|
||||
|
||||
You will require,for each track, on the Arduino:
|
||||
- A GPIO pin (or a HAL vpin perhaps on an I2C extender, code TBA!!!) to switch power.
|
||||
- A GPIO pin to switch the signal direction
|
||||
- A GPIO pin with PWM capability to switch the Brake (you may omit this if you dont want any DC capability)
|
||||
- Optionally An Analog pin to read the current (unless your hardware cant do that, perhaps its just feeding a booster)
|
||||
- Optionally a GPIO fault pin if thats how your hardware works. (NOT recommended as you're going to run out of pins)
|
||||
|
||||
IF you have no more than 3 tracks and you can arrange for the signal pins to be one of 11,12,13 on a Mega, THEN there is a slight advantage internally and the waveform will be super-sharp.
|
||||
|
||||
**Hardware that has two signal pins still needs some code thought!!!!!!!!**
|
||||
|
||||
|
||||
## Configuring the Software
|
||||
|
||||
Configuring the software to provide more tracks is a simple extension of the existing method of customising the #define of MOTOR_SHIELD_TYPE in config.h
|
||||
Since there can be no standard setup of your wiring and hardware choices, it will be necessary to create your custom built MOTOR_SHIELD_TYPE in the manner described in MotorDrivers.h and simply continue to add more `new MotorDriver(` definitions to the list, providing all the pin numbers and electronic limits for each track. (or even shorten the list to 1)
|
||||
|
||||
## Using EXRAIL to control Track Manager
|
||||
EXRAIL has a single additional command that can be used to automate TM.
|
||||
|
||||
- `SET_TRACK(t,mode)`
|
||||
where t is the track letter A..H and mode is one of
|
||||
- `OFF` track is switched off
|
||||
- `DCC` track gets DCC signal
|
||||
- `PROG` track gets DCC prog signal
|
||||
- `DC` track is set to DC mode with the cab address of the currently executing EXRAIL sequence.
|
||||
- `DCX` as DC but with reversed polarity.
|
||||
|
||||
DC/DCX are designed so that you can be automating a DCC loco, drive it onto a separate track and switch to DC without having to know the cab address. (e.g AUTOMATION)
|
||||
If however you are just running a ROUTE you can always do something like this:
|
||||
```
|
||||
ROUTE(77,"Set track G to DC 123")
|
||||
SETLOCO(123)
|
||||
SET_TRACK(G,DC)
|
||||
DONE
|
||||
```
|
||||
|
||||
## Where and How for the Code.
|
||||
The TM code is primarily in TrackManager.cpp which is responsible for coordinating the track settings and commands.
|
||||
|
||||
Each individual track is handled by an instance of MotorDriver created from the MOTOPR_SHIELD_TYPE definition in config.h
|
||||
|
||||
Many functions formerly in the DCCWaveform code have been moved to TrackManager or MotorDriver, notably the power control and checking. This makes the code easier to follow.
|
@@ -1,39 +0,0 @@
|
||||
Using Lew's Duino Gear boards:
|
||||
|
||||
1. DNIN8 Input
|
||||
This is a shift-register implementation of a digital input collector.
|
||||
Multiple DNIN8 may be connected in sequence but it is IMPORTANT that the software
|
||||
configuratuion correctly represents the number of boards connected otherwise the results will be meaningless.
|
||||
|
||||
Use in myAnimation.h
|
||||
|
||||
HAL(IO_DNIN8, firstVpin, numPins, clockPin, latchPin, dataPin)
|
||||
e.g.
|
||||
HAL(IO_DNIN8, 400, 16, 40, 42, 44)
|
||||
|
||||
OR Use in myHal.cpp
|
||||
IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin)
|
||||
|
||||
|
||||
|
||||
This will create virtaul pins 400-415 using two DNIN8 boards connected in sequence.
|
||||
Vpins 400-407 will be on the first board (closest to the CS) and 408-415 on the second.
|
||||
|
||||
Note: 16 pins uses two boards. You may specify a non-multiple-of-8 pins but this will be rounded up to a multiple of 8 and you must connect ONLY the number of boards that this takes.
|
||||
|
||||
This example uses Arduino GPIO pins 40,42,44 as these are conveniently side-by-side on a Mega which is easier when you are using a 3 strand cable.
|
||||
|
||||
The DNIN8K module works the same but you must use DNIN8K in the HAL setup instead of DNIN8. NO you cant mix 8 and 8k versions in the same string of boards but you can create another string of boards.
|
||||
|
||||
|
||||
DNOU8 works the same way,
|
||||
Use in myAnimation.h
|
||||
|
||||
HAL(IO_DNOU8, firstVpin, numPins, clockPin, latchPin, dataPin)
|
||||
e.g.
|
||||
HAL(IO_DNIN8, 450, 16, 45, 47, 49)
|
||||
|
||||
OR Use in myHal.cpp
|
||||
IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin)
|
||||
|
||||
This creates a string of input pins 450-465. Note the clock/latch/data pins must be different to any DNIN8/k pins.
|
111
RingStream.cpp
111
RingStream.cpp
@@ -18,17 +18,9 @@
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
// NOTE: The use of a marker byte without an escape algorithm means
|
||||
// RingStream is unsuitable for binary data. Should binary data need to be
|
||||
// streamed it will be necessary to implementr an escape strategy to handle the
|
||||
// marker char when embedded in data.
|
||||
|
||||
#include "RingStream.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
const byte FLASH_INSERT_MARKER=0xff;
|
||||
|
||||
RingStream::RingStream( const uint16_t len)
|
||||
{
|
||||
_len=len;
|
||||
@@ -39,7 +31,6 @@ RingStream::RingStream( const uint16_t len)
|
||||
_overflow=false;
|
||||
_mark=0;
|
||||
_count=0;
|
||||
_flashInsert=0;
|
||||
}
|
||||
|
||||
size_t RingStream::write(uint8_t b) {
|
||||
@@ -55,84 +46,8 @@ size_t RingStream::write(uint8_t b) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Ideally, I would prefer to override the Print:print(_FlashStringHelper) function
|
||||
// but the library authors omitted to make this virtual.
|
||||
// Therefore we obveride the only other simple function that has no side effects
|
||||
// in order that StringFormatter can recognise a RingStream and call its
|
||||
// printFlash() directly.
|
||||
int RingStream::availableForWrite() {
|
||||
return THIS_IS_A_RINGSTREAM;
|
||||
}
|
||||
|
||||
size_t RingStream::printFlash(const FSH * flashBuffer) {
|
||||
// This function does not work on a 32 bit processor where the runtime
|
||||
// sometimes misrepresents the pointer size in uintptr_t.
|
||||
// In any case its not really necessary in a 32 bit processor because
|
||||
// we have adequate ram.
|
||||
if (sizeof(void*)>2) return print(flashBuffer);
|
||||
|
||||
|
||||
// We are about to add a PROGMEM string to the buffer.
|
||||
// To save RAM we can insert a marker and the
|
||||
// progmem address into the buffer instead.
|
||||
// The buffer reading code must recognise this marker and
|
||||
// silently extract the progmem bytes.
|
||||
// In addition, we must make the count correct as if the
|
||||
// string had been embedded so that things like the wifi code
|
||||
// can read the expected count before reading the buffer.
|
||||
|
||||
// Establish the actual length of the progmem string.
|
||||
char * flash=(char *)flashBuffer;
|
||||
int16_t plength=strlen_P(flash);
|
||||
if (plength==0) return 0; // just ignore empty string
|
||||
|
||||
// Retain the buffer count as it will be modified by the marker+address insert
|
||||
int prevCount=_count;
|
||||
write(FLASH_INSERT_MARKER); // write the marker
|
||||
uintptr_t iFlash=reinterpret_cast<uintptr_t>(flash); // expect size match with pointer
|
||||
|
||||
// write address bytes LSB first (size depends on CPU)
|
||||
for (byte f=0;f<sizeof(iFlash); f++) {
|
||||
write((byte) (iFlash & 0xFF));
|
||||
iFlash>>=8;
|
||||
}
|
||||
|
||||
// correct the buffer count to reflect the flash length, not the marker/addr.
|
||||
_count=prevCount+plength;
|
||||
return plength;
|
||||
}
|
||||
|
||||
int RingStream::read() {
|
||||
if (_flashInsert) {
|
||||
// we are reading out of a flash string
|
||||
byte fb=GETFLASH(_flashInsert);
|
||||
_flashInsert++;
|
||||
if (fb) return fb; // we have a byte from the flash
|
||||
// flash insert complete, clear and drop through to next buffer byte
|
||||
_flashInsert=NULL;
|
||||
}
|
||||
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
|
||||
byte b=readRawByte();
|
||||
if (b!=FLASH_INSERT_MARKER) return b;
|
||||
// Detected a flash insert
|
||||
if (sizeof(void*)>2) {
|
||||
DIAG(F("Detected invalid flash insert marker at pos %d"),_pos_read);
|
||||
return '?';
|
||||
}
|
||||
// read address bytes LSB first (size depends on CPU)
|
||||
uintptr_t iFlash=0;
|
||||
for (byte f=0; f<sizeof(iFlash); f++) {
|
||||
uintptr_t bf=readRawByte();
|
||||
bf&=0x00ff;
|
||||
bf<<= (8*f); // shift byte to correct position in iFlash
|
||||
iFlash |= bf;
|
||||
}
|
||||
_flashInsert=reinterpret_cast<char * >( iFlash);
|
||||
// and try again... so will read the first byte of the insert.
|
||||
return read();
|
||||
}
|
||||
|
||||
byte RingStream::readRawByte() {
|
||||
byte b=_buffer[_pos_read];
|
||||
_pos_read++;
|
||||
if (_pos_read==_len) _pos_read=0;
|
||||
@@ -140,8 +55,9 @@ byte RingStream::readRawByte() {
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
int RingStream::count() {
|
||||
return (readRawByte()<<8) | readRawByte();
|
||||
return (read()<<8) | read();
|
||||
}
|
||||
|
||||
int RingStream::freeSpace() {
|
||||
@@ -153,8 +69,6 @@ int RingStream::freeSpace() {
|
||||
|
||||
// mark start of message with client id (0...9)
|
||||
void RingStream::mark(uint8_t b) {
|
||||
//DIAG(F("RS mark client %d at %d core %d"), b, _pos_write, xPortGetCoreID());
|
||||
_ringClient = b;
|
||||
_mark=_pos_write;
|
||||
write(b); // client id
|
||||
write((uint8_t)0); // count MSB placemarker
|
||||
@@ -165,27 +79,20 @@ void RingStream::mark(uint8_t b) {
|
||||
// peekTargetMark is used by the parser stash routines to know which client
|
||||
// to send a callback response to some time later.
|
||||
uint8_t RingStream::peekTargetMark() {
|
||||
return _ringClient;
|
||||
}
|
||||
|
||||
void RingStream::info() {
|
||||
DIAG(F("Info len=%d count=%d pr=%d pw=%d m=%d"),_len, _count,_pos_read,_pos_write,_mark);
|
||||
return _buffer[_mark];
|
||||
}
|
||||
|
||||
bool RingStream::commit() {
|
||||
_flashInsert=NULL; // prepared for first read
|
||||
if (_overflow) {
|
||||
//DIAG(F("RingStream(%d) commit(%d) OVERFLOW"),_len, _count);
|
||||
DIAG(F("RingStream(%d) commit(%d) OVERFLOW"),_len, _count);
|
||||
// just throw it away
|
||||
_pos_write=_mark;
|
||||
_overflow=false;
|
||||
return false; // commit failed
|
||||
}
|
||||
if (_count==0) {
|
||||
//DIAG(F("RS commit count=0 rewind back to %d core %d"), _mark, xPortGetCoreID());
|
||||
// ignore empty response
|
||||
// ignore empty response
|
||||
_pos_write=_mark;
|
||||
_ringClient = NO_CLIENT; //XXX make else clause later
|
||||
return true; // true=commit ok
|
||||
}
|
||||
// Go back to the _mark and inject the count 1 byte later
|
||||
@@ -195,14 +102,14 @@ bool RingStream::commit() {
|
||||
_mark++;
|
||||
if (_mark==_len) _mark=0;
|
||||
_buffer[_mark]=lowByte(_count);
|
||||
_ringClient = NO_CLIENT;
|
||||
return true; // commit worked
|
||||
}
|
||||
void RingStream::flush() {
|
||||
_pos_write=0;
|
||||
_pos_read=0;
|
||||
_buffer[0]=0;
|
||||
_flashInsert=NULL; // prepared for first read
|
||||
_ringClient = NO_CLIENT;
|
||||
}
|
||||
|
||||
void RingStream::printBuffer(Print * stream) {
|
||||
_buffer[_pos_write]='\0';
|
||||
stream->print((char *)_buffer);
|
||||
}
|
||||
|
22
RingStream.h
22
RingStream.h
@@ -21,38 +21,22 @@
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "FSH.h"
|
||||
|
||||
class RingStream : public Print {
|
||||
|
||||
public:
|
||||
RingStream( const uint16_t len);
|
||||
static const int THIS_IS_A_RINGSTREAM=777;
|
||||
|
||||
virtual size_t write(uint8_t b);
|
||||
|
||||
// This availableForWrite function is subverted from its original intention so that a caller
|
||||
// can destinguish between a normal stream and a RingStream.
|
||||
// The Arduino compiler does not support runtime dynamic cast to perform
|
||||
// an instranceOf check.
|
||||
// This is necessary since the Print functions are mostly not virtual so
|
||||
// we cant override the print(__FlashStringHelper *) function.
|
||||
virtual int availableForWrite() override;
|
||||
using Print::write;
|
||||
size_t printFlash(const FSH * flashBuffer);
|
||||
int read();
|
||||
int count();
|
||||
int freeSpace();
|
||||
void mark(uint8_t b);
|
||||
bool commit();
|
||||
uint8_t peekTargetMark();
|
||||
void printBuffer(Print * streamer);
|
||||
void flush();
|
||||
void info();
|
||||
byte readRawByte();
|
||||
inline int peek() {
|
||||
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
|
||||
return _buffer[_pos_read];
|
||||
};
|
||||
static const byte NO_CLIENT=255;
|
||||
private:
|
||||
int _len;
|
||||
int _pos_write;
|
||||
@@ -61,8 +45,6 @@ class RingStream : public Print {
|
||||
int _mark;
|
||||
int _count;
|
||||
byte * _buffer;
|
||||
char * _flashInsert;
|
||||
byte _ringClient = NO_CLIENT;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -1,5 +1,4 @@
|
||||
/*
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2021 Chris Harlow
|
||||
* © 2022 Harald Barth
|
||||
* All rights reserved.
|
||||
@@ -22,20 +21,6 @@
|
||||
|
||||
#include "SerialManager.h"
|
||||
#include "DCCEXParser.h"
|
||||
#include "StringFormatter.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef SERIAL_BT_COMMANDS
|
||||
#include <BluetoothSerial.h>
|
||||
//#include <BleSerial.h>
|
||||
#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
|
||||
#error No Bluetooth library available
|
||||
#endif //ENABLED
|
||||
BluetoothSerial SerialBT;
|
||||
//BleSerial SerialBT;
|
||||
#endif //COMMANDS
|
||||
#endif //ESP32
|
||||
|
||||
SerialManager * SerialManager::first=NULL;
|
||||
|
||||
SerialManager::SerialManager(Stream * myserial) {
|
||||
@@ -47,22 +32,9 @@ SerialManager::SerialManager(Stream * myserial) {
|
||||
}
|
||||
|
||||
void SerialManager::init() {
|
||||
USB_SERIAL.begin(115200);
|
||||
while (!USB_SERIAL && millis() < 5000); // wait max 5s for Serial to start
|
||||
new SerialManager(&USB_SERIAL);
|
||||
|
||||
#ifdef SERIAL6_COMMANDS
|
||||
Serial6.begin(115200);
|
||||
new SerialManager(&Serial6);
|
||||
#endif
|
||||
#ifdef SERIAL5_COMMANDS
|
||||
Serial5.begin(115200);
|
||||
new SerialManager(&Serial5);
|
||||
#endif
|
||||
#ifdef SERIAL4_COMMANDS
|
||||
Serial4.begin(115200);
|
||||
new SerialManager(&Serial4);
|
||||
#endif
|
||||
while (!Serial && millis() < 5000); // wait max 5s for Serial to start
|
||||
Serial.begin(115200);
|
||||
new SerialManager(&Serial);
|
||||
#ifdef SERIAL3_COMMANDS
|
||||
Serial3.begin(115200);
|
||||
new SerialManager(&Serial3);
|
||||
@@ -75,25 +47,13 @@ void SerialManager::init() {
|
||||
Serial1.begin(115200);
|
||||
new SerialManager(&Serial1);
|
||||
#endif
|
||||
#ifdef SERIAL_BT_COMMANDS
|
||||
{
|
||||
//SerialBT.setPin("6666"); // choose other pin
|
||||
uint64_t chipid = ESP.getEfuseMac();
|
||||
char idstr[16] = {0};
|
||||
snprintf(idstr, 15, "DCCEX-%08X",
|
||||
__builtin_bswap32((uint32_t)(chipid>>16)));
|
||||
SerialBT.begin(idstr);
|
||||
new SerialManager(&SerialBT);
|
||||
delay(1000);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void SerialManager::broadcast(char * stringBuffer) {
|
||||
for (SerialManager * s=first;s;s=s->next) s->broadcast2(stringBuffer);
|
||||
void SerialManager::broadcast(RingStream * ring) {
|
||||
for (SerialManager * s=first;s;s=s->next) s->broadcast2(ring);
|
||||
}
|
||||
void SerialManager::broadcast2(char * stringBuffer) {
|
||||
serial->print(stringBuffer);
|
||||
void SerialManager::broadcast2(RingStream * ring) {
|
||||
ring->printBuffer(serial);
|
||||
}
|
||||
|
||||
void SerialManager::loop() {
|
||||
|
@@ -23,7 +23,7 @@
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "defines.h"
|
||||
|
||||
#include "RingStream.h"
|
||||
|
||||
#ifndef COMMAND_BUFFER_SIZE
|
||||
#define COMMAND_BUFFER_SIZE 100
|
||||
@@ -33,13 +33,13 @@ class SerialManager {
|
||||
public:
|
||||
static void init();
|
||||
static void loop();
|
||||
static void broadcast(char * stringBuffer);
|
||||
static void broadcast(RingStream * ring);
|
||||
|
||||
private:
|
||||
static SerialManager * first;
|
||||
SerialManager(Stream * myserial);
|
||||
void loop2();
|
||||
void broadcast2(char * stringBuffer);
|
||||
void broadcast2(RingStream * ring);
|
||||
Stream * serial;
|
||||
SerialManager * next;
|
||||
byte bufferLength;
|
||||
|
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* © 2022 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "StringBuffer.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
StringBuffer::StringBuffer() {
|
||||
flush();
|
||||
};
|
||||
|
||||
char * StringBuffer::getString() {
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
void StringBuffer::flush() {
|
||||
_pos_write=0;
|
||||
_buffer[0]='\0';
|
||||
}
|
||||
|
||||
size_t StringBuffer::write(uint8_t b) {
|
||||
if (_pos_write>=buffer_max) return 0;
|
||||
_buffer[_pos_write] = b;
|
||||
++_pos_write;
|
||||
_buffer[_pos_write]='\0';
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* © 2022 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef StringBuffer_h
|
||||
#define StringBuffer_h
|
||||
#include <Arduino.h>
|
||||
|
||||
class StringBuffer : public Print {
|
||||
public:
|
||||
StringBuffer();
|
||||
// Override Print default
|
||||
virtual size_t write(uint8_t b);
|
||||
void flush();
|
||||
char * getString();
|
||||
private:
|
||||
static const int buffer_max=64; // enough for long text msgs to throttles
|
||||
int16_t _pos_write;
|
||||
char _buffer[buffer_max+2];
|
||||
};
|
||||
|
||||
#endif
|
@@ -18,6 +18,15 @@
|
||||
*/
|
||||
#include "StringFormatter.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
#if defined(ARDUINO_ARCH_SAMD)
|
||||
// Some processors use a gcc compiler that renames va_list!!!
|
||||
#include <cstdarg>
|
||||
Print * StringFormatter::diagSerial= &SerialUSB;
|
||||
#else
|
||||
Print * StringFormatter::diagSerial=&Serial;
|
||||
#endif
|
||||
|
||||
#include "LCDDisplay.h"
|
||||
|
||||
bool Diag::ACK=false;
|
||||
@@ -29,21 +38,22 @@ bool Diag::LCN=false;
|
||||
|
||||
|
||||
void StringFormatter::diag( const FSH* input...) {
|
||||
USB_SERIAL.print(F("<* "));
|
||||
if (!diagSerial) return;
|
||||
diagSerial->print(F("<* "));
|
||||
va_list args;
|
||||
va_start(args, input);
|
||||
send2(&USB_SERIAL,input,args);
|
||||
USB_SERIAL.print(F(" *>\n"));
|
||||
send2(diagSerial,input,args);
|
||||
diagSerial->print(F(" *>\n"));
|
||||
}
|
||||
|
||||
void StringFormatter::lcd(byte row, const FSH* input...) {
|
||||
va_list args;
|
||||
|
||||
// Issue the LCD as a diag first
|
||||
send(&USB_SERIAL,F("<* LCD%d:"),row);
|
||||
send(diagSerial,F("<* LCD%d:"),row);
|
||||
va_start(args, input);
|
||||
send2(&USB_SERIAL,input,args);
|
||||
send(&USB_SERIAL,F(" *>\n"));
|
||||
send2(diagSerial,input,args);
|
||||
send(diagSerial,F(" *>\n"));
|
||||
|
||||
if (!LCDDisplay::lcdDisplay) return;
|
||||
LCDDisplay::lcdDisplay->setRow(row);
|
||||
@@ -70,7 +80,7 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
|
||||
char* flash=(char*)format;
|
||||
for(int i=0; ; ++i) {
|
||||
char c=GETFLASH(flash+i);
|
||||
if (c=='\0') break; // to va_end()
|
||||
if (c=='\0') return;
|
||||
if(c!='%') { stream->print(c); continue; }
|
||||
|
||||
bool formatContinues=false;
|
||||
@@ -87,29 +97,14 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
|
||||
case 's': stream->print(va_arg(args, char*)); break;
|
||||
case 'e': printEscapes(stream,va_arg(args, char*)); break;
|
||||
case 'E': printEscapes(stream,(const FSH*)va_arg(args, char*)); break;
|
||||
case 'S':
|
||||
{
|
||||
const FSH* flash= (const FSH*)va_arg(args, char*);
|
||||
|
||||
#if WIFI_ON | ETHERNET_ON
|
||||
// RingStream has special logic to handle flash strings
|
||||
// but is not implemented unless wifi or ethernet are enabled.
|
||||
// The define prevents RingStream code being added unnecessariliy.
|
||||
if (stream->availableForWrite()==RingStream::THIS_IS_A_RINGSTREAM)
|
||||
((RingStream *)stream)->printFlash(flash);
|
||||
else
|
||||
#endif
|
||||
stream->print(flash);
|
||||
break;
|
||||
}
|
||||
case 'P': stream->print((uint32_t)va_arg(args, void*), HEX); break;
|
||||
case 'S': stream->print((const FSH*)va_arg(args, char*)); break;
|
||||
case 'd': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break;
|
||||
case 'u': printPadded(stream,va_arg(args, unsigned int), formatWidth, formatLeft); break;
|
||||
case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break;
|
||||
case 'b': stream->print(va_arg(args, int), BIN); break;
|
||||
case 'o': stream->print(va_arg(args, int), OCT); break;
|
||||
case 'x': stream->print(va_arg(args, int), HEX); break;
|
||||
//case 'f': stream->print(va_arg(args, double), 2); break;
|
||||
case 'f': stream->print(va_arg(args, double), 2); break;
|
||||
//format width prefix
|
||||
case '-':
|
||||
formatLeft=true;
|
||||
@@ -155,7 +150,7 @@ void StringFormatter::printEscapes(Print * stream, const FSH * input) {
|
||||
}
|
||||
|
||||
void StringFormatter::printEscape( char c) {
|
||||
printEscape(&USB_SERIAL,c);
|
||||
printEscape(diagSerial,c);
|
||||
}
|
||||
|
||||
void StringFormatter::printEscape(Print * stream, char c) {
|
||||
@@ -165,8 +160,8 @@ void StringFormatter::printEscape(Print * stream, char c) {
|
||||
case '\r': stream->print(F("\\r")); break;
|
||||
case '\0': stream->print(F("\\0")); return;
|
||||
case '\t': stream->print(F("\\t")); break;
|
||||
case '\\': stream->print(F("\\\\")); break;
|
||||
default: stream->write(c);
|
||||
case '\\': stream->print(F("\\")); break;
|
||||
default: stream->print(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -20,7 +20,11 @@
|
||||
#define StringFormatter_h
|
||||
#include <Arduino.h>
|
||||
#include "FSH.h"
|
||||
#include "RingStream.h"
|
||||
#if defined(ARDUINO_ARCH_SAMD)
|
||||
// Some processors use a gcc compiler that renames va_list!!!
|
||||
#include <cstdarg>
|
||||
#endif
|
||||
|
||||
#include "LCDDisplay.h"
|
||||
class Diag {
|
||||
public:
|
||||
@@ -44,6 +48,7 @@ class StringFormatter
|
||||
static void printEscape(Print * serial, char c);
|
||||
|
||||
// DIAG support
|
||||
static Print * diagSerial;
|
||||
static void diag( const FSH* input...);
|
||||
static void lcd(byte row, const FSH* input...);
|
||||
static void printEscapes(char * input);
|
||||
|
454
TrackManager.cpp
454
TrackManager.cpp
@@ -1,454 +0,0 @@
|
||||
/*
|
||||
* © 2022 Chris Harlow
|
||||
* © 2022 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "TrackManager.h"
|
||||
#include "FSH.h"
|
||||
#include "DCCWaveform.h"
|
||||
#include "DCC.h"
|
||||
#include "MotorDriver.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "DIAG.h"
|
||||
// Virtualised Motor shield multi-track hardware Interface
|
||||
#define FOR_EACH_TRACK(t) for (byte t=0;t<=lastTrack;t++)
|
||||
|
||||
#define APPLY_BY_MODE(findmode,function) \
|
||||
FOR_EACH_TRACK(t) \
|
||||
if (trackMode[t]==findmode) \
|
||||
track[t]->function;
|
||||
|
||||
const int16_t HASH_KEYWORD_PROG = -29718;
|
||||
const int16_t HASH_KEYWORD_MAIN = 11339;
|
||||
const int16_t HASH_KEYWORD_OFF = 22479;
|
||||
const int16_t HASH_KEYWORD_DC = 2183;
|
||||
const int16_t HASH_KEYWORD_DCX = 6463; // DC reversed polarity
|
||||
const int16_t HASH_KEYWORD_EXT = 8201; // External DCC signal
|
||||
const int16_t HASH_KEYWORD_A = 65; // parser makes single chars the ascii.
|
||||
|
||||
MotorDriver * TrackManager::track[MAX_TRACKS];
|
||||
TRACK_MODE TrackManager::trackMode[MAX_TRACKS];
|
||||
int16_t TrackManager::trackDCAddr[MAX_TRACKS];
|
||||
|
||||
POWERMODE TrackManager::mainPowerGuess=POWERMODE::OFF;
|
||||
byte TrackManager::lastTrack=0;
|
||||
bool TrackManager::progTrackSyncMain=false;
|
||||
bool TrackManager::progTrackBoosted=false;
|
||||
int16_t TrackManager::joinRelay=UNUSED_PIN;
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
byte TrackManager::tempProgTrack=MAX_TRACKS+1;
|
||||
#endif
|
||||
|
||||
#ifdef ANALOG_READ_INTERRUPT
|
||||
/*
|
||||
* sampleCurrent() runs from Interrupt
|
||||
*/
|
||||
void TrackManager::sampleCurrent() {
|
||||
static byte tr = 0;
|
||||
byte trAtStart = tr;
|
||||
static bool waiting = false;
|
||||
|
||||
if (waiting) {
|
||||
if (! track[tr]->sampleCurrentFromHW()) {
|
||||
return; // no result, continue to wait
|
||||
}
|
||||
// found value, advance at least one track
|
||||
// for scope debug track[1]->setBrake(0);
|
||||
waiting = false;
|
||||
tr++;
|
||||
if (tr > lastTrack) tr = 0;
|
||||
if (lastTrack < 2 || trackMode[tr] & TRACK_MODE_PROG) {
|
||||
return; // We could continue but for prog track we
|
||||
// rather do it in next interrupt beacuse
|
||||
// that gives us well defined sampling point.
|
||||
// For other tracks we care less unless we
|
||||
// have only few (max 2) tracks.
|
||||
}
|
||||
}
|
||||
if (!waiting) {
|
||||
// look for a valid track to sample or until we are around
|
||||
while (true) {
|
||||
if (trackMode[tr] & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_DCX|TRACK_MODE_EXT )) {
|
||||
track[tr]->startCurrentFromHW();
|
||||
// for scope debug track[1]->setBrake(1);
|
||||
waiting = true;
|
||||
break;
|
||||
}
|
||||
tr++;
|
||||
if (tr > lastTrack) tr = 0;
|
||||
if (tr == trAtStart) // we are through and nothing found to do
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// The setup call is done this way so that the tracks can be in a list
|
||||
// from the config... the tracks default to NULL in the declaration
|
||||
void TrackManager::Setup(const FSH * shieldname,
|
||||
MotorDriver * track0, MotorDriver * track1, MotorDriver * track2,
|
||||
MotorDriver * track3, MotorDriver * track4, MotorDriver * track5,
|
||||
MotorDriver * track6, MotorDriver * track7 ) {
|
||||
addTrack(0,track0);
|
||||
addTrack(1,track1);
|
||||
addTrack(2,track2);
|
||||
addTrack(3,track3);
|
||||
addTrack(4,track4);
|
||||
addTrack(5,track5);
|
||||
addTrack(6,track6);
|
||||
addTrack(7,track7);
|
||||
|
||||
// Default the first 2 tracks (which may be null) and perform HA waveform check.
|
||||
setTrackMode(0,TRACK_MODE_MAIN);
|
||||
setTrackMode(1,TRACK_MODE_PROG);
|
||||
|
||||
// TODO Fault pin config for odd motor boards (example pololu)
|
||||
// MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin())
|
||||
// && (mainDriver->getFaultPin() != UNUSED_PIN));
|
||||
DCC::begin(shieldname);
|
||||
}
|
||||
|
||||
void TrackManager::addTrack(byte t, MotorDriver* driver) {
|
||||
trackMode[t]=TRACK_MODE_OFF;
|
||||
track[t]=driver;
|
||||
if (driver) {
|
||||
track[t]->setPower(POWERMODE::OFF);
|
||||
lastTrack=t;
|
||||
}
|
||||
}
|
||||
|
||||
// setDCCSignal(), called from interrupt context
|
||||
// does assume ports are shadowed if they can be
|
||||
void TrackManager::setDCCSignal( bool on) {
|
||||
HAVE_PORTA(shadowPORTA=PORTA);
|
||||
HAVE_PORTB(shadowPORTB=PORTB);
|
||||
HAVE_PORTC(shadowPORTC=PORTC);
|
||||
APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on));
|
||||
HAVE_PORTA(PORTA=shadowPORTA);
|
||||
HAVE_PORTB(PORTB=shadowPORTB);
|
||||
HAVE_PORTC(PORTC=shadowPORTC);
|
||||
}
|
||||
|
||||
void TrackManager::setCutout( bool on) {
|
||||
(void) on;
|
||||
// TODO Cutout needs fake ports as well
|
||||
// TODO APPLY_BY_MODE(TRACK_MODE_MAIN,setCutout(on));
|
||||
}
|
||||
|
||||
// setPROGSignal(), called from interrupt context
|
||||
// does assume ports are shadowed if they can be
|
||||
void TrackManager::setPROGSignal( bool on) {
|
||||
HAVE_PORTA(shadowPORTA=PORTA);
|
||||
HAVE_PORTB(shadowPORTB=PORTB);
|
||||
HAVE_PORTC(shadowPORTC=PORTC);
|
||||
APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on));
|
||||
HAVE_PORTA(PORTA=shadowPORTA);
|
||||
HAVE_PORTB(PORTB=shadowPORTB);
|
||||
HAVE_PORTC(PORTC=shadowPORTC);
|
||||
}
|
||||
|
||||
// setDCSignal(), called from normal context
|
||||
// MotorDriver::setDCSignal handles shadowed IO port changes.
|
||||
// with interrupts turned off around the critical section
|
||||
void TrackManager::setDCSignal(int16_t cab, byte speedbyte) {
|
||||
FOR_EACH_TRACK(t) {
|
||||
if (trackDCAddr[t]!=cab) continue;
|
||||
if (trackMode[t]==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte);
|
||||
else if (trackMode[t]==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128);
|
||||
}
|
||||
}
|
||||
|
||||
bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) {
|
||||
if (trackToSet>lastTrack || track[trackToSet]==NULL) return false;
|
||||
|
||||
//DIAG(F("Track=%c"),trackToSet+'A');
|
||||
// DC tracks require a motorDriver that can set brake!
|
||||
if ((mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX)
|
||||
&& !track[trackToSet]->brakeCanPWM()) {
|
||||
DIAG(F("Brake pin can't PWM: No DC"));
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
// remove pin from MUX matrix and turn it off
|
||||
pinpair p = track[trackToSet]->getSignalPin();
|
||||
//DIAG(F("Track=%c remove pin %d"),trackToSet+'A', p.pin);
|
||||
gpio_reset_pin((gpio_num_t)p.pin);
|
||||
pinMode(p.pin, OUTPUT); // gpio_reset_pin may reset to input
|
||||
if (p.invpin != UNUSED_PIN) {
|
||||
//DIAG(F("Track=%c remove ^pin %d"),trackToSet+'A', p.invpin);
|
||||
gpio_reset_pin((gpio_num_t)p.invpin);
|
||||
pinMode(p.invpin, OUTPUT); // gpio_reset_pin may reset to input
|
||||
}
|
||||
#endif
|
||||
if (mode==TRACK_MODE_PROG) {
|
||||
// only allow 1 track to be prog
|
||||
FOR_EACH_TRACK(t)
|
||||
if (trackMode[t]==TRACK_MODE_PROG && t != trackToSet) {
|
||||
track[t]->setPower(POWERMODE::OFF);
|
||||
trackMode[t]=TRACK_MODE_OFF;
|
||||
track[t]->makeProgTrack(false); // revoke prog track special handling
|
||||
}
|
||||
track[trackToSet]->makeProgTrack(true); // set for prog track special handling
|
||||
} else {
|
||||
track[trackToSet]->makeProgTrack(false); // only the prog track knows it's type
|
||||
}
|
||||
trackMode[trackToSet]=mode;
|
||||
trackDCAddr[trackToSet]=dcAddr;
|
||||
|
||||
// When a track is switched, we must clear any side effects of its previous
|
||||
// state, otherwise trains run away or just dont move.
|
||||
|
||||
// This can be done BEFORE the PWM-Timer evaluation (methinks)
|
||||
if (!(mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX)) {
|
||||
// DCC tracks need to have set the PWM to zero or they will not work.
|
||||
track[trackToSet]->detachDCSignal();
|
||||
track[trackToSet]->setBrake(false);
|
||||
}
|
||||
|
||||
// EXT is a special case where the signal pin is
|
||||
// turned off. So unless that is set, the signal
|
||||
// pin should be turned on
|
||||
track[trackToSet]->enableSignal(mode != TRACK_MODE_EXT);
|
||||
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
// re-evaluate HighAccuracy mode
|
||||
// We can only do this is all main and prog tracks agree
|
||||
bool canDo=true;
|
||||
FOR_EACH_TRACK(t) {
|
||||
// DC tracks must not have the DCC PWM switched on
|
||||
// so we globally turn it off if one of the PWM
|
||||
// capable tracks is now DC or DCX.
|
||||
if (trackMode[t]==TRACK_MODE_DC || trackMode[t]==TRACK_MODE_DCX) {
|
||||
if (track[t]->isPWMCapable()) {
|
||||
canDo=false; // this track is capable but can not run PWM
|
||||
break; // in this mode, so abort and prevent globally below
|
||||
} else {
|
||||
track[t]->trackPWM=false; // this track sure can not run with PWM
|
||||
//DIAG(F("Track %c trackPWM 0 (not capable)"), t+'A');
|
||||
}
|
||||
} else if (trackMode[t]==TRACK_MODE_MAIN || trackMode[t]==TRACK_MODE_PROG) {
|
||||
track[t]->trackPWM = track[t]->isPWMCapable(); // trackPWM is still a guess here
|
||||
//DIAG(F("Track %c trackPWM %d"), t+'A', track[t]->trackPWM);
|
||||
canDo &= track[t]->trackPWM;
|
||||
}
|
||||
}
|
||||
if (!canDo) {
|
||||
// if we discover that HA mode was globally impossible
|
||||
// we must adjust the trackPWM capabilities
|
||||
FOR_EACH_TRACK(t) {
|
||||
track[t]->trackPWM=false;
|
||||
//DIAG(F("Track %c trackPWM 0 (global override)"), t+'A');
|
||||
}
|
||||
DCCTimer::clearPWM(); // has to be AFTER trackPWM changes because if trackPWM==true this is undone for that track
|
||||
}
|
||||
#else
|
||||
// For ESP32 we just reinitialize the DCC Waveform
|
||||
DCCWaveform::begin();
|
||||
#endif
|
||||
|
||||
// This block must be AFTER the PWM-Timer modifications
|
||||
if (mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) {
|
||||
// DC tracks need to be given speed of the throttle for that cab address
|
||||
// otherwise will not match other tracks on same cab.
|
||||
// This also needs to allow for inverted DCX
|
||||
applyDCSpeed(trackToSet);
|
||||
}
|
||||
|
||||
// Normal running tracks are set to the global power state
|
||||
track[trackToSet]->setPower(
|
||||
(mode==TRACK_MODE_MAIN || mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX || mode==TRACK_MODE_EXT) ?
|
||||
mainPowerGuess : POWERMODE::OFF);
|
||||
//DIAG(F("TrackMode=%d"),mode);
|
||||
return true;
|
||||
}
|
||||
|
||||
void TrackManager::applyDCSpeed(byte t) {
|
||||
uint8_t speedByte=DCC::getThrottleSpeedByte(trackDCAddr[t]);
|
||||
if (trackMode[t]==TRACK_MODE_DCX)
|
||||
speedByte = speedByte ^ 128; // reverse direction bit
|
||||
track[t]->setDCSignal(speedByte);
|
||||
}
|
||||
|
||||
bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
|
||||
{
|
||||
|
||||
if (params==0) { // <=> List track assignments
|
||||
FOR_EACH_TRACK(t)
|
||||
if (track[t]!=NULL) {
|
||||
StringFormatter::send(stream,F("<= %c "),'A'+t);
|
||||
switch(trackMode[t]) {
|
||||
case TRACK_MODE_MAIN:
|
||||
StringFormatter::send(stream,F("MAIN"));
|
||||
if (track[t]->trackPWM)
|
||||
StringFormatter::send(stream,F("+"));
|
||||
break;
|
||||
case TRACK_MODE_PROG:
|
||||
StringFormatter::send(stream,F("PROG"));
|
||||
if (track[t]->trackPWM)
|
||||
StringFormatter::send(stream,F("+"));
|
||||
break;
|
||||
case TRACK_MODE_OFF:
|
||||
StringFormatter::send(stream,F("OFF"));
|
||||
break;
|
||||
case TRACK_MODE_EXT:
|
||||
StringFormatter::send(stream,F("EXT"));
|
||||
break;
|
||||
case TRACK_MODE_DC:
|
||||
StringFormatter::send(stream,F("DC %d"),trackDCAddr[t]);
|
||||
break;
|
||||
case TRACK_MODE_DCX:
|
||||
StringFormatter::send(stream,F("DCX %d"),trackDCAddr[t]);
|
||||
break;
|
||||
default:
|
||||
break; // unknown, dont care
|
||||
}
|
||||
StringFormatter::send(stream,F(">\n"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
p[0]-=HASH_KEYWORD_A; // convert A... to 0....
|
||||
|
||||
if (params>1 && (p[0]<0 || p[0]>=MAX_TRACKS))
|
||||
return false;
|
||||
|
||||
if (params==2 && p[1]==HASH_KEYWORD_MAIN) // <= id MAIN>
|
||||
return setTrackMode(p[0],TRACK_MODE_MAIN);
|
||||
|
||||
if (params==2 && p[1]==HASH_KEYWORD_PROG) // <= id PROG>
|
||||
return setTrackMode(p[0],TRACK_MODE_PROG);
|
||||
|
||||
if (params==2 && p[1]==HASH_KEYWORD_OFF) // <= id OFF>
|
||||
return setTrackMode(p[0],TRACK_MODE_OFF);
|
||||
|
||||
if (params==2 && p[1]==HASH_KEYWORD_EXT) // <= id EXT>
|
||||
return setTrackMode(p[0],TRACK_MODE_EXT);
|
||||
|
||||
if (params==3 && p[1]==HASH_KEYWORD_DC && p[2]>0) // <= id DC cab>
|
||||
return setTrackMode(p[0],TRACK_MODE_DC,p[2]);
|
||||
|
||||
if (params==3 && p[1]==HASH_KEYWORD_DCX && p[2]>0) // <= id DCX cab>
|
||||
return setTrackMode(p[0],TRACK_MODE_DCX,p[2]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
byte TrackManager::nextCycleTrack=MAX_TRACKS;
|
||||
|
||||
void TrackManager::loop() {
|
||||
DCCWaveform::loop();
|
||||
DCCACK::loop();
|
||||
bool dontLimitProg=DCCACK::isActive() || progTrackSyncMain || progTrackBoosted;
|
||||
nextCycleTrack++;
|
||||
if (nextCycleTrack>lastTrack) nextCycleTrack=0;
|
||||
if (track[nextCycleTrack]==NULL) return;
|
||||
MotorDriver * motorDriver=track[nextCycleTrack];
|
||||
bool useProgLimit=dontLimitProg? false: trackMode[nextCycleTrack]==TRACK_MODE_PROG;
|
||||
motorDriver->checkPowerOverload(useProgLimit, nextCycleTrack);
|
||||
}
|
||||
|
||||
MotorDriver * TrackManager::getProgDriver() {
|
||||
FOR_EACH_TRACK(t)
|
||||
if (trackMode[t]==TRACK_MODE_PROG) return track[t];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
std::vector<MotorDriver *>TrackManager::getMainDrivers() {
|
||||
std::vector<MotorDriver *> v;
|
||||
FOR_EACH_TRACK(t)
|
||||
if (trackMode[t]==TRACK_MODE_MAIN) v.push_back(track[t]);
|
||||
return v;
|
||||
}
|
||||
#endif
|
||||
|
||||
void TrackManager::setPower2(bool setProg,POWERMODE mode) {
|
||||
if (!setProg) mainPowerGuess=mode;
|
||||
FOR_EACH_TRACK(t) {
|
||||
MotorDriver * driver=track[t];
|
||||
if (!driver) continue;
|
||||
switch (trackMode[t]) {
|
||||
case TRACK_MODE_MAIN:
|
||||
if (setProg) break;
|
||||
// toggle brake before turning power on - resets overcurrent error
|
||||
// on the Pololu board if brake is wired to ^D2.
|
||||
// XXX see if we can make this conditional
|
||||
driver->setBrake(true);
|
||||
driver->setBrake(false); // DCC runs with brake off
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_DC:
|
||||
case TRACK_MODE_DCX:
|
||||
if (setProg) break;
|
||||
driver->setBrake(true); // DC starts with brake on
|
||||
applyDCSpeed(t); // speed match DCC throttles
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_PROG:
|
||||
if (!setProg) break;
|
||||
driver->setBrake(true);
|
||||
driver->setBrake(false);
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_EXT:
|
||||
driver->setBrake(true);
|
||||
driver->setBrake(false);
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_OFF:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
POWERMODE TrackManager::getProgPower() {
|
||||
FOR_EACH_TRACK(t)
|
||||
if (trackMode[t]==TRACK_MODE_PROG)
|
||||
return track[t]->getPower();
|
||||
return POWERMODE::OFF;
|
||||
}
|
||||
|
||||
void TrackManager::setJoinRelayPin(byte joinRelayPin) {
|
||||
joinRelay=joinRelayPin;
|
||||
if (joinRelay!=UNUSED_PIN) {
|
||||
pinMode(joinRelay,OUTPUT);
|
||||
digitalWrite(joinRelay,LOW); // LOW is relay disengaged
|
||||
}
|
||||
}
|
||||
|
||||
void TrackManager::setJoin(bool joined) {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
if (joined) {
|
||||
FOR_EACH_TRACK(t) {
|
||||
if (trackMode[t]==TRACK_MODE_PROG) {
|
||||
tempProgTrack = t;
|
||||
setTrackMode(t, TRACK_MODE_MAIN);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (tempProgTrack != MAX_TRACKS+1) {
|
||||
setTrackMode(tempProgTrack, TRACK_MODE_PROG);
|
||||
tempProgTrack = MAX_TRACKS+1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
progTrackSyncMain=joined;
|
||||
if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,joined?HIGH:LOW);
|
||||
}
|
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* © 2022 Chris Harlow
|
||||
* © 2022 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <vector>
|
||||
#endif
|
||||
#ifndef TrackManager_h
|
||||
#define TrackManager_h
|
||||
#include "FSH.h"
|
||||
#include "MotorDriver.h"
|
||||
// Virtualised Motor shield multi-track hardware Interface
|
||||
|
||||
// use powers of two so we can do logical and/or on the track modes in if clauses.
|
||||
enum TRACK_MODE : byte {TRACK_MODE_OFF = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4,
|
||||
TRACK_MODE_DC = 8, TRACK_MODE_DCX = 16, TRACK_MODE_EXT = 32};
|
||||
|
||||
// These constants help EXRAIL macros say SET_TRACK(2,mode) OR SET_TRACK(C,mode) etc.
|
||||
const byte TRACK_NUMBER_0=0, TRACK_NUMBER_A=0;
|
||||
const byte TRACK_NUMBER_1=1, TRACK_NUMBER_B=1;
|
||||
const byte TRACK_NUMBER_2=2, TRACK_NUMBER_C=2;
|
||||
const byte TRACK_NUMBER_3=3, TRACK_NUMBER_D=3;
|
||||
const byte TRACK_NUMBER_4=4, TRACK_NUMBER_E=4;
|
||||
const byte TRACK_NUMBER_5=5, TRACK_NUMBER_F=5;
|
||||
const byte TRACK_NUMBER_6=6, TRACK_NUMBER_G=6;
|
||||
const byte TRACK_NUMBER_7=7, TRACK_NUMBER_H=7;
|
||||
|
||||
class TrackManager {
|
||||
public:
|
||||
static void Setup(const FSH * shieldName,
|
||||
MotorDriver * track0,
|
||||
MotorDriver * track1=NULL,
|
||||
MotorDriver * track2=NULL,
|
||||
MotorDriver * track3=NULL,
|
||||
MotorDriver * track4=NULL,
|
||||
MotorDriver * track5=NULL,
|
||||
MotorDriver * track6=NULL,
|
||||
MotorDriver * track7=NULL
|
||||
);
|
||||
|
||||
static void setDCCSignal( bool on);
|
||||
static void setCutout( bool on);
|
||||
static void setPROGSignal( bool on);
|
||||
static void setDCSignal(int16_t cab, byte speedbyte);
|
||||
static MotorDriver * getProgDriver();
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
static std::vector<MotorDriver *>getMainDrivers();
|
||||
#endif
|
||||
static void setPower2(bool progTrack,POWERMODE mode);
|
||||
static void setPower(POWERMODE mode) {setMainPower(mode); setProgPower(mode);}
|
||||
static void setMainPower(POWERMODE mode) {setPower2(false,mode);}
|
||||
static void setProgPower(POWERMODE mode) {setPower2(true,mode);}
|
||||
|
||||
static const int16_t MAX_TRACKS=8;
|
||||
static bool setTrackMode(byte track, TRACK_MODE mode, int16_t DCaddr=0);
|
||||
static bool parseJ(Print * stream, int16_t params, int16_t p[]);
|
||||
static void loop();
|
||||
static POWERMODE getMainPower() {return mainPowerGuess;}
|
||||
static POWERMODE getProgPower();
|
||||
static void setJoin(bool join);
|
||||
static bool isJoined() { return progTrackSyncMain;}
|
||||
static void setJoinRelayPin(byte joinRelayPin);
|
||||
static void sampleCurrent();
|
||||
static int16_t joinRelay;
|
||||
static bool progTrackSyncMain; // true when prog track is a siding switched to main
|
||||
static bool progTrackBoosted; // true when prog track is not current limited
|
||||
|
||||
private:
|
||||
static void addTrack(byte t, MotorDriver* driver);
|
||||
static byte lastTrack;
|
||||
static byte nextCycleTrack;
|
||||
static POWERMODE mainPowerGuess;
|
||||
static void applyDCSpeed(byte t);
|
||||
|
||||
static MotorDriver* track[MAX_TRACKS];
|
||||
static TRACK_MODE trackMode[MAX_TRACKS];
|
||||
static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC or TRACK_MODE_DCX
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
static byte tempProgTrack; // holds the prog track number during join
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
19
Turnouts.h
19
Turnouts.h
@@ -3,7 +3,7 @@
|
||||
* © 2021 M Steve Todd
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2021 Harald Barth
|
||||
* © 2020-2022 Chris Harlow
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2013-2016 Gregg E. Berman
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -60,8 +60,7 @@ protected:
|
||||
union {
|
||||
struct {
|
||||
bool closed : 1;
|
||||
bool hidden : 1;
|
||||
bool _rfu : 1;
|
||||
bool _rfu: 2;
|
||||
uint8_t turnoutType : 5;
|
||||
};
|
||||
uint8_t flags;
|
||||
@@ -84,7 +83,6 @@ protected:
|
||||
_turnoutData.id = id;
|
||||
_turnoutData.turnoutType = turnoutType;
|
||||
_turnoutData.closed = closed;
|
||||
_turnoutData.hidden=false;
|
||||
add(this);
|
||||
}
|
||||
|
||||
@@ -106,11 +104,11 @@ protected:
|
||||
* Static functions
|
||||
*/
|
||||
|
||||
static Turnout *get(uint16_t id);
|
||||
|
||||
static void add(Turnout *tt);
|
||||
|
||||
public:
|
||||
static Turnout *get(uint16_t id);
|
||||
/*
|
||||
* Static data
|
||||
*/
|
||||
@@ -122,8 +120,6 @@ public:
|
||||
*/
|
||||
inline bool isClosed() { return _turnoutData.closed; };
|
||||
inline bool isThrown() { return !_turnoutData.closed; }
|
||||
inline bool isHidden() { return _turnoutData.hidden; }
|
||||
inline void setHidden(bool h) { _turnoutData.hidden=h; }
|
||||
inline bool isType(uint8_t type) { return _turnoutData.turnoutType == type; }
|
||||
inline uint16_t getId() { return _turnoutData.id; }
|
||||
inline Turnout *next() { return _nextTurnout; }
|
||||
@@ -171,14 +167,9 @@ public:
|
||||
// Save all turnout definitions
|
||||
static void store();
|
||||
#endif
|
||||
static bool printAll(Print *stream) {
|
||||
bool gotOne=false;
|
||||
static void printAll(Print *stream) {
|
||||
for (Turnout *tt = _firstTurnout; tt != 0; tt = tt->_nextTurnout)
|
||||
if (!tt->isHidden()) {
|
||||
gotOne=true;
|
||||
StringFormatter::send(stream, F("<H %d %d>\n"),tt->getId(), tt->isThrown());
|
||||
}
|
||||
return gotOne;
|
||||
StringFormatter::send(stream, F("<H %d %d>\n"),tt->getId(), tt->isThrown());
|
||||
}
|
||||
|
||||
|
||||
|
291
WiThrottle.cpp
291
WiThrottle.cpp
@@ -55,8 +55,6 @@
|
||||
#include "version.h"
|
||||
#include "EXRAIL2.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "TrackManager.h"
|
||||
#include "DCCTimer.h"
|
||||
|
||||
#define LOOPLOCOS(THROTTLECHAR, CAB) for (int loco=0;loco<MAX_MY_LOCO;loco++) \
|
||||
if ((myLocos[loco].throttle==THROTTLECHAR || '*'==THROTTLECHAR) && (CAB<0 || myLocos[loco].cab==CAB))
|
||||
@@ -69,15 +67,6 @@ WiThrottle* WiThrottle::getThrottle( int wifiClient) {
|
||||
return new WiThrottle( wifiClient);
|
||||
}
|
||||
|
||||
void WiThrottle::forget( byte clientId) {
|
||||
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
|
||||
if (wt->clientid==clientId) {
|
||||
DIAG(F("Withrottle client %d dropped"),clientId);
|
||||
delete wt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool WiThrottle::isThrottleInUse(int cab) {
|
||||
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
|
||||
if (wt->areYouUsingThrottle(cab)) return true;
|
||||
@@ -97,13 +86,15 @@ WiThrottle::WiThrottle( int wificlientid) {
|
||||
nextThrottle=firstThrottle;
|
||||
firstThrottle= this;
|
||||
clientid=wificlientid;
|
||||
initSent=false; // prevent sending heartbeats before connection completed
|
||||
heartBeatEnable=false; // until client turns it on
|
||||
turnoutListHash = -1; // make sure turnout list is sent once
|
||||
exRailSent=false;
|
||||
mostRecentCab=0;
|
||||
for (int loco=0;loco<MAX_MY_LOCO; loco++) myLocos[loco].throttle='\0';
|
||||
}
|
||||
|
||||
WiThrottle::~WiThrottle() {
|
||||
if (Diag::WITHROTTLE) DIAG(F("Deleting WiThrottle client %d"),this->clientid);
|
||||
if (firstThrottle== this) {
|
||||
firstThrottle=this->nextThrottle;
|
||||
return;
|
||||
@@ -122,17 +113,33 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
|
||||
heartBeat=millis();
|
||||
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d)<-[%e]"),millis(),clientid,cmd);
|
||||
|
||||
// On first few commands, send turnout, roster and routes
|
||||
if (introSent) {
|
||||
if (!turnoutsSent) sendTurnouts(stream);
|
||||
else if(!rosterSent) sendRoster(stream);
|
||||
else if (!routesSent) sendRoutes(stream);
|
||||
else if (!heartrateSent) {
|
||||
heartrateSent=true;
|
||||
// allow heartbeat to slow down once all metadata sent
|
||||
StringFormatter::send(stream,F("*%d\nHMConnected\n"),HEARTBEAT_SECONDS);
|
||||
|
||||
if (initSent) {
|
||||
// Send turnout list if changed since last sent (will replace list on client)
|
||||
if (turnoutListHash != Turnout::turnoutlistHash) {
|
||||
StringFormatter::send(stream,F("PTL"));
|
||||
for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){
|
||||
int id=tt->getId();
|
||||
StringFormatter::send(stream,F("]\\[%d}|{"), id);
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
RMFT2::emitTurnoutDescription(stream,id);
|
||||
#else
|
||||
StringFormatter::send(stream,F("%d"), id);
|
||||
#endif
|
||||
StringFormatter::send(stream,F("}|{%c"), Turnout::isClosed(id)?'2':'4');
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
turnoutListHash = Turnout::turnoutlistHash; // keep a copy of hash for later comparison
|
||||
}
|
||||
|
||||
else if (!exRailSent) {
|
||||
// Send EX-RAIL routes list if not already sent (but not at same time as turnouts above)
|
||||
exRailSent=true;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
RMFT2::emitWithrottleRouteList(stream);
|
||||
#endif
|
||||
// allow heartbeat to slow down once all metadata sent
|
||||
StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,12 +151,9 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
break;
|
||||
case 'P':
|
||||
if (cmd[1]=='P' && cmd[2]=='A' ) { //PPA power mode
|
||||
TrackManager::setMainPower(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
||||
/* TODO
|
||||
DCCWaveform::mainTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
||||
if (MotorDriver::commonFaultPin) // commonFaultPin prevents individual track handling
|
||||
DCCWaveform::progTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
||||
*/
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
}
|
||||
#if defined(EXRAIL_ACTIVE)
|
||||
@@ -188,14 +192,25 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
}
|
||||
break;
|
||||
case 'N': // Heartbeat (2), only send if connection completed by 'HU' message
|
||||
StringFormatter::send(stream, F("*%d\n"), heartrateSent ? HEARTBEAT_SECONDS : HEARTBEAT_PRELOAD); // return timeout value
|
||||
if (initSent) {
|
||||
StringFormatter::send(stream, F("*%d\n"),HEARTBEAT_SECONDS); // return timeout value
|
||||
}
|
||||
break;
|
||||
case 'M': // multithrottle
|
||||
multithrottle(stream, cmd);
|
||||
break;
|
||||
case 'H': // send initial connection info after receiving "HU" message
|
||||
if (cmd[1] == 'U') {
|
||||
sendIntro(stream);
|
||||
if (cmd[1] == 'U') {
|
||||
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
|
||||
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
|
||||
StringFormatter::send(stream,F("PPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON);
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
RMFT2::emitWithrottleRoster(stream);
|
||||
#endif
|
||||
// set heartbeat to 1 second because we need to sync the metadata
|
||||
StringFormatter::send(stream,F("*1\n"));
|
||||
initSent = true;
|
||||
}
|
||||
break;
|
||||
case 'Q': //
|
||||
@@ -204,7 +219,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab);
|
||||
}
|
||||
}
|
||||
if (Diag::WITHROTTLE) DIAG(F("WiThrottle(%d) Quit"),clientid);
|
||||
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) Quit"),millis(),clientid);
|
||||
delete this;
|
||||
break;
|
||||
}
|
||||
@@ -216,14 +231,11 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
|
||||
int WiThrottle::getInt(byte * cmd) {
|
||||
int i=0;
|
||||
bool negate=cmd[0]=='-';
|
||||
if (negate) cmd++;
|
||||
while (cmd[0]>='0' && cmd[0]<='9') {
|
||||
i=i*10 + (cmd[0]-'0');
|
||||
cmd++;
|
||||
}
|
||||
if (negate) i=0-i;
|
||||
return i ;
|
||||
return i;
|
||||
}
|
||||
|
||||
int WiThrottle::getLocoId(byte * cmd) {
|
||||
@@ -265,17 +277,65 @@ void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
|
||||
}
|
||||
//use first empty "slot" on this client's list, will be added to DCC registration list
|
||||
for (int loco=0;loco<MAX_MY_LOCO;loco++) {
|
||||
if (myLocos[loco].throttle=='\0') {
|
||||
myLocos[loco].throttle=throttleChar;
|
||||
myLocos[loco].cab=locoid;
|
||||
myLocos[loco].functionMap=DCC::getFunctionMap(locoid);
|
||||
myLocos[loco].broadcastPending=true; // means speed/dir will be sent later
|
||||
mostRecentCab=locoid;
|
||||
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
|
||||
sendFunctions(stream,loco);
|
||||
//speed and direction will be published at next broadcast cycle
|
||||
StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128
|
||||
return;
|
||||
if (myLocos[loco].throttle=='\0') {
|
||||
myLocos[loco].throttle=throttleChar;
|
||||
myLocos[loco].cab=locoid;
|
||||
myLocos[loco].functionMap=DCC::getFunctionMap(locoid);
|
||||
myLocos[loco].broadcastPending=true; // means speed/dir will be sent later
|
||||
mostRecentCab=locoid;
|
||||
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
|
||||
int fkeys=29;
|
||||
myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle
|
||||
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid);
|
||||
if (!functionNames) {
|
||||
// no roster, use presets as above
|
||||
}
|
||||
else if (GETFLASH(functionNames)=='\0') {
|
||||
// "" = Roster but no functions given
|
||||
fkeys=0;
|
||||
}
|
||||
else {
|
||||
// we have function names...
|
||||
// scan names list emitting names, counting functions and
|
||||
// flagging non-toggling things like horn.
|
||||
myLocos[loco].functionToggles =0;
|
||||
StringFormatter::send(stream, F("M%cL%c%d<;>]\\["), throttleChar,cmd[3],locoid);
|
||||
fkeys=0;
|
||||
bool firstchar=true;
|
||||
for (int fx=0;;fx++) {
|
||||
char c=GETFLASH(functionNames+fx);
|
||||
if (c=='\0') {
|
||||
fkeys++;
|
||||
break;
|
||||
}
|
||||
if (c=='/') {
|
||||
fkeys++;
|
||||
StringFormatter::send(stream,F("]\\["));
|
||||
firstchar=true;
|
||||
}
|
||||
else if (firstchar && c=='*') {
|
||||
myLocos[loco].functionToggles |= 1UL<<fkeys;
|
||||
firstchar=false;
|
||||
}
|
||||
else {
|
||||
firstchar=false;
|
||||
stream->write(c);
|
||||
}
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
for(int fKey=0; fKey<fkeys; fKey++) {
|
||||
int fstate=DCC::getFn(locoid,fKey);
|
||||
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),throttleChar,cmd[3],locoid,fstate,fKey);
|
||||
}
|
||||
//speed and direction will be published at next broadcast cycle
|
||||
StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128
|
||||
return;
|
||||
}
|
||||
}
|
||||
StringFormatter::send(stream, F("HMMax locos (%d) exceeded, %d not added!\n"), MAX_MY_LOCO ,locoid);
|
||||
@@ -321,13 +381,9 @@ void WiThrottle::locoAction(RingStream * stream, byte* aval, char throttleChar,
|
||||
case 'q':
|
||||
if (aval[1]=='V' || aval[1]=='R' ) { //qV or qR
|
||||
// just flag the loco for broadcast and it will happen.
|
||||
bool foundone = false;
|
||||
LOOPLOCOS(throttleChar, cab) {
|
||||
foundone = true;
|
||||
LOOPLOCOS(throttleChar, cab) {
|
||||
myLocos[loco].broadcastPending=true;
|
||||
}
|
||||
if (!foundone)
|
||||
StringFormatter::send(stream,F("HMCS loco list empty\n"));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'R':
|
||||
@@ -335,10 +391,7 @@ void WiThrottle::locoAction(RingStream * stream, byte* aval, char throttleChar,
|
||||
bool forward=aval[1]!='0';
|
||||
LOOPLOCOS(throttleChar, cab) {
|
||||
mostRecentCab=myLocos[loco].cab;
|
||||
int8_t speed = DCC::getThrottleSpeed(myLocos[loco].cab);
|
||||
if (speed < 0) //can not find any speed for this cab
|
||||
speed = 0;
|
||||
DCC::setThrottle(myLocos[loco].cab, speed, forward);
|
||||
DCC::setThrottle(myLocos[loco].cab, DCC::getThrottleSpeed(myLocos[loco].cab), forward);
|
||||
// setThrottle will cause a broadcast so notification will be sent
|
||||
}
|
||||
}
|
||||
@@ -379,6 +432,8 @@ void WiThrottle::loop(RingStream * stream) {
|
||||
// for each WiThrottle, check the heartbeat and broadcast needed
|
||||
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
|
||||
wt->checkHeartbeat(stream);
|
||||
|
||||
|
||||
}
|
||||
|
||||
void WiThrottle::checkHeartbeat(RingStream * stream) {
|
||||
@@ -389,10 +444,8 @@ void WiThrottle::checkHeartbeat(RingStream * stream) {
|
||||
if (myLocos[loco].throttle!='\0') {
|
||||
if (Diag::WITHROTTLE) DIAG(F("%l eStopping cab %d"),millis(),myLocos[loco].cab);
|
||||
DCC::setThrottle(myLocos[loco].cab, 1, DCC::getThrottleDirection(myLocos[loco].cab)); // speed 1 is eStop
|
||||
heartBeat=millis(); // We have just stopped everyting, we don't need to do that again at next loop.
|
||||
}
|
||||
}
|
||||
// if it does come back, the throttle should re-acquire
|
||||
delete this;
|
||||
return;
|
||||
}
|
||||
@@ -459,12 +512,10 @@ byte WiThrottle::stashClient;
|
||||
char WiThrottle::stashThrottleChar;
|
||||
|
||||
void WiThrottle::getLocoCallback(int16_t locoid) {
|
||||
//DIAG(F("LocoCallback mark client %d"), stashClient);
|
||||
stashStream->mark(stashClient);
|
||||
|
||||
if (locoid<=0) {
|
||||
StringFormatter::send(stashStream,F("HMNo loco found on prog track\n"));
|
||||
//DIAG(F("LocoCallback commit (noloco)"));
|
||||
stashStream->commit(); // done here, commit and return
|
||||
return;
|
||||
}
|
||||
@@ -475,7 +526,6 @@ void WiThrottle::getLocoCallback(int16_t locoid) {
|
||||
locoid = locoid ^ LONG_ADDR_MARKER; // remove marker bit to get real long addr
|
||||
if (locoid <= HIGHEST_SHORT_ADDR ) { // out of range for long addr
|
||||
StringFormatter::send(stashStream,F("HMLong addr %d <= %d unsupported\n"), locoid, HIGHEST_SHORT_ADDR);
|
||||
//DIAG(F("LocoCallback commit (error)"));
|
||||
stashStream->commit(); // done here, commit and return
|
||||
return;
|
||||
}
|
||||
@@ -487,126 +537,9 @@ void WiThrottle::getLocoCallback(int16_t locoid) {
|
||||
char addcmd[20]={'M',stashThrottleChar,'+', addrchar};
|
||||
itoa(locoid,addcmd+4,10);
|
||||
stashInstance->multithrottle(stashStream, (byte *)addcmd);
|
||||
TrackManager::setMainPower(POWERMODE::ON);
|
||||
TrackManager::setProgPower(POWERMODE::ON);
|
||||
TrackManager::setJoin(true); // <1 JOIN> so we can drive loco away
|
||||
DIAG(F("LocoCallback commit success"));
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
DCC::setProgTrackSyncMain(true); // <1 JOIN> so we can drive loco away
|
||||
stashStream->commit();
|
||||
CommandDistributor::broadcastPower();
|
||||
}
|
||||
|
||||
void WiThrottle::sendIntro(Print* stream) {
|
||||
introSent=true;
|
||||
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
|
||||
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
|
||||
StringFormatter::send(stream,F("PPA%x\n"),TrackManager::getMainPower()==POWERMODE::ON);
|
||||
// set heartbeat to 2 seconds because we need to sync the metadata (1 second is too short!)
|
||||
StringFormatter::send(stream,F("*%d\nHMConnecting..\n"), HEARTBEAT_PRELOAD);
|
||||
}
|
||||
|
||||
void WiThrottle::sendTurnouts(Print* stream) {
|
||||
turnoutsSent=true;
|
||||
StringFormatter::send(stream,F("PTL"));
|
||||
for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){
|
||||
if (tt->isHidden()) continue;
|
||||
int id=tt->getId();
|
||||
const FSH * tdesc=NULL;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
tdesc=RMFT2::getTurnoutDescription(id);
|
||||
#endif
|
||||
char tchar=Turnout::isClosed(id)?'2':'4';
|
||||
if (tdesc==NULL) // turnout with no description
|
||||
StringFormatter::send(stream,F("]\\[%d}|{T%d}|{T%c"), id,id,tchar);
|
||||
else
|
||||
StringFormatter::send(stream,F("]\\[%d}|{%S}|{%c"), id,tdesc,tchar);
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
}
|
||||
void WiThrottle::sendRoster(Print* stream) {
|
||||
rosterSent=true;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount);
|
||||
for (int16_t r=0;r<RMFT2::rosterNameCount;r++) {
|
||||
int16_t cabid=GETHIGHFLASHW(RMFT2::rosterIdList,r*2);
|
||||
StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"),
|
||||
RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L');
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
#endif
|
||||
}
|
||||
void WiThrottle::sendRoutes(Print* stream) {
|
||||
routesSent=true;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL"));
|
||||
// first pass automations
|
||||
for (int ix=0;;ix+=2) {
|
||||
int16_t id =GETHIGHFLASHW(RMFT2::automationIdList,ix);
|
||||
if (id==0) break;
|
||||
const FSH * desc=RMFT2::getRouteDescription(id);
|
||||
StringFormatter::send(stream,F("]\\[A%d}|{%S}|{4"),id,desc);
|
||||
}
|
||||
// second pass routes.
|
||||
for (int ix=0;;ix+=2) {
|
||||
int16_t id=GETHIGHFLASHW(RMFT2::routeIdList,ix);
|
||||
if (id==0) break;
|
||||
const FSH * desc=RMFT2::getRouteDescription(id);
|
||||
StringFormatter::send(stream,F("]\\[R%d}|{%S}|{2"),id,desc);
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void WiThrottle::sendFunctions(Print* stream, byte loco) {
|
||||
int16_t locoid=myLocos[loco].cab;
|
||||
int fkeys=29;
|
||||
myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle
|
||||
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid);
|
||||
if (!functionNames) {
|
||||
// no roster, use non-exrail presets as above
|
||||
}
|
||||
else if (GETFLASH(functionNames)=='\0') {
|
||||
// "" = Roster but no functions given
|
||||
fkeys=0;
|
||||
}
|
||||
else {
|
||||
// we have function names...
|
||||
// scan names list emitting names, counting functions and
|
||||
// flagging non-toggling things like horn.
|
||||
myLocos[loco].functionToggles =0;
|
||||
StringFormatter::send(stream, F("M%cL%c%d<;>]\\["), myLocos[loco].throttle,LorS(locoid),locoid);
|
||||
fkeys=0;
|
||||
bool firstchar=true;
|
||||
for (int fx=0;;fx++) {
|
||||
char c=GETFLASH(functionNames+fx);
|
||||
if (c=='\0') {
|
||||
fkeys++;
|
||||
break;
|
||||
}
|
||||
if (c=='/') {
|
||||
fkeys++;
|
||||
StringFormatter::send(stream,F("]\\["));
|
||||
firstchar=true;
|
||||
}
|
||||
else if (firstchar && c=='*') {
|
||||
myLocos[loco].functionToggles |= 1UL<<fkeys;
|
||||
firstchar=false;
|
||||
}
|
||||
else {
|
||||
firstchar=false;
|
||||
stream->write(c);
|
||||
}
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
for(int fKey=0; fKey<fkeys; fKey++) {
|
||||
int fstate=DCC::getFn(locoid,fKey);
|
||||
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),myLocos[loco].throttle,LorS(locoid),locoid,fstate,fKey);
|
||||
}
|
||||
}
|
22
WiThrottle.h
22
WiThrottle.h
@@ -37,16 +37,13 @@ class WiThrottle {
|
||||
void parse(RingStream * stream, byte * cmd);
|
||||
static WiThrottle* getThrottle( int wifiClient);
|
||||
static void markForBroadcast(int cab);
|
||||
static void forget(byte clientId);
|
||||
static void findUniqThrottle(int id, char *u);
|
||||
|
||||
|
||||
private:
|
||||
WiThrottle( int wifiClientId);
|
||||
~WiThrottle();
|
||||
|
||||
static const int MAX_MY_LOCO=10; // maximum number of locos assigned to a single client
|
||||
static const int HEARTBEAT_SECONDS=10; // heartbeat at 10 secs to provide messaging transport
|
||||
static const int HEARTBEAT_PRELOAD=2; // request fast callback when connecting multiple messages
|
||||
static const int HEARTBEAT_SECONDS=10; // heartbeat at 4secs to provide messaging transport
|
||||
static const int ESTOP_SECONDS=20; // eStop if no incoming messages for more than 8secs
|
||||
static WiThrottle* firstThrottle;
|
||||
static int getInt(byte * cmd);
|
||||
@@ -57,19 +54,15 @@ class WiThrottle {
|
||||
bool areYouUsingThrottle(int cab);
|
||||
WiThrottle* nextThrottle;
|
||||
int clientid;
|
||||
char uniq[17] = "";
|
||||
|
||||
MYLOCO myLocos[MAX_MY_LOCO];
|
||||
bool heartBeatEnable;
|
||||
unsigned long heartBeat;
|
||||
bool introSent=false;
|
||||
bool turnoutsSent=false;
|
||||
bool rosterSent=false;
|
||||
bool routesSent=false;
|
||||
bool heartrateSent=false;
|
||||
bool initSent; // valid connection established
|
||||
bool exRailSent; // valid connection established
|
||||
uint16_t mostRecentCab;
|
||||
int turnoutListHash; // used to check for changes to turnout list
|
||||
bool lastPowerState; // last power state sent to this client
|
||||
|
||||
int DCCToWiTSpeed(int DCCSpeed);
|
||||
int WiTToDCCSpeed(int WiTSpeed);
|
||||
void multithrottle(RingStream * stream, byte * cmd);
|
||||
@@ -77,11 +70,6 @@ class WiThrottle {
|
||||
void accessory(RingStream *, byte* cmd);
|
||||
void checkHeartbeat(RingStream * stream);
|
||||
void markForBroadcast2(int cab);
|
||||
void sendIntro(Print * stream);
|
||||
void sendTurnouts(Print * stream);
|
||||
void sendRoster(Print * stream);
|
||||
void sendRoutes(Print * stream);
|
||||
void sendFunctions(Print* stream, byte loco);
|
||||
// callback stuff to support prog track acquire
|
||||
static RingStream * stashStream;
|
||||
static WiThrottle * stashInstance;
|
||||
|
360
WifiESP32.cpp
360
WifiESP32.cpp
@@ -1,360 +0,0 @@
|
||||
/*
|
||||
© 2021, Harald Barth.
|
||||
|
||||
This file is part of CommandStation-EX
|
||||
|
||||
This is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#include <vector>
|
||||
#include "defines.h"
|
||||
#include <WiFi.h>
|
||||
#include "esp_wifi.h"
|
||||
#include "WifiESP32.h"
|
||||
#include "DIAG.h"
|
||||
#include "RingStream.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "WiThrottle.h"
|
||||
/*
|
||||
#include "soc/rtc_wdt.h"
|
||||
#include "esp_task_wdt.h"
|
||||
*/
|
||||
|
||||
#include "soc/timer_group_struct.h"
|
||||
#include "soc/timer_group_reg.h"
|
||||
void feedTheDog0(){
|
||||
// feed dog 0
|
||||
TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable
|
||||
TIMERG0.wdt_feed=1; // feed dog
|
||||
TIMERG0.wdt_wprotect=0; // write protect
|
||||
// feed dog 1
|
||||
//TIMERG1.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable
|
||||
//TIMERG1.wdt_feed=1; // feed dog
|
||||
//TIMERG1.wdt_wprotect=0; // write protect
|
||||
}
|
||||
|
||||
/*
|
||||
void enableCoreWDT(byte core){
|
||||
TaskHandle_t idle = xTaskGetIdleTaskHandleForCPU(core);
|
||||
if(idle == NULL){
|
||||
DIAG(F("Get idle rask on core %d failed"),core);
|
||||
} else {
|
||||
if(esp_task_wdt_add(idle) != ESP_OK){
|
||||
DIAG(F("Failed to add Core %d IDLE task to WDT"),core);
|
||||
} else {
|
||||
DIAG(F("Added Core %d IDLE task to WDT"),core);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void disableCoreWDT(byte core){
|
||||
TaskHandle_t idle = xTaskGetIdleTaskHandleForCPU(core);
|
||||
if(idle == NULL || esp_task_wdt_delete(idle) != ESP_OK){
|
||||
DIAG(F("Failed to remove Core %d IDLE task from WDT"),core);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
class NetworkClient {
|
||||
public:
|
||||
NetworkClient(WiFiClient c) {
|
||||
wifi = c;
|
||||
};
|
||||
bool ok() {
|
||||
return (inUse && wifi.connected());
|
||||
};
|
||||
bool recycle(WiFiClient c) {
|
||||
|
||||
if (inUse == true) return false;
|
||||
|
||||
// return false here until we have
|
||||
// implemented a LRU timer
|
||||
// if (LRU too recent) return false;
|
||||
return false;
|
||||
|
||||
wifi = c;
|
||||
inUse = true;
|
||||
return true;
|
||||
};
|
||||
WiFiClient wifi;
|
||||
bool inUse = true;
|
||||
};
|
||||
|
||||
static std::vector<NetworkClient> clients; // a list to hold all clients
|
||||
static WiFiServer *server = NULL;
|
||||
static RingStream *outboundRing = new RingStream(10240);
|
||||
static bool APmode = false;
|
||||
|
||||
#ifdef WIFI_TASK_ON_CORE0
|
||||
void wifiLoop(void *){
|
||||
for(;;){
|
||||
WifiESP::loop();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool WifiESP::setup(const char *SSid,
|
||||
const char *password,
|
||||
const char *hostname,
|
||||
int port,
|
||||
const byte channel) {
|
||||
bool havePassword = true;
|
||||
bool haveSSID = true;
|
||||
bool wifiUp = false;
|
||||
uint8_t tries = 40;
|
||||
|
||||
//#ifdef SERIAL_BT_COMMANDS
|
||||
//return false;
|
||||
//#endif
|
||||
|
||||
// tests
|
||||
// enableCoreWDT(1);
|
||||
// disableCoreWDT(0);
|
||||
|
||||
// clean start
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.disconnect(true);
|
||||
// differnet settings that did not improve for haba
|
||||
// WiFi.useStaticBuffers(true);
|
||||
// WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
|
||||
// WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SECURITY);
|
||||
|
||||
const char *yourNetwork = "Your network ";
|
||||
if (strncmp(yourNetwork, SSid, 13) == 0 || strncmp("", SSid, 13) == 0)
|
||||
haveSSID = false;
|
||||
if (strncmp(yourNetwork, password, 13) == 0 || strncmp("", password, 13) == 0)
|
||||
havePassword = false;
|
||||
|
||||
if (haveSSID && havePassword) {
|
||||
WiFi.mode(WIFI_STA);
|
||||
#ifdef SERIAL_BT_COMMANDS
|
||||
WiFi.setSleep(true);
|
||||
#else
|
||||
WiFi.setSleep(false);
|
||||
#endif
|
||||
WiFi.setAutoReconnect(true);
|
||||
WiFi.begin(SSid, password);
|
||||
while (WiFi.status() != WL_CONNECTED && tries) {
|
||||
Serial.print('.');
|
||||
tries--;
|
||||
delay(500);
|
||||
}
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
DIAG(F("Wifi STA IP %s"),WiFi.localIP().toString().c_str());
|
||||
wifiUp = true;
|
||||
} else {
|
||||
DIAG(F("Could not connect to Wifi SSID %s"),SSid);
|
||||
DIAG(F("Forcing one more Wifi restart"));
|
||||
esp_wifi_start();
|
||||
esp_wifi_connect();
|
||||
tries=40;
|
||||
while (WiFi.status() != WL_CONNECTED && tries) {
|
||||
Serial.print('.');
|
||||
tries--;
|
||||
delay(500);
|
||||
}
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
DIAG(F("Wifi STA IP 2nd try %s"),WiFi.localIP().toString().c_str());
|
||||
wifiUp = true;
|
||||
} else {
|
||||
DIAG(F("Wifi STA mode FAIL. Will revert to AP mode"));
|
||||
haveSSID=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!haveSSID) {
|
||||
// prepare all strings
|
||||
String strSSID("DCC_");
|
||||
String strPass("PASS_");
|
||||
String strMac = WiFi.macAddress();
|
||||
strMac.remove(0,9);
|
||||
strMac.replace(":","");
|
||||
strMac.replace(":","");
|
||||
strSSID.concat(strMac);
|
||||
strPass.concat(strMac);
|
||||
|
||||
WiFi.mode(WIFI_AP);
|
||||
#ifdef SERIAL_BT_COMMANDS
|
||||
WiFi.setSleep(true);
|
||||
#else
|
||||
WiFi.setSleep(false);
|
||||
#endif
|
||||
if (WiFi.softAP(strSSID.c_str(),
|
||||
havePassword ? password : strPass.c_str(),
|
||||
channel, false, 8)) {
|
||||
DIAG(F("Wifi AP SSID %s PASS %s"),strSSID.c_str(),havePassword ? password : strPass.c_str());
|
||||
DIAG(F("Wifi AP IP %s"),WiFi.softAPIP().toString().c_str());
|
||||
wifiUp = true;
|
||||
APmode = true;
|
||||
} else {
|
||||
DIAG(F("Could not set up AP with Wifi SSID %s"),strSSID.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!wifiUp) {
|
||||
DIAG(F("Wifi setup all fail (STA and AP mode)"));
|
||||
// no idea to go on
|
||||
return false;
|
||||
}
|
||||
server = new WiFiServer(port); // start listening on tcp port
|
||||
server->begin();
|
||||
// server started here
|
||||
|
||||
#ifdef WIFI_TASK_ON_CORE0
|
||||
//start loop task
|
||||
if (pdPASS != xTaskCreatePinnedToCore(
|
||||
wifiLoop, /* Task function. */
|
||||
"wifiLoop",/* name of task. */
|
||||
10000, /* Stack size of task */
|
||||
NULL, /* parameter of the task */
|
||||
1, /* priority of the task */
|
||||
NULL, /* Task handle to keep track of created task */
|
||||
0)) { /* pin task to core 0 */
|
||||
DIAG(F("Could not create wifiLoop task"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// report server started after wifiLoop creation
|
||||
// when everything looks good
|
||||
DIAG(F("Server starting (core 0) port %d"),port);
|
||||
#else
|
||||
DIAG(F("Server will be started on port %d"),port);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *wlerror[] = {
|
||||
"WL_IDLE_STATUS",
|
||||
"WL_NO_SSID_AVAIL",
|
||||
"WL_SCAN_COMPLETED",
|
||||
"WL_CONNECTED",
|
||||
"WL_CONNECT_FAILED",
|
||||
"WL_CONNECTION_LOST",
|
||||
"WL_DISCONNECTED"
|
||||
};
|
||||
|
||||
void WifiESP::loop() {
|
||||
int clientId; //tmp loop var
|
||||
|
||||
// really no good way to check for LISTEN especially in AP mode?
|
||||
wl_status_t wlStatus;
|
||||
if (APmode || (wlStatus = WiFi.status()) == WL_CONNECTED) {
|
||||
// loop over all clients and remove inactive
|
||||
for (clientId=0; clientId<clients.size(); clientId++){
|
||||
// check if client is there and alive
|
||||
if(clients[clientId].inUse && !clients[clientId].wifi.connected()) {
|
||||
DIAG(F("Remove client %d"), clientId);
|
||||
CommandDistributor::forget(clientId);
|
||||
clients[clientId].wifi.stop();
|
||||
clients[clientId].inUse = false;
|
||||
//Do NOT clients.erase(clients.begin()+clientId) as
|
||||
//that would mix up clientIds for later.
|
||||
}
|
||||
}
|
||||
if (server->hasClient()) {
|
||||
WiFiClient client;
|
||||
while (client = server->available()) {
|
||||
for (clientId=0; clientId<clients.size(); clientId++){
|
||||
if (clients[clientId].recycle(client)) {
|
||||
DIAG(F("Recycle client %d %s"), clientId, client.remoteIP().toString().c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (clientId>=clients.size()) {
|
||||
NetworkClient nc(client);
|
||||
clients.push_back(nc);
|
||||
DIAG(F("New client %d, %s"), clientId, client.remoteIP().toString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
// loop over all connected clients
|
||||
for (clientId=0; clientId<clients.size(); clientId++){
|
||||
if(clients[clientId].ok()) {
|
||||
int len;
|
||||
if ((len = clients[clientId].wifi.available()) > 0) {
|
||||
// read data from client
|
||||
byte cmd[len+1];
|
||||
for(int i=0; i<len; i++) {
|
||||
cmd[i]=clients[clientId].wifi.read();
|
||||
}
|
||||
cmd[len]=0;
|
||||
CommandDistributor::parse(clientId,cmd,outboundRing);
|
||||
}
|
||||
}
|
||||
} // all clients
|
||||
|
||||
WiThrottle::loop(outboundRing);
|
||||
|
||||
// something to write out?
|
||||
clientId=outboundRing->read();
|
||||
if (clientId >= 0) {
|
||||
// We have data to send in outboundRing
|
||||
// and we have a valid clientId.
|
||||
// First read it out to buffer
|
||||
// and then look if it can be sent because
|
||||
// we can not leave it in the ring for ever
|
||||
int count=outboundRing->count();
|
||||
{
|
||||
char buffer[count+1]; // one extra for '\0'
|
||||
for(int i=0;i<count;i++) {
|
||||
int c = outboundRing->read();
|
||||
if (c >= 0) // Panic check, should never be false
|
||||
buffer[i] = (char)c;
|
||||
else {
|
||||
DIAG(F("Ringread fail at %d"),i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// buffer filled, end with '\0' so we can use it as C string
|
||||
buffer[count]='\0';
|
||||
if((unsigned int)clientId <= clients.size() && clients[clientId].ok()) {
|
||||
if (Diag::CMD || Diag::WITHROTTLE)
|
||||
DIAG(F("SEND %d:%s"), clientId, buffer);
|
||||
clients[clientId].wifi.write(buffer,count);
|
||||
} else {
|
||||
DIAG(F("Unsent(%d): %s"), clientId, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!APmode) { // in STA mode but not connected any more
|
||||
// kick it again
|
||||
if (wlStatus <= 6) {
|
||||
DIAG(F("Wifi aborted with error %s. Kicking Wifi!"), wlerror[wlStatus]);
|
||||
esp_wifi_start();
|
||||
esp_wifi_connect();
|
||||
uint8_t tries=40;
|
||||
while (WiFi.status() != WL_CONNECTED && tries) {
|
||||
Serial.print('.');
|
||||
tries--;
|
||||
delay(500);
|
||||
}
|
||||
} else {
|
||||
// all well, probably
|
||||
//DIAG(F("Running BT"));
|
||||
}
|
||||
}
|
||||
|
||||
// when loop() is running on core0 we must
|
||||
// feed the core0 wdt ourselves as yield()
|
||||
// is not necessarily yielding to a low
|
||||
// prio task. On core1 this is not a problem
|
||||
// as there the wdt is disabled by the
|
||||
// arduio IDE startup routines.
|
||||
if (xPortGetCoreID() == 0)
|
||||
feedTheDog0();
|
||||
yield();
|
||||
}
|
||||
#endif //ESP32
|
39
WifiESP32.h
39
WifiESP32.h
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* © 2021, Harald Barth.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#ifndef WifiESP32_h
|
||||
#define WifiESP32_h
|
||||
|
||||
#include "FSH.h"
|
||||
|
||||
class WifiESP
|
||||
{
|
||||
|
||||
public:
|
||||
static bool setup(const char *wifiESSID,
|
||||
const char *wifiPassword,
|
||||
const char *hostname,
|
||||
const int port,
|
||||
const byte channel);
|
||||
static void loop();
|
||||
private:
|
||||
};
|
||||
#endif //WifiESP8266_h
|
||||
#endif //ESP8266
|
@@ -66,7 +66,7 @@ void WifiInboundHandler::loop1() {
|
||||
}
|
||||
|
||||
|
||||
if (pendingCipsend && millis()-lastCIPSEND > CIPSENDgap) {
|
||||
if (pendingCipsend) {
|
||||
if (Diag::WIFI) DIAG( F("WiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize);
|
||||
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientPendingCIPSEND, currentReplySize);
|
||||
pendingCipsend=false;
|
||||
@@ -84,7 +84,13 @@ void WifiInboundHandler::loop1() {
|
||||
cmd[count]=0;
|
||||
if (Diag::WIFI) DIAG(F("%e"),cmd);
|
||||
|
||||
outboundRing->mark(clientId); // remember start of outbound data
|
||||
CommandDistributor::parse(clientId,cmd,outboundRing);
|
||||
// The commit call will either write the lenbgth bytes
|
||||
// OR rollback to the mark because the reply is empty or commend generated more than fits the buffer
|
||||
if (!outboundRing->commit()) {
|
||||
DIAG(F("OUTBOUND FULL processing cmd:%s"),cmd);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -131,13 +137,11 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
|
||||
|
||||
if (ch=='S') { // SEND OK probably
|
||||
loopState=SKIPTOEND;
|
||||
lastCIPSEND=0; // no need to wait next time
|
||||
break;
|
||||
}
|
||||
|
||||
if (ch=='b') { // This is a busy indicator... probabaly must restart a CIPSEND
|
||||
pendingCipsend=(clientPendingCIPSEND>=0);
|
||||
if (pendingCipsend) lastCIPSEND=millis(); // forces a gap to next CIPSEND
|
||||
loopState=SKIPTOEND;
|
||||
break;
|
||||
}
|
||||
|
@@ -68,9 +68,7 @@ class WifiInboundHandler {
|
||||
Stream * wifiStream;
|
||||
|
||||
static const int INBOUND_RING = 512;
|
||||
static const int OUTBOUND_RING = sizeof(void*)==2?2048:8192;
|
||||
|
||||
static const int CIPSENDgap=100; // millis() between retries of cipsend.
|
||||
static const int OUTBOUND_RING = 2048;
|
||||
|
||||
RingStream * inboundRing;
|
||||
RingStream * outboundRing;
|
||||
@@ -81,7 +79,5 @@ class WifiInboundHandler {
|
||||
int clientPendingCIPSEND=-1;
|
||||
int currentReplySize;
|
||||
bool pendingCipsend;
|
||||
uint32_t lastCIPSEND=0; // millis() of previous cipsend
|
||||
|
||||
};
|
||||
#endif
|
||||
|
@@ -22,7 +22,7 @@
|
||||
#ifndef ARDUINO_AVR_UNO_WIFI_REV2
|
||||
// This code is NOT compiled on a unoWifiRev2 processor which uses a different architecture
|
||||
#include "WifiInterface.h" /* config.h included there */
|
||||
//#include <avr/pgmspace.h>
|
||||
#include <avr/pgmspace.h>
|
||||
#include "DIAG.h"
|
||||
#include "StringFormatter.h"
|
||||
|
||||
@@ -276,7 +276,6 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||
checkForOK(2000, true);
|
||||
}
|
||||
}
|
||||
#endif //DONT_TOUCH_WIFI_CONF
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CIPSERVER=0\r\n")); // turn off tcp server (to clean connections before CIPMUX=1)
|
||||
checkForOK(1000, true); // ignore result in case it already was off
|
||||
@@ -292,6 +291,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CIPSERVER=1,%d\r\n"), port); // turn on server on port
|
||||
if (!checkForOK(1000, true)) return WIFI_DISCONNECTED;
|
||||
#endif //DONT_TOUCH_WIFI_CONF
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); // Display ip addresses to the DIAG
|
||||
if (!checkForOK(1000, F("IP,\"") , true, false)) return WIFI_DISCONNECTED;
|
||||
@@ -344,10 +344,11 @@ void WifiInterface::ATCommand(HardwareSerial * stream,const byte * command) {
|
||||
while (wifiStream->available()) stream->write(wifiStream->read());
|
||||
if (stream->available()) {
|
||||
int cx=stream->read();
|
||||
// A newline followed by ! is an exit
|
||||
// A newline followed by !!! is an exit
|
||||
if (cx=='\n' || cx=='\r') startOfLine=true;
|
||||
else if (startOfLine && cx=='!') break;
|
||||
else startOfLine=false;
|
||||
stream->write(cx);
|
||||
wifiStream->write(cx);
|
||||
}
|
||||
}
|
||||
@@ -376,12 +377,11 @@ bool WifiInterface::checkForOK( const unsigned int timeout, const FSH * waitfor,
|
||||
char *locator = (char *)waitfor;
|
||||
DIAG(F("Wifi Check: [%E]"), waitfor);
|
||||
while ( millis() - startTime < timeout) {
|
||||
int nextchar;
|
||||
while (wifiStream->available() && (nextchar = wifiStream->read()) > -1) {
|
||||
char ch = (char)nextchar;
|
||||
while (wifiStream->available()) {
|
||||
int ch = wifiStream->read();
|
||||
if (echo) {
|
||||
if (escapeEcho) StringFormatter::printEscape( ch); /// THIS IS A DIAG IN DISGUISE
|
||||
else USB_SERIAL.print(ch);
|
||||
else StringFormatter::diagSerial->print((char)ch);
|
||||
}
|
||||
if (ch != GETFLASH(locator)) locator = (char *)waitfor;
|
||||
if (ch == GETFLASH(locator)) {
|
||||
|
@@ -23,7 +23,7 @@
|
||||
#include "FSH.h"
|
||||
#include "DCCEXParser.h"
|
||||
#include <Arduino.h>
|
||||
//#include <avr/pgmspace.h>
|
||||
#include <avr/pgmspace.h>
|
||||
|
||||
enum wifiSerialState { WIFI_NOAT, WIFI_DISCONNECTED, WIFI_CONNECTED };
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
/*
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020-2021 Harald Barth
|
||||
* © 2020-2021 Fred Decker
|
||||
@@ -38,7 +37,6 @@ The configuration file for DCC-EX Command Station
|
||||
//
|
||||
// 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)
|
||||
// POLOLU_TB9051FTG : Pololu Dual TB9051FTG Motor Driver
|
||||
// FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection)
|
||||
// FIREBOX_MK1 : The Firebox MK1
|
||||
// FIREBOX_MK1S : The Firebox MK1S
|
||||
@@ -144,9 +142,6 @@ The configuration file for DCC-EX Command Station
|
||||
// and want to use the EX-RAIL automation. Otherwise you do not have enough RAM
|
||||
// to do that. Of course, then none of the EEPROM related commands work.
|
||||
//
|
||||
// EEPROM does not work on ESP32. So on ESP32, EEPROM will always be disabled,
|
||||
// at least until it works.
|
||||
//
|
||||
// #define DISABLE_EEPROM
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -192,36 +187,13 @@ The configuration file for DCC-EX Command Station
|
||||
// HANDLING MULTIPLE SERIAL THROTTLES
|
||||
// The command station always operates with the default Serial port.
|
||||
// Diagnostics are only emitted on the default serial port and not broadcast.
|
||||
// Other serial throttles may be added to the Serial1, Serial2, Serial3, Serial4,
|
||||
// Serial5, and Serial6 ports which may or may not exist on your CPU. (Mega has 3,
|
||||
// SAMD/SAMC and STM32 have up to 6.)
|
||||
// Other serial throttles may be added to the Serial1, Serial2, Serial3 ports
|
||||
// which may or may not exist on your CPU. (Mega has all 3)
|
||||
// To monitor a throttle on one or more serial ports, uncomment the defines below.
|
||||
// NOTE: do not define here the WiFi shield serial port or your wifi will not work.
|
||||
//
|
||||
//#define SERIAL1_COMMANDS
|
||||
//#define SERIAL2_COMMANDS
|
||||
//#define SERIAL3_COMMANDS
|
||||
//#define SERIAL4_COMMANDS
|
||||
//#define SERIAL5_COMMANDS
|
||||
//#define SERIAL6_COMMANDS
|
||||
//
|
||||
// BLUETOOTH SERIAL ON ESP32
|
||||
// On ESP32 you have the possibility to use the builtin BT serial to connect to
|
||||
// the CS.
|
||||
//
|
||||
// The CS shows up as a pairable BT Clasic device. Name is "DCCEX-hexnumber".
|
||||
// BT is as an additional serial port, debug messages are still sent over USB,
|
||||
// not BT serial.
|
||||
//
|
||||
// If you enable this there are some implications:
|
||||
// 1. WiFi will sleep more (as WiFi and BT share the radio. So WiFi performance
|
||||
// may suffer
|
||||
// 2. The app will be bigger that 1.2MB, so the default partition scheme will not
|
||||
// work any more. You need to choose a partition scheme with 2MB (or bigger).
|
||||
// For example "NO OTA (2MB APP, 2MB SPIFFS)" in the Arduino IDE.
|
||||
// 3. There is no securuity (PIN) implemented. Everyone in radio range can pair
|
||||
// with your CS.
|
||||
//
|
||||
//#define SERIAL_BT_COMMANDS
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
133
defines.h
133
defines.h
@@ -1,9 +1,8 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2022 Harald Barth
|
||||
* © 2020-2021 Harald Barth
|
||||
* © 2020-2021 Chris Harlow
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -25,6 +24,7 @@
|
||||
|
||||
#ifndef DEFINES_H
|
||||
#define DEFINES_H
|
||||
|
||||
// defines.h relies on macros defined in config.h
|
||||
// but it may have already been included (for cosmetic convenence) by the .ino
|
||||
#ifndef MOTOR_SHIELD_TYPE
|
||||
@@ -35,133 +35,16 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Create a cpu type we can share and
|
||||
// figure out if we have enough memory for advanced features
|
||||
// so define HAS_ENOUGH_MEMORY until proved otherwise.
|
||||
#define HAS_ENOUGH_MEMORY
|
||||
#undef USB_SERIAL // Teensy has this defined by default...
|
||||
#define USB_SERIAL Serial
|
||||
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
#define ARDUINO_TYPE "UNO"
|
||||
#undef HAS_ENOUGH_MEMORY
|
||||
#elif defined(ARDUINO_AVR_NANO)
|
||||
#define ARDUINO_TYPE "NANO"
|
||||
#undef HAS_ENOUGH_MEMORY
|
||||
#elif defined(ARDUINO_AVR_MEGA)
|
||||
#define ARDUINO_TYPE "MEGA"
|
||||
#elif defined(ARDUINO_AVR_MEGA2560)
|
||||
#define ARDUINO_TYPE "MEGA"
|
||||
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#define ARDUINO_TYPE "MEGAAVR"
|
||||
#undef HAS_ENOUGH_MEMORY
|
||||
#elif defined(ARDUINO_TEENSY31)
|
||||
#define ARDUINO_TYPE "TEENSY3132"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// Teensy support for native I2C is awaiting development
|
||||
#ifndef I2C_NO_INTERRUPTS
|
||||
#define I2C_NO_INTERRUPTS
|
||||
#endif
|
||||
#elif defined(ARDUINO_TEENSY35)
|
||||
#define ARDUINO_TYPE "TEENSY35"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
// Teensy support for I2C is awaiting development
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// Teensy support for native I2C is awaiting development
|
||||
#ifndef I2C_NO_INTERRUPTS
|
||||
#define I2C_NO_INTERRUPTS
|
||||
#endif
|
||||
#elif defined(ARDUINO_TEENSY36)
|
||||
#define ARDUINO_TYPE "TEENSY36"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// Teensy support for native I2C is awaiting development
|
||||
#ifndef I2C_NO_INTERRUPTS
|
||||
#define I2C_NO_INTERRUPTS
|
||||
#endif
|
||||
#elif defined(ARDUINO_TEENSY40)
|
||||
#define ARDUINO_TYPE "TEENSY40"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// Teensy support for native I2C is awaiting development
|
||||
#ifndef I2C_NO_INTERRUPTS
|
||||
#define I2C_NO_INTERRUPTS
|
||||
#endif
|
||||
#elif defined(ARDUINO_TEENSY41)
|
||||
#define ARDUINO_TYPE "TEENSY41"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// Teensy support for native I2C is awaiting development
|
||||
#ifndef I2C_NO_INTERRUPTS
|
||||
#define I2C_NO_INTERRUPTS
|
||||
#endif
|
||||
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||
#define ARDUINO_TYPE "ESP8266"
|
||||
#warning "ESP8266 platform untested, you are on your own"
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
#define ARDUINO_TYPE "ESP32"
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
#elif defined(ARDUINO_ARCH_SAMD)
|
||||
#define ARDUINO_TYPE "SAMD21"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
// SAMD no EEPROM by default
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
#elif defined(ARDUINO_ARCH_STM32)
|
||||
#define ARDUINO_TYPE "STM32"
|
||||
// STM32 no EEPROM by default
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// STM32 support for native I2C is awaiting development
|
||||
#ifndef I2C_NO_INTERRUPTS
|
||||
#define I2C_NO_INTERRUPTS
|
||||
#endif
|
||||
|
||||
|
||||
/* TODO when ready
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
#define ARDUINO_TYPE "RP2040"
|
||||
*/
|
||||
|
||||
#else
|
||||
#define CPU_TYPE_ERROR
|
||||
#endif
|
||||
|
||||
// replace board type if provided by compiler
|
||||
#ifdef BOARD_NAME
|
||||
#undef ARDUINO_TYPE
|
||||
#define ARDUINO_TYPE BOARD_NAME
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// WIFI_ON: All prereqs for running with WIFI are met
|
||||
// Note: WIFI_CHANNEL may not exist in early config.h files so is added here if needed.
|
||||
|
||||
#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO)) || defined(ARDUINO_AVR_NANO_EVERY)
|
||||
#define BIG_RAM
|
||||
#endif
|
||||
#if ENABLE_WIFI
|
||||
#if defined(HAS_ENOUGH_MEMORY)
|
||||
#if defined(BIG_RAM)
|
||||
#define WIFI_ON true
|
||||
#ifndef WIFI_CHANNEL
|
||||
#define WIFI_CHANNEL 1
|
||||
@@ -175,7 +58,7 @@
|
||||
#endif
|
||||
|
||||
#if ENABLE_ETHERNET
|
||||
#if defined(HAS_ENOUGH_MEMORY)
|
||||
#if defined(BIG_RAM)
|
||||
#define ETHERNET_ON true
|
||||
#else
|
||||
#define ETHERNET_WARNING
|
||||
@@ -197,7 +80,7 @@
|
||||
#define WIFI_SERIAL_LINK_SPEED 115200
|
||||
|
||||
#if __has_include ( "myAutomation.h")
|
||||
#if defined(HAS_ENOUGH_MEMORY) || defined(DISABLE_EEPROM)
|
||||
#if defined(BIG_RAM) || defined(DISABLE_EEPROM)
|
||||
#define EXRAIL_ACTIVE
|
||||
#else
|
||||
#define EXRAIL_WARNING
|
||||
|
112
freeMemory.cpp
Normal file
112
freeMemory.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021 Mike S
|
||||
* © 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#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
|
||||
|
||||
|
||||
static volatile int minimum_free_memory = __INT_MAX__;
|
||||
|
||||
#if !defined(__IMXRT1062__)
|
||||
static inline int freeMemory() {
|
||||
char top;
|
||||
#if defined(__arm__)
|
||||
return &top - reinterpret_cast<char*>(sbrk(0));
|
||||
#elif defined(__AVR__)
|
||||
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
|
||||
#else
|
||||
#error bailed out already above
|
||||
#endif
|
||||
}
|
||||
|
||||
// Return low memory value.
|
||||
int minimumFreeMemory() {
|
||||
byte sreg_save = SREG;
|
||||
noInterrupts(); // Disable interrupts
|
||||
int retval = minimum_free_memory;
|
||||
SREG = sreg_save; // Restore interrupt state
|
||||
return retval;
|
||||
}
|
||||
|
||||
#else
|
||||
#if defined(ARDUINO_TEENSY40)
|
||||
static const unsigned DTCM_START = 0x20000000UL;
|
||||
static const unsigned OCRAM_START = 0x20200000UL;
|
||||
static const unsigned OCRAM_SIZE = 512;
|
||||
static const unsigned FLASH_SIZE = 1984;
|
||||
#elif defined(ARDUINO_TEENSY41)
|
||||
static const unsigned DTCM_START = 0x20000000UL;
|
||||
static const unsigned OCRAM_START = 0x20200000UL;
|
||||
static const unsigned OCRAM_SIZE = 512;
|
||||
static const unsigned FLASH_SIZE = 7936;
|
||||
#if TEENSYDUINO>151
|
||||
extern "C" uint8_t external_psram_size;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static inline int freeMemory() {
|
||||
extern unsigned long _ebss;
|
||||
extern unsigned long _sdata;
|
||||
extern unsigned long _estack;
|
||||
const unsigned DTCM_START = 0x20000000UL;
|
||||
unsigned dtcm = (unsigned)&_estack - DTCM_START;
|
||||
unsigned stackinuse = (unsigned) &_estack - (unsigned) __builtin_frame_address(0);
|
||||
unsigned varsinuse = (unsigned)&_ebss - (unsigned)&_sdata;
|
||||
unsigned freemem = dtcm - (stackinuse + varsinuse);
|
||||
return freemem;
|
||||
}
|
||||
|
||||
// Return low memory value.
|
||||
int minimumFreeMemory() {
|
||||
//byte sreg_save = SREG;
|
||||
//noInterrupts(); // Disable interrupts
|
||||
int retval = minimum_free_memory;
|
||||
//SREG = sreg_save; // Restore interrupt state
|
||||
return retval;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// Update low ram level. Allow for extra bytes to be specified
|
||||
// by estimation or inspection, that may be used by other
|
||||
// called subroutines. Must be called with interrupts disabled.
|
||||
//
|
||||
// Although __brkval may go up and down as heap memory is allocated
|
||||
// and freed, this function records only the worst case encountered.
|
||||
// So even if all of the heap is freed, the reported minimum free
|
||||
// memory will not increase.
|
||||
//
|
||||
void updateMinimumFreeMemory(unsigned char extraBytes) {
|
||||
int spare = freeMemory()-extraBytes;
|
||||
if (spare < 0) spare = 0;
|
||||
if (spare < minimum_free_memory) minimum_free_memory = spare;
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* © 2022 Harald Barth
|
||||
* All rights reserved.
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020 Harald Barth
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
* This file is part of DCC-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -17,10 +17,9 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency);
|
||||
void DCCEXanalogWrite(uint8_t pin, int value);
|
||||
#endif
|
||||
|
||||
#ifndef freeMemory_h
|
||||
#define freeMemory_h
|
||||
void updateMinimumFreeMemory(unsigned char extraBytes=0);
|
||||
int minimumFreeMemory();
|
||||
#endif
|
101
installer.sh
101
installer.sh
@@ -1,101 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
#
|
||||
# © 2022 Harald Barth
|
||||
#
|
||||
# This file is part of CommandStation-EX
|
||||
#
|
||||
# This is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# It is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
#
|
||||
# Usage: mkdir DIRNAME ; cd DIRNAME ; ../installer.sh
|
||||
# or from install directory ./installer.sh
|
||||
#
|
||||
|
||||
DCCEXGITURL="https://github.com/DCC-EX/CommandStation-EX"
|
||||
ACLIINSTALL="https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh"
|
||||
ACLI="./bin/arduino-cli"
|
||||
|
||||
function need () {
|
||||
type -p $1 > /dev/null && return
|
||||
sudo apt-get install $1
|
||||
type -p $1 > /dev/null && return
|
||||
echo "Could not install $1, abort"
|
||||
exit 255
|
||||
}
|
||||
|
||||
|
||||
need git
|
||||
if test -d .git ; then
|
||||
: assume we are right here
|
||||
git pull
|
||||
else
|
||||
git clone "$DCCEXGITURL"
|
||||
cd `basename "$DCCEXGITURL"` || exit 255
|
||||
fi
|
||||
if test -f config.h ; then
|
||||
: all well
|
||||
else
|
||||
# need to do this config better
|
||||
cp -p config.example.h config.h
|
||||
fi
|
||||
need curl
|
||||
if test -x "$ACLI" ; then
|
||||
: all well
|
||||
else
|
||||
curl "$ACLIINSTALL" > acliinstall.sh
|
||||
chmod +x acliinstall.sh
|
||||
./acliinstall.sh
|
||||
fi
|
||||
|
||||
$ACLI core update-index || exit 255
|
||||
|
||||
# Board discovery
|
||||
BOARDS=/tmp/boards.$$
|
||||
$ACLI board list | grep serial > $BOARDS
|
||||
if test x`< $BOARDS wc -l` = 'x1' ; then
|
||||
LINE=`cat $BOARDS`
|
||||
else
|
||||
# ask user
|
||||
echo "What board to use? (give line number)"
|
||||
cat -n $BOARDS
|
||||
echo -n "> "
|
||||
LINE=`awk 'BEGIN {getline A < "/dev/tty"} ; A == NR {print}' $BOARDS`
|
||||
fi
|
||||
rm $BOARDS
|
||||
PORT=`echo $LINE | cut -d" " -f1`
|
||||
echo Will use port: $PORT
|
||||
|
||||
# FQBN discovery
|
||||
FQBN=`echo $LINE | egrep 'arduino:avr:[a-z][a-z]*' | sed 's/.*\(arduino:avr:[a-z][a-z]*\) .*/\1/1'`
|
||||
if test x$FQBN = x ; then
|
||||
# ask user
|
||||
cat > /tmp/fqbn.$$ <<EOF
|
||||
arduino:avr:uno
|
||||
arduino:avr:mega
|
||||
esp32:esp32:esp32
|
||||
EOF
|
||||
echo "What board type? (give line number)"
|
||||
cat -n /tmp/fqbn.$$
|
||||
echo -n "> "
|
||||
FQBN=`awk 'BEGIN {getline A < "/dev/tty"} ; A == NR {print}' /tmp/fqbn.$$`
|
||||
fi
|
||||
rm /tmp/fqbn.$$
|
||||
echo FQBN is $FQBN
|
||||
|
||||
# Install phase
|
||||
$ACLI core install `echo $FQBN | sed 's,:[^:]*$,,1'` # remove last component to get package
|
||||
$ACLI board attach -p $PORT --fqbn $FQBN $PWD
|
||||
$ACLI compile --fqbn $FQBN $PWD
|
||||
$ACLI upload -v -t -p $PORT $PWD
|
@@ -1,101 +0,0 @@
|
||||
/**************************************************************************************************
|
||||
* This is an example automation file to control EX-Turntable using recommended techniques.
|
||||
**************************************************************************************************
|
||||
* INSTRUCTIONS
|
||||
**************************************************************************************************
|
||||
* To use this example file as the starting point for your layout, there are two options:
|
||||
*
|
||||
* 1. If you don't have an existing "myAutomation.h" file, simply rename "myEX-Turntable.example.h" to
|
||||
* "myAutomation.h".
|
||||
* 2. If you have an existing "myAutomation.h" file, rename "myEX-Turntable.example.h" to "myEX-Turntable.h",
|
||||
* and then include it by adding the line below at the end of your existing "myAutomation.h", on a
|
||||
* line of its own:
|
||||
*
|
||||
* #include "myEX-Turntable.h"
|
||||
*
|
||||
* Note that there are further instructions in the documentation at https://dcc-ex.com/.
|
||||
*************************************************************************************************/
|
||||
|
||||
/**************************************************************************************************
|
||||
* The MOVETT() command below will automatically move your turntable to the defined step position on
|
||||
* start up.
|
||||
*
|
||||
* If you do not wish this to occur, simply comment the line out.
|
||||
*
|
||||
* NOTE: If you are including this file at the end of an existing "myAutomation.h" file, you will likely
|
||||
* need to move this line to the beginning of your existing "myAutomation.h" file in order for it to
|
||||
* be effective.
|
||||
*************************************************************************************************/
|
||||
MOVETT(600, 114, Turn)
|
||||
DONE
|
||||
|
||||
// For Conductor level users who wish to just use EX-Turntable, you don't need to understand this
|
||||
// and can move to defining the turntable positions below. You must, however, ensure this remains
|
||||
// before any position definitions or you will get compile errors when uploading.
|
||||
//
|
||||
// Definition of the EX_TURNTABLE macro to correctly create the ROUTEs required for each position.
|
||||
// This includes RESERVE()/FREE() to protect any automation activities.
|
||||
//
|
||||
#define EX_TURNTABLE(route_id, reserve_id, vpin, steps, activity, desc) \
|
||||
ROUTE(route_id, desc) \
|
||||
RESERVE(reserve_id) \
|
||||
MOVETT(vpin, steps, activity) \
|
||||
WAITFOR(vpin) \
|
||||
FREE(reserve_id) \
|
||||
DONE
|
||||
|
||||
/**************************************************************************************************
|
||||
* TURNTABLE POSITION DEFINITIONS
|
||||
*************************************************************************************************/
|
||||
// EX_TURNTABLE(route_id, reserve_id, vpin, steps, activity, desc)
|
||||
//
|
||||
// route_id = A unique number for each defined route, the route is what appears in throttles
|
||||
// reserve_id = A unique reservation number (0 - 255) to ensure nothing interferes with automation
|
||||
// vpin = The Vpin defined for the Turntable-EX device driver, default is 600
|
||||
// steps = The target step position
|
||||
// activity = The activity performed for this ROUTE (Note do not enclose in quotes "")
|
||||
// desc = Description that will appear in throttles (Must use quotes "")
|
||||
//
|
||||
EX_TURNTABLE(TTRoute1, Turntable, 600, 114, Turn, "Position 1")
|
||||
EX_TURNTABLE(TTRoute2, Turntable, 600, 227, Turn, "Position 2")
|
||||
EX_TURNTABLE(TTRoute3, Turntable, 600, 341, Turn, "Position 3")
|
||||
EX_TURNTABLE(TTRoute4, Turntable, 600, 2159, Turn, "Position 4")
|
||||
EX_TURNTABLE(TTRoute5, Turntable, 600, 2273, Turn, "Position 5")
|
||||
EX_TURNTABLE(TTRoute6, Turntable, 600, 2386, Turn, "Position 6")
|
||||
EX_TURNTABLE(TTRoute7, Turntable, 600, 0, Home, "Home turntable")
|
||||
|
||||
// Pre-defined aliases to ensure unique IDs are used.
|
||||
// Turntable reserve ID, valid is 0 - 255
|
||||
ALIAS(Turntable, 255)
|
||||
|
||||
// Turntable ROUTE ID reservations, using <? TTRouteX> for uniqueness:
|
||||
ALIAS(TTRoute1)
|
||||
ALIAS(TTRoute2)
|
||||
ALIAS(TTRoute3)
|
||||
ALIAS(TTRoute4)
|
||||
ALIAS(TTRoute5)
|
||||
ALIAS(TTRoute6)
|
||||
ALIAS(TTRoute7)
|
||||
ALIAS(TTRoute8)
|
||||
ALIAS(TTRoute9)
|
||||
ALIAS(TTRoute10)
|
||||
ALIAS(TTRoute11)
|
||||
ALIAS(TTRoute12)
|
||||
ALIAS(TTRoute13)
|
||||
ALIAS(TTRoute14)
|
||||
ALIAS(TTRoute15)
|
||||
ALIAS(TTRoute16)
|
||||
ALIAS(TTRoute17)
|
||||
ALIAS(TTRoute18)
|
||||
ALIAS(TTRoute19)
|
||||
ALIAS(TTRoute20)
|
||||
ALIAS(TTRoute21)
|
||||
ALIAS(TTRoute22)
|
||||
ALIAS(TTRoute23)
|
||||
ALIAS(TTRoute24)
|
||||
ALIAS(TTRoute25)
|
||||
ALIAS(TTRoute26)
|
||||
ALIAS(TTRoute27)
|
||||
ALIAS(TTRoute28)
|
||||
ALIAS(TTRoute29)
|
||||
ALIAS(TTRoute30)
|
@@ -16,10 +16,13 @@
|
||||
#if !defined(IO_NO_HAL)
|
||||
|
||||
// Include devices you need.
|
||||
#include "IODevice.h"
|
||||
#include "IODevice.h" // Always recommended to include this
|
||||
|
||||
// Optional includes, comment out if not in use
|
||||
#include "IO_HCSR04.h" // Ultrasonic range sensor
|
||||
#include "IO_VL53L0X.h" // Laser time-of-flight sensor
|
||||
#include "IO_DFPlayer.h" // MP3 sound player
|
||||
#include "IO_PCA9555.h" // PCA/TCA9555
|
||||
|
||||
|
||||
//==========================================================================
|
||||
@@ -60,6 +63,33 @@ void halSetup() {
|
||||
//MCP23017::create(196, 16, 0x22, 40);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines an PCA9555 / TCA95555 16-port I2C GPIO Extender module.
|
||||
//=======================================================================
|
||||
// The parameters are:
|
||||
// First Vpin=196
|
||||
// Number of VPINs=16 (numbered 196-211)
|
||||
// I2C address of module=0x22
|
||||
|
||||
//PCA9555::create(196, 16, 0x22);
|
||||
|
||||
// Alternative form, which allows the INT pin of the module to request a scan
|
||||
// by pulling Arduino pin 22 to ground. Means that the I2C isn't being polled
|
||||
// all the time, only when a change takes place. Multiple modules' INT pins
|
||||
// may be connected to the same Arduino pin.
|
||||
|
||||
//PCA9555::create(196, 16, 0x22, 22);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines an MCP23008 8-port I2C GPIO Extender module.
|
||||
//=======================================================================
|
||||
// The parameters are:
|
||||
// First Vpin=300
|
||||
// Number of VPINs=8 (numbered 300-307)
|
||||
// I2C address of module=0x22
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines an MCP23008 8-port I2C GPIO Extender module.
|
||||
//=======================================================================
|
||||
|
@@ -2,7 +2,7 @@ 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
|
||||
SET ELF=%TMP%\%a%\CommandStation-EX.ino.elf
|
||||
set PATH="C:\Program Files (x86)\Arduino\hardware\tools\avr\bin\";%PATH%
|
||||
|
||||
avr-objdump --private=mem-usage %ELF% >>%TMP%\OBJDUMP_%a%.txt
|
||||
ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt
|
||||
avr-objdump -x -C %ELF% | find ".text" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
|
||||
|
126
platformio.ini
126
platformio.ini
@@ -15,53 +15,22 @@ default_envs =
|
||||
mega328
|
||||
unowifiR2
|
||||
nano
|
||||
samd21-dev-usb
|
||||
samd21-zero-usb
|
||||
ESP32
|
||||
Nucleo-F411RE
|
||||
Teensy3.2
|
||||
Teensy3.5
|
||||
Teensy3.6
|
||||
Teensy4.0
|
||||
Teensy4.1
|
||||
src_dir = .
|
||||
include_dir = .
|
||||
|
||||
[env]
|
||||
build_flags = -Wall -Wextra
|
||||
|
||||
[env:samd21-dev-usb]
|
||||
[env:samd21]
|
||||
platform = atmelsam
|
||||
board = sparkfun_samd21_dev_usb
|
||||
framework = arduino
|
||||
upload_protocol = sam-ba
|
||||
lib_deps = ${env.lib_deps}
|
||||
upload_protocol = atmel-ice
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
SparkFun External EEPROM Arduino Library
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -std=c++17
|
||||
|
||||
[env:samd21-zero-usb]
|
||||
platform = atmelsam
|
||||
board = zeroUSB
|
||||
framework = arduino
|
||||
upload_protocol = sam-ba
|
||||
lib_deps = ${env.lib_deps}
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -std=c++17
|
||||
|
||||
; Firebox disabled for now
|
||||
; [env:samc21-firebox]
|
||||
; platform = atmelsam
|
||||
; board = firebox
|
||||
; framework = arduino
|
||||
; upload_protocol = atmel-ice
|
||||
; lib_deps =
|
||||
; ${env.lib_deps}
|
||||
; SparkFun External EEPROM Arduino Library
|
||||
;monitor_speed = 115200
|
||||
;monitor_echo = yes
|
||||
;build_flags = -std=c++17
|
||||
monitor_flags = --echo
|
||||
|
||||
[env:mega2560-debug]
|
||||
platform = atmelavr
|
||||
@@ -72,7 +41,7 @@ lib_deps =
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
monitor_flags = --echo
|
||||
build_flags = -DDIAG_IO -DDIAG_LOOPTIMES
|
||||
|
||||
[env:mega2560-no-HAL]
|
||||
@@ -84,7 +53,7 @@ lib_deps =
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
monitor_flags = --echo
|
||||
build_flags = -DIO_NO_HAL
|
||||
|
||||
[env:mega2560-I2C-wire]
|
||||
@@ -96,7 +65,7 @@ lib_deps =
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
monitor_flags = --echo
|
||||
build_flags = -DI2C_USE_WIRE
|
||||
|
||||
[env:mega2560]
|
||||
@@ -108,11 +77,7 @@ lib_deps =
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
; Example, but v12 does generate bigger binaries
|
||||
; platform_packages = toolchain-atmelavr@symlink:///opt/avr-gcc-12.1.0-x64-linux
|
||||
; Should make binaries smaller
|
||||
build_flags = -mcall-prologues
|
||||
monitor_flags = --echo
|
||||
|
||||
[env:mega328]
|
||||
platform = atmelavr
|
||||
@@ -123,7 +88,7 @@ lib_deps =
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
monitor_flags = --echo
|
||||
|
||||
[env:unowifiR2]
|
||||
platform = atmelmegaavr
|
||||
@@ -134,7 +99,7 @@ lib_deps =
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
monitor_flags = --echo
|
||||
build_flags = "-DF_CPU=16000000L -DARDUINO=10813 -DARDUINO_AVR_UNO_WIFI_DEV_ED -DARDUINO_ARCH_AVR -DESP_CH_UART -DESP_CH_UART_BR=19200"
|
||||
|
||||
[env:nanoevery]
|
||||
@@ -146,7 +111,7 @@ lib_deps =
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
monitor_flags = --echo
|
||||
upload_speed = 19200
|
||||
build_flags = -DDIAG_IO
|
||||
|
||||
@@ -159,71 +124,14 @@ lib_deps =
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
; Should make binaries smaller
|
||||
build_flags = -mcall-prologues
|
||||
monitor_flags = --echo
|
||||
|
||||
[env:nano]
|
||||
platform = atmelavr
|
||||
board = nanoatmega328new
|
||||
board_upload.maximum_size = 32256
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
[env:ESP32]
|
||||
platform = espressif32
|
||||
board = esp32dev
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
build_flags = -std=c++17
|
||||
|
||||
[env:Nucleo-F411RE]
|
||||
platform = ststm32
|
||||
board = nucleo_f411re
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
[env:Teensy3.2]
|
||||
platform = teensy
|
||||
board = teensy31
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
[env:Teensy3.5]
|
||||
platform = teensy
|
||||
board = teensy35
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
[env:Teensy3.6]
|
||||
platform = teensy
|
||||
board = teensy36
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
[env:Teensy4.0]
|
||||
platform = teensy
|
||||
board = teensy40
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
[env:Teensy4.1]
|
||||
platform = teensy
|
||||
board = teensy41
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore =
|
||||
monitor_flags = --echo
|
||||
|
58
version.h
58
version.h
@@ -3,62 +3,8 @@
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
|
||||
#define VERSION "4.2.9pre1"
|
||||
// 4.2.9 duinoNodes support
|
||||
// 4.2.8 HIGHMEM (EXRAIL support beyond 64kb)
|
||||
// Withrottle connect/disconnect improvements
|
||||
// Report BOARD_TYPE if provided by compiler
|
||||
// 4.2.7 FIX: Static IP addr
|
||||
// FIX: Reuse WiThrottle list entries
|
||||
// 4.2.6 FIX: Remove RAM thief
|
||||
// FIX: ADC port 8-15 fix
|
||||
// 4.2.5 Make GETFLASHW code more universal
|
||||
// FIX: Withrottle roster index
|
||||
// Ethernet start improvement and link detection
|
||||
// 4.2.4 ESP32 experimental BT support
|
||||
// More DC configurations possible and lower frequency
|
||||
// Handle decoders that do not ack at write better
|
||||
// 4.2.3 Bugfix direction when togging between MAIN and DC
|
||||
// Bugfix return fail when F/f argument out of range
|
||||
// More error checking for out of bounds motor driver current trip limit
|
||||
// 4.2.2 ESP32 beta
|
||||
// JOIN/UMJOIN on ESP32
|
||||
// 4.2.1 ESP32 alpha
|
||||
// Ready for alpha test on ESP32. Track switching with <=> untested
|
||||
// Send DCC signal on MAIN
|
||||
// Detects ACK on PROG
|
||||
// 4.2.0 Track Manager additions:
|
||||
// Broadcast improvements to separate <> and Withrottle responses
|
||||
// Float eliminated saving >1.5kb PROGMEM and speed.
|
||||
// SET_TRACK(track,mode) Functions (A-H, MAIN|PROG|DC|DCX|OFF)
|
||||
// New DC track function and DCX reverse polarity function
|
||||
// TrackManager DCC & DC up to 8 Districts Architecture
|
||||
// Automatic ALIAS(name)
|
||||
// Command Parser now accepts Underscore in Alias Names
|
||||
// 4.1.1 Bugfix: preserve turnout format
|
||||
// Bugfix: parse multiple commands in one buffer string correct
|
||||
// Bugfix: </> command signal status in Exrail
|
||||
// 4.1.0 ...
|
||||
//
|
||||
// 4.0.2 EXRAIL additions:
|
||||
// ACK defaults set to 50mA LIMIT, 2000uS MIN, 20000uS MAX
|
||||
// myFilter automatic detection (no need to call setFilter)
|
||||
// FIX negative route ids in WIthrottle problem.
|
||||
// IFRED(signal_id), IFAMBER(signal_id), IFGREEN(signal_id)
|
||||
// </RED signal_id> </AMBER signal_id> </GREEN signal_id> commands
|
||||
// <t cab> command to obtain current throttle settings
|
||||
// JA, JR, JT commands to obtain route, roster and turnout descriptions
|
||||
// HIDDEN turnouts
|
||||
// PARSE <> commands in EXRAIL
|
||||
// VIRTUAL_TURNOUT
|
||||
// </KILL ALL> and KILLALL command to stop all tasks.
|
||||
// FORGET forgets the current loco in DCC reminder tables.
|
||||
// Servo signals (SERVO_SIGNAL)
|
||||
// High-On signal pins (SIGNALH)
|
||||
// Wait for analog value (ATGTE, ATLT)
|
||||
// 4.0.1 Small EXRAIL updates
|
||||
// EXRAIL BROADCAST("msg")
|
||||
#define VERSION "4.0.1"
|
||||
// 4.0.1 EXRAIL BROADCAST("msg")
|
||||
// EXRAIL POWERON
|
||||
// 4.0.0 Major functional and non-functional changes.
|
||||
// Engine Driver "DriveAway" feature enhancement
|
||||
|
Reference in New Issue
Block a user