mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-29 02:13:45 +02:00
Compare commits
34 Commits
v5.2.12-De
...
devel-dcfr
Author | SHA1 | Date | |
---|---|---|---|
|
daa2ffc459 | ||
|
9728d19b19 | ||
|
99a09c713f | ||
|
a5b73c823a | ||
|
8036ba1c48 | ||
|
6f076720f7 | ||
|
d899da5898 | ||
|
3ce9d2ec88 | ||
|
9ebb1c5fb1 | ||
|
19efa749b8 | ||
|
36cc46e88d | ||
|
bba74a08f6 | ||
|
ab58c38e7b | ||
|
d4f0a7c8f3 | ||
|
ba0a41b6f2 | ||
|
bf17f2018b | ||
|
67387d2dc3 | ||
|
adb8b56c92 | ||
|
bd44184f57 | ||
|
e7d3d92c23 | ||
|
e3bab887a2 | ||
|
041a6534da | ||
|
198d762a21 | ||
|
1398cf1999 | ||
|
797028b223 | ||
|
1881d4c9ad | ||
|
18116a391c | ||
|
a1accec79a | ||
|
08f0a2b37d | ||
|
6637ea6fe7 | ||
|
a69017f8bb | ||
|
763c9d8ae6 | ||
|
3f4099520a | ||
|
07fd4bc309 |
@@ -209,7 +209,9 @@ int16_t CommandDistributor::retClockTime() {
|
||||
|
||||
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);
|
||||
uint32_t func = sp->functions;
|
||||
func = func & 0x1fffffff; // mask out bits 0-28
|
||||
broadcastReply(COMMAND_TYPE, F("<l %d %d %d %l>\n"), sp->loco,slot,sp->speedCode,func);
|
||||
#ifdef SABERTOOTH
|
||||
if (Serial2 && sp->loco == SABERTOOTH) {
|
||||
static uint8_t rampingmode = 0;
|
||||
@@ -283,7 +285,7 @@ void CommandDistributor::broadcastPower() {
|
||||
//DIAG(F("m=%d p=%d j=%d"), main, prog, join);
|
||||
const FSH * reason=F("");
|
||||
if (join) {
|
||||
reason = F("JOIN");
|
||||
reason = F(" JOIN"); // with space at start so we can append without space
|
||||
broadcastReply(COMMAND_TYPE, F("<p1 %S>\n"),reason);
|
||||
} else {
|
||||
if (main) {
|
||||
@@ -303,7 +305,7 @@ void CommandDistributor::broadcastPower() {
|
||||
broadcastReply(WITHROTTLE_TYPE, F("PPA%c\n"), main?'1': state);
|
||||
#endif
|
||||
|
||||
LCD(2,F("Power %S %S"),state=='1'?F("On"): ( state=='0'? F("Off") : F("SC") ),reason);
|
||||
LCD(2,F("Power %S%S"),state=='1'?F("On"): ( state=='0'? F("Off") : F("SC") ),reason);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastRaw(clientType type, char * msg) {
|
||||
|
82
DCC.cpp
82
DCC.cpp
@@ -122,7 +122,7 @@ void DCC::setThrottle2( uint16_t cab, byte speedCode) {
|
||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
|
||||
}
|
||||
|
||||
void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
|
||||
void DCC::setFunctionInternal(int cab, byte byte1, byte byte2, byte count) {
|
||||
// DIAG(F("setFunctionInternal %d %x %x"),cab,byte1,byte2);
|
||||
byte b[4];
|
||||
byte nB = 0;
|
||||
@@ -133,7 +133,7 @@ void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
|
||||
if (byte1!=0) b[nB++] = byte1;
|
||||
b[nB++] = byte2;
|
||||
|
||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
|
||||
DCCWaveform::mainTrack.schedulePacket(b, nB, count);
|
||||
}
|
||||
|
||||
// returns speed steps 0 to 127 (1 == emergency stop)
|
||||
@@ -153,6 +153,22 @@ uint8_t DCC::getThrottleSpeedByte(int cab) {
|
||||
return speedTable[reg].speedCode;
|
||||
}
|
||||
|
||||
// returns 0 to 7 for frequency
|
||||
uint8_t DCC::getThrottleFrequency(int cab) {
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
(void)cab;
|
||||
return 0;
|
||||
#else
|
||||
int reg=lookupSpeedTable(cab);
|
||||
if (reg<0)
|
||||
return 0; // use default frequency
|
||||
// shift out first 29 bits so we have the 3 "frequency bits" left
|
||||
uint8_t res = (uint8_t)(speedTable[reg].functions >>29);
|
||||
//DIAG(F("Speed table %d functions %l shifted %d"), reg, speedTable[reg].functions, res);
|
||||
return res;
|
||||
#endif
|
||||
}
|
||||
|
||||
// returns direction on loco
|
||||
// or true/forward on "loco not found"
|
||||
bool DCC::getThrottleDirection(int cab) {
|
||||
@@ -183,43 +199,54 @@ 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;
|
||||
}
|
||||
|
||||
// We use the reminder table up to 28 for normal functions.
|
||||
// We use 29 to 31 for DC frequency as well so up to 28
|
||||
// are "real" functions and 29 to 31 are frequency bits
|
||||
// controlled by function buttons
|
||||
if (functionNumber > 31)
|
||||
return true;
|
||||
|
||||
int reg = lookupSpeedTable(cab);
|
||||
if (reg<0) return false;
|
||||
|
||||
// Take care of functions:
|
||||
// Set state of function
|
||||
unsigned long previous=speedTable[reg].functions;
|
||||
unsigned long funcmask = (1UL<<functionNumber);
|
||||
uint32_t previous=speedTable[reg].functions;
|
||||
uint32_t funcmask = (1UL<<functionNumber);
|
||||
if (on) {
|
||||
speedTable[reg].functions |= funcmask;
|
||||
} else {
|
||||
speedTable[reg].functions &= ~funcmask;
|
||||
}
|
||||
if (speedTable[reg].functions != previous) {
|
||||
if (speedTable[reg].functions != previous && functionNumber <= 28) {
|
||||
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
||||
CommandDistributor::broadcastLoco(reg);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Flip function state
|
||||
// Flip function state (used from withrottle protocol)
|
||||
void DCC::changeFn( int cab, int16_t functionNumber) {
|
||||
if (cab<=0 || functionNumber>28) return;
|
||||
if (cab<=0 || functionNumber>31) return;
|
||||
int reg = lookupSpeedTable(cab);
|
||||
if (reg<0) return;
|
||||
unsigned long funcmask = (1UL<<functionNumber);
|
||||
speedTable[reg].functions ^= funcmask;
|
||||
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
||||
CommandDistributor::broadcastLoco(reg);
|
||||
if (functionNumber <= 28) {
|
||||
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
||||
CommandDistributor::broadcastLoco(reg);
|
||||
}
|
||||
}
|
||||
|
||||
int DCC::getFn( int cab, int16_t functionNumber) {
|
||||
if (cab<=0 || functionNumber>28) return -1; // unknown
|
||||
// Report function state (used from withrottle protocol)
|
||||
// returns 0 false, 1 true or -1 for do not know
|
||||
int8_t DCC::getFn( int cab, int16_t functionNumber) {
|
||||
if (cab<=0 || functionNumber>28)
|
||||
return -1; // unknown
|
||||
int reg = lookupSpeedTable(cab);
|
||||
if (reg<0) return -1;
|
||||
if (reg<0)
|
||||
return -1;
|
||||
|
||||
unsigned long funcmask = (1UL<<functionNumber);
|
||||
return (speedTable[reg].functions & funcmask)? 1 : 0;
|
||||
@@ -595,7 +622,7 @@ void DCC::loop() {
|
||||
|
||||
void DCC::issueReminders() {
|
||||
// if the main track transmitter still has a pending packet, skip this time around.
|
||||
if ( DCCWaveform::mainTrack.getPacketPending()) return;
|
||||
if (!DCCWaveform::mainTrack.isReminderWindowOpen()) 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
|
||||
@@ -619,24 +646,39 @@ bool DCC::issueReminder(int reg) {
|
||||
break;
|
||||
case 1: // remind function group 1 (F0-F4)
|
||||
if (flags & FN_GROUP_1)
|
||||
setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4)); // 100D DDDD
|
||||
#ifndef DISABLE_FUNCTION_REMINDERS
|
||||
setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4),0); // 100D DDDD
|
||||
#else
|
||||
setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4),2);
|
||||
flags&= ~FN_GROUP_1; // dont send them again
|
||||
#endif
|
||||
break;
|
||||
case 2: // remind function group 2 F5-F8
|
||||
if (flags & FN_GROUP_2)
|
||||
setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F)); // 1011 DDDD
|
||||
#ifndef DISABLE_FUNCTION_REMINDERS
|
||||
setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F),0); // 1011 DDDD
|
||||
#else
|
||||
setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F),2);
|
||||
flags&= ~FN_GROUP_2; // dont send them again
|
||||
#endif
|
||||
break;
|
||||
case 3: // remind function group 3 F9-F12
|
||||
if (flags & FN_GROUP_3)
|
||||
setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F)); // 1010 DDDD
|
||||
#ifndef DISABLE_FUNCTION_REMINDERS
|
||||
setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F),0); // 1010 DDDD
|
||||
#else
|
||||
setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F),2);
|
||||
flags&= ~FN_GROUP_3; // dont send them again
|
||||
#endif
|
||||
break;
|
||||
case 4: // remind function group 4 F13-F20
|
||||
if (flags & FN_GROUP_4)
|
||||
setFunctionInternal(loco,222, ((functions>>13)& 0xFF));
|
||||
setFunctionInternal(loco,222, ((functions>>13)& 0xFF),2);
|
||||
flags&= ~FN_GROUP_4; // dont send them again
|
||||
break;
|
||||
case 5: // remind function group 5 F21-F28
|
||||
if (flags & FN_GROUP_5)
|
||||
setFunctionInternal(loco,223, ((functions>>21)& 0xFF));
|
||||
setFunctionInternal(loco,223, ((functions>>21)& 0xFF),2);
|
||||
flags&= ~FN_GROUP_5; // dont send them again
|
||||
break;
|
||||
}
|
||||
|
7
DCC.h
7
DCC.h
@@ -61,13 +61,14 @@ public:
|
||||
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 getThrottleFrequency(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 changeFn(int cab, int16_t functionNumber);
|
||||
static int getFn(int cab, int16_t functionNumber);
|
||||
static int8_t 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);
|
||||
@@ -98,7 +99,7 @@ public:
|
||||
int loco;
|
||||
byte speedCode;
|
||||
byte groupFlags;
|
||||
unsigned long functions;
|
||||
uint32_t functions;
|
||||
};
|
||||
static LOCO speedTable[MAX_LOCOS];
|
||||
static int lookupSpeedTable(int locoId, bool autoCreate=true);
|
||||
@@ -109,7 +110,7 @@ private:
|
||||
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 void setFunctionInternal(int cab, byte fByte, byte eByte, byte count);
|
||||
static bool issueReminder(int reg);
|
||||
static int lastLocoReminder;
|
||||
static int highestUsedReg;
|
||||
|
@@ -587,7 +587,6 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
}
|
||||
else break; // will reply <X>
|
||||
}
|
||||
CommandDistributor::broadcastPower();
|
||||
//TrackManager::streamTrackState(NULL,t);
|
||||
|
||||
return;
|
||||
@@ -619,7 +618,6 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
}
|
||||
else break; // will reply <X>
|
||||
}
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -627,12 +625,13 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
|
||||
return;
|
||||
|
||||
#ifdef HAS_ENOUGH_MEMORY
|
||||
case 'c': // SEND METER RESPONSES <c>
|
||||
// No longer useful because of multiple tracks See <JG> and <JI>
|
||||
if (params>0) break;
|
||||
TrackManager::reportObsoleteCurrent(stream);
|
||||
return;
|
||||
|
||||
#endif
|
||||
case 'Q': // SENSORS <Q>
|
||||
Sensor::printAll(stream);
|
||||
return;
|
||||
|
@@ -85,6 +85,7 @@ class DCCTimer {
|
||||
static void reset();
|
||||
|
||||
private:
|
||||
static void DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t frequency);
|
||||
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
|
||||
|
@@ -29,6 +29,7 @@
|
||||
#include <avr/boot.h>
|
||||
#include <avr/wdt.h>
|
||||
#include "DCCTimer.h"
|
||||
#include "DIAG.h"
|
||||
#ifdef DEBUG_ADC
|
||||
#include "TrackManager.h"
|
||||
#endif
|
||||
@@ -125,6 +126,81 @@ void DCCTimer::reset() {
|
||||
|
||||
}
|
||||
|
||||
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, f);
|
||||
}
|
||||
void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t fbits) {
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
// Not worth doin something here as:
|
||||
// If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source.
|
||||
// If we are on pin 5 or 6 we are on Timer 0 ad we can not touch Timer0 as that is millis() etc.
|
||||
// We are most likely not on pin 3 or 11 as no known motor shield has that as brake.
|
||||
#endif
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
// Speed mapping is done like this:
|
||||
// No functions buttons: 000 0 -> low 131Hz
|
||||
// Only F29 pressed 001 1 -> mid 490Hz
|
||||
// F30 with or w/o F29 01x 2-3 -> high 3400Hz
|
||||
// F31 with or w/o F29/30 1xx 4-7 -> supersonic 62500Hz
|
||||
uint8_t abits;
|
||||
uint8_t bbits;
|
||||
if (pin == 9 || pin == 10) { // timer 2 is different
|
||||
|
||||
if (fbits >= 4)
|
||||
abits = B00000011;
|
||||
else
|
||||
abits = B00000001;
|
||||
|
||||
if (fbits >= 4)
|
||||
bbits = B0001;
|
||||
else if (fbits >= 2)
|
||||
bbits = B0010;
|
||||
else if (fbits == 1)
|
||||
bbits = B0100;
|
||||
else // fbits == 0
|
||||
bbits = B0110;
|
||||
|
||||
TCCR2A = (TCCR2A & B11111100) | abits; // set WGM0 and WGM1
|
||||
TCCR2B = (TCCR2B & B11110000) | bbits; // set WGM2 and 3 bits of prescaler
|
||||
DIAG(F("Timer 2 A=%x B=%x"), TCCR2A, TCCR2B);
|
||||
|
||||
} else { // not timer 9 or 10
|
||||
abits = B01;
|
||||
|
||||
if (fbits >= 4)
|
||||
bbits = B1001;
|
||||
else if (fbits >= 2)
|
||||
bbits = B0010;
|
||||
else if (fbits == 1)
|
||||
bbits = B0011;
|
||||
else
|
||||
bbits = B0100;
|
||||
|
||||
switch (pin) {
|
||||
// case 9 and 10 taken care of above by if()
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
// Timer4
|
||||
TCCR4A = (TCCR4A & B11111100) | abits; // set WGM0 and WGM1
|
||||
TCCR4B = (TCCR4B & B11100000) | bbits; // set WGM2 and WGM3 and divisor
|
||||
//DIAG(F("Timer 4 A=%x B=%x"), TCCR4A, TCCR4B);
|
||||
break;
|
||||
case 46:
|
||||
case 45:
|
||||
case 44:
|
||||
// Timer5
|
||||
TCCR5A = (TCCR5A & B11111100) | abits; // set WGM0 and WGM1
|
||||
TCCR5B = (TCCR5B & B11100000) | bbits; // set WGM2 and WGM3 and divisor
|
||||
//DIAG(F("Timer 5 A=%x B=%x"), TCCR5A, TCCR5B);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
#define NUM_ADC_INPUTS 16
|
||||
#else
|
||||
|
@@ -151,10 +151,26 @@ void DCCTimer::reset() {
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
|
||||
if (f >= 16)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, f);
|
||||
else if (f == 7)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 62500);
|
||||
else if (f >= 4)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 32000);
|
||||
else if (f >= 3)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 16000);
|
||||
else if (f >= 2)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 3400);
|
||||
else if (f == 1)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 480);
|
||||
else
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 131);
|
||||
}
|
||||
|
||||
#include "esp32-hal.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
|
||||
#ifdef SOC_LEDC_SUPPORT_HS_MODE
|
||||
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM<<1)
|
||||
#else
|
||||
@@ -164,7 +180,7 @@ void DCCTimer::reset() {
|
||||
static int8_t pin_to_channel[SOC_GPIO_PIN_COUNT] = { 0 };
|
||||
static int cnt_channel = LEDC_CHANNELS;
|
||||
|
||||
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency) {
|
||||
void DCCTimer::DCCEXanalogWriteFrequencyInternal(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);
|
||||
|
@@ -125,6 +125,11 @@ void DCCTimer::reset() {
|
||||
while(true){}
|
||||
}
|
||||
|
||||
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
|
||||
}
|
||||
void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t fbits) {
|
||||
}
|
||||
|
||||
int16_t ADCee::ADCmax() {
|
||||
return 4095;
|
||||
}
|
||||
|
@@ -156,6 +156,11 @@ void DCCTimer::reset() {
|
||||
while(true) {};
|
||||
}
|
||||
|
||||
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
|
||||
}
|
||||
void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t fbits) {
|
||||
}
|
||||
|
||||
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
|
||||
|
||||
uint16_t ADCee::usedpins = 0;
|
||||
|
@@ -257,6 +257,23 @@ void DCCTimer::reset() {
|
||||
while(true) {};
|
||||
}
|
||||
|
||||
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
|
||||
if (f >= 16)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, f);
|
||||
else if (f == 7)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 62500);
|
||||
else if (f >= 4)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 32000);
|
||||
else if (f >= 3)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 16000);
|
||||
else if (f >= 2)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 3400);
|
||||
else if (f == 1)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 480);
|
||||
else
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 131);
|
||||
}
|
||||
|
||||
// TODO: rationalise the size of these... could really use sparse arrays etc.
|
||||
static HardwareTimer * pin_timer[100] = {0};
|
||||
static uint32_t channel_frequency[100] = {0};
|
||||
@@ -267,7 +284,7 @@ static uint32_t pin_channel[100] = {0};
|
||||
// sophisticated about detecting any clash between the timer we'd like to use for PWM and the ones
|
||||
// currently used for HA so they don't interfere with one another. For now we'll just make PWM
|
||||
// work well... then work backwards to integrate with HA mode if we can.
|
||||
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency)
|
||||
void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t frequency)
|
||||
{
|
||||
if (pin_timer[pin] == NULL) {
|
||||
// Automatically retrieve TIM instance and channel associated to pin
|
||||
|
@@ -141,6 +141,11 @@ void DCCTimer::reset() {
|
||||
SCB_AIRCR = 0x05FA0004;
|
||||
}
|
||||
|
||||
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
|
||||
}
|
||||
void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t fbits) {
|
||||
}
|
||||
|
||||
int16_t ADCee::ADCmax() {
|
||||
return 4095;
|
||||
}
|
||||
|
@@ -106,6 +106,7 @@ void DCCWaveform::interruptHandler() {
|
||||
DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
||||
isMainTrack = isMain;
|
||||
packetPending = false;
|
||||
reminderWindowOpen = false;
|
||||
memcpy(transmitPacket, idlePacket, sizeof(idlePacket));
|
||||
state = WAVE_START;
|
||||
// The +1 below is to allow the preamble generator to create the stop bit
|
||||
@@ -127,9 +128,15 @@ void DCCWaveform::interrupt2() {
|
||||
if (remainingPreambles > 0 ) {
|
||||
state=WAVE_MID_1; // switch state to trigger LOW on next interrupt
|
||||
remainingPreambles--;
|
||||
|
||||
// As we get to the end of the preambles, open the reminder window.
|
||||
// This delays any reminder insertion until the last moment so
|
||||
// that the reminder doesn't block a more urgent packet.
|
||||
reminderWindowOpen=transmitRepeats==0 && remainingPreambles<4 && remainingPreambles>1;
|
||||
if (remainingPreambles==1) promotePendingPacket();
|
||||
// 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);
|
||||
else DCCTimer::updateMinimumFreeMemoryISR(22);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -148,30 +155,9 @@ void DCCWaveform::interrupt2() {
|
||||
if (bytes_sent >= transmitLength) {
|
||||
// end of transmission buffer... repeat or switch to next message
|
||||
bytes_sent = 0;
|
||||
// preamble for next packet will start...
|
||||
remainingPreambles = requiredPreambles;
|
||||
|
||||
if (transmitRepeats > 0) {
|
||||
transmitRepeats--;
|
||||
}
|
||||
else if (packetPending) {
|
||||
// Copy pending packet to transmit packet
|
||||
// a fixed length memcpy is faster than a variable length loop for these small lengths
|
||||
// for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b];
|
||||
memcpy( transmitPacket, pendingPacket, sizeof(pendingPacket));
|
||||
|
||||
transmitLength = pendingLength;
|
||||
transmitRepeats = pendingRepeats;
|
||||
packetPending = false;
|
||||
clearResets();
|
||||
}
|
||||
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!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
@@ -193,8 +179,39 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
|
||||
packetPending = true;
|
||||
clearResets();
|
||||
}
|
||||
bool DCCWaveform::getPacketPending() {
|
||||
return packetPending;
|
||||
|
||||
bool DCCWaveform::isReminderWindowOpen() {
|
||||
return reminderWindowOpen && ! packetPending;
|
||||
}
|
||||
|
||||
void DCCWaveform::promotePendingPacket() {
|
||||
// fill the transmission packet from the pending packet
|
||||
|
||||
// Just keep going if repeating
|
||||
if (transmitRepeats > 0) {
|
||||
transmitRepeats--;
|
||||
return;
|
||||
}
|
||||
|
||||
if (packetPending) {
|
||||
// Copy pending packet to transmit packet
|
||||
// a fixed length memcpy is faster than a variable length loop for these small lengths
|
||||
// for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b];
|
||||
memcpy( transmitPacket, pendingPacket, sizeof(pendingPacket));
|
||||
|
||||
transmitLength = pendingLength;
|
||||
transmitRepeats = pendingRepeats;
|
||||
packetPending = false;
|
||||
clearResets();
|
||||
return;
|
||||
}
|
||||
|
||||
// nothing to do, just send idles or resets
|
||||
// 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!)
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -266,15 +283,15 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
|
||||
}
|
||||
}
|
||||
|
||||
bool DCCWaveform::getPacketPending() {
|
||||
bool DCCWaveform::isReminderWindowOpen() {
|
||||
if(isMainTrack) {
|
||||
if (rmtMainChannel == NULL)
|
||||
return true;
|
||||
return rmtMainChannel->busy();
|
||||
return false;
|
||||
return !rmtMainChannel->busy();
|
||||
} else {
|
||||
if (rmtProgChannel == NULL)
|
||||
return true;
|
||||
return rmtProgChannel->busy();
|
||||
return false;
|
||||
return !rmtProgChannel->busy();
|
||||
}
|
||||
}
|
||||
void IRAM_ATTR DCCWaveform::loop() {
|
||||
|
@@ -76,11 +76,13 @@ class DCCWaveform {
|
||||
};
|
||||
#endif
|
||||
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
|
||||
bool getPacketPending();
|
||||
bool isReminderWindowOpen();
|
||||
void promotePendingPacket();
|
||||
|
||||
private:
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
volatile bool packetPending;
|
||||
volatile bool reminderWindowOpen;
|
||||
volatile byte sentResetsSincePacket;
|
||||
#else
|
||||
volatile uint32_t resetPacketBase;
|
||||
|
44
EXRAIL2.cpp
44
EXRAIL2.cpp
@@ -414,7 +414,6 @@ void RMFT2::driveLoco(byte speed) {
|
||||
power on appropriate track if DC or main if dcc
|
||||
if (TrackManager::getMainPowerMode()==POWERMODE::OFF) {
|
||||
TrackManager::setMainPower(POWERMODE::ON);
|
||||
CommandDistributor::broadcastPower();
|
||||
}
|
||||
**********/
|
||||
|
||||
@@ -642,7 +641,6 @@ void RMFT2::loop2() {
|
||||
case OPCODE_POWEROFF:
|
||||
TrackManager::setPower(POWERMODE::OFF);
|
||||
TrackManager::setJoin(false);
|
||||
CommandDistributor::broadcastPower();
|
||||
break;
|
||||
|
||||
case OPCODE_SET_POWER:
|
||||
@@ -669,6 +667,45 @@ void RMFT2::loop2() {
|
||||
}
|
||||
break;
|
||||
|
||||
case OPCODE_SETFREQ:
|
||||
// Frequency is default 0, or 1, 2,3
|
||||
//if (loco) DCC::setFn(loco,operand,true);
|
||||
switch (operand) {
|
||||
case 0: // default - all F-s off
|
||||
if (loco) {
|
||||
DCC::setFn(loco,29,false);
|
||||
DCC::setFn(loco,30,false);
|
||||
DCC::setFn(loco,31,false);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (loco) {
|
||||
DCC::setFn(loco,29,true);
|
||||
DCC::setFn(loco,30,false);
|
||||
DCC::setFn(loco,31,false);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (loco) {
|
||||
DCC::setFn(loco,29,false);
|
||||
DCC::setFn(loco,30,true);
|
||||
DCC::setFn(loco,31,false);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (loco) {
|
||||
DCC::setFn(loco,29,false);
|
||||
DCC::setFn(loco,30,false);
|
||||
DCC::setFn(loco,31,true);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
; // do nothing
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case OPCODE_RESUME:
|
||||
pausingTask=NULL;
|
||||
driveLoco(speedo);
|
||||
@@ -837,12 +874,10 @@ void RMFT2::loop2() {
|
||||
case OPCODE_JOIN:
|
||||
TrackManager::setPower(POWERMODE::ON);
|
||||
TrackManager::setJoin(true);
|
||||
CommandDistributor::broadcastPower();
|
||||
break;
|
||||
|
||||
case OPCODE_UNJOIN:
|
||||
TrackManager::setJoin(false);
|
||||
CommandDistributor::broadcastPower();
|
||||
break;
|
||||
|
||||
case OPCODE_READ_LOCO1: // READ_LOCO is implemented as 2 separate opcodes
|
||||
@@ -870,7 +905,6 @@ void RMFT2::loop2() {
|
||||
case OPCODE_POWERON:
|
||||
TrackManager::setMainPower(POWERMODE::ON);
|
||||
TrackManager::setJoin(false);
|
||||
CommandDistributor::broadcastPower();
|
||||
break;
|
||||
|
||||
case OPCODE_START:
|
||||
|
@@ -51,7 +51,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,
|
||||
#endif
|
||||
OPCODE_POM,
|
||||
OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,OPCODE_FORGET,
|
||||
OPCODE_START,OPCODE_SETLOCO,OPCODE_SETFREQ,OPCODE_SENDLOCO,OPCODE_FORGET,
|
||||
OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,OPCODE_POWERON,
|
||||
OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT,
|
||||
OPCODE_PRINT,OPCODE_DCCACTIVATE,
|
||||
|
@@ -151,11 +151,13 @@
|
||||
#undef SET_TRACK
|
||||
#undef SET_POWER
|
||||
#undef SETLOCO
|
||||
#undef SETFREQ
|
||||
#undef SIGNAL
|
||||
#undef SIGNALH
|
||||
#undef SPEED
|
||||
#undef START
|
||||
#undef STASH
|
||||
#undef STEALTH
|
||||
#undef STOP
|
||||
#undef THROW
|
||||
#undef TT_ADDPOSITION
|
||||
@@ -301,11 +303,13 @@
|
||||
#define SET_TRACK(track,mode)
|
||||
#define SET_POWER(track,onoff)
|
||||
#define SETLOCO(loco)
|
||||
#define SETFREQ(loco,freq)
|
||||
#define SIGNAL(redpin,amberpin,greenpin)
|
||||
#define SIGNALH(redpin,amberpin,greenpin)
|
||||
#define SPEED(speed)
|
||||
#define START(route)
|
||||
#define STASH(id)
|
||||
#define STEALTH(code...)
|
||||
#define STOP
|
||||
#define THROW(id)
|
||||
#define TT_ADDPOSITION(turntable_id,position,value,angle,description...)
|
||||
|
@@ -210,6 +210,8 @@ case (__COUNTER__ - StringMacroTracker1) : {\
|
||||
lcdid=id;\
|
||||
break;\
|
||||
}
|
||||
#undef STEALTH
|
||||
#define STEALTH(code...) case (__COUNTER__ - StringMacroTracker1) : {code} return;
|
||||
#undef WITHROTTLE
|
||||
#define WITHROTTLE(msg) THRUNGE(msg,thrunge_withrottle)
|
||||
|
||||
@@ -422,6 +424,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
OPCODE_PAD,V((((uint64_t)sender)>>0)&0xFFFF),
|
||||
#define LCD(id,msg) PRINT(msg)
|
||||
#define SCREEN(display,id,msg) PRINT(msg)
|
||||
#define STEALTH(code...) PRINT(dummy)
|
||||
#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),
|
||||
@@ -490,6 +493,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#define SET_TRACK(track,mode) OPCODE_SET_TRACK,V(TRACK_MODE_##mode <<8 | TRACK_NUMBER_##track),
|
||||
#define SET_POWER(track,onoff) OPCODE_SET_POWER,V(TRACK_POWER_##onoff),OPCODE_PAD, V(TRACK_NUMBER_##track),
|
||||
#define SETLOCO(loco) OPCODE_SETLOCO,V(loco),
|
||||
#define SETFREQ(loco,freq) OPCODE_SETLOCO,V(loco), OPCODE_SETFREQ,V(freq),
|
||||
#define SIGNAL(redpin,amberpin,greenpin)
|
||||
#define SIGNALH(redpin,amberpin,greenpin)
|
||||
#define SPEED(speed) OPCODE_SPEED,V(speed),
|
||||
|
@@ -1 +1 @@
|
||||
#define GITHUB_SHA "devel-202311270714Z"
|
||||
#define GITHUB_SHA "devel-202401202235Z"
|
||||
|
@@ -110,7 +110,7 @@ void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
|
||||
// Calculate a rise time appropriate to the requested bus speed
|
||||
// Use 10x the rise time spec to enable integer divide of 50ns clock period
|
||||
uint16_t t_rise;
|
||||
uint32_t ccr_freq;
|
||||
//uint32_t ccr_freq;
|
||||
|
||||
while (s->CR1 & I2C_CR1_STOP); // Prevents lockup by guarding further
|
||||
// writes to CR1 while STOP is being executed!
|
||||
|
@@ -22,13 +22,10 @@
|
||||
* This device driver will configure the device on startup, along with
|
||||
* interacting with the device for all input/output duties.
|
||||
*
|
||||
* To create EX-IOExpander devices, these are defined in myHal.cpp:
|
||||
* To create EX-IOExpander devices, these are defined in myAutomation.h:
|
||||
* (Note the device driver is included by default)
|
||||
*
|
||||
* void halSetup() {
|
||||
* // EXIOExpander::create(vpin, num_vpins, i2c_address);
|
||||
* EXIOExpander::create(800, 18, 0x65);
|
||||
* }
|
||||
* HAL(EXIOExpander,800,18,0x65)
|
||||
*
|
||||
* All pins on an EX-IOExpander device are allocated according to the pin map for the specific
|
||||
* device in use. There is no way for the device driver to sanity check pins are used for the
|
||||
@@ -98,25 +95,30 @@ private:
|
||||
_numAnaloguePins = receiveBuffer[2];
|
||||
|
||||
// See if we already have suitable buffers assigned
|
||||
size_t digitalBytesNeeded = (_numDigitalPins + 7) / 8;
|
||||
if (_digitalPinBytes < digitalBytesNeeded) {
|
||||
// Not enough space, free any existing buffer and allocate a new one
|
||||
if (_digitalPinBytes > 0) free(_digitalInputStates);
|
||||
_digitalInputStates = (byte*) calloc(_digitalPinBytes, 1);
|
||||
_digitalPinBytes = digitalBytesNeeded;
|
||||
}
|
||||
size_t analogueBytesNeeded = _numAnaloguePins * 2;
|
||||
if (_analoguePinBytes < analogueBytesNeeded) {
|
||||
// Free any existing buffers and allocate new ones.
|
||||
if (_analoguePinBytes > 0) {
|
||||
free(_analogueInputBuffer);
|
||||
free(_analogueInputStates);
|
||||
free(_analoguePinMap);
|
||||
if (_numDigitalPins>0) {
|
||||
size_t digitalBytesNeeded = (_numDigitalPins + 7) / 8;
|
||||
if (_digitalPinBytes < digitalBytesNeeded) {
|
||||
// Not enough space, free any existing buffer and allocate a new one
|
||||
if (_digitalPinBytes > 0) free(_digitalInputStates);
|
||||
_digitalInputStates = (byte*) calloc(_digitalPinBytes, 1);
|
||||
_digitalPinBytes = digitalBytesNeeded;
|
||||
}
|
||||
}
|
||||
|
||||
if (_numAnaloguePins>0) {
|
||||
size_t analogueBytesNeeded = _numAnaloguePins * 2;
|
||||
if (_analoguePinBytes < analogueBytesNeeded) {
|
||||
// Free any existing buffers and allocate new ones.
|
||||
if (_analoguePinBytes > 0) {
|
||||
free(_analogueInputBuffer);
|
||||
free(_analogueInputStates);
|
||||
free(_analoguePinMap);
|
||||
}
|
||||
_analogueInputStates = (uint8_t*) calloc(analogueBytesNeeded, 1);
|
||||
_analogueInputBuffer = (uint8_t*) calloc(analogueBytesNeeded, 1);
|
||||
_analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1);
|
||||
_analoguePinBytes = analogueBytesNeeded;
|
||||
}
|
||||
_analogueInputStates = (uint8_t*) calloc(analogueBytesNeeded, 1);
|
||||
_analogueInputBuffer = (uint8_t*) calloc(analogueBytesNeeded, 1);
|
||||
_analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1);
|
||||
_analoguePinBytes = analogueBytesNeeded;
|
||||
}
|
||||
} else {
|
||||
DIAG(F("EX-IOExpander I2C:%s ERROR configuring device"), _I2CAddress.toString());
|
||||
@@ -124,8 +126,8 @@ private:
|
||||
return;
|
||||
}
|
||||
}
|
||||
// We now need to retrieve the analogue pin map
|
||||
if (status == I2C_STATUS_OK) {
|
||||
// We now need to retrieve the analogue pin map if there are analogue pins
|
||||
if (status == I2C_STATUS_OK && _numAnaloguePins>0) {
|
||||
commandBuffer[0] = EXIOINITA;
|
||||
status = I2CManager.read(_I2CAddress, _analoguePinMap, _numAnaloguePins, commandBuffer, 1);
|
||||
}
|
||||
@@ -239,7 +241,7 @@ private:
|
||||
|
||||
// If we're not doing anything now, check to see if a new input transfer is due.
|
||||
if (_readState == RDS_IDLE) {
|
||||
if (currentMicros - _lastDigitalRead > _digitalRefresh) { // Delay for digital read refresh
|
||||
if (currentMicros - _lastDigitalRead > _digitalRefresh && _numDigitalPins>0) { // Delay for digital read refresh
|
||||
// Issue new read request for digital states. As the request is non-blocking, the buffer has to
|
||||
// be allocated from heap (object state).
|
||||
_readCommandBuffer[0] = EXIORDD;
|
||||
@@ -247,7 +249,7 @@ private:
|
||||
// non-blocking read
|
||||
_lastDigitalRead = currentMicros;
|
||||
_readState = RDS_DIGITAL;
|
||||
} else if (currentMicros - _lastAnalogueRead > _analogueRefresh) { // Delay for analogue read refresh
|
||||
} else if (currentMicros - _lastAnalogueRead > _analogueRefresh && _numAnaloguePins>0) { // Delay for analogue read refresh
|
||||
// Issue new read for analogue input states
|
||||
_readCommandBuffer[0] = EXIORDAN;
|
||||
I2CManager.read(_I2CAddress, _analogueInputBuffer,
|
||||
|
108
MotorDriver.cpp
108
MotorDriver.cpp
@@ -325,49 +325,23 @@ uint16_t taurustones[28] = { 165, 175, 196, 220,
|
||||
220, 196, 175, 165 };
|
||||
#endif
|
||||
#endif
|
||||
void MotorDriver::setDCSignal(byte speedcode) {
|
||||
void MotorDriver::setDCSignal(byte speedcode, uint8_t frequency /*default =0*/) {
|
||||
if (brakePin == UNUSED_PIN)
|
||||
return;
|
||||
switch(brakePin) {
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
// Not worth doin something here as:
|
||||
// If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source.
|
||||
// If we are on pin 5 or 6 we are on Timer 0 ad we can not touch Timer0 as that is millis() etc.
|
||||
// We are most likely not on pin 3 or 11 as no known motor shield has that as brake.
|
||||
#endif
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
case 9:
|
||||
case 10:
|
||||
// Timer2 (is differnet)
|
||||
TCCR2A = (TCCR2A & B11111100) | B00000001; // set WGM1=0 and WGM0=1 phase correct PWM
|
||||
TCCR2B = (TCCR2B & B11110000) | B00000110; // set WGM2=0 ; set divisor on timer 2 to 1/256 for 122.55Hz
|
||||
//DIAG(F("2 A=%x B=%x"), TCCR2A, TCCR2B);
|
||||
break;
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
// Timer4
|
||||
TCCR4A = (TCCR4A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for normal PWM 8-bit
|
||||
TCCR4B = (TCCR4B & B11100000) | B00000100; // set WGM2=0 and WGM3=0 for normal PWM 8 bit and div 1/256 for 122.55Hz
|
||||
break;
|
||||
case 46:
|
||||
case 45:
|
||||
case 44:
|
||||
// Timer5
|
||||
TCCR5A = (TCCR5A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for normal PWM 8-bit
|
||||
TCCR5B = (TCCR5B & B11100000) | B00000100; // set WGM2=0 and WGM3=0 for normal PWM 8 bit and div 1/256 for 122.55Hz
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// 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 (tSpeed <= 1) brake = 255;
|
||||
else if (tSpeed >= 127) brake = 0;
|
||||
else brake = 2 * (128-tSpeed);
|
||||
if (invertBrake)
|
||||
brake=255-brake;
|
||||
|
||||
{ // new block because of variable f
|
||||
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_STM32)
|
||||
{
|
||||
int f = 131;
|
||||
int f = frequency;
|
||||
#ifdef VARIABLE_TONES
|
||||
if (tSpeed > 2) {
|
||||
if (tSpeed <= 58) {
|
||||
@@ -375,19 +349,15 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup
|
||||
//DIAG(F("Brake pin %d freqency %d"), brakePin, f);
|
||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency
|
||||
DCCTimer::DCCEXanalogWrite(brakePin,brake);
|
||||
#else // all AVR here
|
||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, frequency); // frequency steps
|
||||
analogWrite(brakePin,brake);
|
||||
#endif
|
||||
}
|
||||
#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) || defined(ARDUINO_ARCH_STM32)
|
||||
DCCTimer::DCCEXanalogWrite(brakePin,brake);
|
||||
#else
|
||||
analogWrite(brakePin,brake);
|
||||
#endif
|
||||
|
||||
//DIAG(F("DCSignal %d"), speedcode);
|
||||
if (HAVE_PORTA(fastSignalPin.shadowinout == &PORTA)) {
|
||||
noInterrupts();
|
||||
@@ -436,58 +406,26 @@ void MotorDriver::throttleInrush(bool on) {
|
||||
return;
|
||||
if ( !(trackMode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_EXT)))
|
||||
return;
|
||||
byte duty = on ? 208 : 0;
|
||||
byte duty = on ? 207 : 0; // duty of 81% at 62500Hz this gives pauses of 3usec
|
||||
if (invertBrake)
|
||||
duty = 255-duty;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
if(on) {
|
||||
DCCTimer::DCCEXanalogWrite(brakePin,duty);
|
||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 62500);
|
||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 7); // 7 means max
|
||||
} else {
|
||||
ledcDetachPin(brakePin);
|
||||
}
|
||||
#elif defined(ARDUINO_ARCH_STM32)
|
||||
if(on) {
|
||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 62500);
|
||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 7); // 7 means max
|
||||
DCCTimer::DCCEXanalogWrite(brakePin,duty);
|
||||
} else {
|
||||
pinMode(brakePin, OUTPUT);
|
||||
}
|
||||
#else
|
||||
#else // all AVR here
|
||||
if(on){
|
||||
switch(brakePin) {
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
// Not worth doin something here as:
|
||||
// If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source.
|
||||
// If we are on pin 5 or 6 we are on Timer 0 ad we can not touch Timer0 as that is millis() etc.
|
||||
// We are most likely not on pin 3 or 11 as no known motor shield has that as brake.
|
||||
#endif
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
case 9:
|
||||
case 10:
|
||||
// Timer2 (is different)
|
||||
TCCR2A = (TCCR2A & B11111100) | B00000011; // set WGM0=1 and WGM1=1 for fast PWM
|
||||
TCCR2B = (TCCR2B & B11110000) | B00000001; // set WGM2=0 and prescaler div=1 (max)
|
||||
DIAG(F("2 A=%x B=%x"), TCCR2A, TCCR2B);
|
||||
break;
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
// Timer4
|
||||
TCCR4A = (TCCR4A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for fast PWM 8-bit
|
||||
TCCR4B = (TCCR4B & B11100000) | B00001001; // set WGM2=1 and WGM3=0 for fast PWM 8 bit and div=1 (max)
|
||||
break;
|
||||
case 46:
|
||||
case 45:
|
||||
case 44:
|
||||
// Timer5
|
||||
TCCR5A = (TCCR5A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for fast PWM 8-bit
|
||||
TCCR5B = (TCCR5B & B11100000) | B00001001; // set WGM2=1 and WGM3=0 for fast PWM 8 bit and div=1 (max)
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 7); // 7 means max
|
||||
}
|
||||
analogWrite(brakePin,duty);
|
||||
#endif
|
||||
|
@@ -187,7 +187,7 @@ class MotorDriver {
|
||||
}
|
||||
};
|
||||
inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); };
|
||||
void setDCSignal(byte speedByte);
|
||||
void setDCSignal(byte speedByte, uint8_t frequency=0);
|
||||
void throttleInrush(bool on);
|
||||
inline void detachDCSignal() {
|
||||
#if defined(__arm__)
|
||||
|
@@ -19,6 +19,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "defines.h"
|
||||
#include "TrackManager.h"
|
||||
#include "FSH.h"
|
||||
#include "DCCWaveform.h"
|
||||
@@ -202,7 +203,7 @@ void TrackManager::setDCSignal(int16_t cab, byte speedbyte) {
|
||||
FOR_EACH_TRACK(t) {
|
||||
if (trackDCAddr[t]!=cab && cab != 0) continue;
|
||||
if (track[t]->getMode() & TRACK_MODE_DC)
|
||||
track[t]->setDCSignal(speedbyte);
|
||||
track[t]->setDCSignal(speedbyte, DCC::getThrottleFrequency(trackDCAddr[t]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,8 +349,8 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
}
|
||||
|
||||
void TrackManager::applyDCSpeed(byte t) {
|
||||
uint8_t speedByte=DCC::getThrottleSpeedByte(trackDCAddr[t]);
|
||||
track[t]->setDCSignal(speedByte);
|
||||
track[t]->setDCSignal(DCC::getThrottleSpeedByte(trackDCAddr[t]),
|
||||
DCC::getThrottleFrequency(trackDCAddr[t]));
|
||||
}
|
||||
|
||||
bool TrackManager::parseEqualSign(Print *stream, int16_t params, int16_t p[])
|
||||
@@ -489,10 +490,13 @@ std::vector<MotorDriver *>TrackManager::getMainDrivers() {
|
||||
|
||||
// Set track power for all tracks with this mode
|
||||
void TrackManager::setTrackPower(TRACK_MODE trackmodeToMatch, POWERMODE powermode) {
|
||||
bool didChange=false;
|
||||
FOR_EACH_TRACK(t) {
|
||||
MotorDriver *driver=track[t];
|
||||
TRACK_MODE trackmodeOfTrack = driver->getMode();
|
||||
if (trackmodeToMatch & trackmodeOfTrack) {
|
||||
if (powermode != driver->getPower())
|
||||
didChange=true;
|
||||
if (powermode == POWERMODE::ON) {
|
||||
if (trackmodeOfTrack & TRACK_MODE_DC) {
|
||||
driver->setBrake(true); // DC starts with brake on
|
||||
@@ -507,12 +511,15 @@ void TrackManager::setTrackPower(TRACK_MODE trackmodeToMatch, POWERMODE powermod
|
||||
driver->setPower(powermode);
|
||||
}
|
||||
}
|
||||
if (didChange)
|
||||
CommandDistributor::broadcastPower();
|
||||
}
|
||||
|
||||
// Set track power for this track, inependent of mode
|
||||
void TrackManager::setTrackPower(POWERMODE powermode, byte t) {
|
||||
MotorDriver *driver=track[t];
|
||||
TRACK_MODE trackmode = driver->getMode();
|
||||
POWERMODE oldpower = driver->getPower();
|
||||
if (trackmode & TRACK_MODE_NONE) {
|
||||
driver->setBrake(true); // Track is unused. Brake is good to have.
|
||||
powermode = POWERMODE::OFF; // Track is unused. Force it to OFF
|
||||
@@ -530,6 +537,8 @@ void TrackManager::setTrackPower(POWERMODE powermode, byte t) {
|
||||
}
|
||||
}
|
||||
driver->setPower(powermode);
|
||||
if (oldpower != driver->getPower())
|
||||
CommandDistributor::broadcastPower();
|
||||
}
|
||||
|
||||
// returns state of the one and only prog track
|
||||
@@ -566,14 +575,17 @@ bool TrackManager::getPower(byte t, char s[]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void TrackManager::reportObsoleteCurrent(Print* stream) {
|
||||
// This function is for backward JMRI compatibility only
|
||||
// It reports the first track only, as main, regardless of track settings.
|
||||
// <c MeterName value C/V unit min max res warn>
|
||||
#ifdef HAS_ENOUGH_MEMORY
|
||||
int maxCurrent=track[0]->raw2mA(track[0]->getRawCurrentTripValue());
|
||||
StringFormatter::send(stream, F("<c CurrentMAIN %d C Milli 0 %d 1 %d>\n"),
|
||||
track[0]->raw2mA(track[0]->getCurrentRaw(false)), maxCurrent, maxCurrent);
|
||||
track[0]->raw2mA(track[0]->getCurrentRaw(false)), maxCurrent, maxCurrent);
|
||||
#else
|
||||
(void)stream;
|
||||
#endif
|
||||
}
|
||||
|
||||
void TrackManager::reportCurrent(Print* stream) {
|
||||
|
@@ -150,7 +150,6 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
DCCWaveform::progTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
||||
*/
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
}
|
||||
#if defined(EXRAIL_ACTIVE)
|
||||
else if (cmd[1]=='R' && cmd[2]=='A' && cmd[3]=='2' ) { // Route activate
|
||||
@@ -496,7 +495,6 @@ void WiThrottle::getLocoCallback(int16_t locoid) {
|
||||
TrackManager::setJoin(true); // <1 JOIN> so we can drive loco away
|
||||
DIAG(F("LocoCallback commit success"));
|
||||
stashStream->commit();
|
||||
CommandDistributor::broadcastPower();
|
||||
}
|
||||
|
||||
void WiThrottle::sendIntro(Print* stream) {
|
||||
@@ -620,7 +618,7 @@ void WiThrottle::sendFunctions(Print* stream, byte loco) {
|
||||
#endif
|
||||
|
||||
for(int fKey=0; fKey<fkeys; fKey++) {
|
||||
int fstate=DCC::getFn(locoid,fKey);
|
||||
int8_t 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);
|
||||
}
|
||||
}
|
||||
|
@@ -74,25 +74,39 @@ 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;
|
||||
};
|
||||
bool active(byte clientId) {
|
||||
if (!inUse)
|
||||
return false;
|
||||
if(!wifi.connected()) {
|
||||
DIAG(F("Remove client %d"), clientId);
|
||||
CommandDistributor::forget(clientId);
|
||||
wifi.stop();
|
||||
inUse = false;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool recycle(WiFiClient c) {
|
||||
if (wifi == c) {
|
||||
if (inUse == true)
|
||||
DIAG(F("WARNING: Duplicate"));
|
||||
else
|
||||
DIAG(F("Returning"));
|
||||
inUse = true;
|
||||
return true;
|
||||
}
|
||||
if (inUse == false) {
|
||||
wifi = c;
|
||||
inUse = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
WiFiClient wifi;
|
||||
bool inUse = true;
|
||||
private:
|
||||
bool inUse;
|
||||
};
|
||||
|
||||
static std::vector<NetworkClient> clients; // a list to hold all clients
|
||||
@@ -165,7 +179,7 @@ bool WifiESP::setup(const char *SSid,
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
// DIAG(F("Wifi STA IP %s"),WiFi.localIP().toString().c_str());
|
||||
DIAG(F("Wifi in STA mode"));
|
||||
LCD(7, F("IP: %s"), WiFi.softAPIP().toString().c_str());
|
||||
LCD(7, F("IP: %s"), WiFi.localIP().toString().c_str());
|
||||
wifiUp = true;
|
||||
} else {
|
||||
DIAG(F("Could not connect to Wifi SSID %s"),SSid);
|
||||
@@ -282,37 +296,26 @@ void WifiESP::loop() {
|
||||
// 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());
|
||||
DIAG(F("Recycle client %d %s:%d"), clientId, client.remoteIP().toString().c_str(),client.remotePort());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (clientId>=clients.size()) {
|
||||
NetworkClient nc(client);
|
||||
clients.push_back(nc);
|
||||
DIAG(F("New client %d, %s"), clientId, client.remoteIP().toString().c_str());
|
||||
DIAG(F("New client %d, %s:%d"), clientId, client.remoteIP().toString().c_str(),client.remotePort());
|
||||
}
|
||||
}
|
||||
}
|
||||
// loop over all connected clients
|
||||
// this removes as a side effect inactive clients when checking ::active()
|
||||
for (clientId=0; clientId<clients.size(); clientId++){
|
||||
if(clients[clientId].ok()) {
|
||||
if(clients[clientId].active(clientId)) {
|
||||
int len;
|
||||
if ((len = clients[clientId].wifi.available()) > 0) {
|
||||
// read data from client
|
||||
@@ -350,7 +353,7 @@ void WifiESP::loop() {
|
||||
}
|
||||
// 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((unsigned int)clientId <= clients.size() && clients[clientId].active(clientId)) {
|
||||
if (Diag::CMD || Diag::WITHROTTLE)
|
||||
DIAG(F("SEND %d:%s"), clientId, buffer);
|
||||
clients[clientId].wifi.write(buffer,count);
|
||||
@@ -383,8 +386,9 @@ void WifiESP::loop() {
|
||||
// prio task. On core1 this is not a problem
|
||||
// as there the wdt is disabled by the
|
||||
// arduio IDE startup routines.
|
||||
if (xPortGetCoreID() == 0)
|
||||
if (xPortGetCoreID() == 0) {
|
||||
feedTheDog0();
|
||||
yield();
|
||||
yield();
|
||||
}
|
||||
}
|
||||
#endif //ESP32
|
||||
|
@@ -68,7 +68,9 @@ Stream * WifiInterface::wifiStream;
|
||||
#define NUM_SERIAL 3
|
||||
#define SERIAL1 Serial3
|
||||
#define SERIAL3 Serial5
|
||||
#elif defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) || defined(ARDUINO_NUCLEO_F412ZG)
|
||||
#elif defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) \
|
||||
|| defined(ARDUINO_NUCLEO_F446ZE) || defined(ARDUINO_NUCLEO_F412ZG) \
|
||||
|| defined(ARDUINO_NUCLEO_F439ZI)
|
||||
#define NUM_SERIAL 2
|
||||
#define SERIAL1 Serial6
|
||||
#else
|
||||
|
11
version.h
11
version.h
@@ -3,7 +3,16 @@
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
#define VERSION "5.2.12"
|
||||
#define VERSION "5.2.XX"
|
||||
// 5.2.XX - Variable frequency for DC mode
|
||||
// 5.2.18 - Display network IP fix
|
||||
// 5.2.17 - ESP32 simplify network logic
|
||||
// 5.2.16 - Bugfix to allow for devices using the EX-IOExpander protocol to have no analogue or no digital pins
|
||||
// 5.2.15 - move call to CommandDistributor::broadcastPower() into the TrackManager::setTrackPower(*) functions
|
||||
// - add repeats to function packets that are not reminded in accordance with accessory packets
|
||||
// 5.2.14 - Reminder window DCC packet optimization
|
||||
// - Optional #define DISABLE_FUNCTION_REMINDERS
|
||||
// 5.2.13 - EXRAIL STEALTH
|
||||
// 5.2.12 - ESP32 add AP mode LCD messages with SSID/PW for
|
||||
// - STM32 change to UID_BASE constants in DCCTimerSTM32 rather than raw hex addresses for UID registers
|
||||
// - STM32 extra UART/USARTs for larger Nucleo models
|
||||
|
Reference in New Issue
Block a user