1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-07-29 10:23:45 +02:00

Compare commits

...

42 Commits

Author SHA1 Message Date
Harald Barth
f9e08b1283 add rotary encoder 2022-10-31 22:58:42 +01: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
30 changed files with 766 additions and 166 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

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

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

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,108 @@ 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 7
#else
#define NUM_ADC_INPUTS 15
#endif
uint16_t ADCee::usedpins = 0;
int * ADCee::analogvals = NULL;
/*
* Register a new pin to be scanned
* Returns current reading of pin and
* stores that as well
*/
int ADCee::init(uint8_t pin) {
uint8_t id = pin - A0;
if (id > NUM_ADC_INPUTS)
return -1023;
pinMode(pin, INPUT);
int value = analogRead(pin);
if (analogvals == NULL)
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
analogvals[id] = value;
usedpins |= (1<<id);
return value;
}
int16_t ADCee::ADCmax() {
return 1023;
}
/*
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/
int ADCee::read(uint8_t pin, bool fromISR) {
(void)fromISR; // AVR does ignore this arg
uint8_t id = pin - A0;
if ((usedpins & (1<<id) ) == 0)
return -1023;
// we do not need to check (analogvals == NULL)
// because usedpins would still be 0 in that case
return analogvals[id];
}
/*
* Scan function that is called from interrupt
*/
#pragma GCC push_options
#pragma GCC optimize ("-O3")
void ADCee::scan() {
static byte id = 0; // id and mask are the same thing but it is faster to
static uint16_t mask = 1; // increment and shift instead to calculate mask from id
static bool waiting = false;
if (waiting) {
// look if we have a result
byte low, high;
if (bit_is_set(ADCSRA, ADSC))
return; // no result, continue to wait
// found value
low = ADCL; //must read low before high
high = ADCH;
bitSet(ADCSRA, ADIF);
analogvals[id] = (high << 8) | low;
// advance at least one track
// for scope debug TrackManager::track[1]->setBrake(0);
waiting = false;
id++;
mask = mask << 1;
if (id == NUM_ADC_INPUTS+1) {
id = 0;
mask = 1;
}
}
if (!waiting) {
if (usedpins == 0) // otherwise we would loop forever
return;
// look for a valid track to sample or until we are around
while (true) {
if (mask & usedpins) {
// start new ADC aquire on id
ADMUX=(1<<REFS0)|id; //select AVCC as reference and set MUX
bitSet(ADCSRA,ADSC); // start conversion
// for scope debug TrackManager::track[1]->setBrake(1);
waiting = true;
return;
}
id++;
mask = mask << 1;
if (id == NUM_ADC_INPUTS+1) {
id = 0;
mask = 1;
}
}
}
}
#pragma GCC pop_options
void ADCee::begin() {
noInterrupts();
// ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time
// Set up ADC for free running mode
ADMUX=(1<<REFS0); //select AVCC as reference. We set MUX later
ADCSRA = (1<<ADEN)|(1 << ADPS2); // ADPS2 means divisor 32 and 16Mhz/32=500kHz.
//bitSet(ADCSRA, ADSC); //do not start the ADC yet. Done when we have set the MUX
interrupts();
}
#endif

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

@@ -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,6 +90,7 @@ LookList * RMFT2::onDeactivateLookup=NULL;
LookList * RMFT2::onRedLookup=NULL;
LookList * RMFT2::onAmberLookup=NULL;
LookList * RMFT2::onGreenLookup=NULL;
LookList * RMFT2::onChangeLookup=NULL;
#define GET_OPCODE GETFLASH(RMFT2::RouteCode+progCounter)
#define GET_OPERAND(n) GETFLASHW(RMFT2::RouteCode+progCounter+1+(n*3))
@@ -153,6 +154,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
onRedLookup=LookListLoader(OPCODE_ONRED);
onAmberLookup=LookListLoader(OPCODE_ONAMBER);
onGreenLookup=LookListLoader(OPCODE_ONGREEN);
onChangeLookup=LookListLoader(OPCODE_ONCHANGE);
// Second pass startup, define any turnouts or servos, set signals red
// add sequences onRoutines to the lookups
@@ -180,6 +182,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);
@@ -712,6 +723,10 @@ void RMFT2::loop2() {
case OPCODE_IFNOT: // do next operand if sensor not set
skipIf=readSensor(operand);
break;
case OPCODE_IFRE: // do next operand if rotary encoder != position
skipIf=IODevice::readAnalogue(operand)!=(int)(GET_OPERAND(1));
break;
case OPCODE_IFRANDOM: // do block on random percentage
skipIf=(int16_t)(micros()%100) >= operand;
@@ -935,6 +950,7 @@ void RMFT2::loop2() {
case OPCODE_ONRED:
case OPCODE_ONAMBER:
case OPCODE_ONGREEN:
case OPCODE_ONCHANGE:
break;
@@ -1054,13 +1070,17 @@ void RMFT2::turnoutEvent(int16_t turnoutId, bool closed) {
else handleEvent(F("THROW"),onThrowLookup,turnoutId);
}
void RMFT2::activateEvent(int16_t addr, bool activate) {
// Hunt for an ONACTIVATE/ONDEACTIVATE for this accessory
if (activate) handleEvent(F("ACTIVATE"),onActivateLookup,addr);
else handleEvent(F("DEACTIVATE"),onDeactivateLookup,addr);
}
void RMFT2::changeEvent(int16_t vpin, bool change) {
// Hunt for an ONCHANGE for this sensor
if (change) handleEvent(F("CHANGE"),onChangeLookup,vpin);
}
void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
int pc= handlers->find(id);
if (pc<0) return;

View File

@@ -54,6 +54,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_ENDTASK,OPCODE_ENDEXRAIL,
OPCODE_SET_TRACK,
OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN,
OPCODE_ONCHANGE,
// OPcodes below this point are skip-nesting IF operations
// placed here so that they may be skipped as a group
@@ -64,7 +65,8 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_IFTIMEOUT,
OPCODE_IF,OPCODE_IFNOT,
OPCODE_IFRANDOM,OPCODE_IFRESERVE,
OPCODE_IFCLOSED,OPCODE_IFTHROWN
OPCODE_IFCLOSED,OPCODE_IFTHROWN,
OPCODE_IFRE,
};
@@ -107,6 +109,7 @@ class LookList {
static void createNewTask(int route, uint16_t cab);
static void turnoutEvent(int16_t id, bool closed);
static void activateEvent(int16_t addr, bool active);
static void changeEvent(int16_t id, bool change);
static const int16_t SERVO_SIGNAL_FLAG=0x4000;
static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000;
static const int16_t DCC_SIGNAL_FLAG=0x1000;
@@ -161,6 +164,7 @@ private:
static LookList * onRedLookup;
static LookList * onAmberLookup;
static LookList * onGreenLookup;
static LookList * onChangeLookup;
// Local variables - exist for each instance/task

View File

@@ -72,6 +72,7 @@
#undef IFRESERVE
#undef IFTHROWN
#undef IFTIMEOUT
#undef IFRE
#undef INVERT_DIRECTION
#undef JOIN
#undef KILLALL
@@ -88,6 +89,7 @@
#undef ONGREEN
#undef ONRED
#undef ONTHROW
#undef ONCHANGE
#undef PARSE
#undef PAUSE
#undef PIN_TURNOUT
@@ -182,6 +184,7 @@
#define IFTHROWN(turnout_id)
#define IFRESERVE(block)
#define IFTIMEOUT
#define IFRE(sensor_id,value)
#define INVERT_DIRECTION
#define JOIN
#define KILLALL
@@ -198,6 +201,7 @@
#define ONGREEN(signal_id)
#define ONRED(signal_id)
#define ONTHROW(turnout_id)
#define ONCHANGE(sensor_id)
#define PAUSE
#define PIN_TURNOUT(id,pin,description...)
#define PRINT(msg)

View File

@@ -261,6 +261,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),
#define IFTIMEOUT OPCODE_IFTIMEOUT,0,0,
#define IFRE(sensor_id,value) OPCODE_IFRE,V(sensor_id),OPCODE_PAD,V(value),
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0,
#define JOIN OPCODE_JOIN,0,0,
#define KILLALL OPCODE_KILLALL,0,0,
@@ -277,6 +278,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
#define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id),
#define ONRED(signal_id) OPCODE_ONRED,V(signal_id),
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
#define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id),
#define PAUSE OPCODE_PAUSE,0,0,
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),

8
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
@@ -48,10 +48,10 @@ 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)
#define GETFLASHW(addr) (*(const unsigned int8_t *)(addr)) | ((*(const unsigned int8_t *)(addr+1)) << 8)
#ifdef FLASH
#undef FLASH
#endif

View File

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

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

@@ -161,7 +161,7 @@ protected:
uint8_t pin = vpin - _firstVpin;
// Validate parameter.
volume = min(30,volume);
volume = min((uint8_t)30,volume);
if (pin == 0) {
// Play track

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

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

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

@@ -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;
@@ -287,7 +298,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
#ifdef EXRAIL_ACTIVE
StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount);
for (int16_t r=0;r<RMFT2::rosterNameCount;r++) {
int16_t cabid=GETFLASHW(RMFT2::rosterIdList+r);
int16_t cabid=GETFLASHW(RMFT2::rosterIdList+r*2);
StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"),
RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L');
}
@@ -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