1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-07-30 19:03:44 +02:00

Compare commits

..

72 Commits

Author SHA1 Message Date
Harald Barth
f6f5c22bbd more random bits there 2022-11-26 00:15:09 +01:00
Harald Barth
fc3a28143d move multiplicaton factor to macro for better optimization 2022-11-25 10:53:00 +01:00
Harald Barth
96f47bf44f protect analogvals read from interrupt 2022-11-25 10:48:18 +01:00
Harald Barth
9b04cd791b scan ADC pins with id array improved 2022-11-24 21:12:45 +01:00
Harald Barth
37b679ae2c better pseudo random 2022-11-24 20:24:15 +01:00
Harald Barth
3e4d6863bb scan ADC pins with id array 2022-11-23 23:20:23 +01:00
Harald Barth
e1ec63464c Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2022-11-23 22:45:59 +01:00
Harald Barth
aa02cd11e3 firebox disable, gcc flag for smaller binary 2022-11-23 22:44:29 +01:00
Neil McKechnie
ce5d546b9b Update DCC.cpp to remove diagnostic code. 2022-11-22 17:26:11 +00:00
Neil McKechnie
644cb29a3a Performance improvements in function DCC::issueReminders
Function issueReminders was taking around 1700us to complete.  It has been refactored to optimise calculations and reduce the amount of the loco table that needs to be scanned each time.  It now takes typically under 50us to execute.
2022-11-22 17:24:11 +00:00
Neil McKechnie
70203c3733 Fix to IO_DFPlayer.h - device was ignoring commands
The DFPlayer device does not like successive commands arriving to quickly after one another and may ignore a command.  The driver has been modified to enforce a delay between commands by sending pad characters where necessary.
2022-11-22 17:15:13 +00:00
Harald Barth
f4aa572df2 Remove RAM thief 2022-11-18 20:19:53 +01:00
Harald Barth
01e5d49332 version 2022-11-16 00:14:54 +01:00
Harald Barth
6a3a891682 break to va_end() 2022-11-16 00:14:11 +01:00
Harald Barth
f5b48619bf AVR Mega2560: Set timer reg ADCSRB correct 2022-11-16 00:13:31 +01:00
Harald Barth
4a3d8729c6 Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2022-11-09 00:52:31 +01:00
Harald Barth
d874ad8cc3 Make GETFLASHW code more universal 2022-11-09 00:52:07 +01:00
Harald Barth
bd8439c2f9 Fix: Roster indexing when sending to withrottle 2022-11-08 23:35:07 +01:00
pmantoine
65714ed1f2 Rename Nucleo build target 2022-11-08 07:48:35 +11:00
Harald Barth
280e61e1fc Make EthernetInterface code more robust 2022-11-07 11:53:56 +01:00
Harald Barth
b061c0b347 version 2022-11-07 11:22:15 +01:00
Harald Barth
7f3d547541 Initialize outboundRing properly to NULL 2022-11-07 11:20:00 +01:00
Harald Barth
eb0861959c version 2022-11-06 21:33:40 +01:00
Harald Barth
f1d445e056 Do not abort ethernet startup on W5100 2022-11-06 21:32:54 +01:00
Harald Barth
4f19a60621 number of ADC inputs was reversed 2022-11-06 21:30:32 +01:00
Harald Barth
2b3ba514b0 Use X as the questionmark sign in <T 17 X> 2022-11-05 23:11:54 +01:00
Harald Barth
a199de6d3e Make <T nn ?> return long config print 2022-11-04 23:43:26 +01:00
Harald Barth
c0cb643cb5 When sending all turnouts, keep it short 2022-11-04 23:15:29 +01:00
Harald Barth
be2f3b0db7 Ethernet restructure 2022-11-04 16:08:43 +01:00
pmantoine
f939ea0768 Add MEGAAVR ADCeee skeleton. 2022-11-02 13:55:10 +08:00
pmantoine
863c839563 Add Teensy ADCee class skeleton. 2022-11-02 13:46:16 +08:00
Harald Barth
ee4963dfca devel version string update 2022-10-31 19:43:55 +01:00
Asbelos
5e2b416c30 roster list half error 2022-10-31 19:38:49 +01:00
Asbelos
eed1237b9f FIX Driveaway! 2022-10-31 19:14:33 +01:00
peteGSX
07f1d6fc20 Updated .gitignore (#261) 2022-10-31 19:13:43 +01:00
Harald Barth
45504db1ad stacked motor shield example typo fix 2022-10-26 18:59:39 +02:00
Harald Barth
d60a55091f Merge branch 'PORTX_HAL-cursense2' of https://github.com/DCC-EX/CommandStation-EX into PORTX_HAL-cursense2 2022-10-25 12:01:13 +02:00
Harald Barth
90897ff2d1 stacked motor shield example 2022-10-25 11:59:29 +02:00
pmantoine
88c7e540fa STM32 additional serial port support 2022-10-24 11:12:56 +08:00
pmantoine
888165e987 Skeletal ADCee class for STM32 2022-10-24 08:33:08 +08:00
pmantoine
57aa1457e0 GETFLASHW hack for SAMD/STM32 2022-10-23 17:49:39 +08:00
Harald Barth
1a67930af4 make tones like a Taurus 2022-10-22 00:48:53 +02:00
Harald Barth
604a69e0a8 version 2022-10-20 00:54:35 +02:00
Harald Barth
f56a9a2c43 compiler warning fixes and stupid cast for ESP toolchain 2022-10-20 00:53:05 +02:00
Harald Barth
56ed6ab6dc free memory value updated on ESP 2022-10-20 00:52:19 +02:00
Harald Barth
9afce0a7df change from xstrcmp to xstrncmp 2022-10-19 11:10:37 +02:00
Harald Barth
e8180603ba version tag 2022-10-18 23:41:21 +02:00
Harald Barth
f792e07d40 remove possible RAM corruption and improve findUniqThrottle 2022-10-18 22:48:02 +02:00
Harald Barth
2a51fa6f5d Do not specify inline (because of linker) 2022-10-18 22:47:43 +02:00
pmantoine
1c73a3d7bf SAMD21 IRQ priority exploration 2022-10-16 16:15:02 +08:00
Harald Barth
050eb52a22 Merge branch 'PORTX_HAL-cursense' of https://github.com/DCC-EX/CommandStation-EX into PORTX_HAL-cursense 2022-10-14 14:20:41 +02:00
Harald Barth
81b5b25430 disable EEPROM on ESP32 2022-10-14 14:20:32 +02:00
pmantoine
f39a9d1510 SAMD21 ADCee fixes 2022-10-14 18:55:12 +08:00
pmantoine
578cbd08e5 SAMD21 senseFactor fix for 12-bit 2022-10-13 09:12:51 +08:00
Harald Barth
cf89fe2a72 make max ADC value a per platform ADC function 2022-10-12 23:45:10 +02:00
pmantoine
65364212ca SAMD21 fix analogresolution in ADCee::init 2022-10-12 17:30:20 +08:00
pmantoine
eb766aa27f Fix to SAMD21 ADCee pin init 2022-10-12 17:12:00 +08:00
pmantoine
4d205be007 SAMD21 ADCee class full implementation 2022-10-12 16:46:37 +08:00
Harald Barth
208905e7b9 explain in comment 2022-10-10 07:59:54 +02:00
Harald Barth
c36234df73 Dont forget ESP32 has 12 bits ADC 2022-10-05 23:43:09 +02:00
Harald Barth
452ffc5725 changed IODevice code to use ADCee 2022-10-05 23:14:07 +02:00
Harald Barth
08c114fd22 Fixed minor typo in ESP32 code which broke everything 2022-10-05 22:27:27 +02:00
Harald Barth
e0bf978f2b Change Adc to ADCee because of SAMD conflict 2022-10-04 22:19:51 +02:00
Harald Barth
3e214ab77a version 2022-10-04 21:56:58 +02:00
Harald Barth
ee568fcd11 make the Adc class functions the normal code path 2022-10-04 21:55:13 +02:00
Harald Barth
367d2dfe20 version 2022-10-04 00:35:58 +02:00
Harald Barth
b7295c4923 add class Adc instead of motordriver specific analog pin read 2022-10-04 00:32:48 +02:00
Harald Barth
5e616a9eb2 make compile for other arch but AVR 2022-10-02 22:53:35 +02:00
Harald Barth
24e5e648b8 improve current sense sampling on AVR 2022-10-02 13:40:46 +02:00
Harald Barth
24a7475482 version 2022-10-02 00:44:46 +02:00
Harald Barth
c47e9b79ca do analogRead without need for noInterrupt - first test 2022-10-02 00:43:46 +02:00
Harald Barth
052f5807f0 installer shell script 2022-09-24 23:32:24 +02:00
40 changed files with 1028 additions and 290 deletions

11
.gitignore vendored
View File

@@ -8,13 +8,14 @@ Release/*
.vscode/
config.h
.vscode/*
mySetup.h
# mySetup.h
mySetup.cpp
myHal.cpp
myAutomation.h
# myAutomation.h
myFilter.cpp
myAutomation.h
myFilter.cpp
myLayout.h
# myAutomation.h
# myLayout.h
my*.h
!my*.example.h
.vscode/extensions.json
.vscode/extensions.json

41
DCC.cpp
View File

@@ -595,30 +595,15 @@ 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;
// This loop searches for a loco in the speed table starting at nextLoco and cycling back around
/*
for (int reg=0;reg<MAX_LOCOS;reg++) {
int slot=reg+nextLoco;
if (slot>=MAX_LOCOS) slot-=MAX_LOCOS;
if (speedTable[slot].loco > 0) {
// have found the next loco to remind
// issueReminder will return true if this loco is completed (ie speed and functions)
if (issueReminder(slot)) nextLoco=slot+1;
return;
}
}
*/
for (int reg=nextLoco;reg<MAX_LOCOS+nextLoco;reg++) {
int slot=reg%MAX_LOCOS;
if (speedTable[slot].loco > 0) {
// have found the next loco to remind
// issueReminder will return true if this loco is completed (ie speed and functions)
if (issueReminder(slot))
nextLoco=(slot+1)%MAX_LOCOS;
return;
}
}
// Move to next loco slot. If occupied, send a reminder.
int reg = lastLocoReminder+1;
if (reg > highestUsedReg) reg = 0; // Go to start of table
if (speedTable[reg].loco > 0) {
// have found loco to remind
if (issueReminder(reg))
lastLocoReminder = reg;
} else
lastLocoReminder = reg;
}
bool DCC::issueReminder(int reg) {
@@ -698,6 +683,7 @@ int DCC::lookupSpeedTable(int locoId, bool autoCreate) {
speedTable[reg].groupFlags=0;
speedTable[reg].functions=0;
}
if (reg > highestUsedReg) highestUsedReg = reg;
return reg;
}
@@ -705,7 +691,7 @@ void DCC::updateLocoReminder(int loco, byte speedCode) {
if (loco==0) {
// broadcast stop/estop but dont change direction
for (int reg = 0; reg < MAX_LOCOS; reg++) {
for (int reg = 0; reg < highestUsedReg; reg++) {
if (speedTable[reg].loco==0) continue;
byte newspeed=(speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f);
if (speedTable[reg].speedCode != newspeed) {
@@ -725,13 +711,14 @@ void DCC::updateLocoReminder(int loco, byte speedCode) {
}
DCC::LOCO DCC::speedTable[MAX_LOCOS];
int DCC::nextLoco = 0;
int DCC::lastLocoReminder = 0;
int DCC::highestUsedReg = 0;
void DCC::displayCabList(Print * stream) {
int used=0;
for (int reg = 0; reg < MAX_LOCOS; reg++) {
for (int reg = 0; reg <= highestUsedReg; reg++) {
if (speedTable[reg].loco>0) {
used ++;
StringFormatter::send(stream,F("cab=%d, speed=%d, dir=%c \n"),

3
DCC.h
View File

@@ -108,7 +108,8 @@ private:
static void updateLocoReminder(int loco, byte speedCode);
static void setFunctionInternal(int cab, byte fByte, byte eByte);
static bool issueReminder(int reg);
static int nextLoco;
static int lastLocoReminder;
static int highestUsedReg;
static FSH *shieldName;
static byte globalSpeedsteps;

View File

@@ -425,7 +425,7 @@ void DCCACK::checkAck(byte sentResetsSincePacket) {
return;
}
int current=progDriver->getCurrentRawInInterrupt();
int current=progDriver->getCurrentRaw(true); // true means "from interrupt"
numAckSamples++;
if (current > ackMaxCurrent) ackMaxCurrent=current;
// An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba)

View File

@@ -73,6 +73,7 @@ const int16_t HASH_KEYWORD_A='A';
const int16_t HASH_KEYWORD_C='C';
const int16_t HASH_KEYWORD_R='R';
const int16_t HASH_KEYWORD_T='T';
const int16_t HASH_KEYWORD_X='X';
const int16_t HASH_KEYWORD_LCN = 15137;
const int16_t HASH_KEYWORD_HAL = 10853;
const int16_t HASH_KEYWORD_SHOW = -21309;
@@ -730,15 +731,7 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
switch (params)
{
case 0: // <T> list turnout definitions
{
bool gotOne = false;
for (Turnout *tt = Turnout::first(); tt != NULL; tt = tt->next())
{
gotOne = true;
tt->print(stream);
}
return gotOne; // will <X> if none found
}
return Turnout::printAll(stream); // will <X> if none found
case 1: // <T id> delete turnout
if (!Turnout::remove(p[0]))
@@ -759,12 +752,19 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
case HASH_KEYWORD_T:
state= false;
break;
default:
return false; // Invalid parameter
case HASH_KEYWORD_X:
{
Turnout *tt = Turnout::get(p[0]);
if (tt) {
tt->print(stream);
return true;
}
return false;
}
default: // Invalid parameter
return false;
}
if (!Turnout::setClosed(p[0], state)) return false;
return true;
}

View File

@@ -21,6 +21,7 @@
#include "defines.h"
#include "DIAG.h"
#include "DCCRMT.h"
#include "DCCTimer.h"
#include "DCCWaveform.h" // for MAX_PACKET_SIZE
#include "soc/gpio_sig_map.h"
@@ -67,6 +68,8 @@ RMTChannel *channelHandle[8] = { 0 };
void IRAM_ATTR interrupt(rmt_channel_t channel, void *t) {
RMTChannel *tt = channelHandle[channel];
if (tt) tt->RMTinterrupt();
if (channel == 0)
DCCTimer::updateMinimumFreeMemoryISR(0);
}
RMTChannel::RMTChannel(pinpair pins, bool isMain) {

View File

@@ -1,7 +1,7 @@
/*
* © 2022 Paul M Antoine
* © 2022 Paul M. Antoine
* © 2021 Mike S
* © 2021 Harald Barth
* © 2021-2022 Harald Barth
* © 2021 Fred Decker
* All rights reserved.
*
@@ -93,4 +93,40 @@ private:
};
// Class ADCee implements caching of the ADC value for platforms which
// have a too slow ADC read to wait for. On these platforms the ADC is
// scanned continiously in the background from an ISR. On such
// architectures that use the analog read during DCC waveform with
// specially configured ADC, for example AVR, init must be called
// PRIOR to the start of the waveform. It returns the current value so
// that an offset can be initialized.
class ADCee {
public:
// init does add the pin to the list of scanned pins (if this
// platform's implementation scans pins) and returns the first
// read value. It is called before the regular scan is started.
static int init(uint8_t pin);
// read does read the pin value from the scanned cache or directly
// if this is a platform that does not scan. fromISR is a hint if
// it was called from ISR because for some implementations that
// makes a difference.
static int read(uint8_t pin, bool fromISR=false);
// returns possible max value that the ADC can return
static int16_t ADCmax();
private:
// On platforms that scan, it is called from waveform ISR
// only on a regular basis.
static void scan();
// begin is called for any setup that must be done before
// scan can be called.
static void begin();
// bit array of used pins (max 16)
static uint16_t usedpins;
// cached analog values (malloc:ed to actual number of ADC channels)
static int *analogvals;
// ids to scan (new way)
static byte *idarr;
// friend so that we can call scan() and begin()
friend class DCCWaveform;
};
#endif

View File

@@ -1,6 +1,6 @@
/*
* © 2021 Mike S
* © 2021 Harald Barth
* © 2021-2022 Harald Barth
* © 2021 Fred Decker
* © 2021 Chris Harlow
* © 2021 David Cutting
@@ -26,7 +26,6 @@
// Please refer to DCCTimer.h for general comments about how this class works
// This is to avoid repetition and duplication.
#ifdef ARDUINO_ARCH_AVR
#include <avr/boot.h>
#include <avr/wdt.h>
#include "DCCTimer.h"
@@ -44,8 +43,7 @@ INTERRUPT_CALLBACK interruptHandler=0;
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler=callback;
noInterrupts();
ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time
noInterrupts();
TCCR1A = 0;
ICR1 = CLOCK_CYCLES;
TCNT1 = 0;
@@ -120,4 +118,114 @@ void DCCTimer::reset() {
delay(50); // wait for the prescaller time to expire
}
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
#define NUM_ADC_INPUTS 16
#else
#define NUM_ADC_INPUTS 8
#endif
uint16_t ADCee::usedpins = 0;
int * ADCee::analogvals = NULL;
byte *ADCee::idarr = NULL;
static bool ADCusesHighPort = false;
/*
* Register a new pin to be scanned
* Returns current reading of pin and
* stores that as well
*/
int ADCee::init(uint8_t pin) {
uint8_t id = pin - A0;
byte n;
if (id >= NUM_ADC_INPUTS)
return -1023;
if (id > 7)
ADCusesHighPort = true;
pinMode(pin, INPUT);
int value = analogRead(pin);
if (analogvals == NULL) {
analogvals = (int *)calloc(NUM_ADC_INPUTS, 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;
}
}
return value;
}
int16_t ADCee::ADCmax() {
return 1023;
}
/*
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/
int ADCee::read(uint8_t pin, bool fromISR) {
(void)fromISR; // AVR does ignore this arg
uint8_t id = pin - A0;
int a;
// we do not need to check (analogvals == NULL)
// because usedpins would still be 0 in that case
noInterrupts();
a = analogvals[id];
interrupts();
return a;
}
/*
* Scan function that is called from interrupt
*/
#pragma GCC push_options
#pragma GCC optimize ("-O3")
void ADCee::scan() {
static byte num = 0; // index into id array
static bool waiting = false;
if (waiting) {
// look if we have a result
byte low, high;
if (bit_is_set(ADCSRA, ADSC))
return; // no result, continue to wait
// found value
low = ADCL; //must read low before high
high = ADCH;
bitSet(ADCSRA, ADIF);
analogvals[idarr[num]] = (high << 8) | low;
waiting = false;
}
if (!waiting) {
// cycle around in-use analogue pins
num++;
if (idarr[num] == 255)
num = 0;
// 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
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
waiting = true;
}
}
#pragma GCC pop_options
void ADCee::begin() {
noInterrupts();
// ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time
// Set up ADC for free running mode
ADMUX=(1<<REFS0); //select AVCC as reference. We set MUX later
ADCSRA = (1<<ADEN)|(1 << ADPS2); // ADPS2 means divisor 32 and 16Mhz/32=500kHz.
//bitSet(ADCSRA, ADSC); //do not start the ADC yet. Done when we have set the MUX
interrupts();
}
#endif

View File

@@ -78,6 +78,23 @@ int DCCTimer::freeMemory() {
////////////////////////////////////////////////////////////////////////
#ifdef ARDUINO_ARCH_ESP32
#include <driver/adc.h>
#include <soc/sens_reg.h>
#include <soc/sens_struct.h>
#undef ADC_INPUT_MAX_VALUE
#define ADC_INPUT_MAX_VALUE 4095 // 12 bit ADC
#define pinToADC1Channel(X) (adc1_channel_t)(((X) > 35) ? (X)-36 : (X)-28)
int IRAM_ATTR local_adc1_get_raw(int channel) {
uint16_t adc_value;
SENS.sar_meas_start1.sar1_en_pad = (1 << channel); // only one channel is selected
while (SENS.sar_slave_addr1.meas_status != 0);
SENS.sar_meas_start1.meas1_start_sar = 0;
SENS.sar_meas_start1.meas1_start_sar = 1;
while (SENS.sar_meas_start1.meas1_done_sar == 0);
adc_value = SENS.sar_meas_start1.meas1_data_sar;
return adc_value;
}
#include "DCCTimer.h"
INTERRUPT_CALLBACK interruptHandler=0;
@@ -133,5 +150,29 @@ int DCCTimer::freeMemory() {
void DCCTimer::reset() {
ESP.restart();
}
#endif
int ADCee::init(uint8_t pin) {
pinMode(pin, ANALOG);
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(pinToADC1Channel(pin),ADC_ATTEN_DB_11);
return adc1_get_raw(pinToADC1Channel(pin));
}
int16_t ADCee::ADCmax() {
return 4095;
}
/*
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/
int ADCee::read(uint8_t pin, bool fromISR) {
return local_adc1_get_raw(pinToADC1Channel(pin));
}
/*
* Scan function that is called from interrupt
*/
void ADCee::scan() {
}
void ADCee::begin() {
}
#endif //ESP32

View File

@@ -1,4 +1,5 @@
/*
* © 2022 Paul M. Antoine
* © 2021 Mike S
* © 2021 Harald Barth
* © 2021 Fred Decker
@@ -124,5 +125,31 @@ void DCCTimer::reset() {
while(true){}
}
int16_t ADCee::ADCmax() {
return 4095;
}
int ADCee::init(uint8_t pin) {
return analogRead(pin);
}
/*
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/
int ADCee::read(uint8_t pin, bool fromISR) {
int current;
if (!fromISR) noInterrupts();
current = analogRead(pin);
if (!fromISR) interrupts();
return current;
}
/*
* Scan function that is called from interrupt
*/
void ADCee::scan() {
}
void ADCee::begin() {
noInterrupts();
interrupts();
}
#endif

View File

@@ -1,7 +1,7 @@
/*
* © 2022 Paul M Antoine
* © 2022 Paul M. Antoine
* © 2021 Mike S
* © 2021 Harald Barth
* © 2021-2022 Harald Barth
* © 2021 Fred Decker
* © 2021 Chris Harlow
* © 2021 David Cutting
@@ -28,33 +28,14 @@
// This is to avoid repetition and duplication.
#ifdef ARDUINO_ARCH_SAMD
#include "FSH.h" //PMA temp debug
#include "DIAG.h" //PMA temp debug
#include "DCCTimer.h"
#include <wiring_private.h>
INTERRUPT_CALLBACK interruptHandler=0;
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler=callback;
noInterrupts();
// Set up ADC to do faster reads... default for Arduino Zero platform configs is 436uS,
// and we need sub-100uS. This code sets it to a read speed of around 21uS, and for now
// enables 10-bit mode, although 12-bit is possible
ADC->CTRLA.bit.ENABLE = 0; // disable ADC
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
ADC->CTRLB.reg &= 0b1111100011111111; // mask PRESCALER bits
ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 64
ADC_CTRLB_RESSEL_10BIT; // Result on 10 bits default, 12 bits possible
ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time
ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0
ADC->SAMPCTRL.reg = 0x00; // sampling Time Length = 0
ADC->CTRLA.bit.ENABLE = 1; // enable ADC
while(ADC->STATUS.bit.SYNCBUSY == 1); // wait for synchronization
// Timer setup - setup clock sources first
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide 48MHz by 1
GCLK_GENDIV_ID(4); // Apply to GCLK4
@@ -88,7 +69,9 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
// Set the interrupt condition, priority and enable it in the NVIC
TCC0->INTENSET.reg = TCC_INTENSET_OVF; // Only interrupt on overflow
NVIC_SetPriority((IRQn_Type)TCC0_IRQn, 0); // Make this highest priority
int USBprio = NVIC_GetPriority((IRQn_Type) USB_IRQn); // Fetch the USB priority
NVIC_SetPriority((IRQn_Type)TCC0_IRQn, USBprio); // Match the USB priority
// NVIC_SetPriority((IRQn_Type)TCC0_IRQn, 0); // Make this highest priority
NVIC_EnableIRQ((IRQn_Type)TCC0_IRQn); // Enable the interrupt
interrupts();
}
@@ -173,4 +156,137 @@ void DCCTimer::reset() {
while(true) {};
}
#endif
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
uint16_t ADCee::usedpins = 0;
int * ADCee::analogvals = NULL;
int ADCee::init(uint8_t pin) {
uint id = pin - A0;
int value = 0;
if (id > NUM_ADC_INPUTS)
return -1023;
// Dummy read using Arduino library
analogReadResolution(12);
value = analogRead(pin);
// Reconfigure ADC
ADC->CTRLA.bit.ENABLE = 0; // disable ADC
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
ADC->CTRLB.reg &= 0b1111100011001111; // mask PRESCALER and RESSEL bits
ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 16
ADC_CTRLB_RESSEL_12BIT; // Result 12 bits, 10 bits possible
ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time
ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0
ADC->SAMPCTRL.reg = 0x00ul; // sampling Time Length = 0
ADC->CTRLA.bit.ENABLE = 1; // enable ADC
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
// Permanently configure SAMD IO MUX for that pin
pinPeripheral(pin, PIO_ANALOG);
ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[pin].ulADCChannelNumber; // Selection for the positive ADC input
// Start conversion
ADC->SWTRIG.bit.START = 1;
// Wait for the conversion to be ready
while (ADC->INTFLAG.bit.RESRDY == 0); // Waiting for conversion to complete
// Read the value
value = ADC->RESULT.reg;
if (analogvals == NULL)
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
analogvals[id] = value;
usedpins |= (1<<id);
return value;
}
int16_t ADCee::ADCmax() {
return 4095;
}
/*
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/
int ADCee::read(uint8_t pin, bool fromISR) {
uint8_t id = pin - A0;
if ((usedpins & (1<<id) ) == 0)
return -1023;
// we do not need to check (analogvals == NULL)
// because usedpins would still be 0 in that case
return analogvals[id];
}
/*
* Scan function that is called from interrupt
*/
#pragma GCC push_options
#pragma GCC optimize ("-O3")
void ADCee::scan() {
static uint id = 0; // id and mask are the same thing but it is faster to
static uint16_t mask = 1; // increment and shift instead to calculate mask from id
static bool waiting = false;
if (waiting) {
// look if we have a result
if (ADC->INTFLAG.bit.RESRDY == 0)
return; // no result, continue to wait
// found value
analogvals[id] = ADC->RESULT.reg;
// advance at least one track
// for scope debug TrackManager::track[1]->setBrake(0);
waiting = false;
id++;
mask = mask << 1;
if (id == NUM_ADC_INPUTS+1) {
id = 0;
mask = 1;
}
}
if (!waiting) {
if (usedpins == 0) // otherwise we would loop forever
return;
// look for a valid track to sample or until we are around
while (true) {
if (mask & usedpins) {
// start new ADC aquire on id
ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[id + A0].ulADCChannelNumber; // Selection for the positive ADC input
// Start conversion
ADC->SWTRIG.bit.START = 1;
// for scope debug TrackManager::track[1]->setBrake(1);
waiting = true;
return;
}
id++;
mask = mask << 1;
if (id == NUM_ADC_INPUTS+1) {
id = 0;
mask = 1;
}
}
}
}
#pragma GCC pop_options
void ADCee::begin() {
noInterrupts();
// Set up ADC to do faster reads... default for Arduino Zero platform configs is 436uS,
// and we need sub-58uS. This code sets it to a read speed of around 5-6uS, and enables
// 12-bit mode
// Reconfigure ADC
ADC->CTRLA.bit.ENABLE = 0; // disable ADC
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
ADC->CTRLB.reg &= 0b1111100011001111; // mask PRESCALER and RESSEL bits
ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 16
ADC_CTRLB_RESSEL_12BIT; // Result 12 bits, 10 bits possible
ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time
ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0
ADC->SAMPCTRL.reg = 0x00ul; // sampling Time Length = 0
ADC->CTRLA.bit.ENABLE = 1; // enable ADC
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
interrupts();
}
#endif

View File

@@ -1,5 +1,5 @@
/*
* © 2022 Paul M Antoine
* © 2022 Paul M. Antoine
* © 2021 Mike S
* © 2021 Harald Barth
* © 2021 Fred Decker
@@ -37,6 +37,11 @@
#if defined(STM32F411RE)
// STM32F411RE doesn't have Serial1 defined by default
HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
// via the debugger on the Nucleo-64 STM32F411RE. It is therefore unavailable
// for other DCC-EX uses like WiFi, DFPlayer, etc.
// Let's define Serial6 as an additional serial port (the only other option for the F411RE)
HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE
#elif defined(STM32F446ZE)
// STM32F446ZE doesn't have Serial1 defined by default
HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F446ZE
@@ -127,4 +132,31 @@ void DCCTimer::reset() {
while(true) {};
}
int16_t ADCee::ADCmax() {
return 4095;
}
int ADCee::init(uint8_t pin) {
return analogRead(pin);
}
/*
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/
int ADCee::read(uint8_t pin, bool fromISR) {
int current;
if (!fromISR) noInterrupts();
current = analogRead(pin);
if (!fromISR) interrupts();
return current;
}
/*
* Scan function that is called from interrupt
*/
void ADCee::scan() {
}
void ADCee::begin() {
noInterrupts();
interrupts();
}
#endif

View File

@@ -141,4 +141,31 @@ void DCCTimer::reset() {
SCB_AIRCR = 0x05FA0004;
}
int16_t ADCee::ADCmax() {
return 4095;
}
int ADCee::init(uint8_t pin) {
return analogRead(pin);
}
/*
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/
int ADCee::read(uint8_t pin, bool fromISR) {
int current;
if (!fromISR) noInterrupts();
current = analogRead(pin);
if (!fromISR) interrupts();
return current;
}
/*
* Scan function that is called from interrupt
*/
void ADCee::scan() {
}
void ADCee::begin() {
noInterrupts();
interrupts();
}
#endif

View File

@@ -62,6 +62,7 @@ const bool signalTransform[]={
/* WAVE_PENDING (should not happen) -> */ LOW};
void DCCWaveform::begin() {
ADCee::begin();
DCCTimer::begin(DCCWaveform::interruptHandler);
}
@@ -80,12 +81,14 @@ void DCCWaveform::interruptHandler() {
// Set the signal state for both tracks
TrackManager::setDCCSignal(sigMain);
TrackManager::setPROGSignal(sigProg);
// Refresh the values in the ADCee object buffering the values of the ADC HW
ADCee::scan();
// Move on in the state engine
mainTrack.state=stateTransform[mainTrack.state];
progTrack.state=stateTransform[progTrack.state];
// WAVE_PENDING means we dont yet know what the next bit is
if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2();
if (progTrack.state==WAVE_PENDING) progTrack.interrupt2();

View File

@@ -90,9 +90,13 @@ LookList * RMFT2::onDeactivateLookup=NULL;
LookList * RMFT2::onRedLookup=NULL;
LookList * RMFT2::onAmberLookup=NULL;
LookList * RMFT2::onGreenLookup=NULL;
#define GET_OPCODE GETFLASH(RMFT2::RouteCode+progCounter)
#ifdef ARDUINO_ARCH_AVR
#define GET_OPERAND(n) GETFLASHW(RMFT2::RouteCode+progCounter+1+(n*3))
#else
#define GET_OPERAND(n) GETOPW(RMFT2::RouteCode+progCounter+1+(n*3))
#define GETOPW(A) (((uint32_t)A)%2 ? GETFLASH((const byte *)A) | (GETFLASH(1+(const byte *)A)<<8) : GETFLASHW(A))
#endif
#define SKIPOP progCounter+=3
@@ -180,6 +184,15 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
break;
}
case OPCODE_ATGTE:
case OPCODE_ATLT:
case OPCODE_IFGTE:
case OPCODE_IFLT:
case OPCODE_DRIVE: {
IODevice::configureAnalogIn((VPIN)operand);
break;
}
case OPCODE_TURNOUT: {
VPIN id=operand;
int addr=GET_OPERAND(1);
@@ -714,7 +727,7 @@ void RMFT2::loop2() {
break;
case OPCODE_IFRANDOM: // do block on random percentage
skipIf=(int16_t)(micros()%100) >= operand;
skipIf= ((uint8_t)((uint16_t)micros()>>4)) >= operand;
break;
case OPCODE_IFRESERVE: // do block if we successfully RERSERVE

View File

@@ -256,7 +256,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
#define IFGTE(sensor_id,value) OPCODE_IFGTE,V(sensor_id),OPCODE_PAD,V(value),
#define IFLT(sensor_id,value) OPCODE_IFLT,V(sensor_id),OPCODE_PAD,V(value),
#define IFNOT(sensor_id) OPCODE_IFNOT,V(sensor_id),
#define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent),
#define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent*255/100),
#define IFRED(signal_id) OPCODE_IFRED,V(signal_id),
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),

View File

@@ -1,6 +1,7 @@
/*
* © 2022 Bruno Sanches
* © 2021 Fred Decker
* © 2020-2021 Harald Barth
* © 2020-2022 Harald Barth
* © 2020-2021 Chris Harlow
* © 2020 Gregor Baues
* All rights reserved.
@@ -36,8 +37,13 @@ EthernetInterface * EthernetInterface::singleton=NULL;
*/
void EthernetInterface::setup()
{
singleton=new EthernetInterface();
if (!singleton->connected) singleton=NULL;
if (singleton!=NULL) {
DIAG(F("Prog Error!"));
return;
}
if ((singleton=new EthernetInterface()))
return;
DIAG(F("Ethernet not initialized"));
};
@@ -54,45 +60,41 @@ EthernetInterface::EthernetInterface()
connected=false;
#ifdef IP_ADDRESS
Ethernet.begin(mac, IP_ADDRESS);
if (Ethernet.begin(mac, IP_ADDRESS) == 0)
#else
if (Ethernet.begin(mac) == 0)
#endif
{
DIAG(F("Ethernet.begin FAILED"));
return;
}
#endif
DIAG(F("begin OK."));
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
DIAG(F("Ethernet shield not found"));
return;
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
DIAG(F("Ethernet shield not found or W5100"));
}
unsigned long startmilli = millis();
while ((millis() - startmilli) < 5500) // Loop to give time to check for cable connection
{
while ((millis() - startmilli) < 5500) { // Loop to give time to check for cable connection
if (Ethernet.linkStatus() == LinkON)
break;
DIAG(F("Ethernet waiting for link (1sec) "));
delay(1000);
}
// now we either do have link of we have a W5100
// where we do not know if we have link. That's
// the reason to now run checkLink.
// CheckLinks sets up outboundRing if it does
// not exist yet as well.
checkLink();
}
if (Ethernet.linkStatus() == LinkOFF) {
DIAG(F("Ethernet cable not connected"));
return;
}
connected=true;
IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT
server->begin();
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
LCD(5,F("Port:%d"), IP_PORT);
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
/**
* @brief Cleanup any resources
*
* @return none
*/
EthernetInterface::~EthernetInterface() {
delete server;
delete outboundRing;
}
/**
@@ -101,33 +103,70 @@ EthernetInterface::EthernetInterface()
*/
void EthernetInterface::loop()
{
if (!singleton) return;
if (!singleton || (!singleton->checkLink()))
return;
switch (Ethernet.maintain())
{
switch (Ethernet.maintain()) {
case 1:
//renewed fail
DIAG(F("Ethernet Error: renewed fail"));
singleton=NULL;
return;
case 3:
//rebind fail
DIAG(F("Ethernet Error: rebind fail"));
singleton=NULL;
return;
default:
//nothing happened
break;
}
singleton->loop2();
}
void EthernetInterface::loop2()
{
/**
* @brief Checks ethernet link cable status and detects when it connects / disconnects
*
* @return true when cable is connected, false otherwise
*/
bool EthernetInterface::checkLink() {
if (Ethernet.linkStatus() != LinkOFF) { // check for not linkOFF instead of linkON as the W5100 does return LinkUnknown
//if we are not connected yet, setup a new server
if(!connected) {
DIAG(F("Ethernet cable connected"));
connected=true;
IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT
server->begin();
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
LCD(5,F("Port:%d"), IP_PORT);
// only create a outboundRing it none exists, this may happen if the cable
// gets disconnected and connected again
if(!outboundRing)
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
}
return true;
} else { // connected
DIAG(F("Ethernet cable disconnected"));
connected=false;
//clean up any client
for (byte socket = 0; socket < MAX_SOCK_NUM; socket++) {
if(clients[socket].connected())
clients[socket].stop();
}
// tear down server
delete server;
server = nullptr;
LCD(4,F("IP: None"));
}
return false;
}
void EthernetInterface::loop2() {
if (!outboundRing) { // no idea to call loop2() if we can't handle outgoing data in it
if (Diag::ETHERNET) DIAG(F("No outboundRing"));
return;
}
// get client from the server
EthernetClient client = server->accept();
@@ -182,7 +221,9 @@ void EthernetInterface::loop()
// handle at most 1 outbound transmission
int socketOut=outboundRing->read();
if (socketOut>=0) {
if (socketOut >= MAX_SOCK_NUM) {
DIAG(F("Ethernet outboundRing socket=%d error"), socketOut);
} else if (socketOut >= 0) {
int count=outboundRing->count();
if (Diag::ETHERNET) DIAG(F("Ethernet reply socket=%d, count=:%d"), socketOut,count);
for(;count>0;count--) clients[socketOut].write(outboundRing->read());

View File

@@ -56,15 +56,16 @@ class EthernetInterface {
static void loop();
private:
static EthernetInterface * singleton;
bool connected;
EthernetInterface();
void loop2();
EthernetServer * server;
static EthernetInterface * singleton;
bool connected;
EthernetInterface();
~EthernetInterface();
void loop2();
bool checkLink();
EthernetServer * server = NULL;
EthernetClient clients[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield
uint8_t buffer[MAX_ETH_BUFFER+1]; // buffer used by TCP for the recv
RingStream * outboundRing;
RingStream * outboundRing = NULL;
};
#endif

24
FSH.h
View File

@@ -1,5 +1,5 @@
/*
* © 2022 Paul M Antoine
* © 2022 Paul M. Antoine
* © 2021 Neil McKechnie
* © 2021 Harald Barth
* © 2021 Fred Decker
@@ -37,7 +37,9 @@
*
*/
#include <Arduino.h>
#if defined(ARDUINO_ARCH_MEGAAVR)
#ifdef F
#undef F
#endif
@@ -48,18 +50,26 @@ typedef char FSH;
#define FLASH
#define strlen_P strlen
#define strcpy_P strcpy
#elif defined(ARDUINO_ARCH_STM32)
#elif defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32)
typedef __FlashStringHelper FSH;
#define GETFLASH(addr) pgm_read_byte_near(addr)
#define GETFLASHW(addr) pgm_read_word_near(addr)
#define GETFLASH(addr) pgm_read_byte(addr)
// pgm_read_word is buggy if addr is odd but here
// we do only read well aligned addrs, the others are
// taken care about in the GET_OPERAND(n) macro in EXRAIL2.cpp.
#define GETFLASHW(addr) pgm_read_word(addr)
#ifdef FLASH
#undef FLASH
#endif
#define FLASH PROGMEM
#else
#else // AVR and AVR compat here
typedef __FlashStringHelper FSH;
#define GETFLASH(addr) pgm_read_byte_near(addr)
#define GETFLASHW(addr) pgm_read_word_near(addr)
#define FLASH PROGMEM
#endif
#endif
#endif // flash stuff
#endif // FSH

View File

@@ -1 +1 @@
#define GITHUB_SHA "PORTX-HAL-20220918"
#define GITHUB_SHA "devel-202211242012Z"

View File

@@ -113,7 +113,8 @@ void I2CManagerClass::I2C_init()
#if defined(I2C_USE_INTERRUPTS)
// Setting NVIC
NVIC_EnableIRQ(SERCOM3_IRQn);
NVIC_SetPriority (SERCOM3_IRQn, 0); /* set Priority */
NVIC_SetPriority (SERCOM3_IRQn, SERCOM_NVIC_PRIORITY); // Match default SERCOM priorities
// NVIC_SetPriority (SERCOM3_IRQn, 0); // Set highest priority
// Enable all interrupts
s->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_MB | SERCOM_I2CM_INTENSET_SB | SERCOM_I2CM_INTENSET_ERROR;

View File

@@ -25,6 +25,7 @@
#include "DIAG.h"
#include "FSH.h"
#include "IO_MCP23017.h"
#include "DCCTimer.h"
#if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_MEGAAVR)
#define USE_FAST_IO
@@ -195,7 +196,17 @@ int IODevice::readAnalogue(VPIN vpin) {
#ifdef DIAG_IO
DIAG(F("IODevice::readAnalogue(): Vpin %d not found!"), (int)vpin);
#endif
return false;
return -1023;
}
int IODevice::configureAnalogIn(VPIN vpin) {
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
if (dev->owns(vpin))
return dev->_configureAnalogIn(vpin);
}
#ifdef DIAG_IO
DIAG(F("IODevice::configureAnalogIn(): Vpin %d not found!"), (int)vpin);
#endif
return -1023;
}
// Write value to virtual pin(s). If multiple devices are allocated the same pin
@@ -361,11 +372,10 @@ int IODevice::read(VPIN vpin) {
return !digitalRead(vpin); // Return inverted state (5v=0, 0v=1)
}
int IODevice::readAnalogue(VPIN vpin) {
pinMode(vpin, INPUT);
noInterrupts();
int value = analogRead(vpin);
interrupts();
return value;
return ADCee::read(vpin);
}
int IODevice::configureAnalogIn(VPIN vpin) {
return ADCee::init(vpin);
}
void IODevice::loop() {}
void IODevice::DumpAll() {
@@ -467,7 +477,18 @@ int ArduinoPins::_read(VPIN vpin) {
// Device-specific readAnalogue function (analogue input)
int ArduinoPins::_readAnalogue(VPIN vpin) {
int pin = vpin;
if (vpin > 255) return -1023;
uint8_t pin = vpin;
int value = ADCee::read(pin);
#ifdef DIAG_IO
DIAG(F("Arduino Read Pin:%d Value:%d"), pin, value);
#endif
return value;
}
int ArduinoPins::_configureAnalogIn(VPIN vpin) {
if (vpin > 255) return -1023;
uint8_t pin = vpin;
uint8_t mask = 1 << ((pin-_firstVpin) % 8);
uint8_t index = (pin-_firstVpin) / 8;
if (_pinModes[index] & mask) {
@@ -479,22 +500,9 @@ int ArduinoPins::_readAnalogue(VPIN vpin) {
else
pinMode(pin, INPUT);
}
// Since AnalogRead is also called from interrupt code, disable interrupts
// while we're using it. There's only one ADC shared by all analogue inputs
// on the Arduino, so we don't want interruptions.
//******************************************************************************
// NOTE: If the HAL is running on a computer without the DCC signal generator,
// then interrupts needn't be disabled. Also, the DCC signal generator puts
// the ADC into fast mode, so if it isn't present, analogueRead calls will be much
// slower!!
//******************************************************************************
noInterrupts();
int value = analogRead(pin);
interrupts();
int value = ADCee::init(pin);
#ifdef DIAG_IO
DIAG(F("Arduino Read Pin:%d Value:%d"), pin, value);
DIAG(F("configureAnalogIn Pin:%d Value:%d"), pin, value);
#endif
return value;
}

View File

@@ -143,6 +143,7 @@ public:
// read invokes the IODevice instance's _readAnalogue method.
static int readAnalogue(VPIN vpin);
static int configureAnalogIn(VPIN vpin);
// loop invokes the IODevice instance's _loop method.
static void loop();
@@ -201,6 +202,10 @@ protected:
(void)vpin;
return 0;
};
virtual int _configureAnalogIn(VPIN vpin) {
(void)vpin;
return 0;
};
// Method to perform updates on an ongoing basis (optionally implemented within device class)
virtual void _loop(unsigned long currentMicros) {
@@ -356,6 +361,7 @@ private:
// Device-specific read functions.
int _read(VPIN vpin) override;
int _readAnalogue(VPIN vpin) override;
int _configureAnalogIn(VPIN vpin) override;
void _display() override;
@@ -403,4 +409,4 @@ private:
#include "IO_MCP23017.h"
#include "IO_PCF8574.h"
#endif // iodevice_h
#endif // iodevice_h

View File

@@ -1,5 +1,5 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
* © 2022, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
@@ -36,24 +36,31 @@
* In mySetup function within mySetup.cpp:
* DFPlayer::create(3500, 5, Serial1);
*
* Writing an analogue value 0-2999 to the first pin will select a numbered file from the SD card;
* Writing an analogue value 0-30 to the second pin will set the volume of the output;
* Writing a digital value to the first pin will play or stop the file;
* Writing an analogue value 1-2999 to the first pin (3500) will play the numbered file from the SD card;
* Writing an analogue value 0 to the first pin (3500) will stop the file playing;
* Writing an analogue value 0-30 to the second pin (3501) will set the volume;
* Writing a digital value of 1 to a pin will play the file corresponding to that pin, e.g.
the first file will be played by setting pin 3500, the second by setting pin 3501 etc.;
* Writing a digital value of 0 to any pin will stop the player;
* Reading a digital value from any pin will return true(1) if the player is playing, false(0) otherwise.
*
* From EX-RAIL, the following commands may be used:
* SET(3500) -- starts playing the first file on the SD card
* SET(3501) -- starts playing the second file on the SD card
* SET(3500) -- starts playing the first file (file 1) on the SD card
* SET(3501) -- starts playing the second file (file 2) on the SD card
* etc.
* RESET(3500) -- stops all playing on the player
* WAITFOR(3500) -- wait for the file currently being played by the player to complete
* SERVO(3500,23,0) -- plays file 23 at current volume
* SERVO(3500,23,30) -- plays file 23 at volume 30 (maximum)
* SERVO(3501,20,0) -- Sets the volume to 20
* SERVO(3500,2,Instant) -- plays file 2 at current volume
* SERVO(3501,20,Instant) -- Sets the volume to 20
*
* NB The DFPlayer's serial lines are not 5V safe, so connecting the Arduino TX directly
* to the DFPlayer's RX terminal will cause lots of noise over the speaker, or worse.
* A 1k resistor in series with the module's RX terminal will alleviate this.
*
* Files on the SD card are numbered according to their order in the directory on the
* card (as listed by the DIR command in Windows). This may not match the order of the files
* as displayed by Windows File Manager, which sorts the file names. It is suggested that
* files be copied into an empty SDcard in the desired order, one at a time.
*/
#ifndef IO_DFPlayer_h
@@ -68,6 +75,19 @@ private:
uint8_t _inputIndex = 0;
unsigned long _commandSendTime; // Allows timeout processing
// When two commands are sent in quick succession, the device sometimes
// fails to execute one. A delay is required between successive commands.
// This could be implemented by buffering commands and outputting them
// from the loop() function, but it would somewhat complicate the
// driver. A simpler solution is to output a number of NUL pad characters
// between successive command strings if there isn't sufficient elapsed time
// between them. At 9600 baud, each pad character takes approximately
// 1ms to complete. Experiments indicate that the minimum number of pads
// for reliable operation is 17. This gives 17.7ms between the end of one
// command and the beginning of the next, or 28ms between successive commands
// being completed. I've allowed 20 characters, which is almost 21ms.
const int numPadCharacters = 20; // Number of pad characters between commands
public:
static void create(VPIN firstVpin, int nPins, HardwareSerial &serial) {
@@ -83,8 +103,10 @@ protected:
addDevice(this);
}
void _begin() override {
_serial->begin(9600);
void _begin() override {
_serial->begin(9600, SERIAL_8N1); // 9600baud, no parity, 1 stop bit
// Flush any data in input queue
while (_serial->available()) _serial->read();
_deviceState = DEVSTATE_INITIALISING;
// Send a query to the device to see if it responds
@@ -94,10 +116,10 @@ protected:
void _loop(unsigned long currentMicros) override {
// Check for incoming data on _serial, and update busy flag accordingly.
// Expected message is in the form "7F FF 06 3D xx xx xx xx xx EF"
// Expected message is in the form "7E FF 06 3D xx xx xx xx xx EF"
while (_serial->available()) {
int c = _serial->read();
if (c == 0x7E)
if (c == 0x7E && _inputIndex == 0)
_inputIndex = 1;
else if ((c==0xFF && _inputIndex==1)
|| (c==0x3D && _inputIndex==3)
@@ -124,8 +146,8 @@ protected:
} else
_inputIndex = 0; // Unrecognised character sequence, start again!
}
// Check if the initial prompt to device has timed out. Allow 1 second
if (_deviceState == DEVSTATE_INITIALISING && currentMicros - _commandSendTime > 1000000UL) {
// Check if the initial prompt to device has timed out. Allow 5 seconds
if (_deviceState == DEVSTATE_INITIALISING && currentMicros - _commandSendTime > 5000000UL) {
DIAG(F("DFPlayer device not responding on serial port"));
_deviceState = DEVSTATE_FAILED;
}
@@ -161,7 +183,7 @@ protected:
uint8_t pin = vpin - _firstVpin;
// Validate parameter.
volume = min(30,volume);
volume = min((uint8_t)30,volume);
if (pin == 0) {
// Play track
@@ -218,6 +240,7 @@ private:
void sendPacket(uint8_t command, uint16_t arg = 0)
{
unsigned long currentMillis = millis();
uint8_t out[] = { 0x7E,
0xFF,
06,
@@ -231,7 +254,19 @@ private:
setChecksum(out);
// Check how long since the last command was sent.
// Each character takes approx 1ms at 9600 baud
unsigned long minimumGap = numPadCharacters + sizeof(out);
if (currentMillis - _commandSendTime < minimumGap) {
// Output some pad characters to add an
// artificial delay between commands
for (int i=0; i<numPadCharacters; i++)
_serial->write(0);
}
// Now output the command
_serial->write(out, sizeof(out));
_commandSendTime = currentMillis;
}
uint16_t calcChecksum(uint8_t* packet)

View File

@@ -138,7 +138,7 @@ private:
//
void read_HCSR04device() {
// uint16 enough to time up to 65ms
uint16_t startTime, waitTime, currentTime, maxTime;
uint16_t startTime, waitTime = 0, currentTime, maxTime;
// If receive pin is still set on from previous call, abort the read.
if (ArduinoPins::fastReadDigital(_echoPin))
@@ -186,4 +186,4 @@ private:
};
#endif //IO_HCSR04_H
#endif //IO_HCSR04_H

View File

@@ -26,28 +26,9 @@
#include "DCCWaveform.h"
#include "DCCTimer.h"
#include "DIAG.h"
#define ADC_INPUT_MAX_VALUE 1023 // 10 bit ADC
#if defined(ARDUINO_ARCH_ESP32)
#include "ESP32-fixes.h"
#include <driver/adc.h>
#include <soc/sens_reg.h>
#include <soc/sens_struct.h>
#undef ADC_INPUT_MAX_VALUE
#define ADC_INPUT_MAX_VALUE 4095 // 12 bit ADC
#define pinToADC1Channel(X) (adc1_channel_t)(((X) > 35) ? (X)-36 : (X)-28)
int IRAM_ATTR local_adc1_get_raw(int channel) {
uint16_t adc_value;
SENS.sar_meas_start1.sar1_en_pad = (1 << channel); // only one channel is selected
while (SENS.sar_slave_addr1.meas_status != 0);
SENS.sar_meas_start1.meas1_start_sar = 0;
SENS.sar_meas_start1.meas1_start_sar = 1;
while (SENS.sar_meas_start1.meas1_done_sar == 0);
adc_value = SENS.sar_meas_start1.meas1_data_sar;
return adc_value;
}
#endif
bool MotorDriver::commonFaultPin=false;
@@ -110,15 +91,7 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
currentPin=current_pin;
if (currentPin!=UNUSED_PIN) {
#ifdef ARDUINO_ARCH_ESP32
pinMode(currentPin, ANALOG);
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(pinToADC1Channel(currentPin),ADC_ATTEN_DB_11);
senseOffset = adc1_get_raw(pinToADC1Channel(currentPin));
#else
pinMode(currentPin, INPUT);
senseOffset=analogRead(currentPin); // value of sensor at zero current
#endif
senseOffset = ADCee::init(currentPin);
}
faultPin=fault_pin;
@@ -133,7 +106,7 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
tripMilliamps=trip_milliamps;
rawCurrentTripValue=mA2raw(trip_milliamps);
if (rawCurrentTripValue + senseOffset > ADC_INPUT_MAX_VALUE) {
if (rawCurrentTripValue + senseOffset > ADCee::ADCmax()) {
// This would mean that the values obtained from the ADC never
// can reach the trip value. So independent of the current, the
// short circuit protection would never trip. So we adjust the
@@ -141,8 +114,8 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
// maximum value instead.
// DIAG(F("Changing short detection value from %d to %d mA"),
// raw2mA(rawCurrentTripValue), raw2mA(ADC_INPUT_MAX_VALUE-senseOffset));
rawCurrentTripValue=ADC_INPUT_MAX_VALUE-senseOffset;
// raw2mA(rawCurrentTripValue), raw2mA(ADCee::ADCmax()-senseOffset));
rawCurrentTripValue=ADCee::ADCmax()-senseOffset;
}
if (currentPin==UNUSED_PIN)
@@ -209,24 +182,18 @@ bool MotorDriver::canMeasureCurrent() {
/*
* Return the current reading as pin reading 0 to 1023. If the fault
* pin is activated return a negative current to show active fault pin.
* As there is no -0, create a little and return -1 in that case.
* 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
* a central value depending on direction.
*
* Bool fromISR should be adjusted dependent how function is called
*/
int MotorDriver::getCurrentRaw() {
int MotorDriver::getCurrentRaw(bool fromISR) {
(void)fromISR;
if (currentPin==UNUSED_PIN) return 0;
int current;
// This function should NOT be called in an interruot so we
// dont need to fart about saving and restoring CPU specific
// interrupt registers.
#ifdef ARDUINO_ARCH_ESP32
current = local_adc1_get_raw(pinToADC1Channel(currentPin))-senseOffset;
#else
noInterrupts();
current = analogRead(currentPin)-senseOffset;
interrupts();
#endif
current = ADCee::read(currentPin, fromISR)-senseOffset;
if (current<0) current=0-current;
if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && powerMode==POWERMODE::ON)
return (current == 0 ? -1 : -current);
@@ -234,12 +201,52 @@ int MotorDriver::getCurrentRaw() {
}
#ifdef ANALOG_READ_INTERRUPT
/*
* This should only be called in interrupt context
* Copies current value from HW to cached value in
* Motordriver.
*/
#pragma GCC push_options
#pragma GCC optimize ("-O3")
bool MotorDriver::sampleCurrentFromHW() {
byte low, high;
//if (!bit_is_set(ADCSRA, ADIF))
if (bit_is_set(ADCSRA, ADSC))
return false;
// if ((ADMUX & mask) != (currentPin - A0))
// return false;
low = ADCL; //must read low before high
high = ADCH;
bitSet(ADCSRA, ADIF);
sampleCurrent = (high << 8) | low;
sampleCurrentTimestamp = millis();
return true;
}
void MotorDriver::startCurrentFromHW() {
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
const byte mask = 7;
#else
const byte mask = 31;
#endif
ADMUX=(1<<REFS0)|((currentPin-A0) & mask); //select AVCC as reference and set MUX
bitSet(ADCSRA,ADSC); // start conversion
}
#pragma GCC pop_options
#endif //ANALOG_READ_INTERRUPT
#if defined(ARDUINO_ARCH_ESP32)
uint16_t taurustones[28] = { 165, 175, 196, 220,
247, 262, 294, 330,
249, 392, 440, 494,
523, 587, 659, 698,
494, 440, 392, 249,
330, 284, 262, 247,
220, 196, 175, 165 };
#endif
void MotorDriver::setDCSignal(byte speedcode) {
if (brakePin == UNUSED_PIN)
return;
#if defined(ARDUINO_ARCH_ESP32)
DCCEXanalogWriteFrequency(brakePin, 100); // set DC PWM frequency to 100Hz XXX May move to setup
#endif
#if defined(ARDUINO_AVR_UNO)
TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz
#endif
@@ -251,6 +258,17 @@ void MotorDriver::setDCSignal(byte speedcode) {
byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127
byte tDir=speedcode & 0x80;
byte brake;
#if defined(ARDUINO_ARCH_ESP32)
{
int f = 131;
if (tSpeed > 2) {
if (tSpeed <= 58) {
f = taurustones[ (tSpeed-2)/2 ] ;
}
}
DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup
}
#endif
if (tSpeed <= 1) brake = 255;
else if (tSpeed >= 127) brake = 0;
else brake = 2 * (128-tSpeed);
@@ -287,23 +305,12 @@ void MotorDriver::setDCSignal(byte speedcode) {
}
}
int MotorDriver::getCurrentRawInInterrupt() {
// IMPORTANT: This function must be called in Interrupt() time within the 56uS timer
// The default analogRead takes ~100uS which is catastrphic
// so DCCTimer has set the sample time to be much faster.
if (currentPin==UNUSED_PIN) return 0;
#ifdef ARDUINO_ARCH_ESP32 //On ESP we do all in loop() instead of in interrupt
return getCurrentRaw();
#else
return analogRead(currentPin)-senseOffset;
#endif
}
unsigned int MotorDriver::raw2mA( int raw) {
//DIAG(F("%d = %d * %d / %d"), (int32_t)raw * senseFactorInternal / senseScale, raw, senseFactorInternal, senseScale);
return (int32_t)raw * senseFactorInternal / senseScale;
}
unsigned int MotorDriver::mA2raw( unsigned int mA) {
//DIAG(F("%d = %d * %d / %d"), (int32_t)mA * senseScale / senseFactorInternal, mA, senseScale, senseFactorInternal);
return (int32_t)mA * senseScale / senseFactorInternal;
}

View File

@@ -153,8 +153,7 @@ class MotorDriver {
setDCSignal(128);
#endif
};
int getCurrentRaw();
int getCurrentRawInInterrupt();
int getCurrentRaw(bool fromISR=false);
unsigned int raw2mA( int raw);
unsigned int mA2raw( unsigned int mA);
inline bool brakeCanPWM() {
@@ -183,6 +182,10 @@ class MotorDriver {
isProgTrack = on;
}
void checkPowerOverload(bool useProgLimit, byte trackno);
#ifdef ANALOG_READ_INTERRUPT
bool sampleCurrentFromHW();
void startCurrentFromHW();
#endif
private:
bool isProgTrack = false; // tells us if this is a prog track
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
@@ -212,6 +215,10 @@ class MotorDriver {
unsigned int sampleDelay;
int progTripValue;
int lastCurrent;
#ifdef ANALOG_READ_INTERRUPT
volatile unsigned long sampleCurrentTimestamp;
volatile uint16_t sampleCurrent;
#endif
int maxmA;
int tripmA;

View File

@@ -1,5 +1,5 @@
/*
* © 2022 Paul M Antoine
* © 2022 Paul M. Antoine
* © 2021 Fred Decker
* © 2020-2022 Harald Barth
* (c) 2020 Chris Harlow. All rights reserved.
@@ -59,15 +59,16 @@
// Arduino STANDARD Motor Shield, used on different architectures:
#if defined(ARDUINO_ARCH_SAMD)
#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32)
// Setup for SAMD21 Sparkfun DEV board using Arduino standard Motor Shield R3 (MUST be R3
// for 3v3 compatibility!!) senseFactor for 3.3v systems is 1.95 as calculated when using
// 10-bit A/D samples, and for 12-bit samples it's more like 0.488, but we probably need
// to tweak both these
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 1.95, 1500, UNUSED_PIN), \
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 1.95, 1500, UNUSED_PIN)
new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 0.488, 1500, UNUSED_PIN), \
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 0.488, 1500, UNUSED_PIN)
#define SAMD_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD
#define STM32_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD
#elif defined(ARDUINO_ARCH_ESP32)
// STANDARD shield on an ESPDUINO-32 (ESP32 in Uno form factor). The shield must be eiter the
@@ -157,4 +158,31 @@
new MotorDriver(5, 6, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 1500, UNUSED_PIN),\
new MotorDriver(9, 10, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 1500, UNUSED_PIN)
// This is an example how to stack two standard motor shields. The upper shield
// needs pins 3 8 9 11 12 13 A0 A1 disconnected from the lower shield and
// jumpered instead like this: 2-3 6-8 7-9 4-13 5-11 10-12 A0-A4 A1-A5
// Pin assigment table:
// 2 Enable C jumpered
// 3 Enable A direct
// 4 Dir D jumpered
// 5 Enable D jumpered
// 6 Brake D jumpered
// 7 Brake C jumpered
// 8 Brake B direct
// 9 Brake A direct
// 10 Dir C jumpered
// 11 Enable B direct
// 12 Dir A direct
// 13 Dir B direct
// A0 Sense A direct
// A1 Sense B direct
// A4 Sense C jumpered
// A5 Sense D jumpered
//
#define STACKED_MOTOR_SHIELD F("STACKED_MOTOR_SHIELD"),\
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 2.99, 1500, UNUSED_PIN), \
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 1500, UNUSED_PIN), \
new MotorDriver( 2, 10, UNUSED_PIN, 7, A3, 2.99, 1500, UNUSED_PIN), \
new MotorDriver( 5, 4, UNUSED_PIN, 6, A4, 2.99, 1500, UNUSED_PIN)
//
#endif

View File

@@ -189,11 +189,12 @@ bool RingStream::commit() {
_mark++;
if (_mark==_len) _mark=0;
_buffer[_mark]=lowByte(_count);
{ char s[_count+2];
strncpy(s, (const char*)&(_buffer[_mark+1]), _count);
s[_count]=0;
//DIAG(F("RS commit count=%d core %d \"%s\""), _count, xPortGetCoreID(), s);
}
// Enable this for debugging only, it requires A LOT of RAM
//{ char s[_count+2];
// strncpy(s, (const char*)&(_buffer[_mark+1]), _count);
// s[_count]=0;
// DIAG(F("RS commit count=%d core %d \"%s\""), _count, xPortGetCoreID(), s);
//}
_ringClient = NO_CLIENT;
return true; // commit worked
}

View File

@@ -1,5 +1,5 @@
/*
* © 2022 Paul M Antoine
* © 2022 Paul M. Antoine
* © 2021 Chris Harlow
* © 2022 Harald Barth
* All rights reserved.
@@ -51,6 +51,18 @@ void SerialManager::init() {
while (!USB_SERIAL && millis() < 5000); // wait max 5s for Serial to start
new SerialManager(&USB_SERIAL);
#ifdef SERIAL6_COMMANDS
Serial6.begin(115200);
new SerialManager(&Serial6);
#endif
#ifdef SERIAL5_COMMANDS
Serial5.begin(115200);
new SerialManager(&Serial5);
#endif
#ifdef SERIAL4_COMMANDS
Serial4.begin(115200);
new SerialManager(&Serial4);
#endif
#ifdef SERIAL3_COMMANDS
Serial3.begin(115200);
new SerialManager(&Serial3);

View File

@@ -70,7 +70,7 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
char* flash=(char*)format;
for(int i=0; ; ++i) {
char c=GETFLASH(flash+i);
if (c=='\0') return;
if (c=='\0') break; // to va_end()
if(c!='%') { stream->print(c); continue; }
bool formatContinues=false;

View File

@@ -54,6 +54,49 @@ int16_t TrackManager::joinRelay=UNUSED_PIN;
byte TrackManager::tempProgTrack=MAX_TRACKS+1;
#endif
#ifdef ANALOG_READ_INTERRUPT
/*
* sampleCurrent() runs from Interrupt
*/
void TrackManager::sampleCurrent() {
static byte tr = 0;
byte trAtStart = tr;
static bool waiting = false;
if (waiting) {
if (! track[tr]->sampleCurrentFromHW()) {
return; // no result, continue to wait
}
// found value, advance at least one track
// for scope debug track[1]->setBrake(0);
waiting = false;
tr++;
if (tr > lastTrack) tr = 0;
if (lastTrack < 2 || trackMode[tr] & TRACK_MODE_PROG) {
return; // We could continue but for prog track we
// rather do it in next interrupt beacuse
// that gives us well defined sampling point.
// For other tracks we care less unless we
// have only few (max 2) tracks.
}
}
if (!waiting) {
// look for a valid track to sample or until we are around
while (true) {
if (trackMode[tr] & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_DCX|TRACK_MODE_EXT )) {
track[tr]->startCurrentFromHW();
// for scope debug track[1]->setBrake(1);
waiting = true;
break;
}
tr++;
if (tr > lastTrack) tr = 0;
if (tr == trAtStart) // we are through and nothing found to do
return;
}
}
}
#endif
// The setup call is done this way so that the tracks can be in a list
// from the config... the tracks default to NULL in the declaration

View File

@@ -27,8 +27,9 @@
#include "MotorDriver.h"
// Virtualised Motor shield multi-track hardware Interface
enum TRACK_MODE : byte {TRACK_MODE_OFF, TRACK_MODE_MAIN, TRACK_MODE_PROG,
TRACK_MODE_DC, TRACK_MODE_DCX, TRACK_MODE_EXT};
// use powers of two so we can do logical and/or on the track modes in if clauses.
enum TRACK_MODE : byte {TRACK_MODE_OFF = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4,
TRACK_MODE_DC = 8, TRACK_MODE_DCX = 16, TRACK_MODE_EXT = 32};
// These constants help EXRAIL macros say SET_TRACK(2,mode) OR SET_TRACK(C,mode) etc.
const byte TRACK_NUMBER_0=0, TRACK_NUMBER_A=0;
@@ -75,10 +76,10 @@ class TrackManager {
static void setJoin(bool join);
static bool isJoined() { return progTrackSyncMain;}
static void setJoinRelayPin(byte joinRelayPin);
static void sampleCurrent();
static int16_t joinRelay;
static bool progTrackSyncMain; // true when prog track is a siding switched to main
static bool progTrackBoosted; // true when prog track is not current limited
private:
static void addTrack(byte t, MotorDriver* driver);

View File

@@ -171,9 +171,14 @@ public:
// Save all turnout definitions
static void store();
#endif
static void printAll(Print *stream) {
static bool printAll(Print *stream) {
bool gotOne=false;
for (Turnout *tt = _firstTurnout; tt != 0; tt = tt->_nextTurnout)
if (!tt->isHidden()) StringFormatter::send(stream, F("<H %d %d>\n"),tt->getId(), tt->isThrown());
if (!tt->isHidden()) {
gotOne=true;
StringFormatter::send(stream, F("<H %d %d>\n"),tt->getId(), tt->isThrown());
}
return gotOne;
}

View File

@@ -63,42 +63,53 @@
WiThrottle * WiThrottle::firstThrottle=NULL;
static uint8_t xstrcmp(const char *s1, const char *s2) {
while(*s1 != '\0' && *s2 != '\0') {
if (*s1 != *s2) return 1;
s1++;
s2++;
}
if (*s1 == '\0' && *s2 == '\0')
static uint8_t xstrncmp(const char *s1, const char *s2, uint8_t n) {
if (n == 0)
return 0;
return 1;
do {
if (*s1 != *s2++)
return 1;
if (*s1++ == 0)
break;
} while (--n != 0);
return 0;
}
void WiThrottle::findUniqThrottle(int id, char *u) {
WiThrottle *wtmyid = NULL;
WiThrottle *wtmyuniq = NULL;
u[16] = '\0';
// search 1, look for clientid match
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){
//DIAG(F("looking at %d as %s"),wt->clientid, wt->uniq);
if (wtmyid == NULL && wt->clientid == id)
if (wt->clientid == id) {
if (xstrncmp(u, wt->uniq, 16) == 0) // should be most common case
return;
wtmyid = wt;
if (wtmyuniq == NULL && xstrcmp(u, wt->uniq) == 0)
wtmyuniq = wt;
break;
}
}
// search 2, look for string match
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){
if (xstrncmp(u, wt->uniq, 16) == 0) {
wtmyuniq = wt;
break;
}
}
// analyse result of the two for loops:
if (wtmyid == NULL) { // should not happen
DIAG(F("Did not find my own wiThrottle handle"));
return;
}
if (wtmyid == wtmyuniq) { // all well, just return;
return;
}
// wtmyuniq == wtmyid has already returned in for loop 1
if (wtmyuniq == NULL) { // register uniq in the found id
strncpy(wtmyid->uniq, u, 16);
wtmyid->uniq[16] = '\0';
if (Diag::WITHROTTLE) DIAG(F("Client %d registered as %s"),wtmyid->clientid, wtmyid->uniq);
return;
}
// do the copy (all other options above)
// if we get here wtmyid and wtmyuniq point on objects but differnet ones
// so we need to do the copy (all other options covered above)
for(int n=0; n < MAX_MY_LOCO; n++)
wtmyid->myLocos[n] = wtmyuniq->myLocos[n];
wtmyid->heartBeatEnable = wtmyuniq->heartBeatEnable;
@@ -640,8 +651,9 @@ void WiThrottle::getLocoCallback(int16_t locoid) {
itoa(locoid,addcmd+4,10);
stashInstance->multithrottle(stashStream, (byte *)addcmd);
TrackManager::setMainPower(POWERMODE::ON);
TrackManager::setProgPower(POWERMODE::ON);
TrackManager::setJoin(true); // <1 JOIN> so we can drive loco away
//DIAG(F("LocoCallback commit success"));
DIAG(F("LocoCallback commit success"));
stashStream->commit();
CommandDistributor::broadcastPower();

View File

@@ -1,4 +1,5 @@
/*
* © 2022 Paul M. Antoine
* © 2021 Neil McKechnie
* © 2020-2021 Harald Barth
* © 2020-2021 Fred Decker
@@ -143,6 +144,9 @@ The configuration file for DCC-EX Command Station
// and want to use the EX-RAIL automation. Otherwise you do not have enough RAM
// to do that. Of course, then none of the EEPROM related commands work.
//
// EEPROM does not work on ESP32. So on ESP32, EEPROM will always be disabled,
// at least until it works.
//
// #define DISABLE_EEPROM
/////////////////////////////////////////////////////////////////////////////////////
@@ -188,14 +192,18 @@ The configuration file for DCC-EX Command Station
// HANDLING MULTIPLE SERIAL THROTTLES
// The command station always operates with the default Serial port.
// Diagnostics are only emitted on the default serial port and not broadcast.
// Other serial throttles may be added to the Serial1, Serial2, Serial3 ports
// which may or may not exist on your CPU. (Mega has all 3)
// Other serial throttles may be added to the Serial1, Serial2, Serial3, Serial4,
// Serial5, and Serial6 ports which may or may not exist on your CPU. (Mega has 3,
// SAMD/SAMC and STM32 have up to 6.)
// To monitor a throttle on one or more serial ports, uncomment the defines below.
// NOTE: do not define here the WiFi shield serial port or your wifi will not work.
//
//#define SERIAL1_COMMANDS
//#define SERIAL2_COMMANDS
//#define SERIAL3_COMMANDS
//#define SERIAL4_COMMANDS
//#define SERIAL5_COMMANDS
//#define SERIAL6_COMMANDS
//
// BLUETOOTH SERIAL ON ESP32
// On ESP32 you have the possibility to use the builtin BT serial to connect to

View File

@@ -3,7 +3,7 @@
* © 2021 Neil McKechnie
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020-2021 Harald Barth
* © 2020-2022 Harald Barth
* © 2020-2021 Chris Harlow
*
* This file is part of CommandStation-EX
@@ -114,8 +114,12 @@
#endif
#elif defined(ARDUINO_ARCH_ESP8266)
#define ARDUINO_TYPE "ESP8266"
#warning "ESP8266 platform untested, you are on your own"
#elif defined(ARDUINO_ARCH_ESP32)
#define ARDUINO_TYPE "ESP32"
#ifndef DISABLE_EEPROM
#define DISABLE_EEPROM
#endif
#elif defined(ARDUINO_ARCH_SAMD)
#define ARDUINO_TYPE "SAMD21"
#undef USB_SERIAL

101
installer.sh Executable file
View File

@@ -0,0 +1,101 @@
#!/bin/bash
#
# © 2022 Harald Barth
#
# This file is part of CommandStation-EX
#
# This is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# It is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
#
#
# Usage: mkdir DIRNAME ; cd DIRNAME ; ../installer.sh
# or from install directory ./installer.sh
#
DCCEXGITURL="https://github.com/DCC-EX/CommandStation-EX"
ACLIINSTALL="https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh"
ACLI="./bin/arduino-cli"
function need () {
type -p $1 > /dev/null && return
sudo apt-get install $1
type -p $1 > /dev/null && return
echo "Could not install $1, abort"
exit 255
}
need git
if test -d .git ; then
: assume we are right here
git pull
else
git clone "$DCCEXGITURL"
cd `basename "$DCCEXGITURL"` || exit 255
fi
if test -f config.h ; then
: all well
else
# need to do this config better
cp -p config.example.h config.h
fi
need curl
if test -x "$ACLI" ; then
: all well
else
curl "$ACLIINSTALL" > acliinstall.sh
chmod +x acliinstall.sh
./acliinstall.sh
fi
$ACLI core update-index || exit 255
# Board discovery
BOARDS=/tmp/boards.$$
$ACLI board list | grep serial > $BOARDS
if test x`< $BOARDS wc -l` = 'x1' ; then
LINE=`cat $BOARDS`
else
# ask user
echo "What board to use? (give line number)"
cat -n $BOARDS
echo -n "> "
LINE=`awk 'BEGIN {getline A < "/dev/tty"} ; A == NR {print}' $BOARDS`
fi
rm $BOARDS
PORT=`echo $LINE | cut -d" " -f1`
echo Will use port: $PORT
# FQBN discovery
FQBN=`echo $LINE | egrep 'arduino:avr:[a-z][a-z]*' | sed 's/.*\(arduino:avr:[a-z][a-z]*\) .*/\1/1'`
if test x$FQBN = x ; then
# ask user
cat > /tmp/fqbn.$$ <<EOF
arduino:avr:uno
arduino:avr:mega
esp32:esp32:esp32
EOF
echo "What board type? (give line number)"
cat -n /tmp/fqbn.$$
echo -n "> "
FQBN=`awk 'BEGIN {getline A < "/dev/tty"} ; A == NR {print}' /tmp/fqbn.$$`
fi
rm /tmp/fqbn.$$
echo FQBN is $FQBN
# Install phase
$ACLI core install `echo $FQBN | sed 's,:[^:]*$,,1'` # remove last component to get package
$ACLI board attach -p $PORT --fqbn $FQBN $PWD
$ACLI compile --fqbn $FQBN $PWD
$ACLI upload -v -t -p $PORT $PWD

View File

@@ -18,7 +18,7 @@ default_envs =
samd21-dev-usb
samd21-zero-usb
ESP32
Nucleo-STM32F411RE
Nucleo-F411RE
Teensy3.2
Teensy3.5
Teensy3.6
@@ -50,17 +50,18 @@ monitor_speed = 115200
monitor_echo = yes
build_flags = -std=c++17
[env:samc21-firebox]
platform = atmelsam
board = firebox
framework = arduino
upload_protocol = atmel-ice
lib_deps =
${env.lib_deps}
SparkFun External EEPROM Arduino Library
monitor_speed = 115200
monitor_echo = yes
build_flags = -std=c++17
; Firebox disabled for now
; [env:samc21-firebox]
; platform = atmelsam
; board = firebox
; framework = arduino
; upload_protocol = atmel-ice
; lib_deps =
; ${env.lib_deps}
; SparkFun External EEPROM Arduino Library
;monitor_speed = 115200
;monitor_echo = yes
;build_flags = -std=c++17
[env:mega2560-debug]
platform = atmelavr
@@ -108,6 +109,10 @@ lib_deps =
SPI
monitor_speed = 115200
monitor_echo = yes
; Example, but v12 does generate bigger binaries
; platform_packages = toolchain-atmelavr@symlink:///opt/avr-gcc-12.1.0-x64-linux
; Should make binaries smaller
build_flags = -mcall-prologues
[env:mega328]
platform = atmelavr
@@ -155,6 +160,8 @@ lib_deps =
SPI
monitor_speed = 115200
monitor_echo = yes
; Should make binaries smaller
build_flags = -mcall-prologues
[env:nano]
platform = atmelavr
@@ -172,7 +179,7 @@ framework = arduino
lib_deps = ${env.lib_deps}
build_flags = -std=c++17
[env:Nucleo-STM32F411RE]
[env:Nucleo-F411RE]
platform = ststm32
board = nucleo_f411re
framework = arduino

View File

@@ -4,7 +4,12 @@
#include "StringFormatter.h"
#define VERSION "4.2.4"
#define VERSION "4.2.6"
// 4.2.6 FIX: Remove RAM thief
// FIX: ADC port 8-15 fix
// 4.2.5 Make GETFLASHW code more universal
// FIX: Withrottle roster index
// Ethernet start improvement and link detection
// 4.2.4 ESP32 experimental BT support
// More DC configurations possible and lower frequency
// Handle decoders that do not ack at write better