mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-11-24 00:26:13 +01:00
Merge branch 'devel' into devel-z21
This commit is contained in:
commit
259452c5ac
|
@ -30,6 +30,7 @@
|
|||
* © 2021 Neil McKechnie
|
||||
* © 2020-2021 Chris Harlow, Harald Barth, David Cutting,
|
||||
* Fred Decker, Gregor Baues, Anthony W - Dayton
|
||||
* © 2023 Nathan Kellenicki
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
|
@ -78,6 +79,12 @@ void setup()
|
|||
// Initialise HAL layer before reading EEprom or setting up MotorDrivers
|
||||
IODevice::begin();
|
||||
|
||||
// As the setup of a motor shield may require a read of the current sense input from the ADC,
|
||||
// let's make sure to initialise the ADCee class!
|
||||
ADCee::begin();
|
||||
// Set up MotorDrivers early to initialize all pins
|
||||
TrackManager::Setup(MOTOR_SHIELD_TYPE);
|
||||
|
||||
DISPLAY_START (
|
||||
// This block is still executed for DIAGS if display not in use
|
||||
LCD(0,F("DCC-EX v%S"),F(VERSION));
|
||||
|
@ -89,26 +96,19 @@ void setup()
|
|||
// 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);
|
||||
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
|
||||
#endif // WIFI_ON
|
||||
#else
|
||||
// ESP32 needs wifi on always
|
||||
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL);
|
||||
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
|
||||
#endif // ARDUINO_ARCH_ESP32
|
||||
|
||||
#if ETHERNET_ON
|
||||
EthernetInterface::setup();
|
||||
#endif // ETHERNET_ON
|
||||
|
||||
// As the setup of a motor shield may require a read of the current sense input from the ADC,
|
||||
// let's make sure to initialise the ADCee class!
|
||||
ADCee::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();
|
||||
|
||||
// Start RMFT aka EX-RAIL (ignored if no automnation)
|
||||
RMFT::begin();
|
||||
|
|
3
DCC.cpp
3
DCC.cpp
|
@ -60,8 +60,7 @@ const byte FN_GROUP_5=0x10;
|
|||
FSH* DCC::shieldName=NULL;
|
||||
byte DCC::globalSpeedsteps=128;
|
||||
|
||||
void DCC::begin(const FSH * motorShieldName) {
|
||||
shieldName=(FSH *)motorShieldName;
|
||||
void DCC::begin() {
|
||||
StringFormatter::send(&USB_SERIAL,F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
|
||||
#ifndef DISABLE_EEPROM
|
||||
// Load stuff from EEprom
|
||||
|
|
5
DCC.h
5
DCC.h
|
@ -51,7 +51,10 @@ const byte MAX_LOCOS = 30;
|
|||
class DCC
|
||||
{
|
||||
public:
|
||||
static void begin(const FSH * motorShieldName);
|
||||
static inline void setShieldName(const FSH * motorShieldName) {
|
||||
shieldName=(FSH *)motorShieldName;
|
||||
};
|
||||
static void begin();
|
||||
static void loop();
|
||||
|
||||
// Public DCC API functions
|
||||
|
|
|
@ -467,7 +467,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
|||
bool prog=false;
|
||||
bool join=false;
|
||||
if (params > 1) break;
|
||||
if (params==0 || MotorDriver::commonFaultPin) { // <1> or tracks can not be handled individually
|
||||
if (params==0) { // All
|
||||
main=true;
|
||||
prog=true;
|
||||
}
|
||||
|
@ -487,9 +487,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
|||
#endif
|
||||
else break; // will reply <X>
|
||||
}
|
||||
TrackManager::setJoin(join);
|
||||
if (main) TrackManager::setMainPower(POWERMODE::ON);
|
||||
if (prog) TrackManager::setProgPower(POWERMODE::ON);
|
||||
TrackManager::setJoin(join);
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
|
@ -500,7 +500,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
|||
bool main=false;
|
||||
bool prog=false;
|
||||
if (params > 1) break;
|
||||
if (params==0 || MotorDriver::commonFaultPin) { // <0> or tracks can not be handled individually
|
||||
if (params==0) { // All
|
||||
main=true;
|
||||
prog=true;
|
||||
}
|
||||
|
@ -516,12 +516,12 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
|||
else break; // will reply <X>
|
||||
}
|
||||
|
||||
TrackManager::setJoin(false);
|
||||
if (main) TrackManager::setMainPower(POWERMODE::OFF);
|
||||
if (prog) {
|
||||
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
|
||||
TrackManager::setProgPower(POWERMODE::OFF);
|
||||
}
|
||||
TrackManager::setJoin(false);
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
|
|
|
@ -194,8 +194,10 @@ int RMTChannel::RMTfillData(const byte buffer[], byte byteCount, byte repeatCoun
|
|||
setDCCBit1(data + bitcounter-1); // overwrite previous zero bit with one bit
|
||||
setEOT(data + bitcounter++); // EOT marker
|
||||
dataLen = bitcounter;
|
||||
noInterrupts(); // keep dataReady and dataRepeat consistnet to each other
|
||||
dataReady = true;
|
||||
dataRepeat = repeatCount+1; // repeatCount of 0 means send once
|
||||
interrupts();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -212,6 +214,8 @@ void IRAM_ATTR RMTChannel::RMTinterrupt() {
|
|||
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) // all data should go out at least once
|
||||
DIAG(F("Channel %d DCC signal lost data"), channel);
|
||||
}
|
||||
if (dataRepeat > 0) // if a repeat count was specified, work on that
|
||||
dataRepeat--;
|
||||
|
|
15
DCCTimer.h
15
DCCTimer.h
|
@ -105,9 +105,14 @@ private:
|
|||
// that an offset can be initialized.
|
||||
class ADCee {
|
||||
public:
|
||||
// init does add the pin to the list of scanned pins (if this
|
||||
// begin is called for any setup that must be done before
|
||||
// **init** can be called. On some architectures this involves ADC
|
||||
// initialisation and clock routing, sampling times etc.
|
||||
static void begin();
|
||||
// init adds 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.
|
||||
// read value (which is why it required begin to have been called first!)
|
||||
// It must be 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
|
||||
|
@ -116,19 +121,15 @@ public:
|
|||
static int read(uint8_t pin, bool fromISR=false);
|
||||
// returns possible max value that the ADC can return
|
||||
static int16_t ADCmax();
|
||||
// begin is called for any setup that must be done before
|
||||
// scan can be called.
|
||||
static void begin();
|
||||
private:
|
||||
// On platforms that scan, it is called from waveform ISR
|
||||
// only on a regular basis.
|
||||
static void scan();
|
||||
// bit array of used pins (max 16)
|
||||
static uint16_t usedpins;
|
||||
static uint8_t highestPin;
|
||||
// cached analog values (malloc:ed to actual number of ADC channels)
|
||||
static int *analogvals;
|
||||
// ids to scan (new way)
|
||||
static byte *idarr;
|
||||
// friend so that we can call scan() and begin()
|
||||
friend class DCCWaveform;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* © 2021 Mike S
|
||||
* © 2021-2022 Harald Barth
|
||||
* © 2021-2023 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* © 2021 Chris Harlow
|
||||
* © 2021 David Cutting
|
||||
|
@ -29,6 +29,9 @@
|
|||
#include <avr/boot.h>
|
||||
#include <avr/wdt.h>
|
||||
#include "DCCTimer.h"
|
||||
#ifdef DEBUG_ADC
|
||||
#include "TrackManager.h"
|
||||
#endif
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
|
||||
// Arduino nano, uno, mega etc
|
||||
|
@ -128,8 +131,8 @@ void DCCTimer::reset() {
|
|||
#define NUM_ADC_INPUTS 8
|
||||
#endif
|
||||
uint16_t ADCee::usedpins = 0;
|
||||
uint8_t ADCee::highestPin = 0;
|
||||
int * ADCee::analogvals = NULL;
|
||||
byte *ADCee::idarr = NULL;
|
||||
static bool ADCusesHighPort = false;
|
||||
|
||||
/*
|
||||
|
@ -139,28 +142,17 @@ static bool ADCusesHighPort = false;
|
|||
*/
|
||||
int ADCee::init(uint8_t pin) {
|
||||
uint8_t id = pin - A0;
|
||||
byte n;
|
||||
if (id >= NUM_ADC_INPUTS)
|
||||
return -1023;
|
||||
if (id > 7)
|
||||
ADCusesHighPort = true;
|
||||
pinMode(pin, INPUT);
|
||||
int value = analogRead(pin);
|
||||
if (analogvals == NULL) {
|
||||
if (analogvals == NULL)
|
||||
analogvals = (int *)calloc(NUM_ADC_INPUTS, sizeof(int));
|
||||
for (n=0 ; n < NUM_ADC_INPUTS; n++) // set unreasonable value at startup as marker
|
||||
analogvals[n] = -32768; // 16 bit int min value
|
||||
idarr = (byte *)calloc(NUM_ADC_INPUTS+1, sizeof(byte)); // +1 for terminator value
|
||||
for (n=0 ; n <= NUM_ADC_INPUTS; n++)
|
||||
idarr[n] = 255; // set 255 as end of array marker
|
||||
}
|
||||
analogvals[id] = value; // store before enable by idarr[n]
|
||||
for (n=0 ; n <= NUM_ADC_INPUTS; n++) {
|
||||
if (idarr[n] == 255) {
|
||||
idarr[n] = id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
analogvals[id] = value;
|
||||
usedpins |= (1<<id);
|
||||
if (id > highestPin) highestPin = id;
|
||||
return value;
|
||||
}
|
||||
int16_t ADCee::ADCmax() {
|
||||
|
@ -170,14 +162,14 @@ int16_t ADCee::ADCmax() {
|
|||
* 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;
|
||||
int a;
|
||||
if ((usedpins & (1<<id) ) == 0)
|
||||
return -1023;
|
||||
// we do not need to check (analogvals == NULL)
|
||||
// because usedpins would still be 0 in that case
|
||||
noInterrupts();
|
||||
a = analogvals[id];
|
||||
interrupts();
|
||||
if (!fromISR) noInterrupts();
|
||||
int a = analogvals[id];
|
||||
if (!fromISR) interrupts();
|
||||
return a;
|
||||
}
|
||||
/*
|
||||
|
@ -186,7 +178,8 @@ int ADCee::read(uint8_t pin, bool fromISR) {
|
|||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
void ADCee::scan() {
|
||||
static byte num = 0; // index into id array
|
||||
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) {
|
||||
|
@ -198,26 +191,49 @@ void ADCee::scan() {
|
|||
low = ADCL; //must read low before high
|
||||
high = ADCH;
|
||||
bitSet(ADCSRA, ADIF);
|
||||
analogvals[idarr[num]] = (high << 8) | low;
|
||||
analogvals[id] = (high << 8) | low;
|
||||
// advance at least one track
|
||||
#ifdef DEBUG_ADC
|
||||
if (id == 1) TrackManager::track[1]->setBrake(0);
|
||||
#endif
|
||||
waiting = false;
|
||||
id++;
|
||||
mask = mask << 1;
|
||||
if (id > highestPin) {
|
||||
id = 0;
|
||||
mask = 1;
|
||||
}
|
||||
}
|
||||
if (!waiting) {
|
||||
// cycle around in-use analogue pins
|
||||
num++;
|
||||
if (idarr[num] == 255)
|
||||
num = 0;
|
||||
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 (idarr[num] > 7) // if we use a high ADC pin
|
||||
if (id > 7) // if we use a high ADC pin
|
||||
bitSet(ADCSRB, MUX5); // set MUX5 bit
|
||||
else
|
||||
bitClear(ADCSRB, MUX5);
|
||||
}
|
||||
#endif
|
||||
ADMUX = (1 << REFS0) | (idarr[num] & 0x07); // select AVCC as reference and set MUX
|
||||
bitSet(ADCSRA, ADSC); // start conversion
|
||||
ADMUX=(1<<REFS0)|(id & 0x07); //select AVCC as reference and set MUX
|
||||
bitSet(ADCSRA,ADSC); // start conversion
|
||||
#ifdef DEBUG_ADC
|
||||
if (id == 1) TrackManager::track[1]->setBrake(1);
|
||||
#endif
|
||||
waiting = true;
|
||||
return;
|
||||
}
|
||||
id++;
|
||||
mask = mask << 1;
|
||||
if (id > highestPin) {
|
||||
id = 0;
|
||||
mask = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
|
|
|
@ -30,25 +30,31 @@
|
|||
#ifdef ARDUINO_ARCH_STM32
|
||||
|
||||
#include "DCCTimer.h"
|
||||
#ifdef DEBUG_ADC
|
||||
#include "TrackManager.h"
|
||||
#endif
|
||||
#include "DIAG.h"
|
||||
|
||||
#if defined(ARDUINO_NUCLEO_F411RE)
|
||||
// Nucleo-64 boards don't have Serial1 defined by default
|
||||
// Nucleo-64 boards don't have additional serial ports 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. 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 Nucleo-64s)
|
||||
HardwareSerial Serial3(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE
|
||||
HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE
|
||||
#elif defined(ARDUINO_NUCLEO_F446RE)
|
||||
// Nucleo-64 boards don't have Serial1 defined by default
|
||||
// Nucleo-64 boards don't have additional serial ports defined by default
|
||||
// On the F446RE, Serial1 isn't really useable as it's Rx/Tx pair sit on already used D2/D10 pins
|
||||
// HardwareSerial Serial1(PA10, PB6); // Rx=PA10 (D2), Tx=PB6 (D10) -- CN10 pins 17 and 9 - F446RE
|
||||
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
|
||||
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
|
||||
HardwareSerial Serial1(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F446RE
|
||||
HardwareSerial Serial3(PD2, PC12); // Rx=PC7, Tx=PC6 -- UART5 - F446RE
|
||||
// NB: USART3 and USART6 are available but as yet undefined
|
||||
// On the F446RE, Serial3 and Serial5 are easy to use:
|
||||
HardwareSerial Serial3(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F446RE
|
||||
HardwareSerial Serial5(PD2, PC12); // Rx=PC7, Tx=PC6 -- UART5 - F446RE
|
||||
// On the F446RE, Serial4 and Serial6 also use pins we can't readily map while using the Arduino pins
|
||||
#elif 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 -- USART6
|
||||
HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6
|
||||
// Serial3 is defined to use USART3 by default, but is in fact used as the diag console
|
||||
// via the debugger on the Nucleo-144. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
|
||||
#else
|
||||
|
@ -229,13 +235,16 @@ void DCCTimer::reset() {
|
|||
while(true) {};
|
||||
}
|
||||
|
||||
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
|
||||
|
||||
// TODO: may need to use uint32_t on STMF4xx variants with > 16 analog inputs!
|
||||
#if defined(ARDUINO_NUCLEO_F446RE) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||
#warning STM32 board selected not fully supported - only use ADC1 inputs 0-15 for current sensing!
|
||||
#endif
|
||||
// For now, define the max of 16 ports - some variants have more, but this not **yet** supported
|
||||
#define NUM_ADC_INPUTS 16
|
||||
// #define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
|
||||
|
||||
uint16_t ADCee::usedpins = 0;
|
||||
uint8_t ADCee::highestPin = 0;
|
||||
int * ADCee::analogvals = NULL;
|
||||
uint32_t * analogchans = NULL;
|
||||
bool adc1configured = false;
|
||||
|
@ -306,6 +315,9 @@ int ADCee::init(uint8_t pin) {
|
|||
analogvals[id] = value; // Store sampled value
|
||||
analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin
|
||||
usedpins |= (1 << id); // This pin is now ready
|
||||
if (id > highestPin) highestPin = id; // Store our highest pin in use
|
||||
|
||||
DIAG(F("ADCee::init(): value=%d, channel=%d, id=%d"), value, adcchan, id);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
@ -340,11 +352,13 @@ void ADCee::scan() {
|
|||
// found value
|
||||
analogvals[id] = ADC1->DR;
|
||||
// advance at least one track
|
||||
// for scope debug TrackManager::track[1]->setBrake(0);
|
||||
#ifdef DEBUG_ADC
|
||||
if (id == 1) TrackManager::track[1]->setBrake(0);
|
||||
#endif
|
||||
waiting = false;
|
||||
id++;
|
||||
mask = mask << 1;
|
||||
if (id == NUM_ADC_INPUTS+1) {
|
||||
if (id > highestPin) { // the 1 has been shifted out
|
||||
id = 0;
|
||||
mask = 1;
|
||||
}
|
||||
|
@ -358,13 +372,15 @@ void ADCee::scan() {
|
|||
// start new ADC aquire on id
|
||||
ADC1->SQR3 = analogchans[id]; //1st conversion in regular sequence
|
||||
ADC1->CR2 |= (1 << 30); //Start 1st conversion SWSTART
|
||||
// for scope debug TrackManager::track[1]->setBrake(1);
|
||||
#ifdef DEBUG_ADC
|
||||
if (id == 1) TrackManager::track[1]->setBrake(1);
|
||||
#endif
|
||||
waiting = true;
|
||||
return;
|
||||
}
|
||||
id++;
|
||||
mask = mask << 1;
|
||||
if (id == NUM_ADC_INPUTS+1) {
|
||||
if (id > highestPin) {
|
||||
id = 0;
|
||||
mask = 1;
|
||||
}
|
||||
|
|
|
@ -247,6 +247,9 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
|
|||
pendingPacket[byteCount] = checksum;
|
||||
pendingLength = byteCount + 1;
|
||||
pendingRepeats = repeats;
|
||||
// DIAG repeated commands (accesories)
|
||||
// if (pendingRepeats > 0)
|
||||
// DIAG(F("Repeats=%d on %s track"), pendingRepeats, isMainTrack ? "MAIN" : "PROG");
|
||||
// The resets will be zero not only now but as well repeats packets into the future
|
||||
clearResets(repeats+1);
|
||||
{
|
||||
|
|
|
@ -1 +1 @@
|
|||
#define GITHUB_SHA "devel-z21-202306082116Z"
|
||||
#define GITHUB_SHA "devel-z21-202307082214Z"
|
||||
|
|
|
@ -50,12 +50,12 @@ EXTurntable::EXTurntable(VPIN firstVpin, int nPins, I2CAddress I2CAddress) {
|
|||
// Initialisation of EXTurntable
|
||||
void EXTurntable::_begin() {
|
||||
I2CManager.begin();
|
||||
I2CManager.setClock(1000000);
|
||||
if (I2CManager.exists(_I2CAddress)) {
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
} else {
|
||||
DIAG(F("EX-Turntable I2C:%s device not found"), _I2CAddress.toString());
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
/*
|
||||
* © 2023, Peter Cole. All rights reserved.
|
||||
* © 2022, Peter Cole. All rights reserved.
|
||||
*
|
||||
* This file is part of EX-CommandStation
|
||||
|
@ -28,9 +29,23 @@
|
|||
* ONCHANGE(vpin) - flag when the rotary encoder position has changed from the previous position
|
||||
* IFRE(vpin, position) - test to see if specified rotary encoder position has been received
|
||||
*
|
||||
* Further to this, feedback can be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin.
|
||||
* Feedback can also be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin.
|
||||
* A SET(vpin) will flag that a turntable (or anything else) is in motion, and a RESET(vpin) that the motion has finished.
|
||||
*
|
||||
* In addition, defining a third Vpin will allow a position number to be sent so that when an EXRAIL automation or some other
|
||||
* activity has moved a turntable, the position can be reflected in the rotary encoder software. This can be accomplished
|
||||
* using the EXRAIL SERVO(vpin, position, profile) command, where:
|
||||
* - vpin = the third defined Vpin (any other is ignored)
|
||||
* - position = the defined position in the DCC-EX Rotary Encoder software, 0 (Home) to 255
|
||||
* - profile = Must be defined as per the SERVO() command, but is ignored as it has no relevance
|
||||
*
|
||||
* Defining in myAutomation.h requires the device driver to be included in addition to the HAL() statement. Examples:
|
||||
*
|
||||
* #include "IO_RotaryEncoder.h"
|
||||
* HAL(RotaryEncoder, 700, 1, 0x70) // Define single Vpin, no feedback or position sent to rotary encoder software
|
||||
* HAL(RotaryEncoder, 700, 2, 0x70) // Define two Vpins, feedback only sent to rotary encoder software
|
||||
* HAL(RotaryEncoder, 700, 3, 0x70) // Define three Vpins, can send feedback and position update to rotary encoder software
|
||||
*
|
||||
* Refer to the documentation for further information including the valid activities and examples.
|
||||
*/
|
||||
|
||||
|
@ -44,58 +59,88 @@
|
|||
|
||||
class RotaryEncoder : public IODevice {
|
||||
public:
|
||||
// Constructor
|
||||
RotaryEncoder(VPIN firstVpin, int nPins, I2CAddress i2cAddress){
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_I2CAddress = i2cAddress;
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
|
||||
if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new RotaryEncoder(firstVpin, nPins, i2cAddress);
|
||||
}
|
||||
|
||||
private:
|
||||
// Constructor
|
||||
RotaryEncoder(VPIN firstVpin, int nPins, I2CAddress i2cAddress){
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
if (_nPins > 3) {
|
||||
_nPins = 3;
|
||||
DIAG(F("RotaryEncoder WARNING:%d vpins defined, only 3 supported"), _nPins);
|
||||
}
|
||||
_I2CAddress = i2cAddress;
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
// Initiate the device
|
||||
void _begin() {
|
||||
uint8_t _status;
|
||||
// Attempt to initilalise device
|
||||
I2CManager.begin();
|
||||
if (I2CManager.exists(_I2CAddress)) {
|
||||
byte _getVersion[1] = {RE_VER};
|
||||
I2CManager.read(_I2CAddress, _versionBuffer, 3, _getVersion, 1);
|
||||
// Send RE_RDY, must receive RE_RDY to be online
|
||||
_sendBuffer[0] = RE_RDY;
|
||||
_status = I2CManager.read(_I2CAddress, _rcvBuffer, 1, _sendBuffer, 1);
|
||||
if (_status == I2C_STATUS_OK) {
|
||||
if (_rcvBuffer[0] == RE_RDY) {
|
||||
_sendBuffer[0] = RE_VER;
|
||||
if (I2CManager.read(_I2CAddress, _versionBuffer, 3, _sendBuffer, 1) == I2C_STATUS_OK) {
|
||||
_majorVer = _versionBuffer[0];
|
||||
_minorVer = _versionBuffer[1];
|
||||
_patchVer = _versionBuffer[2];
|
||||
_buffer[0] = RE_OP;
|
||||
I2CManager.write(_I2CAddress, _buffer, 1);
|
||||
}
|
||||
} else {
|
||||
DIAG(F("RotaryEncoder I2C:%s garbage received: %d"), _I2CAddress.toString(), _rcvBuffer[0]);
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
DIAG(F("RotaryEncoder I2C:%s ERROR connecting"), _I2CAddress.toString());
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
return;
|
||||
}
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
} else {
|
||||
DIAG(F("RotaryEncoder I2C:%s device not found"), _I2CAddress.toString());
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
I2CManager.read(_I2CAddress, _buffer, 1);
|
||||
_position = _buffer[0];
|
||||
// This here needs to have a change check, ie. position is a different value.
|
||||
#if defined(EXRAIL_ACTIVE)
|
||||
if (_deviceState == DEVSTATE_FAILED) return; // Return if device has failed
|
||||
if (_i2crb.isBusy()) return; // Return if I2C operation still in progress
|
||||
|
||||
if (currentMicros - _lastPositionRead > _positionRefresh) {
|
||||
_lastPositionRead = currentMicros;
|
||||
_sendBuffer[0] = RE_READ;
|
||||
I2CManager.read(_I2CAddress, _rcvBuffer, 1, _sendBuffer, 1, &_i2crb); // Read position from encoder
|
||||
_position = _rcvBuffer[0];
|
||||
// If EXRAIL is active, we need to trigger the ONCHANGE() event handler if it's in use
|
||||
#if defined(EXRAIL_ACTIVE)
|
||||
if (_position != _previousPosition) {
|
||||
_previousPosition = _position;
|
||||
RMFT2::changeEvent(_firstVpin,1);
|
||||
RMFT2::changeEvent(_firstVpin, 1);
|
||||
} else {
|
||||
RMFT2::changeEvent(_firstVpin,0);
|
||||
RMFT2::changeEvent(_firstVpin, 0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
delayUntil(currentMicros + 100000);
|
||||
}
|
||||
|
||||
// Device specific read function
|
||||
// Return the position sent by the rotary encoder software
|
||||
int _readAnalogue(VPIN vpin) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return 0;
|
||||
return _position;
|
||||
}
|
||||
|
||||
// Send the feedback value to the rotary encoder software
|
||||
void _write(VPIN vpin, int value) override {
|
||||
if (vpin == _firstVpin + 1) {
|
||||
if (value != 0) value = 0x01;
|
||||
|
@ -104,6 +149,19 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
// Send a position update to the rotary encoder software
|
||||
// To be valid, must be 0 to 255, and different to the current position
|
||||
// If the current position is the same, it was initiated by the rotary encoder
|
||||
void _writeAnalogue(VPIN vpin, int position, uint8_t profile, uint16_t duration) override {
|
||||
if (vpin == _firstVpin + 2) {
|
||||
if (position >= 0 && position <= 255 && position != _position) {
|
||||
byte newPosition = position & 0xFF;
|
||||
byte _positionBuffer[2] = {RE_MOVE, newPosition};
|
||||
I2CManager.write(_I2CAddress, _positionBuffer, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("Rotary Encoder I2C:%s v%d.%d.%d Configured on VPIN:%u-%d %S"), _I2CAddress.toString(), _majorVer, _minorVer, _patchVer,
|
||||
(int)_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
|
@ -112,14 +170,21 @@ private:
|
|||
int8_t _position;
|
||||
int8_t _previousPosition = 0;
|
||||
uint8_t _versionBuffer[3];
|
||||
uint8_t _buffer[1];
|
||||
uint8_t _sendBuffer[1];
|
||||
uint8_t _rcvBuffer[1];
|
||||
uint8_t _majorVer = 0;
|
||||
uint8_t _minorVer = 0;
|
||||
uint8_t _patchVer = 0;
|
||||
I2CRB _i2crb;
|
||||
unsigned long _lastPositionRead = 0;
|
||||
const unsigned long _positionRefresh = 100000UL; // Delay refreshing position for 100ms
|
||||
|
||||
enum {
|
||||
RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device
|
||||
RE_OP = 0xA1, // Flag for normal operation
|
||||
RE_RDY = 0xA0, // Flag to check if encoder is ready for operation
|
||||
RE_VER = 0xA1, // Flag to retrieve rotary encoder software version
|
||||
RE_READ = 0xA2, // Flag to read the current position of the encoder
|
||||
RE_OP = 0xA3, // Flag for operation start/end, sent to when sending feedback on move start/end
|
||||
RE_MOVE = 0xA4, // Flag for sending a position update from the device driver to the encoder
|
||||
};
|
||||
|
||||
};
|
||||
|
|
222
MotorDriver.cpp
222
MotorDriver.cpp
|
@ -27,7 +27,7 @@
|
|||
#include "DCCTimer.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
bool MotorDriver::commonFaultPin=false;
|
||||
unsigned long MotorDriver::globalOverloadStart = 0;
|
||||
|
||||
volatile portreg_t shadowPORTA;
|
||||
volatile portreg_t shadowPORTB;
|
||||
|
@ -108,8 +108,13 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
|||
}
|
||||
|
||||
currentPin=current_pin;
|
||||
if (currentPin!=UNUSED_PIN)
|
||||
ADCee::init(currentPin);
|
||||
if (currentPin!=UNUSED_PIN) {
|
||||
int ret = ADCee::init(currentPin);
|
||||
if (ret < -1010) { // XXX give value a name later
|
||||
DIAG(F("ADCee::init error %d, disable current pin %d"), ret, currentPin);
|
||||
currentPin = UNUSED_PIN;
|
||||
}
|
||||
}
|
||||
senseOffset=0; // value can not be obtained until waveform is activated
|
||||
|
||||
if (fault_pin != UNUSED_PIN) {
|
||||
|
@ -130,7 +135,11 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
|||
// float calculations or libraray code.
|
||||
senseFactorInternal=sense_factor * senseScale;
|
||||
tripMilliamps=trip_milliamps;
|
||||
rawCurrentTripValue=mA2raw(trip_milliamps);
|
||||
#ifdef MAX_CURRENT
|
||||
if (MAX_CURRENT > 0 && MAX_CURRENT < tripMilliamps)
|
||||
tripMilliamps = MAX_CURRENT;
|
||||
#endif
|
||||
rawCurrentTripValue=mA2raw(tripMilliamps);
|
||||
|
||||
if (rawCurrentTripValue + senseOffset > ADCee::ADCmax()) {
|
||||
// This would mean that the values obtained from the ADC never
|
||||
|
@ -154,11 +163,7 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
|||
// senseFactorInternal, raw2mA(1000),mA2raw(1000));
|
||||
}
|
||||
|
||||
// prepare values for current detection
|
||||
sampleDelay = 0;
|
||||
lastSampleTaken = millis();
|
||||
progTripValue = mA2raw(TRIP_CURRENT_PROG);
|
||||
|
||||
}
|
||||
|
||||
bool MotorDriver::isPWMCapable() {
|
||||
|
@ -167,7 +172,12 @@ bool MotorDriver::isPWMCapable() {
|
|||
|
||||
|
||||
void MotorDriver::setPower(POWERMODE mode) {
|
||||
bool on=mode==POWERMODE::ON;
|
||||
if (powerMode == mode) return;
|
||||
//DIAG(F("Track %c POWERMODE=%d"), trackLetter, (int)mode);
|
||||
lastPowerChange[(int)mode] = micros();
|
||||
if (mode == POWERMODE::OVERLOAD)
|
||||
globalOverloadStart = lastPowerChange[(int)mode];
|
||||
bool on=(mode==POWERMODE::ON || mode ==POWERMODE::ALERT);
|
||||
if (on) {
|
||||
// when switching a track On, we need to check the crrentOffset with the pin OFF
|
||||
if (powerMode==POWERMODE::OFF && currentPin!=UNUSED_PIN) {
|
||||
|
@ -207,8 +217,8 @@ 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.
|
||||
* Return the current reading as pin reading 0 to max resolution (1024 or 4096).
|
||||
* 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.
|
||||
*
|
||||
* senseOffset handles the case where a shield returns values above or below
|
||||
|
@ -366,64 +376,166 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res
|
|||
// 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();
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// checkPowerOverload(useProgLimit, trackno)
|
||||
// bool useProgLimit: Trackmanager knows if this track is in prog mode or in main mode
|
||||
// byte trackno: trackmanager knows it's number (could be skipped?)
|
||||
//
|
||||
// Short ciruit handling strategy:
|
||||
//
|
||||
// There are the following power states: ON ALERT OVERLOAD OFF
|
||||
// OFF state is only changed to/from manually. Power is on
|
||||
// during ON and ALERT. Power is off during OVERLOAD and OFF.
|
||||
// The overload mechanism changes between the other states like
|
||||
//
|
||||
// ON -1-> ALERT -2-> OVERLOAD -3-> ALERT -4-> ON
|
||||
// or
|
||||
// ON -1-> ALERT -4-> ON
|
||||
//
|
||||
// Times are in class MotorDriver (MotorDriver.h).
|
||||
//
|
||||
// 1. ON to ALERT:
|
||||
// Transition on fault pin condition or current overload
|
||||
//
|
||||
// 2. ALERT to OVERLOAD:
|
||||
// Transition happens if different timeouts have elapsed.
|
||||
// If only the fault pin is active, timeout is
|
||||
// POWER_SAMPLE_IGNORE_FAULT_LOW (100ms)
|
||||
// If only overcurrent is detected, timeout is
|
||||
// POWER_SAMPLE_IGNORE_CURRENT (100ms)
|
||||
// If fault pin and overcurrent are active, timeout is
|
||||
// POWER_SAMPLE_IGNORE_FAULT_HIGH (5ms)
|
||||
// Transition to OVERLOAD turns off power to the affected
|
||||
// output (unless fault pins are shared)
|
||||
// If the transition conditions are not fullfilled,
|
||||
// transition according to 4 is tested.
|
||||
//
|
||||
// 3. OVERLOAD to ALERT
|
||||
// Transiton happens when timeout has elapsed, timeout
|
||||
// is named power_sample_overload_wait. It is started
|
||||
// at POWER_SAMPLE_OVERLOAD_WAIT (40ms) at first entry
|
||||
// to OVERLOAD and then increased by a factor of 2
|
||||
// at further entries to the OVERLOAD condition. This
|
||||
// happens until POWER_SAMPLE_RETRY_MAX (10sec) is reached.
|
||||
// power_sample_overload_wait is reset by a poweroff or
|
||||
// a POWER_SAMPLE_ALL_GOOD (5sec) period during ON.
|
||||
// After timeout power is turned on again and state
|
||||
// goes back to ALERT.
|
||||
//
|
||||
// 4. ALERT to ON
|
||||
// Transition happens by watching the current and fault pin
|
||||
// samples during POWER_SAMPLE_ALERT_GOOD (20ms) time. If
|
||||
// values have been good during that time, transition is
|
||||
// made back to ON. Note that even if state is back to ON,
|
||||
// the power_sample_overload_wait time is first reset
|
||||
// later (see above).
|
||||
//
|
||||
// The time keeping is handled by timestamps lastPowerChange[]
|
||||
// which are set by each power change and by lastBadSample which
|
||||
// keeps track if conditions during ALERT have been good enough
|
||||
// to go back to ON. The time differences are calculated by
|
||||
// microsSinceLastPowerChange().
|
||||
//
|
||||
|
||||
void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
|
||||
|
||||
// Trackname for diag messages later
|
||||
switch (powerMode) {
|
||||
case POWERMODE::OFF:
|
||||
sampleDelay = POWER_SAMPLE_OFF_WAIT;
|
||||
|
||||
case POWERMODE::OFF: {
|
||||
lastPowerMode = POWERMODE::OFF;
|
||||
power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_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');
|
||||
|
||||
case POWERMODE::ON: {
|
||||
lastPowerMode = POWERMODE::ON;
|
||||
bool cF = checkFault();
|
||||
bool cC = checkCurrent(useProgLimit);
|
||||
if(cF || cC ) {
|
||||
if (cC) {
|
||||
unsigned int mA=raw2mA(lastCurrent);
|
||||
DIAG(F("TRACK %c ALERT %s %dmA"), trackno + 'A',
|
||||
cF ? "FAULT" : "",
|
||||
mA);
|
||||
} else {
|
||||
DIAG(F("TRACK %c FAULT PIN ACTIVE - OVERLOAD"), trackno + 'A');
|
||||
if (lastCurrent < tripValue) {
|
||||
lastCurrent = tripValue; // exaggerate
|
||||
DIAG(F("TRACK %c ALERT FAULT"), trackno + 'A');
|
||||
}
|
||||
setPower(POWERMODE::ALERT);
|
||||
break;
|
||||
}
|
||||
// all well
|
||||
if (microsSinceLastPowerChange(POWERMODE::ON) > POWER_SAMPLE_ALL_GOOD) {
|
||||
power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
|
||||
}
|
||||
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 {
|
||||
break;
|
||||
}
|
||||
|
||||
case POWERMODE::ALERT: {
|
||||
// set local flags that handle how much is output to diag (do not output duplicates)
|
||||
bool notFromOverload = (lastPowerMode != POWERMODE::OVERLOAD);
|
||||
bool newPowerMode = (powerMode != lastPowerMode);
|
||||
unsigned long now = micros();
|
||||
if (newPowerMode)
|
||||
lastBadSample = now;
|
||||
lastPowerMode = POWERMODE::ALERT;
|
||||
// check how long we have been in this state
|
||||
unsigned long mslpc = microsSinceLastPowerChange(POWERMODE::ALERT);
|
||||
if(checkFault()) {
|
||||
lastBadSample = now;
|
||||
unsigned long timeout = checkCurrent(useProgLimit) ? POWER_SAMPLE_IGNORE_FAULT_HIGH : POWER_SAMPLE_IGNORE_FAULT_LOW;
|
||||
if ( mslpc < timeout) {
|
||||
if (newPowerMode)
|
||||
DIAG(F("TRACK %c FAULT PIN (%M ignore)"), trackno + 'A', timeout);
|
||||
break;
|
||||
}
|
||||
DIAG(F("TRACK %c FAULT PIN detected after %4M. Pause %4M)"), trackno + 'A', mslpc, power_sample_overload_wait);
|
||||
setPower(POWERMODE::OVERLOAD);
|
||||
break;
|
||||
}
|
||||
if (checkCurrent(useProgLimit)) {
|
||||
lastBadSample = now;
|
||||
if (mslpc < POWER_SAMPLE_IGNORE_CURRENT) {
|
||||
if (newPowerMode) {
|
||||
unsigned int mA=raw2mA(lastCurrent);
|
||||
DIAG(F("TRACK %c CURRENT (%M ignore) %dmA"), trackno + 'A', POWER_SAMPLE_IGNORE_CURRENT, mA);
|
||||
}
|
||||
break;
|
||||
}
|
||||
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;
|
||||
DIAG(F("TRACK %c POWER OVERLOAD %4dmA (max %4dmA) detected after %4M. Pause %4M"),
|
||||
trackno + 'A', mA, maxmA, mslpc, power_sample_overload_wait);
|
||||
setPower(POWERMODE::OVERLOAD);
|
||||
break;
|
||||
}
|
||||
// all well
|
||||
unsigned long goodtime = micros() - lastBadSample;
|
||||
if (goodtime > POWER_SAMPLE_ALERT_GOOD) {
|
||||
if (true || notFromOverload) { // we did a RESTORE message XXX
|
||||
unsigned int mA=raw2mA(lastCurrent);
|
||||
DIAG(F("TRACK %c NORMAL (after %M/%M) %dmA"), trackno + 'A', goodtime, mslpc, mA);
|
||||
}
|
||||
setPower(POWERMODE::ON);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
case POWERMODE::OVERLOAD: {
|
||||
lastPowerMode = POWERMODE::OVERLOAD;
|
||||
unsigned long mslpc = (commonFaultPin ? (micros() - globalOverloadStart) : microsSinceLastPowerChange(POWERMODE::OVERLOAD));
|
||||
if (mslpc > power_sample_overload_wait) {
|
||||
// adjust next wait time
|
||||
power_sample_overload_wait *= 2;
|
||||
if (power_sample_overload_wait > POWER_SAMPLE_RETRY_MAX)
|
||||
power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX;
|
||||
// power on test
|
||||
DIAG(F("TRACK %c POWER RESTORE (after %4M)"), trackno + 'A', mslpc);
|
||||
setPower(POWERMODE::ALERT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,7 +107,7 @@ extern volatile portreg_t shadowPORTA;
|
|||
extern volatile portreg_t shadowPORTB;
|
||||
extern volatile portreg_t shadowPORTC;
|
||||
|
||||
enum class POWERMODE : byte { OFF, ON, OVERLOAD };
|
||||
enum class POWERMODE : byte { OFF, ON, OVERLOAD, ALERT };
|
||||
|
||||
class MotorDriver {
|
||||
public:
|
||||
|
@ -175,7 +175,10 @@ class MotorDriver {
|
|||
bool isPWMCapable();
|
||||
bool canMeasureCurrent();
|
||||
bool trackPWM = false; // this track uses PWM timer to generate the DCC waveform
|
||||
static bool commonFaultPin; // This is a stupid motor shield which has only a common fault pin for both outputs
|
||||
bool commonFaultPin = false; // This is a stupid motor shield which has only a common fault pin for both outputs
|
||||
inline byte setCommonFaultPin() {
|
||||
return commonFaultPin = true;
|
||||
}
|
||||
inline byte getFaultPin() {
|
||||
return faultPin;
|
||||
}
|
||||
|
@ -186,6 +189,16 @@ class MotorDriver {
|
|||
inline void setTrackLetter(char c) {
|
||||
trackLetter = c;
|
||||
};
|
||||
// this returns how much time has passed since the last power change. If it
|
||||
// was really long ago (approx > 52min) advance counter approx 35 min so that
|
||||
// we are at 18 minutes again. Times for 32 bit unsigned long.
|
||||
inline unsigned long microsSinceLastPowerChange(POWERMODE mode) {
|
||||
unsigned long now = micros();
|
||||
unsigned long diff = now - lastPowerChange[(int)mode];
|
||||
if (diff > (1UL << (7 *sizeof(unsigned long)))) // 2^(4*7)us = 268.4 seconds
|
||||
lastPowerChange[(int)mode] = now - 30000000UL; // 30 seconds ago
|
||||
return diff;
|
||||
};
|
||||
#ifdef ANALOG_READ_INTERRUPT
|
||||
bool sampleCurrentFromHW();
|
||||
void startCurrentFromHW();
|
||||
|
@ -194,9 +207,22 @@ class MotorDriver {
|
|||
char trackLetter = '?';
|
||||
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) {
|
||||
inline void getFastPin(const FSH* type,int pin, FASTPIN & result) {
|
||||
getFastPin(type, pin, 0, result);
|
||||
}
|
||||
};
|
||||
// side effect sets lastCurrent and tripValue
|
||||
inline bool checkCurrent(bool useProgLimit) {
|
||||
tripValue= useProgLimit?progTripValue:getRawCurrentTripValue();
|
||||
lastCurrent = getCurrentRaw();
|
||||
if (lastCurrent < 0)
|
||||
lastCurrent = -lastCurrent;
|
||||
return lastCurrent >= tripValue;
|
||||
};
|
||||
// side effect sets lastCurrent
|
||||
inline bool checkFault() {
|
||||
lastCurrent = getCurrentRaw();
|
||||
return lastCurrent < 0;
|
||||
};
|
||||
VPIN powerPin;
|
||||
byte signalPin, signalPin2, currentPin, faultPin, brakePin;
|
||||
FASTPIN fastSignalPin, fastSignalPin2, fastBrakePin,fastFaultPin;
|
||||
|
@ -217,10 +243,14 @@ class MotorDriver {
|
|||
int rawCurrentTripValue;
|
||||
// current sampling
|
||||
POWERMODE powerMode;
|
||||
unsigned long lastSampleTaken;
|
||||
unsigned int sampleDelay;
|
||||
POWERMODE lastPowerMode;
|
||||
unsigned long lastPowerChange[4]; // timestamp in microseconds
|
||||
unsigned long lastBadSample; // timestamp in microseconds
|
||||
// used to sync restore time when common Fault pin detected
|
||||
static unsigned long globalOverloadStart; // timestamp in microseconds
|
||||
int progTripValue;
|
||||
int lastCurrent;
|
||||
int lastCurrent; //temp value
|
||||
int tripValue; //temp value
|
||||
#ifdef ANALOG_READ_INTERRUPT
|
||||
volatile unsigned long sampleCurrentTimestamp;
|
||||
volatile uint16_t sampleCurrent;
|
||||
|
@ -228,10 +258,21 @@ class MotorDriver {
|
|||
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;
|
||||
// Times for overload management. Unit: microseconds.
|
||||
// Base for wait time until power is turned on again
|
||||
static const unsigned long POWER_SAMPLE_OVERLOAD_WAIT = 40000UL;
|
||||
// Time after we consider all faults old and forgotten
|
||||
static const unsigned long POWER_SAMPLE_ALL_GOOD = 5000000UL;
|
||||
// Time after which we consider a ALERT over
|
||||
static const unsigned long POWER_SAMPLE_ALERT_GOOD = 20000UL;
|
||||
// How long to ignore fault pin if current is under limit
|
||||
static const unsigned long POWER_SAMPLE_IGNORE_FAULT_LOW = 100000UL;
|
||||
// How long to ignore fault pin if current is higher than limit
|
||||
static const unsigned long POWER_SAMPLE_IGNORE_FAULT_HIGH = 5000UL;
|
||||
// How long to wait between overcurrent and turning off
|
||||
static const unsigned long POWER_SAMPLE_IGNORE_CURRENT = 100000UL;
|
||||
// Upper limit for retry period
|
||||
static const unsigned long POWER_SAMPLE_RETRY_MAX = 10000000UL;
|
||||
|
||||
// Trip current for programming track, 250mA. Change only if you really
|
||||
// need to be non-NMRA-compliant because of decoders that are not either.
|
||||
|
|
|
@ -120,6 +120,24 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
|
|||
case 'o': stream->print(va_arg(args, int), OCT); break;
|
||||
case 'x': stream->print((unsigned int)va_arg(args, unsigned int), HEX); break;
|
||||
case 'X': stream->print((unsigned long)va_arg(args, unsigned long), HEX); break;
|
||||
case 'M':
|
||||
{ // this prints a unsigned long microseconds time in readable format
|
||||
unsigned long time = va_arg(args, long);
|
||||
if (time >= 2000) {
|
||||
time = time / 1000;
|
||||
if (time >= 2000) {
|
||||
printPadded(stream, time/1000, formatWidth, formatLeft);
|
||||
stream->print(F("sec"));
|
||||
} else {
|
||||
printPadded(stream,time, formatWidth, formatLeft);
|
||||
stream->print(F("msec"));
|
||||
}
|
||||
} else {
|
||||
printPadded(stream,time, formatWidth, formatLeft);
|
||||
stream->print(F("usec"));
|
||||
}
|
||||
}
|
||||
break;
|
||||
//case 'f': stream->print(va_arg(args, double), 2); break;
|
||||
//format width prefix
|
||||
case '-':
|
||||
|
|
|
@ -123,10 +123,18 @@ void TrackManager::Setup(const FSH * shieldname,
|
|||
setTrackMode(1,TRACK_MODE_MAIN);
|
||||
#endif
|
||||
|
||||
// TODO Fault pin config for odd motor boards (example pololu)
|
||||
// MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin())
|
||||
// && (mainDriver->getFaultPin() != UNUSED_PIN));
|
||||
DCC::begin(shieldname);
|
||||
// Fault pin config for odd motor boards (example pololu)
|
||||
FOR_EACH_TRACK(t) {
|
||||
for (byte s=t+1;s<=lastTrack;s++) {
|
||||
if (track[t]->getFaultPin() != UNUSED_PIN &&
|
||||
track[t]->getFaultPin() == track[s]->getFaultPin()) {
|
||||
track[t]->setCommonFaultPin();
|
||||
track[s]->setCommonFaultPin();
|
||||
DIAG(F("Common Fault pin tracks %c and %c"), t+'A', s+'A');
|
||||
}
|
||||
}
|
||||
}
|
||||
DCC::setShieldName(shieldname);
|
||||
}
|
||||
|
||||
void TrackManager::addTrack(byte t, MotorDriver* driver) {
|
||||
|
|
|
@ -86,6 +86,13 @@ class TrackManager {
|
|||
static bool progTrackSyncMain; // true when prog track is a siding switched to main
|
||||
static bool progTrackBoosted; // true when prog track is not current limited
|
||||
|
||||
#ifdef DEBUG_ADC
|
||||
public:
|
||||
#else
|
||||
private:
|
||||
#endif
|
||||
static MotorDriver* track[MAX_TRACKS];
|
||||
|
||||
private:
|
||||
static void addTrack(byte t, MotorDriver* driver);
|
||||
static byte lastTrack;
|
||||
|
@ -93,7 +100,6 @@ class TrackManager {
|
|||
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
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
/*
|
||||
© 2021, Harald Barth.
|
||||
© 2023 Paul M. Antoine
|
||||
© 2021 Harald Barth
|
||||
© 2023 Nathan Kellenicki
|
||||
|
||||
This file is part of CommandStation-EX
|
||||
|
||||
|
@ -20,6 +22,7 @@
|
|||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#include <vector>
|
||||
#include "defines.h"
|
||||
#include "ESPmDNS.h"
|
||||
#include <WiFi.h>
|
||||
#include "esp_wifi.h"
|
||||
#include "WifiESP32.h"
|
||||
|
@ -106,11 +109,18 @@ void wifiLoop(void *){
|
|||
}
|
||||
#endif
|
||||
|
||||
char asciitolower(char in) {
|
||||
if (in <= 'Z' && in >= 'A')
|
||||
return in - ('Z' - 'z');
|
||||
return in;
|
||||
}
|
||||
|
||||
bool WifiESP::setup(const char *SSid,
|
||||
const char *password,
|
||||
const char *hostname,
|
||||
int port,
|
||||
const byte channel) {
|
||||
const byte channel,
|
||||
const bool forceAP) {
|
||||
bool havePassword = true;
|
||||
bool haveSSID = true;
|
||||
bool wifiUp = false;
|
||||
|
@ -139,7 +149,8 @@ bool WifiESP::setup(const char *SSid,
|
|||
if (strncmp(yourNetwork, password, 13) == 0 || strncmp("", password, 13) == 0)
|
||||
havePassword = false;
|
||||
|
||||
if (haveSSID && havePassword) {
|
||||
if (haveSSID && havePassword && !forceAP) {
|
||||
WiFi.setHostname(hostname); // Strangely does not work unless we do it HERE!
|
||||
WiFi.mode(WIFI_STA);
|
||||
#ifdef SERIAL_BT_COMMANDS
|
||||
WiFi.setSleep(true);
|
||||
|
@ -176,16 +187,20 @@ bool WifiESP::setup(const char *SSid,
|
|||
}
|
||||
}
|
||||
}
|
||||
if (!haveSSID) {
|
||||
if (!haveSSID || forceAP) {
|
||||
// prepare all strings
|
||||
String strSSID("DCC_");
|
||||
String strPass("PASS_");
|
||||
String strSSID(forceAP ? SSid : "DCCEX_");
|
||||
String strPass(forceAP ? password : "PASS_");
|
||||
if (!forceAP) {
|
||||
String strMac = WiFi.macAddress();
|
||||
strMac.remove(0,9);
|
||||
strMac.replace(":","");
|
||||
strMac.replace(":","");
|
||||
// convert mac addr hex chars to lower case to be compatible with AT software
|
||||
std::transform(strMac.begin(), strMac.end(), strMac.begin(), asciitolower);
|
||||
strSSID.concat(strMac);
|
||||
strPass.concat(strMac);
|
||||
}
|
||||
|
||||
WiFi.mode(WIFI_AP);
|
||||
#ifdef SERIAL_BT_COMMANDS
|
||||
|
@ -211,6 +226,15 @@ bool WifiESP::setup(const char *SSid,
|
|||
// no idea to go on
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now Wifi is up, register the mDNS service
|
||||
if(!MDNS.begin(hostname)) {
|
||||
DIAG(F("Wifi setup failed to start mDNS"));
|
||||
}
|
||||
if(!MDNS.addService("withrottle", "tcp", 2560)) {
|
||||
DIAG(F("Wifi setup failed to add withrottle service to mDNS"));
|
||||
}
|
||||
|
||||
server = new WiFiServer(port); // start listening on tcp port
|
||||
server->begin();
|
||||
// server started here
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* © 2021, Harald Barth.
|
||||
* © 2021 Harald Barth
|
||||
* © 2023 Nathan Kellenicki
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
|
@ -31,7 +32,8 @@ public:
|
|||
const char *wifiPassword,
|
||||
const char *hostname,
|
||||
const int port,
|
||||
const byte channel);
|
||||
const byte channel,
|
||||
const bool forceAP);
|
||||
static void loop();
|
||||
private:
|
||||
};
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* © 2021 Fred Decker
|
||||
* © 2020-2022 Harald Barth
|
||||
* © 2020-2022 Chris Harlow
|
||||
* © 2023 Nathan Kellenicki
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
|
@ -52,26 +53,39 @@ Stream * WifiInterface::wifiStream;
|
|||
|
||||
#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560))
|
||||
#define NUM_SERIAL 3
|
||||
#define SERIAL1 Serial1
|
||||
#define SERIAL3 Serial3
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO_ARCH_STM32)
|
||||
// Handle serial ports availability on STM32 for variants!
|
||||
// #undef NUM_SERIAL
|
||||
#if defined(ARDUINO_NUCLEO_F411RE)
|
||||
#define NUM_SERIAL 3
|
||||
#define SERIAL1 Serial1
|
||||
#define SERIAL3 Serial6
|
||||
#elif defined(ARDUINO_NUCLEO_F446RE)
|
||||
#define NUM_SERIAL 3
|
||||
#define SERIAL1 Serial3
|
||||
#define SERIAL3 Serial5
|
||||
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||
#define NUM_SERIAL 2
|
||||
#define SERIAL1 Serial6
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef NUM_SERIAL
|
||||
#define NUM_SERIAL 1
|
||||
#define SERIAL1 Serial1
|
||||
#endif
|
||||
|
||||
// For STM32 we need to define Serial3 in the platform specific
|
||||
// DCCTimerSTM32.cpp file, we here make the assumption that it
|
||||
// exists to link against.
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
#if NUM_SERIAL > 2
|
||||
extern HardwareSerial Serial3;
|
||||
#endif
|
||||
#endif
|
||||
bool WifiInterface::setup(long serial_link_speed,
|
||||
const FSH *wifiESSID,
|
||||
const FSH *wifiPassword,
|
||||
const FSH *hostname,
|
||||
const int port,
|
||||
const byte channel) {
|
||||
const byte channel,
|
||||
const bool forceAP) {
|
||||
|
||||
wifiSerialState wifiUp = WIFI_NOAT;
|
||||
|
||||
|
@ -83,21 +97,23 @@ bool WifiInterface::setup(long serial_link_speed,
|
|||
(void) hostname;
|
||||
(void) port;
|
||||
(void) channel;
|
||||
(void) forceAP;
|
||||
#endif
|
||||
|
||||
// See if the WiFi is attached to the first serial port
|
||||
#if NUM_SERIAL > 0 && !defined(SERIAL1_COMMANDS)
|
||||
Serial1.begin(serial_link_speed);
|
||||
wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port, channel);
|
||||
SERIAL1.begin(serial_link_speed);
|
||||
wifiUp = setup(SERIAL1, wifiESSID, wifiPassword, hostname, port, channel, forceAP);
|
||||
#endif
|
||||
|
||||
// Other serials are tried, depending on hardware.
|
||||
// Currently only the Arduino Mega 2560 has usable Serial2
|
||||
// Currently only the Arduino Mega 2560 has usable Serial2 (Nucleo-64 boards use Serial 2 for console!)
|
||||
#if defined(ARDUINO_AVR_MEGA2560)
|
||||
#if NUM_SERIAL > 1 && !defined(SERIAL2_COMMANDS)
|
||||
if (wifiUp == WIFI_NOAT)
|
||||
{
|
||||
Serial2.begin(serial_link_speed);
|
||||
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel);
|
||||
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel, forceAP);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
@ -107,8 +123,8 @@ bool WifiInterface::setup(long serial_link_speed,
|
|||
#if NUM_SERIAL > 2 && !defined(SERIAL3_COMMANDS)
|
||||
if (wifiUp == WIFI_NOAT)
|
||||
{
|
||||
Serial3.begin(serial_link_speed);
|
||||
wifiUp = setup(Serial3, wifiESSID, wifiPassword, hostname, port, channel);
|
||||
SERIAL3.begin(serial_link_speed);
|
||||
wifiUp = setup(SERIAL3, wifiESSID, wifiPassword, hostname, port, channel, forceAP);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -126,7 +142,7 @@ bool WifiInterface::setup(long serial_link_speed,
|
|||
}
|
||||
|
||||
wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, const FSH* password,
|
||||
const FSH* hostname, int port, byte channel) {
|
||||
const FSH* hostname, int port, byte channel, bool forceAP) {
|
||||
wifiSerialState wifiState;
|
||||
static uint8_t ntry = 0;
|
||||
ntry++;
|
||||
|
@ -135,7 +151,7 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, con
|
|||
|
||||
DIAG(F("++ Wifi Setup Try %d ++"), ntry);
|
||||
|
||||
wifiState = setup2( SSid, password, hostname, port, channel);
|
||||
wifiState = setup2( SSid, password, hostname, port, channel, forceAP);
|
||||
|
||||
if (wifiState == WIFI_NOAT) {
|
||||
LCD(4, F("WiFi no AT chip"));
|
||||
|
@ -159,7 +175,7 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, con
|
|||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
#endif
|
||||
wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||
const FSH* hostname, int port, byte channel) {
|
||||
const FSH* hostname, int port, byte channel, bool forceAP) {
|
||||
bool ipOK = false;
|
||||
bool oldCmd = false;
|
||||
|
||||
|
@ -212,7 +228,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
|||
if (!checkForOK(1000, F("0.0.0.0"), true,false))
|
||||
ipOK = true;
|
||||
}
|
||||
} else {
|
||||
} else if (!forceAP) {
|
||||
// SSID was configured, so we assume station (client) mode.
|
||||
if (oldCmd) {
|
||||
// AT command early version supports CWJAP/CWSAP
|
||||
|
@ -272,6 +288,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
|||
|
||||
i=0;
|
||||
do {
|
||||
if (!forceAP) {
|
||||
if (STRNCMP_P(yourNetwork, (const char*)password, 13) == 0) {
|
||||
// unconfigured
|
||||
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"PASS_%s\",%d,4\r\n"),
|
||||
|
@ -281,6 +298,10 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
|||
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"%S\",%d,4\r\n"), oldCmd ? "" : "_CUR",
|
||||
macTail, password, channel);
|
||||
}
|
||||
} else {
|
||||
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"%S\",\"%S\",%d,4\r\n"),
|
||||
oldCmd ? "" : "_CUR", SSid, password, channel);
|
||||
}
|
||||
} while (!checkForOK(WIFI_CONNECT_TIMEOUT, true) && i++<2); // do twice if necessary but ignore failure as AP mode may still be ok
|
||||
if (i >= 2)
|
||||
DIAG(F("Warning: Setting AP SSID and password failed")); // but issue warning
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2020, Harald Barth.
|
||||
* © 2023 Nathan Kellenicki
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
|
@ -36,17 +37,18 @@ public:
|
|||
const FSH *wifiPassword,
|
||||
const FSH *hostname,
|
||||
const int port,
|
||||
const byte channel);
|
||||
const byte channel,
|
||||
const bool forceAP);
|
||||
static void loop();
|
||||
static void ATCommand(HardwareSerial * stream,const byte *command);
|
||||
|
||||
private:
|
||||
static wifiSerialState setup(Stream &setupStream, const FSH *SSSid, const FSH *password,
|
||||
const FSH *hostname, int port, byte channel);
|
||||
const FSH *hostname, int port, byte channel, bool forceAP);
|
||||
static Stream *wifiStream;
|
||||
static DCCEXParser parser;
|
||||
static wifiSerialState setup2(const FSH *SSSid, const FSH *password,
|
||||
const FSH *hostname, int port, byte channel);
|
||||
const FSH *hostname, int port, byte channel, bool forceAP);
|
||||
static bool checkForOK(const unsigned int timeout, bool echo, bool escapeEcho = true);
|
||||
static bool checkForOK(const unsigned int timeout, const FSH *waitfor, bool echo, bool escapeEcho = true);
|
||||
static bool connected;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* © 2020-2023 Harald Barth
|
||||
* © 2020-2021 Fred Decker
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2023 Nathan Kellenicki
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
|
@ -57,6 +58,21 @@ The configuration file for DCC-EX Command Station
|
|||
// +-----------------------v
|
||||
//
|
||||
#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// If you want to restrict the maximum current LOWER than what your
|
||||
// motor shield can provide, you can do that here. For example if you
|
||||
// have a motor shield that can provide 5A and your power supply can
|
||||
// only provide 2.5A then you should restict the maximum current to
|
||||
// 2.25A (90% of 2.5A) so that DCC-EX does shut off the track before
|
||||
// your PS does shut DCC-EX. MAX_CURRENT is in mA so for this example
|
||||
// it would be 2250, adjust the number according to your PS. If your
|
||||
// PS has a higher rating than your motor shield you do not need this.
|
||||
// You can use this as well if you are cautious and your trains do not
|
||||
// need full current.
|
||||
// #define MAX_CURRENT 2250
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// The IP port to talk to a WIFI or Ethernet shield.
|
||||
|
@ -108,6 +124,11 @@ The configuration file for DCC-EX Command Station
|
|||
// this line exists or not. If you need to use an alternate channel (we recommend
|
||||
// using only 1,6, or 11) you may change it here.
|
||||
#define WIFI_CHANNEL 1
|
||||
//
|
||||
// WIFI_FORCE_AP: If you'd like to specify your own WIFI_SSID in AP mode, set this
|
||||
// true. Otherwise it is assumed that you'd like to connect to an existing network
|
||||
// with that SSID.
|
||||
#define WIFI_FORCE_AP false
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
|
|
|
@ -147,8 +147,6 @@
|
|||
#ifndef I2C_USE_WIRE
|
||||
#define I2C_USE_WIRE
|
||||
#endif
|
||||
#undef NUM_SERIAL
|
||||
#define NUM_SERIAL 3
|
||||
|
||||
/* TODO when ready
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
|
@ -207,7 +205,7 @@
|
|||
#define WIFI_SERIAL_LINK_SPEED 115200
|
||||
|
||||
#if __has_include ( "myAutomation.h")
|
||||
#if defined(HAS_ENOUGH_MEMORY) || defined(DISABLE_EEPROM)
|
||||
#if defined(HAS_ENOUGH_MEMORY) || defined(DISABLE_EEPROM) || defined(DISABLE_PROG)
|
||||
#define EXRAIL_ACTIVE
|
||||
#else
|
||||
#define EXRAIL_WARNING
|
||||
|
|
23
installer.sh
23
installer.sh
|
@ -1,7 +1,7 @@
|
|||
#!/bin/bash
|
||||
|
||||
#
|
||||
# © 2022 Harald Barth
|
||||
# © 2022,2023 Harald Barth
|
||||
#
|
||||
# This file is part of CommandStation-EX
|
||||
#
|
||||
|
@ -29,14 +29,33 @@ ACLI="./bin/arduino-cli"
|
|||
|
||||
function need () {
|
||||
type -p $1 > /dev/null && return
|
||||
dpkg -l $1 2>&1 | egrep ^ii >/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 cat /etc/issue | egrep '^Raspbian' 2>&1 >/dev/null ; then
|
||||
# we are on a raspi where we do not support graphical
|
||||
unset DISPLAY
|
||||
fi
|
||||
|
||||
if [ x$DISPLAY != x ] ; then
|
||||
# we have DISPLAY, do the graphic thing
|
||||
need python3-tk
|
||||
need python3.8-venv
|
||||
mkdir -p ~/ex-installer/venv
|
||||
python3 -m venv ~/ex-installer/venv
|
||||
cd ~/ex-installer/venv || exit 255
|
||||
source ./bin/activate
|
||||
git clone https://github.com/DCC-EX/EX-Installer
|
||||
cd EX-Installer || exit 255
|
||||
pip3 install -r requirements.txt
|
||||
exec python3 -m ex_installer
|
||||
fi
|
||||
if test -d `basename "$DCCEXGITURL"` ; then
|
||||
: assume we are almost there
|
||||
cd `basename "$DCCEXGITURL"` || exit 255
|
||||
|
|
|
@ -173,6 +173,8 @@ board = esp32dev
|
|||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
build_flags = -std=c++17
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
[env:Nucleo-F411RE]
|
||||
platform = ststm32
|
||||
|
@ -188,7 +190,7 @@ platform = ststm32
|
|||
board = nucleo_f446re
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
build_flags = -std=c++17 -Os -g2 -Wunused-variable -DDIAG_LOOPTIMES ; -DDIAG_IO
|
||||
build_flags = -std=c++17 -Os -g2 -Wunused-variable ; -DDIAG_LOOPTIMES ; -DDIAG_IO
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
|
|
20
version.h
20
version.h
|
@ -3,9 +3,23 @@
|
|||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
|
||||
#define VERSION "4.2.54-Z21"
|
||||
// Z21 experiments ongoing
|
||||
#define VERSION "4.2.64"
|
||||
// 4.2.64 - new config WIFI_FORCE_AP option
|
||||
// 4.2.63 - completely new overcurrent detection
|
||||
// - ESP32 protect from race in RMT code
|
||||
// 4.2.62 - Update IO_RotaryEncoder.h to ignore sending current position
|
||||
// - Update IO_EXTurntable.h to remove forced I2C clock speed
|
||||
// - Show device offline if EX-Turntable not connected
|
||||
// 4.2.61 - MAX_CURRENT restriction (caps motor shield value)
|
||||
// 4.2.60 - Add mDNS capability to ESP32 for autodiscovery
|
||||
// 4.2.59 - Fix: AP SSID was DCC_ instead of DCCEX_
|
||||
// 4.2.58 - Start motordriver as soon as possible but without waveform
|
||||
// 4.2.57 - New overload handling (faster and handles commonFaultPin again)
|
||||
// - Optimize analog read STM32
|
||||
// 4.2.56 - Update IO_RotaryEncoder.h:
|
||||
// - Improved I2C communication, non-blocking reads
|
||||
// - Enable sending positions to the encoder from EXRAIL via SERVO()
|
||||
// 4.2.55 - Optimize analog read for AVR
|
||||
// 4.2.54 - EX8874 shield in config.example.h
|
||||
// - Fix: Better warnings for pin number errors
|
||||
// - Fix: Default roster list possible in Withrottle and <jR>
|
||||
|
|
Loading…
Reference in New Issue
Block a user