mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-12-03 12:55:03 +01:00
Merge branch 'devel' into devel-invfault
This commit is contained in:
commit
235bcc9ff0
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -7,15 +7,9 @@ Release/*
|
||||||
.pio/
|
.pio/
|
||||||
.vscode/
|
.vscode/
|
||||||
config.h
|
config.h
|
||||||
.vscode/*
|
|
||||||
# mySetup.h
|
|
||||||
mySetup.cpp
|
mySetup.cpp
|
||||||
myHal.cpp
|
myHal.cpp
|
||||||
# myAutomation.h
|
|
||||||
myFilter.cpp
|
myFilter.cpp
|
||||||
# myAutomation.h
|
|
||||||
# myLayout.h
|
|
||||||
my*.h
|
my*.h
|
||||||
!my*.example.h
|
!my*.example.h
|
||||||
.vscode/extensions.json
|
compile_commands.json
|
||||||
.vscode/extensions.json
|
|
||||||
|
|
10
.vscode/extensions.json
vendored
10
.vscode/extensions.json
vendored
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
|
||||||
// for the documentation about the extensions.json format
|
|
||||||
"recommendations": [
|
|
||||||
"platformio.platformio-ide"
|
|
||||||
],
|
|
||||||
"unwantedRecommendations": [
|
|
||||||
"ms-vscode.cpptools-extension-pack"
|
|
||||||
]
|
|
||||||
}
|
|
12
.vscode/settings.json
vendored
12
.vscode/settings.json
vendored
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"files.associations": {
|
|
||||||
"array": "cpp",
|
|
||||||
"deque": "cpp",
|
|
||||||
"string": "cpp",
|
|
||||||
"unordered_map": "cpp",
|
|
||||||
"vector": "cpp",
|
|
||||||
"string_view": "cpp",
|
|
||||||
"initializer_list": "cpp",
|
|
||||||
"cstdint": "cpp"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -233,3 +233,7 @@ void CommandDistributor::broadcastText(const FSH * msg) {
|
||||||
broadcastReply(WITHROTTLE_TYPE, F("Hm%S\n"), msg);
|
broadcastReply(WITHROTTLE_TYPE, F("Hm%S\n"), msg);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr) {
|
||||||
|
broadcastReply(COMMAND_TYPE, format,trackLetter,dcAddr);
|
||||||
|
}
|
||||||
|
|
|
@ -51,6 +51,7 @@ public :
|
||||||
static int16_t retClockTime();
|
static int16_t retClockTime();
|
||||||
static void broadcastPower();
|
static void broadcastPower();
|
||||||
static void broadcastText(const FSH * msg);
|
static void broadcastText(const FSH * msg);
|
||||||
|
static void broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr);
|
||||||
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
|
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
|
||||||
static void forget(byte clientId);
|
static void forget(byte clientId);
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,9 @@ void setup()
|
||||||
|
|
||||||
DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com"));
|
DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com"));
|
||||||
|
|
||||||
|
// Initialise HAL layer before reading EEprom or setting up MotorDrivers
|
||||||
|
IODevice::begin();
|
||||||
|
|
||||||
DISPLAY_START (
|
DISPLAY_START (
|
||||||
// This block is still executed for DIAGS if display not in use
|
// This block is still executed for DIAGS if display not in use
|
||||||
LCD(0,F("DCC-EX v%S"),F(VERSION));
|
LCD(0,F("DCC-EX v%S"),F(VERSION));
|
||||||
|
@ -97,9 +100,6 @@ void setup()
|
||||||
EthernetInterface::setup();
|
EthernetInterface::setup();
|
||||||
#endif // ETHERNET_ON
|
#endif // ETHERNET_ON
|
||||||
|
|
||||||
// Initialise HAL layer before reading EEprom or setting up MotorDrivers
|
|
||||||
IODevice::begin();
|
|
||||||
|
|
||||||
// As the setup of a motor shield may require a read of the current sense input from the ADC,
|
// As the setup of a motor shield may require a read of the current sense input from the ADC,
|
||||||
// let's make sure to initialise the ADCee class!
|
// let's make sure to initialise the ADCee class!
|
||||||
ADCee::begin();
|
ADCee::begin();
|
||||||
|
|
|
@ -344,6 +344,20 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||||
return;
|
return;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'z': // direct pin manipulation
|
||||||
|
if (p[0]==0) break;
|
||||||
|
if (params==1) { // <z vpin | -vpin>
|
||||||
|
if (p[0]>0) IODevice::write(p[0],HIGH);
|
||||||
|
else IODevice::write(-p[0],LOW);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (params>=2 && params<=4) { // <z vpin ana;og profile duration>
|
||||||
|
// unused params default to 0
|
||||||
|
IODevice::writeAnalogue(p[0],p[1],p[2],p[3]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case 'Z': // OUTPUT <Z ...>
|
case 'Z': // OUTPUT <Z ...>
|
||||||
if (parseZ(stream, params, p))
|
if (parseZ(stream, params, p))
|
||||||
return;
|
return;
|
||||||
|
@ -516,9 +530,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||||
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||||
CommandDistributor::broadcastPower(); // <s> is the only "get power status" command we have
|
CommandDistributor::broadcastPower(); // <s> is the only "get power status" command we have
|
||||||
Turnout::printAll(stream); //send all Turnout states
|
Turnout::printAll(stream); //send all Turnout states
|
||||||
Output::printAll(stream); //send all Output states
|
|
||||||
Sensor::printAll(stream); //send all Sensor states
|
Sensor::printAll(stream); //send all Sensor states
|
||||||
// TODO Send stats of speed reminders table
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
#ifndef DISABLE_EEPROM
|
#ifndef DISABLE_EEPROM
|
||||||
|
@ -955,7 +967,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case HASH_KEYWORD_ANIN: // <D ANIN vpin> Display analogue input value
|
case HASH_KEYWORD_ANIN: // <D ANIN vpin> Display analogue input value
|
||||||
DIAG(F("VPIN=%d value=%d"), p[1], IODevice::readAnalogue(p[1]));
|
DIAG(F("VPIN=%u value=%d"), p[1], IODevice::readAnalogue(p[1]));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
#if !defined(IO_NO_HAL)
|
#if !defined(IO_NO_HAL)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* © 2022 Paul M. Antoine
|
* © 2022-2023 Paul M. Antoine
|
||||||
* © 2021 Mike S
|
* © 2021 Mike S
|
||||||
* © 2021-2022 Harald Barth
|
* © 2021-2023 Harald Barth
|
||||||
* © 2021 Fred Decker
|
* © 2021 Fred Decker
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
|
@ -62,6 +62,9 @@ class DCCTimer {
|
||||||
static bool isPWMPin(byte pin);
|
static bool isPWMPin(byte pin);
|
||||||
static void setPWM(byte pin, bool high);
|
static void setPWM(byte pin, bool high);
|
||||||
static void clearPWM();
|
static void clearPWM();
|
||||||
|
static void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency);
|
||||||
|
static void DCCEXanalogWrite(uint8_t pin, int value);
|
||||||
|
|
||||||
// Update low ram level. Allow for extra bytes to be specified
|
// Update low ram level. Allow for extra bytes to be specified
|
||||||
// by estimation or inspection, that may be used by other
|
// by estimation or inspection, that may be used by other
|
||||||
// called subroutines. Must be called with interrupts disabled.
|
// called subroutines. Must be called with interrupts disabled.
|
||||||
|
|
|
@ -150,6 +150,45 @@ int DCCTimer::freeMemory() {
|
||||||
void DCCTimer::reset() {
|
void DCCTimer::reset() {
|
||||||
ESP.restart();
|
ESP.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
#include "soc/soc_caps.h"
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef SOC_LEDC_SUPPORT_HS_MODE
|
||||||
|
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM<<1)
|
||||||
|
#else
|
||||||
|
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int8_t pin_to_channel[SOC_GPIO_PIN_COUNT] = { 0 };
|
||||||
|
static int cnt_channel = LEDC_CHANNELS;
|
||||||
|
|
||||||
|
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency) {
|
||||||
|
if (pin < SOC_GPIO_PIN_COUNT) {
|
||||||
|
if (pin_to_channel[pin] != 0) {
|
||||||
|
ledcSetup(pin_to_channel[pin], frequency, 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value) {
|
||||||
|
if (pin < SOC_GPIO_PIN_COUNT) {
|
||||||
|
if (pin_to_channel[pin] == 0) {
|
||||||
|
if (!cnt_channel) {
|
||||||
|
log_e("No more PWM channels available! All %u already used", LEDC_CHANNELS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pin_to_channel[pin] = --cnt_channel;
|
||||||
|
ledcAttachPin(pin, cnt_channel);
|
||||||
|
ledcSetup(cnt_channel, 1000, 8);
|
||||||
|
} else {
|
||||||
|
ledcAttachPin(pin, pin_to_channel[pin]);
|
||||||
|
}
|
||||||
|
ledcWrite(pin_to_channel[pin], value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int ADCee::init(uint8_t pin) {
|
int ADCee::init(uint8_t pin) {
|
||||||
pinMode(pin, ANALOG);
|
pinMode(pin, ANALOG);
|
||||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||||
|
|
|
@ -162,7 +162,7 @@ uint16_t ADCee::usedpins = 0;
|
||||||
int * ADCee::analogvals = NULL;
|
int * ADCee::analogvals = NULL;
|
||||||
|
|
||||||
int ADCee::init(uint8_t pin) {
|
int ADCee::init(uint8_t pin) {
|
||||||
uint id = pin - A0;
|
uint8_t id = pin - A0;
|
||||||
int value = 0;
|
int value = 0;
|
||||||
|
|
||||||
if (id > NUM_ADC_INPUTS)
|
if (id > NUM_ADC_INPUTS)
|
||||||
|
@ -210,7 +210,7 @@ int ADCee::read(uint8_t pin, bool fromISR) {
|
||||||
#pragma GCC push_options
|
#pragma GCC push_options
|
||||||
#pragma GCC optimize ("-O3")
|
#pragma GCC optimize ("-O3")
|
||||||
void ADCee::scan() {
|
void ADCee::scan() {
|
||||||
static uint id = 0; // id and mask are the same thing but it is faster to
|
static uint8_t 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 uint16_t mask = 1; // increment and shift instead to calculate mask from id
|
||||||
static bool waiting = false;
|
static bool waiting = false;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
/*
|
/*
|
||||||
* © 2023 Neil McKechnie
|
* © 2023 Neil McKechnie
|
||||||
* © 2022 Paul M. Antoine
|
* © 2022-23 Paul M. Antoine
|
||||||
* © 2021 Mike S
|
* © 2021 Mike S
|
||||||
* © 2021 Harald Barth
|
* © 2021, 2023 Harald Barth
|
||||||
* © 2021 Fred Decker
|
* © 2021 Fred Decker
|
||||||
* © 2021 Chris Harlow
|
* © 2021 Chris Harlow
|
||||||
* © 2021 David Cutting
|
* © 2021 David Cutting
|
||||||
|
@ -40,27 +40,123 @@ HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F
|
||||||
HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE
|
HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE
|
||||||
#elif defined(ARDUINO_NUCLEO_F446RE)
|
#elif defined(ARDUINO_NUCLEO_F446RE)
|
||||||
// Nucleo-64 boards don't have Serial1 defined by default
|
// Nucleo-64 boards don't have Serial1 defined by default
|
||||||
HardwareSerial Serial1(PA10, PB6); // Rx=PA10, Tx=PB6 -- CN10 pins 33 and 17 - F446RE
|
HardwareSerial Serial1(PA10, PB6); // Rx=PA10 (D2), Tx=PB6 (D10) -- CN10 pins 17 and 9 - F446RE
|
||||||
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
|
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
|
||||||
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
|
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
|
||||||
|
// NB: USART3 and USART6 are available but as yet undefined
|
||||||
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||||
// Nucleo-144 boards don't have Serial1 defined by default
|
// Nucleo-144 boards don't have Serial1 defined by default
|
||||||
HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE
|
HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE
|
||||||
|
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
|
||||||
|
// via the debugger on the Nucleo-144. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
|
||||||
|
// NB:
|
||||||
|
// On all of the above, USART3, and USART6 are available but as yet undefined
|
||||||
|
// On F446ZE and F429ZI, UART4, UART5 are also available but as yet undefined
|
||||||
|
// On F429ZI, UART7 and UART8 are also available but as yet undefined
|
||||||
#else
|
#else
|
||||||
#warning Serial1 not defined
|
#error STM32 board selected is not yet explicitly supported - so Serial1 peripheral is not defined
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Experimental code for High Accuracy (HA) DCC Signal mode
|
||||||
|
// Warning - use of TIM2 and TIM3 can affect the use of analogWrite() function on certain pins,
|
||||||
|
// which is used by the DC motor types.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// INTERRUPT_CALLBACK interruptHandler=0;
|
||||||
|
// // Let's use STM32's timer #2 which supports hardware pulse generation on pin D13.
|
||||||
|
// // Also, timer #3 will do hardware pulses on pin D12. This gives
|
||||||
|
// // accurate timing, independent of the latency of interrupt handling.
|
||||||
|
// // We only need to interrupt on one of these (TIM2), the other will just generate
|
||||||
|
// // pulses.
|
||||||
|
// HardwareTimer timer(TIM2);
|
||||||
|
// HardwareTimer timerAux(TIM3);
|
||||||
|
// static bool tim2ModeHA = false;
|
||||||
|
// static bool tim3ModeHA = false;
|
||||||
|
|
||||||
|
// // Timer IRQ handler
|
||||||
|
// void Timer_Handler() {
|
||||||
|
// interruptHandler();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||||
|
// interruptHandler=callback;
|
||||||
|
// noInterrupts();
|
||||||
|
|
||||||
|
// // adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES);
|
||||||
|
// timer.pause();
|
||||||
|
// timerAux.pause();
|
||||||
|
// timer.setPrescaleFactor(1);
|
||||||
|
// timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
|
||||||
|
// timer.attachInterrupt(Timer_Handler);
|
||||||
|
// timer.refresh();
|
||||||
|
// timerAux.setPrescaleFactor(1);
|
||||||
|
// timerAux.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
|
||||||
|
// timerAux.refresh();
|
||||||
|
|
||||||
|
// timer.resume();
|
||||||
|
// timerAux.resume();
|
||||||
|
|
||||||
|
// interrupts();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// bool DCCTimer::isPWMPin(byte pin) {
|
||||||
|
// // Timer 2 Channel 1 controls pin D13, and Timer3 Channel 1 controls D12.
|
||||||
|
// // Enable the appropriate timer channel.
|
||||||
|
// switch (pin) {
|
||||||
|
// case 12:
|
||||||
|
// return true;
|
||||||
|
// case 13:
|
||||||
|
// return true;
|
||||||
|
// default:
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// void DCCTimer::setPWM(byte pin, bool high) {
|
||||||
|
// // Set the timer so that, at the next counter overflow, the requested
|
||||||
|
// // pin state is activated automatically before the interrupt code runs.
|
||||||
|
// // TIM2 is timer, TIM3 is timerAux.
|
||||||
|
// switch (pin) {
|
||||||
|
// case 12:
|
||||||
|
// if (!tim3ModeHA) {
|
||||||
|
// timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D12);
|
||||||
|
// tim3ModeHA = true;
|
||||||
|
// }
|
||||||
|
// if (high)
|
||||||
|
// TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0;
|
||||||
|
// else
|
||||||
|
// TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1;
|
||||||
|
// break;
|
||||||
|
// case 13:
|
||||||
|
// if (!tim2ModeHA) {
|
||||||
|
// timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D13);
|
||||||
|
// tim2ModeHA = true;
|
||||||
|
// }
|
||||||
|
// if (high)
|
||||||
|
// TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0;
|
||||||
|
// else
|
||||||
|
// TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// void DCCTimer::clearPWM() {
|
||||||
|
// timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC);
|
||||||
|
// tim2ModeHA = false;
|
||||||
|
// timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC);
|
||||||
|
// tim3ModeHA = false;
|
||||||
|
// }
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
INTERRUPT_CALLBACK interruptHandler=0;
|
INTERRUPT_CALLBACK interruptHandler=0;
|
||||||
// Let's use STM32's timer #2 which supports hardware pulse generation on pin D13.
|
// Let's use STM32's timer #11 until disabused of this notion
|
||||||
// Also, timer #3 will do hardware pulses on pin D12. This gives
|
// Timer #11 is used for "servo" library, but as DCC-EX is not using
|
||||||
// accurate timing, independent of the latency of interrupt handling.
|
// this libary, we should be free and clear.
|
||||||
// We only need to interrupt on one of these (TIM2), the other will just generate
|
HardwareTimer timer(TIM11);
|
||||||
// pulses.
|
|
||||||
HardwareTimer timer(TIM2);
|
|
||||||
HardwareTimer timerAux(TIM3);
|
|
||||||
|
|
||||||
// Timer IRQ handler
|
// Timer IRQ handler
|
||||||
void Timer_Handler() {
|
void Timer11_Handler() {
|
||||||
interruptHandler();
|
interruptHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,59 +166,31 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||||
|
|
||||||
// adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES);
|
// adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES);
|
||||||
timer.pause();
|
timer.pause();
|
||||||
timerAux.pause();
|
|
||||||
timer.setPrescaleFactor(1);
|
timer.setPrescaleFactor(1);
|
||||||
|
// timer.setOverflow(CLOCK_CYCLES * 2);
|
||||||
timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
|
timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
|
||||||
timer.attachInterrupt(Timer_Handler);
|
timer.attachInterrupt(Timer11_Handler);
|
||||||
timer.refresh();
|
timer.refresh();
|
||||||
timerAux.setPrescaleFactor(1);
|
|
||||||
timerAux.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
|
|
||||||
timerAux.refresh();
|
|
||||||
|
|
||||||
timer.resume();
|
timer.resume();
|
||||||
timerAux.resume();
|
|
||||||
|
|
||||||
interrupts();
|
interrupts();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DCCTimer::isPWMPin(byte pin) {
|
bool DCCTimer::isPWMPin(byte pin) {
|
||||||
// Timer 2 Channel 1 controls pin D13, and Timer3 Channel 1 controls D12.
|
//TODO: SAMD whilst this call to digitalPinHasPWM will reveal which pins can do PWM,
|
||||||
// Enable the appropriate timer channel.
|
// there's no support yet for High Accuracy, so for now return false
|
||||||
switch (pin) {
|
// return digitalPinHasPWM(pin);
|
||||||
case 12:
|
|
||||||
timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D12);
|
|
||||||
return true;
|
|
||||||
case 13:
|
|
||||||
timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D13);
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void DCCTimer::setPWM(byte pin, bool high) {
|
void DCCTimer::setPWM(byte pin, bool high) {
|
||||||
// Set the timer so that, at the next counter overflow, the requested
|
// TODO: High Accuracy mode is not supported as yet, and may never need to be
|
||||||
// pin state is activated automatically before the interrupt code runs.
|
(void) pin;
|
||||||
// TIM2 is timer, TIM3 is timerAux.
|
(void) high;
|
||||||
switch (pin) {
|
|
||||||
case 12:
|
|
||||||
if (high)
|
|
||||||
TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0;
|
|
||||||
else
|
|
||||||
TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1;
|
|
||||||
break;
|
|
||||||
case 13:
|
|
||||||
if (high)
|
|
||||||
TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0;
|
|
||||||
else
|
|
||||||
TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCCTimer::clearPWM() {
|
void DCCTimer::clearPWM() {
|
||||||
timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC);
|
return;
|
||||||
timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||||
|
@ -166,6 +234,9 @@ void DCCTimer::reset() {
|
||||||
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
|
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
|
||||||
|
|
||||||
// TODO: may need to use uint32_t on STMF4xx variants with > 16 analog inputs!
|
// TODO: may need to use uint32_t on STMF4xx variants with > 16 analog inputs!
|
||||||
|
#if defined(ARDUINO_NUCLEO_F446RE) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||||
|
#warning STM32 board selected not fully supported - only use ADC1 inputs 0-15 for current sensing!
|
||||||
|
#endif
|
||||||
uint16_t ADCee::usedpins = 0;
|
uint16_t ADCee::usedpins = 0;
|
||||||
int * ADCee::analogvals = NULL;
|
int * ADCee::analogvals = NULL;
|
||||||
uint32_t * analogchans = NULL;
|
uint32_t * analogchans = NULL;
|
||||||
|
@ -176,10 +247,13 @@ int16_t ADCee::ADCmax() {
|
||||||
}
|
}
|
||||||
|
|
||||||
int ADCee::init(uint8_t pin) {
|
int ADCee::init(uint8_t pin) {
|
||||||
uint id = pin - A0;
|
|
||||||
int value = 0;
|
int value = 0;
|
||||||
PinName stmpin = digitalPin[analogInputPin[id]];
|
PinName stmpin = analogInputToPinName(pin);
|
||||||
uint32_t stmgpio = stmpin / 16; // 16-bits per GPIO port group on STM32
|
if (stmpin == NC) // do not continue if this is not an analog pin at all
|
||||||
|
return -1024; // some silly value as error
|
||||||
|
|
||||||
|
uint32_t stmgpio = STM_PORT(stmpin); // converts to the GPIO port (16-bits per port group on STM32)
|
||||||
uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC channel (only valid for ADC1!)
|
uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC channel (only valid for ADC1!)
|
||||||
GPIO_TypeDef * gpioBase;
|
GPIO_TypeDef * gpioBase;
|
||||||
|
|
||||||
|
@ -197,12 +271,20 @@ int ADCee::init(uint8_t pin) {
|
||||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; //Power up PORTC
|
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; //Power up PORTC
|
||||||
gpioBase = GPIOC;
|
gpioBase = GPIOC;
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
return -1023; // some silly value as error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set pin mux mode to analog input
|
// Set pin mux mode to analog input, the 32 bit port mode register has 2 bits per pin
|
||||||
gpioBase->MODER |= (0b011 << (stmpin << 1)); // Set pin mux to analog mode
|
gpioBase->MODER |= (0b011 << (STM_PIN(stmpin) << 1)); // Set pin mux to analog mode (binary 11)
|
||||||
|
|
||||||
// Set the sampling rate for that analog input
|
// Set the sampling rate for that analog input
|
||||||
|
// This is F411x specific! Different on for example F334
|
||||||
|
// STM32F11xC/E Reference manual
|
||||||
|
// 11.12.4 ADC sample time register 1 (ADC_SMPR1) (channels 10 to 18)
|
||||||
|
// 11.12.5 ADC sample time register 2 (ADC_SMPR2) (channels 0 to 9)
|
||||||
|
if (adcchan > 18)
|
||||||
|
return -1022; // silly value as error
|
||||||
if (adcchan < 10)
|
if (adcchan < 10)
|
||||||
ADC1->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
|
ADC1->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
|
||||||
else
|
else
|
||||||
|
@ -214,8 +296,12 @@ int ADCee::init(uint8_t pin) {
|
||||||
while(!(ADC1->SR & (1 << 1))); // Wait until conversion is complete
|
while(!(ADC1->SR & (1 << 1))); // Wait until conversion is complete
|
||||||
value = ADC1->DR; // Read value from register
|
value = ADC1->DR; // Read value from register
|
||||||
|
|
||||||
if (analogvals == NULL)
|
uint8_t id = pin - PNUM_ANALOG_BASE;
|
||||||
{
|
if (id > 15) { // today we have not enough bits in the mask to support more
|
||||||
|
return -1021;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (analogvals == NULL) { // allocate analogvals and analogchans if this is the first invocation of init.
|
||||||
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
|
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
|
||||||
analogchans = (uint32_t *)calloc(NUM_ADC_INPUTS+1, sizeof(uint32_t));
|
analogchans = (uint32_t *)calloc(NUM_ADC_INPUTS+1, sizeof(uint32_t));
|
||||||
}
|
}
|
||||||
|
@ -230,7 +316,7 @@ int ADCee::init(uint8_t pin) {
|
||||||
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
|
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
|
||||||
*/
|
*/
|
||||||
int ADCee::read(uint8_t pin, bool fromISR) {
|
int ADCee::read(uint8_t pin, bool fromISR) {
|
||||||
uint8_t id = pin - A0;
|
uint8_t id = pin - PNUM_ANALOG_BASE;
|
||||||
// Was this pin initialised yet?
|
// Was this pin initialised yet?
|
||||||
if ((usedpins & (1<<id) ) == 0)
|
if ((usedpins & (1<<id) ) == 0)
|
||||||
return -1023;
|
return -1023;
|
||||||
|
@ -245,7 +331,7 @@ int ADCee::read(uint8_t pin, bool fromISR) {
|
||||||
#pragma GCC push_options
|
#pragma GCC push_options
|
||||||
#pragma GCC optimize ("-O3")
|
#pragma GCC optimize ("-O3")
|
||||||
void ADCee::scan() {
|
void ADCee::scan() {
|
||||||
static uint id = 0; // id and mask are the same thing but it is faster to
|
static uint8_t 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 uint16_t mask = 1; // increment and shift instead to calculate mask from id
|
||||||
static bool waiting = false;
|
static bool waiting = false;
|
||||||
|
|
||||||
|
|
119
Display.cpp
119
Display.cpp
|
@ -36,7 +36,7 @@
|
||||||
* not held up significantly. The exception to this is when
|
* not held up significantly. The exception to this is when
|
||||||
* the loop2() function is called with force=true, where
|
* the loop2() function is called with force=true, where
|
||||||
* a screen update is executed to completion. This is normally
|
* a screen update is executed to completion. This is normally
|
||||||
* only noMoreRowsToDisplay during start-up.
|
* only done during start-up.
|
||||||
* The scroll mode is selected by defining SCROLLMODE as 0, 1 or 2
|
* The scroll mode is selected by defining SCROLLMODE as 0, 1 or 2
|
||||||
* in the config.h.
|
* in the config.h.
|
||||||
* #define SCROLLMODE 0 is scroll continuous (fill screen if poss),
|
* #define SCROLLMODE 0 is scroll continuous (fill screen if poss),
|
||||||
|
@ -51,11 +51,10 @@
|
||||||
Display::Display(DisplayDevice *deviceDriver) {
|
Display::Display(DisplayDevice *deviceDriver) {
|
||||||
_deviceDriver = deviceDriver;
|
_deviceDriver = deviceDriver;
|
||||||
// Get device dimensions in characters (e.g. 16x2).
|
// Get device dimensions in characters (e.g. 16x2).
|
||||||
numCharacterColumns = _deviceDriver->getNumCols();
|
numScreenColumns = _deviceDriver->getNumCols();
|
||||||
numCharacterRows = _deviceDriver->getNumRows();;
|
numScreenRows = _deviceDriver->getNumRows();
|
||||||
for (uint8_t row = 0; row < MAX_CHARACTER_ROWS; row++)
|
for (uint8_t row = 0; row < MAX_CHARACTER_ROWS; row++)
|
||||||
rowBuffer[row][0] = '\0';
|
rowBuffer[row][0] = '\0';
|
||||||
topRow = ROW_INITIAL; // loop2 will fill from row 0
|
|
||||||
|
|
||||||
addDisplay(0); // Add this display as display number 0
|
addDisplay(0); // Add this display as display number 0
|
||||||
};
|
};
|
||||||
|
@ -69,20 +68,19 @@ void Display::_clear() {
|
||||||
_deviceDriver->clearNative();
|
_deviceDriver->clearNative();
|
||||||
for (uint8_t row = 0; row < MAX_CHARACTER_ROWS; row++)
|
for (uint8_t row = 0; row < MAX_CHARACTER_ROWS; row++)
|
||||||
rowBuffer[row][0] = '\0';
|
rowBuffer[row][0] = '\0';
|
||||||
topRow = ROW_INITIAL; // loop2 will fill from row 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Display::_setRow(uint8_t line) {
|
void Display::_setRow(uint8_t line) {
|
||||||
hotRow = line;
|
hotRow = line;
|
||||||
hotCol = 0;
|
hotCol = 0;
|
||||||
rowBuffer[hotRow][0] = 0; // Clear existing text
|
rowBuffer[hotRow][0] = '\0'; // Clear existing text
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Display::_write(uint8_t b) {
|
size_t Display::_write(uint8_t b) {
|
||||||
if (hotRow >= MAX_CHARACTER_ROWS || hotCol >= MAX_CHARACTER_COLS) return -1;
|
if (hotRow >= MAX_CHARACTER_ROWS || hotCol >= MAX_CHARACTER_COLS) return -1;
|
||||||
rowBuffer[hotRow][hotCol] = b;
|
rowBuffer[hotRow][hotCol] = b;
|
||||||
hotCol++;
|
hotCol++;
|
||||||
rowBuffer[hotRow][hotCol] = 0;
|
rowBuffer[hotRow][hotCol] = '\0';
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,8 +107,8 @@ Display *Display::loop2(bool force) {
|
||||||
return NULL;
|
return NULL;
|
||||||
} else {
|
} else {
|
||||||
// force full screen update from the beginning.
|
// force full screen update from the beginning.
|
||||||
rowFirst = ROW_INITIAL;
|
rowFirst = 0;
|
||||||
rowNext = ROW_INITIAL;
|
rowCurrent = 0;
|
||||||
bufferPointer = 0;
|
bufferPointer = 0;
|
||||||
noMoreRowsToDisplay = false;
|
noMoreRowsToDisplay = false;
|
||||||
slot = 0;
|
slot = 0;
|
||||||
|
@ -118,15 +116,20 @@ Display *Display::loop2(bool force) {
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if (bufferPointer == 0) {
|
if (bufferPointer == 0) {
|
||||||
// Find a line of data to write to the screen.
|
// Search for non-blank row
|
||||||
if (rowFirst == ROW_INITIAL) rowFirst = rowNext;
|
while (!noMoreRowsToDisplay) {
|
||||||
if (findNextNonBlankRow()) {
|
if (!isCurrentRowBlank()) break;
|
||||||
|
moveToNextRow();
|
||||||
|
if (rowCurrent == rowFirst) noMoreRowsToDisplay = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noMoreRowsToDisplay) {
|
||||||
|
// No non-blank lines left, so draw blank line
|
||||||
|
buffer[0] = '\0';
|
||||||
|
} else {
|
||||||
// Non-blank line found, so copy it (including terminator)
|
// Non-blank line found, so copy it (including terminator)
|
||||||
for (uint8_t i = 0; i <= MAX_CHARACTER_COLS; i++)
|
for (uint8_t i = 0; i <= MAX_CHARACTER_COLS; i++)
|
||||||
buffer[i] = rowBuffer[rowNext][i];
|
buffer[i] = rowBuffer[rowCurrent][i];
|
||||||
} else {
|
|
||||||
// No non-blank lines left, so draw a blank line
|
|
||||||
buffer[0] = 0;
|
|
||||||
}
|
}
|
||||||
_deviceDriver->setRowNative(slot); // Set position for display
|
_deviceDriver->setRowNative(slot); // Set position for display
|
||||||
charIndex = 0;
|
charIndex = 0;
|
||||||
|
@ -142,21 +145,49 @@ Display *Display::loop2(bool force) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (++charIndex >= MAX_CHARACTER_COLS) {
|
if (++charIndex >= MAX_CHARACTER_COLS) {
|
||||||
// Screen slot completed, move to next slot on screen
|
// Screen slot completed, move to next nonblank row
|
||||||
bufferPointer = 0;
|
bufferPointer = 0;
|
||||||
|
for (;;) {
|
||||||
|
moveToNextRow();
|
||||||
|
if (rowCurrent == rowFirst) {
|
||||||
|
noMoreRowsToDisplay = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!isCurrentRowBlank()) break;
|
||||||
|
}
|
||||||
|
// Move to next screen slot, if available
|
||||||
slot++;
|
slot++;
|
||||||
if (slot >= numCharacterRows) {
|
if (slot >= numScreenRows) {
|
||||||
// Last slot on screen written, reset ready for next screen update.
|
// Last slot on screen written, so get ready for next screen update.
|
||||||
#if SCROLLMODE==2
|
#if SCROLLMODE==0
|
||||||
if (!noMoreRowsToDisplay) {
|
// Scrollmode 0 scrolls continuously. If the rows fit on the screen,
|
||||||
// On next refresh, restart one row on from previous start.
|
// then restart at row 0, but otherwise continue with the row
|
||||||
rowNext = rowFirst;
|
// after the last one displayed.
|
||||||
findNextNonBlankRow();
|
if (countNonBlankRows() <= numScreenRows)
|
||||||
|
rowCurrent = 0;
|
||||||
|
rowFirst = rowCurrent;
|
||||||
|
#elif SCROLLMODE==1
|
||||||
|
// Scrollmode 1 scrolls by page, so if the last page has just completed then
|
||||||
|
// next time restart with row 0.
|
||||||
|
if (noMoreRowsToDisplay)
|
||||||
|
rowFirst = rowCurrent = 0;
|
||||||
|
#else
|
||||||
|
// Scrollmode 2 scrolls by row. If the rows don't fit on the screen,
|
||||||
|
// then start one row further on next time. If they do fit, then
|
||||||
|
// show them in order and start next page at row 0.
|
||||||
|
if (countNonBlankRows() <= numScreenRows) {
|
||||||
|
rowFirst = rowCurrent = 0;
|
||||||
|
} else {
|
||||||
|
// Find first non-blank row after the previous first row
|
||||||
|
rowCurrent = rowFirst;
|
||||||
|
do {
|
||||||
|
moveToNextRow();
|
||||||
|
} while (isCurrentRowBlank());
|
||||||
|
rowFirst = rowCurrent;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
noMoreRowsToDisplay = false;
|
noMoreRowsToDisplay = false;
|
||||||
slot = 0;
|
slot = 0;
|
||||||
rowFirst = ROW_INITIAL;
|
|
||||||
lastScrollTime = currentMillis;
|
lastScrollTime = currentMillis;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -167,30 +198,22 @@ Display *Display::loop2(bool force) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Display::findNextNonBlankRow() {
|
bool Display::isCurrentRowBlank() {
|
||||||
while (!noMoreRowsToDisplay) {
|
return (rowBuffer[rowCurrent][0] == '\0');
|
||||||
if (rowNext == ROW_INITIAL)
|
|
||||||
rowNext = 0;
|
|
||||||
else
|
|
||||||
rowNext = rowNext + 1;
|
|
||||||
if (rowNext >= MAX_CHARACTER_ROWS) rowNext = ROW_INITIAL;
|
|
||||||
#if SCROLLMODE == 1
|
|
||||||
// Finished if we've looped back to start
|
|
||||||
if (rowNext == ROW_INITIAL) {
|
|
||||||
noMoreRowsToDisplay = true;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
// Finished if we're back to the first one shown
|
void Display::moveToNextRow() {
|
||||||
if (rowNext == rowFirst) {
|
// Skip blank rows
|
||||||
noMoreRowsToDisplay = true;
|
if (++rowCurrent >= MAX_CHARACTER_ROWS)
|
||||||
return false;
|
rowCurrent = 0;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
if (rowBuffer[rowNext][0] != 0) {
|
uint8_t Display::countNonBlankRows() {
|
||||||
// Found non-blank row
|
uint8_t count = 0;
|
||||||
return true;
|
for (uint8_t rowNumber=0; rowNumber<MAX_CHARACTER_ROWS; rowNumber++) {
|
||||||
|
if (rowBuffer[rowNumber][0] != '\0')
|
||||||
|
count++;
|
||||||
}
|
}
|
||||||
|
return count;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
15
Display.h
15
Display.h
|
@ -40,7 +40,6 @@ public:
|
||||||
static const int MAX_CHARACTER_ROWS = 8;
|
static const int MAX_CHARACTER_ROWS = 8;
|
||||||
static const int MAX_CHARACTER_COLS = MAX_MSG_SIZE;
|
static const int MAX_CHARACTER_COLS = MAX_MSG_SIZE;
|
||||||
static const long DISPLAY_SCROLL_TIME = 3000; // 3 seconds
|
static const long DISPLAY_SCROLL_TIME = 3000; // 3 seconds
|
||||||
static const uint8_t ROW_INITIAL = 255;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DisplayDevice *_deviceDriver;
|
DisplayDevice *_deviceDriver;
|
||||||
|
@ -48,16 +47,15 @@ private:
|
||||||
unsigned long lastScrollTime = 0;
|
unsigned long lastScrollTime = 0;
|
||||||
uint8_t hotRow = 0;
|
uint8_t hotRow = 0;
|
||||||
uint8_t hotCol = 0;
|
uint8_t hotCol = 0;
|
||||||
uint8_t topRow = 0;
|
|
||||||
uint8_t slot = 0;
|
uint8_t slot = 0;
|
||||||
uint8_t rowFirst = ROW_INITIAL;
|
uint8_t rowFirst = 0;
|
||||||
uint8_t rowNext = ROW_INITIAL;
|
uint8_t rowCurrent = 0;
|
||||||
uint8_t charIndex = 0;
|
uint8_t charIndex = 0;
|
||||||
char buffer[MAX_CHARACTER_COLS + 1];
|
char buffer[MAX_CHARACTER_COLS + 1];
|
||||||
char* bufferPointer = 0;
|
char* bufferPointer = 0;
|
||||||
bool noMoreRowsToDisplay = false;
|
bool noMoreRowsToDisplay = false;
|
||||||
uint16_t numCharacterRows;
|
uint16_t numScreenRows;
|
||||||
uint16_t numCharacterColumns = MAX_CHARACTER_COLS;
|
uint16_t numScreenColumns = MAX_CHARACTER_COLS;
|
||||||
|
|
||||||
char rowBuffer[MAX_CHARACTER_ROWS][MAX_CHARACTER_COLS+1];
|
char rowBuffer[MAX_CHARACTER_ROWS][MAX_CHARACTER_COLS+1];
|
||||||
|
|
||||||
|
@ -69,7 +67,10 @@ public:
|
||||||
void _refresh() override;
|
void _refresh() override;
|
||||||
void _displayLoop() override;
|
void _displayLoop() override;
|
||||||
Display *loop2(bool force);
|
Display *loop2(bool force);
|
||||||
bool findNextNonBlankRow();
|
bool findNonBlankRow();
|
||||||
|
bool isCurrentRowBlank();
|
||||||
|
void moveToNextRow();
|
||||||
|
uint8_t countNonBlankRows();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
/*
|
|
||||||
* © 2022 Harald Barth
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* This file is part of CommandStation-EX
|
|
||||||
*
|
|
||||||
* This is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* It is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include "ESP32-fixes.h"
|
|
||||||
|
|
||||||
#include "esp32-hal.h"
|
|
||||||
#include "soc/soc_caps.h"
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef SOC_LEDC_SUPPORT_HS_MODE
|
|
||||||
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM<<1)
|
|
||||||
#else
|
|
||||||
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static int8_t pin_to_channel[SOC_GPIO_PIN_COUNT] = { 0 };
|
|
||||||
static int cnt_channel = LEDC_CHANNELS;
|
|
||||||
|
|
||||||
void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency) {
|
|
||||||
if (pin < SOC_GPIO_PIN_COUNT) {
|
|
||||||
if (pin_to_channel[pin] != 0) {
|
|
||||||
ledcSetup(pin_to_channel[pin], frequency, 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DCCEXanalogWrite(uint8_t pin, int value) {
|
|
||||||
if (pin < SOC_GPIO_PIN_COUNT) {
|
|
||||||
if (pin_to_channel[pin] == 0) {
|
|
||||||
if (!cnt_channel) {
|
|
||||||
log_e("No more PWM channels available! All %u already used", LEDC_CHANNELS);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pin_to_channel[pin] = --cnt_channel;
|
|
||||||
ledcAttachPin(pin, cnt_channel);
|
|
||||||
ledcSetup(cnt_channel, 1000, 8);
|
|
||||||
} else {
|
|
||||||
ledcAttachPin(pin, pin_to_channel[pin]);
|
|
||||||
}
|
|
||||||
ledcWrite(pin_to_channel[pin], value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
|
@ -1,26 +0,0 @@
|
||||||
/*
|
|
||||||
* © 2022 Harald Barth
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* This file is part of CommandStation-EX
|
|
||||||
*
|
|
||||||
* This is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* It is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
|
||||||
#pragma once
|
|
||||||
#include <Arduino.h>
|
|
||||||
void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency);
|
|
||||||
void DCCEXanalogWrite(uint8_t pin, int value);
|
|
||||||
#endif
|
|
||||||
|
|
BIN
EX-CommandStation-installer.exe
Normal file
BIN
EX-CommandStation-installer.exe
Normal file
Binary file not shown.
25
EXRAIL2.cpp
25
EXRAIL2.cpp
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* © 2021 Neil McKechnie
|
* © 2021 Neil McKechnie
|
||||||
* © 2021-2023 Harald Barth
|
* © 2021-2023 Harald Barth
|
||||||
* © 2020-2022 Chris Harlow
|
* © 2020-2023 Chris Harlow
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
|
@ -24,8 +24,8 @@
|
||||||
F1. [DONE] DCC accessory packet opcodes (short and long form)
|
F1. [DONE] DCC accessory packet opcodes (short and long form)
|
||||||
F2. [DONE] ONAccessory catchers
|
F2. [DONE] ONAccessory catchers
|
||||||
F3. [DONE] Turnout descriptions for Withrottle
|
F3. [DONE] Turnout descriptions for Withrottle
|
||||||
F4. Oled announcements (depends on HAL)
|
F4. [DONE] Oled announcements (depends on HAL)
|
||||||
F5. Withrottle roster info
|
F5. [DONE] Withrottle roster info
|
||||||
F6. Multi-occupancy semaphore
|
F6. Multi-occupancy semaphore
|
||||||
F7. [DONE see AUTOSTART] Self starting sequences
|
F7. [DONE see AUTOSTART] Self starting sequences
|
||||||
F8. Park/unpark
|
F8. Park/unpark
|
||||||
|
@ -105,13 +105,10 @@ uint16_t RMFT2::getOperand(byte n) {
|
||||||
// getOperand static version, must be provided prog counter from loop etc.
|
// getOperand static version, must be provided prog counter from loop etc.
|
||||||
uint16_t RMFT2::getOperand(int progCounter,byte n) {
|
uint16_t RMFT2::getOperand(int progCounter,byte n) {
|
||||||
int offset=progCounter+1+(n*3);
|
int offset=progCounter+1+(n*3);
|
||||||
if (offset&1) {
|
|
||||||
byte lsb=GETHIGHFLASH(RouteCode,offset);
|
byte lsb=GETHIGHFLASH(RouteCode,offset);
|
||||||
byte msb=GETHIGHFLASH(RouteCode,offset+1);
|
byte msb=GETHIGHFLASH(RouteCode,offset+1);
|
||||||
return msb<<8|lsb;
|
return msb<<8|lsb;
|
||||||
}
|
}
|
||||||
return GETHIGHFLASHW(RouteCode,offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
LookList::LookList(int16_t size) {
|
LookList::LookList(int16_t size) {
|
||||||
m_size=size;
|
m_size=size;
|
||||||
|
@ -201,7 +198,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||||
case OPCODE_IFNOT: {
|
case OPCODE_IFNOT: {
|
||||||
int16_t pin = (int16_t)operand;
|
int16_t pin = (int16_t)operand;
|
||||||
if (pin<0) pin = -pin;
|
if (pin<0) pin = -pin;
|
||||||
DIAG(F("EXRAIL input vpin %d"),pin);
|
DIAG(F("EXRAIL input VPIN %u"),pin);
|
||||||
IODevice::configureInput((VPIN)pin,true);
|
IODevice::configureInput((VPIN)pin,true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -211,7 +208,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||||
case OPCODE_IFGTE:
|
case OPCODE_IFGTE:
|
||||||
case OPCODE_IFLT:
|
case OPCODE_IFLT:
|
||||||
case OPCODE_DRIVE: {
|
case OPCODE_DRIVE: {
|
||||||
DIAG(F("EXRAIL analog input vpin %d"),(VPIN)operand);
|
DIAG(F("EXRAIL analog input VPIN %u"),(VPIN)operand);
|
||||||
IODevice::configureAnalogIn((VPIN)operand);
|
IODevice::configureAnalogIn((VPIN)operand);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -243,8 +240,9 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||||
|
|
||||||
case OPCODE_AUTOSTART:
|
case OPCODE_AUTOSTART:
|
||||||
// automatically create a task from here at startup.
|
// automatically create a task from here at startup.
|
||||||
// but we will do one at 0 anyway by default.
|
// Removed if (progCounter>0) check 4.2.31 because
|
||||||
if (progCounter>0) new RMFT2(progCounter);
|
// default start it top of file is now removed. .
|
||||||
|
new RMFT2(progCounter);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: // Ignore
|
default: // Ignore
|
||||||
|
@ -255,7 +253,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||||
|
|
||||||
DIAG(F("EXRAIL %db, fl=%d"),progCounter,MAX_FLAGS);
|
DIAG(F("EXRAIL %db, fl=%d"),progCounter,MAX_FLAGS);
|
||||||
|
|
||||||
new RMFT2(0); // add the startup route
|
// Removed for 4.2.31 new RMFT2(0); // add the startup route
|
||||||
diag=saved_diag;
|
diag=saved_diag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1131,7 +1129,10 @@ void RMFT2::clockEvent(int16_t clocktime, bool change) {
|
||||||
// Hunt for an ONTIME for this time
|
// Hunt for an ONTIME for this time
|
||||||
if (Diag::CMD)
|
if (Diag::CMD)
|
||||||
DIAG(F("Looking for clock event at : %d"), clocktime);
|
DIAG(F("Looking for clock event at : %d"), clocktime);
|
||||||
if (change) handleEvent(F("CLOCK"),onClockLookup,clocktime);
|
if (change) {
|
||||||
|
handleEvent(F("CLOCK"),onClockLookup,clocktime);
|
||||||
|
handleEvent(F("CLOCK"),onClockLookup,25*60+clocktime%60);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
|
void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
|
||||||
|
|
|
@ -91,6 +91,7 @@
|
||||||
#undef ONCLOSE
|
#undef ONCLOSE
|
||||||
#undef ONTIME
|
#undef ONTIME
|
||||||
#undef ONCLOCKTIME
|
#undef ONCLOCKTIME
|
||||||
|
#undef ONCLOCKMINS
|
||||||
#undef ONGREEN
|
#undef ONGREEN
|
||||||
#undef ONRED
|
#undef ONRED
|
||||||
#undef ONTHROW
|
#undef ONTHROW
|
||||||
|
@ -134,6 +135,7 @@
|
||||||
#undef STOP
|
#undef STOP
|
||||||
#undef THROW
|
#undef THROW
|
||||||
#undef TURNOUT
|
#undef TURNOUT
|
||||||
|
#undef TURNOUTL
|
||||||
#undef UNJOIN
|
#undef UNJOIN
|
||||||
#undef UNLATCH
|
#undef UNLATCH
|
||||||
#undef VIRTUAL_SIGNAL
|
#undef VIRTUAL_SIGNAL
|
||||||
|
@ -208,6 +210,7 @@
|
||||||
#define ONAMBER(signal_id)
|
#define ONAMBER(signal_id)
|
||||||
#define ONTIME(value)
|
#define ONTIME(value)
|
||||||
#define ONCLOCKTIME(hours,mins)
|
#define ONCLOCKTIME(hours,mins)
|
||||||
|
#define ONCLOCKMINS(mins)
|
||||||
#define ONDEACTIVATE(addr,subaddr)
|
#define ONDEACTIVATE(addr,subaddr)
|
||||||
#define ONDEACTIVATEL(linear)
|
#define ONDEACTIVATEL(linear)
|
||||||
#define ONCLOSE(turnout_id)
|
#define ONCLOSE(turnout_id)
|
||||||
|
@ -254,6 +257,7 @@
|
||||||
#define STOP
|
#define STOP
|
||||||
#define THROW(id)
|
#define THROW(id)
|
||||||
#define TURNOUT(id,addr,subaddr,description...)
|
#define TURNOUT(id,addr,subaddr,description...)
|
||||||
|
#define TURNOUTL(id,addr,description...)
|
||||||
#define UNJOIN
|
#define UNJOIN
|
||||||
#define UNLATCH(sensor_id)
|
#define UNLATCH(sensor_id)
|
||||||
#define VIRTUAL_SIGNAL(id)
|
#define VIRTUAL_SIGNAL(id)
|
||||||
|
|
|
@ -316,6 +316,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||||
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
|
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
|
||||||
#define ONTIME(value) OPCODE_ONTIME,V(value),
|
#define ONTIME(value) OPCODE_ONTIME,V(value),
|
||||||
#define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)),
|
#define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)),
|
||||||
|
#define ONCLOCKMINS(mins) ONCLOCKTIME(25,mins)
|
||||||
#define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr),
|
#define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr),
|
||||||
#define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3),
|
#define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3),
|
||||||
#define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id),
|
#define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id),
|
||||||
|
@ -361,6 +362,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||||
#define STOP OPCODE_SPEED,V(0),
|
#define STOP OPCODE_SPEED,V(0),
|
||||||
#define THROW(id) OPCODE_THROW,V(id),
|
#define THROW(id) OPCODE_THROW,V(id),
|
||||||
#define TURNOUT(id,addr,subaddr,description...) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr),
|
#define TURNOUT(id,addr,subaddr,description...) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr),
|
||||||
|
#define TURNOUTL(id,addr,description...) TURNOUT(id,(addr-1)/4+1,(addr-1)%4, description)
|
||||||
#define UNJOIN OPCODE_UNJOIN,0,0,
|
#define UNJOIN OPCODE_UNJOIN,0,0,
|
||||||
#define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id),
|
#define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id),
|
||||||
#define VIRTUAL_SIGNAL(id)
|
#define VIRTUAL_SIGNAL(id)
|
||||||
|
|
|
@ -136,7 +136,7 @@ bool EthernetInterface::checkLink() {
|
||||||
DIAG(F("Ethernet cable connected"));
|
DIAG(F("Ethernet cable connected"));
|
||||||
connected=true;
|
connected=true;
|
||||||
#ifdef IP_ADDRESS
|
#ifdef IP_ADDRESS
|
||||||
setLocalIP(IP_ADDRESS); // for static IP, set it again
|
Ethernet.setLocalIP(IP_ADDRESS); // for static IP, set it again
|
||||||
#endif
|
#endif
|
||||||
IPAddress ip = Ethernet.localIP(); // look what IP was obtained (dynamic or static)
|
IPAddress ip = Ethernet.localIP(); // look what IP was obtained (dynamic or static)
|
||||||
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT
|
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
#define GITHUB_SHA "devel-202302281422Z"
|
#define GITHUB_SHA "devel-202304111013Z"
|
||||||
|
|
|
@ -72,18 +72,23 @@ static const FSH * guessI2CDeviceType(uint8_t address) {
|
||||||
void I2CManagerClass::begin(void) {
|
void I2CManagerClass::begin(void) {
|
||||||
if (!_beginCompleted) {
|
if (!_beginCompleted) {
|
||||||
_beginCompleted = true;
|
_beginCompleted = true;
|
||||||
|
|
||||||
|
// Check for short-circuit or floating lines (no pull-up) on I2C before enabling I2C
|
||||||
|
const FSH *message = F("WARNING: Check I2C %S line for short/pullup");
|
||||||
|
pinMode(SDA, INPUT);
|
||||||
|
if (!digitalRead(SDA))
|
||||||
|
DIAG(message, F("SDA"));
|
||||||
|
pinMode(SCL, INPUT);
|
||||||
|
if (!digitalRead(SCL))
|
||||||
|
DIAG(message, F("SCL"));
|
||||||
|
|
||||||
|
// Now initialise I2C
|
||||||
_initialise();
|
_initialise();
|
||||||
|
|
||||||
#if defined(I2C_USE_WIRE)
|
#if defined(I2C_USE_WIRE)
|
||||||
DIAG(F("I2CManager: Using Wire library"));
|
DIAG(F("I2CManager: Using Wire library"));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Check for short-circuits on I2C
|
|
||||||
if (!digitalRead(SDA))
|
|
||||||
DIAG(F("WARNING: Possible short-circuit on I2C SDA line"));
|
|
||||||
if (!digitalRead(SCL))
|
|
||||||
DIAG(F("WARNING: Possible short-circuit on I2C SCL line"));
|
|
||||||
|
|
||||||
// Probe and list devices. Use standard mode
|
// Probe and list devices. Use standard mode
|
||||||
// (clock speed 100kHz) for best device compatibility.
|
// (clock speed 100kHz) for best device compatibility.
|
||||||
_setClock(100000);
|
_setClock(100000);
|
||||||
|
|
44
IODevice.cpp
44
IODevice.cpp
|
@ -63,15 +63,31 @@ void IODevice::begin() {
|
||||||
if (exrailHalSetup)
|
if (exrailHalSetup)
|
||||||
exrailHalSetup();
|
exrailHalSetup();
|
||||||
|
|
||||||
// Predefine two PCA9685 modules 0x40-0x41
|
// Predefine two PCA9685 modules 0x40-0x41 if no conflicts
|
||||||
// Allocates 32 pins 100-131
|
// Allocates 32 pins 100-131
|
||||||
|
if (checkNoOverlap(100, 16, 0x40)) {
|
||||||
PCA9685::create(100, 16, 0x40);
|
PCA9685::create(100, 16, 0x40);
|
||||||
|
} else {
|
||||||
|
DIAG(F("Default PCA9685 at I2C 0x40 disabled due to configured user device"));
|
||||||
|
}
|
||||||
|
if (checkNoOverlap(116, 16, 0x41)) {
|
||||||
PCA9685::create(116, 16, 0x41);
|
PCA9685::create(116, 16, 0x41);
|
||||||
|
} else {
|
||||||
|
DIAG(F("Default PCA9685 at I2C 0x41 disabled due to configured user device"));
|
||||||
|
}
|
||||||
|
|
||||||
// Predefine two MCP23017 module 0x20/0x21
|
// Predefine two MCP23017 module 0x20/0x21 if no conflicts
|
||||||
// Allocates 32 pins 164-195
|
// Allocates 32 pins 164-195
|
||||||
|
if (checkNoOverlap(164, 16, 0x20)) {
|
||||||
MCP23017::create(164, 16, 0x20);
|
MCP23017::create(164, 16, 0x20);
|
||||||
|
} else {
|
||||||
|
DIAG(F("Default MCP23017 at I2C 0x20 disabled due to configured user device"));
|
||||||
|
}
|
||||||
|
if (checkNoOverlap(180, 16, 0x21)) {
|
||||||
MCP23017::create(180, 16, 0x21);
|
MCP23017::create(180, 16, 0x21);
|
||||||
|
} else {
|
||||||
|
DIAG(F("Default MCP23017 at I2C 0x21 disabled due to configured user device"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset() function to reinitialise all devices
|
// reset() function to reinitialise all devices
|
||||||
|
@ -169,7 +185,7 @@ bool IODevice::hasCallback(VPIN vpin) {
|
||||||
|
|
||||||
// Display (to diagnostics) details of the device.
|
// Display (to diagnostics) details of the device.
|
||||||
void IODevice::_display() {
|
void IODevice::_display() {
|
||||||
DIAG(F("Unknown device Vpins:%d-%d %S"),
|
DIAG(F("Unknown device Vpins:%u-%u %S"),
|
||||||
(int)_firstVpin, (int)_firstVpin+_nPins-1, _deviceState==DEVSTATE_FAILED ? F("OFFLINE") : F(""));
|
(int)_firstVpin, (int)_firstVpin+_nPins-1, _deviceState==DEVSTATE_FAILED ? F("OFFLINE") : F(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,7 +195,7 @@ bool IODevice::configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i
|
||||||
IODevice *dev = findDevice(vpin);
|
IODevice *dev = findDevice(vpin);
|
||||||
if (dev) return dev->_configure(vpin, configType, paramCount, params);
|
if (dev) return dev->_configure(vpin, configType, paramCount, params);
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("IODevice::configure(): Vpin ID %d not found!"), (int)vpin);
|
DIAG(F("IODevice::configure(): VPIN %u not found!"), (int)vpin);
|
||||||
#endif
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -191,7 +207,7 @@ int IODevice::read(VPIN vpin) {
|
||||||
return dev->_read(vpin);
|
return dev->_read(vpin);
|
||||||
}
|
}
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("IODevice::read(): Vpin %d not found!"), (int)vpin);
|
DIAG(F("IODevice::read(): VPIN %u not found!"), (int)vpin);
|
||||||
#endif
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -203,7 +219,7 @@ int IODevice::readAnalogue(VPIN vpin) {
|
||||||
return dev->_readAnalogue(vpin);
|
return dev->_readAnalogue(vpin);
|
||||||
}
|
}
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("IODevice::readAnalogue(): Vpin %d not found!"), (int)vpin);
|
DIAG(F("IODevice::readAnalogue(): VPIN %u not found!"), (int)vpin);
|
||||||
#endif
|
#endif
|
||||||
return -1023;
|
return -1023;
|
||||||
}
|
}
|
||||||
|
@ -213,7 +229,7 @@ int IODevice::configureAnalogIn(VPIN vpin) {
|
||||||
return dev->_configureAnalogIn(vpin);
|
return dev->_configureAnalogIn(vpin);
|
||||||
}
|
}
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("IODevice::configureAnalogIn(): Vpin %d not found!"), (int)vpin);
|
DIAG(F("IODevice::configureAnalogIn(): VPIN %u not found!"), (int)vpin);
|
||||||
#endif
|
#endif
|
||||||
return -1023;
|
return -1023;
|
||||||
}
|
}
|
||||||
|
@ -227,7 +243,7 @@ void IODevice::write(VPIN vpin, int value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("IODevice::write(): Vpin ID %d not found!"), (int)vpin);
|
DIAG(F("IODevice::write(): VPIN %u not found!"), (int)vpin);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,7 +262,7 @@ void IODevice::writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t para
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("IODevice::writeAnalogue(): Vpin ID %d not found!"), (int)vpin);
|
DIAG(F("IODevice::writeAnalogue(): VPIN %u not found!"), (int)vpin);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,9 +330,11 @@ IODevice *IODevice::findDeviceFollowing(VPIN vpin) {
|
||||||
|
|
||||||
// Private helper function to check for vpin overlap. Run during setup only.
|
// Private helper function to check for vpin overlap. Run during setup only.
|
||||||
// returns true if pins DONT overlap with existing device
|
// returns true if pins DONT overlap with existing device
|
||||||
|
// TODO: Move the I2C address reservation and checks into the I2CManager code.
|
||||||
|
// That will enable non-HAL devices to reserve I2C addresses too.
|
||||||
bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddress) {
|
bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddress) {
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("Check no overlap %d %d %s"), firstPin,nPins,i2cAddress.toString());
|
DIAG(F("Check no overlap %u %u %s"), firstPin,nPins,i2cAddress.toString());
|
||||||
#endif
|
#endif
|
||||||
VPIN lastPin=firstPin+nPins-1;
|
VPIN lastPin=firstPin+nPins-1;
|
||||||
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
|
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
|
||||||
|
@ -327,7 +345,7 @@ bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddres
|
||||||
VPIN lastDevPin=firstDevPin+dev->_nPins-1;
|
VPIN lastDevPin=firstDevPin+dev->_nPins-1;
|
||||||
bool noOverlap= firstPin>lastDevPin || lastPin<firstDevPin;
|
bool noOverlap= firstPin>lastDevPin || lastPin<firstDevPin;
|
||||||
if (!noOverlap) {
|
if (!noOverlap) {
|
||||||
DIAG(F("WARNING HAL Overlap definition of pins %d to %d ignored."),
|
DIAG(F("WARNING HAL Overlap, redefinition of Vpins %u to %u ignored."),
|
||||||
firstPin, lastPin);
|
firstPin, lastPin);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -374,7 +392,7 @@ void IODevice::begin() { DIAG(F("NO HAL CONFIGURED!")); }
|
||||||
bool IODevice::configure(VPIN pin, ConfigTypeEnum configType, int nParams, int p[]) {
|
bool IODevice::configure(VPIN pin, ConfigTypeEnum configType, int nParams, int p[]) {
|
||||||
if (configType!=CONFIGURE_INPUT || nParams!=1 || pin >= NUM_DIGITAL_PINS) return false;
|
if (configType!=CONFIGURE_INPUT || nParams!=1 || pin >= NUM_DIGITAL_PINS) return false;
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("Arduino _configurePullup Pin:%d Val:%d"), pin, p[0]);
|
DIAG(F("Arduino _configurePullup pin:%d Val:%d"), pin, p[0]);
|
||||||
#endif
|
#endif
|
||||||
pinMode(pin, p[0] ? INPUT_PULLUP : INPUT);
|
pinMode(pin, p[0] ? INPUT_PULLUP : INPUT);
|
||||||
return true;
|
return true;
|
||||||
|
@ -528,7 +546,7 @@ int ArduinoPins::_configureAnalogIn(VPIN vpin) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArduinoPins::_display() {
|
void ArduinoPins::_display() {
|
||||||
DIAG(F("Arduino Vpins:%d-%d"), (int)_firstVpin, (int)_firstVpin+_nPins-1);
|
DIAG(F("Arduino Vpins:%u-%u"), (int)_firstVpin, (int)_firstVpin+_nPins-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
69
IODevice.h
69
IODevice.h
|
@ -467,6 +467,75 @@ protected:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// This HAL device driver is intended for communication in automation
|
||||||
|
// sequences. A VPIN can be SET or RESET within a sequence, and its
|
||||||
|
// current state checked elsewhere using IF, IFNOT, AT etc. or monitored
|
||||||
|
// from JMRI using a Sensor object (DCC-EX <S ...> command).
|
||||||
|
// Alternatively, the flag can be set from JMRI and other interfaces
|
||||||
|
// using the <Z ...> command, to enable or disable actions within a sequence.
|
||||||
|
//
|
||||||
|
// Example of configuration in halSetup.h:
|
||||||
|
//
|
||||||
|
// FLAGS::create(32000, 128);
|
||||||
|
//
|
||||||
|
// or in myAutomation.h:
|
||||||
|
//
|
||||||
|
// HAL(FLAGS, 32000, 128);
|
||||||
|
//
|
||||||
|
// Both create 128 flags numbered with VPINs 32000-32127.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
class FLAGS : IODevice {
|
||||||
|
private:
|
||||||
|
uint8_t *_states = NULL;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static void create(VPIN firstVpin, unsigned int nPins) {
|
||||||
|
if (checkNoOverlap(firstVpin, nPins))
|
||||||
|
new FLAGS(firstVpin, nPins);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Constructor performs static initialisation of the device object
|
||||||
|
FLAGS (VPIN firstVpin, int nPins) {
|
||||||
|
_firstVpin = firstVpin;
|
||||||
|
_nPins = nPins;
|
||||||
|
_states = (uint8_t *)calloc(1, (_nPins+7)/8);
|
||||||
|
if (!_states) {
|
||||||
|
DIAG(F("FLAGS: ERROR Memory Allocation Failure"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addDevice(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _read(VPIN vpin) override {
|
||||||
|
int pin = vpin - _firstVpin;
|
||||||
|
if (pin >= _nPins || pin < 0) return 0;
|
||||||
|
uint8_t mask = 1 << (pin & 7);
|
||||||
|
return (_states[pin>>3] & mask) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _write(VPIN vpin, int value) override {
|
||||||
|
int pin = vpin - _firstVpin;
|
||||||
|
if (pin >= _nPins || pin < 0) return;
|
||||||
|
uint8_t mask = 1 << (pin & 7);
|
||||||
|
if (value)
|
||||||
|
_states[pin>>3] |= mask;
|
||||||
|
else
|
||||||
|
_states[pin>>3] &= ~mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _display() override {
|
||||||
|
DIAG(F("FLAGS configured on VPINs %u-%u"),
|
||||||
|
_firstVpin, _firstVpin+_nPins-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
#include "IO_MCP23008.h"
|
#include "IO_MCP23008.h"
|
||||||
#include "IO_MCP23017.h"
|
#include "IO_MCP23017.h"
|
||||||
#include "IO_PCF8574.h"
|
#include "IO_PCF8574.h"
|
||||||
|
|
|
@ -119,7 +119,7 @@ private:
|
||||||
case STATE_GETVALUE:
|
case STATE_GETVALUE:
|
||||||
_value[_currentPin] = ((uint16_t)_inBuffer[0] << 8) + (uint16_t)_inBuffer[1];
|
_value[_currentPin] = ((uint16_t)_inBuffer[0] << 8) + (uint16_t)_inBuffer[1];
|
||||||
#ifdef IO_ANALOGUE_SLOW
|
#ifdef IO_ANALOGUE_SLOW
|
||||||
DIAG(F("ADS111x pin:%d value:%d"), _currentPin, _value[_currentPin]);
|
DIAG(F("ADS111x VPIN:%u value:%d"), _currentPin, _value[_currentPin]);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Move to next pin
|
// Move to next pin
|
||||||
|
@ -142,7 +142,7 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
void _display() override {
|
void _display() override {
|
||||||
DIAG(F("ADS111x I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1,
|
DIAG(F("ADS111x I2C:%s Configured on Vpins:%u-%u %S"), _I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1,
|
||||||
_deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F(""));
|
_deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ void DCCAccessoryDecoder::_write(VPIN id, int state) {
|
||||||
|
|
||||||
void DCCAccessoryDecoder::_display() {
|
void DCCAccessoryDecoder::_display() {
|
||||||
int endAddress = _packedAddress + _nPins - 1;
|
int endAddress = _packedAddress + _nPins - 1;
|
||||||
DIAG(F("DCCAccessoryDecoder Configured on Vpins:%d-%d Addresses %d/%d-%d/%d)"), _firstVpin, _firstVpin+_nPins-1,
|
DIAG(F("DCCAccessoryDecoder Configured on Vpins:%u-%u Addresses %d/%d-%d/%d)"), _firstVpin, _firstVpin+_nPins-1,
|
||||||
ADDRESS(_packedAddress), SUBADDRESS(_packedAddress), ADDRESS(endAddress), SUBADDRESS(endAddress));
|
ADDRESS(_packedAddress), SUBADDRESS(_packedAddress), ADDRESS(endAddress), SUBADDRESS(endAddress));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
210
IO_DFPlayer.h
210
IO_DFPlayer.h
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* © 2022, Neil McKechnie. All rights reserved.
|
* © 2023, Neil McKechnie. All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of DCC++EX API
|
* This file is part of DCC++EX API
|
||||||
*
|
*
|
||||||
|
@ -33,10 +33,13 @@
|
||||||
* and Serialn is the name of the Serial port connected to the DFPlayer (e.g. Serial1).
|
* and Serialn is the name of the Serial port connected to the DFPlayer (e.g. Serial1).
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* In mySetup function within mySetup.cpp:
|
* In halSetup function within myHal.cpp:
|
||||||
* DFPlayer::create(3500, 5, Serial1);
|
* DFPlayer::create(3500, 5, Serial1);
|
||||||
|
* or in myAutomation.h:
|
||||||
|
* HAL(DFPlayer, 3500, 5, Serial1)
|
||||||
*
|
*
|
||||||
* Writing an analogue value 1-2999 to the first pin (3500) will play the numbered file from the SD card;
|
* Writing an analogue value 1-2999 to the first pin (3500) will play the numbered file from the
|
||||||
|
* SD card; e.g. a value of 1 will play the first file, 2 for the second file etc.
|
||||||
* Writing an analogue value 0 to the first pin (3500) will stop the file playing;
|
* 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 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.
|
* Writing a digital value of 1 to a pin will play the file corresponding to that pin, e.g.
|
||||||
|
@ -61,6 +64,10 @@
|
||||||
* card (as listed by the DIR command in Windows). This may not match the order of the files
|
* 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
|
* 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.
|
* files be copied into an empty SDcard in the desired order, one at a time.
|
||||||
|
*
|
||||||
|
* The driver now polls the device for its current status every second. Should the device
|
||||||
|
* fail to respond it will be marked off-line and its busy indicator cleared, to avoid
|
||||||
|
* lock-ups in automation scripts that are executing for a WAITFOR().
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef IO_DFPlayer_h
|
#ifndef IO_DFPlayer_h
|
||||||
|
@ -74,21 +81,13 @@ private:
|
||||||
HardwareSerial *_serial;
|
HardwareSerial *_serial;
|
||||||
bool _playing = false;
|
bool _playing = false;
|
||||||
uint8_t _inputIndex = 0;
|
uint8_t _inputIndex = 0;
|
||||||
unsigned long _commandSendTime; // Allows timeout processing
|
unsigned long _commandSendTime; // Time (us) that last transmit took place.
|
||||||
uint8_t _lastVolumeLevel = MAXVOLUME;
|
unsigned long _timeoutTime;
|
||||||
|
uint8_t _recvCMD; // Last received command code byte
|
||||||
// When two commands are sent in quick succession, the device sometimes
|
bool _awaitingResponse = false;
|
||||||
// fails to execute one. A delay is required between successive commands.
|
uint8_t _requestedVolumeLevel = MAXVOLUME;
|
||||||
// This could be implemented by buffering commands and outputting them
|
uint8_t _currentVolume = MAXVOLUME;
|
||||||
// from the loop() function, but it would somewhat complicate the
|
int _requestedSong = -1; // -1=none, 0=stop, >0=file number
|
||||||
// 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:
|
public:
|
||||||
|
|
||||||
|
@ -113,30 +112,64 @@ protected:
|
||||||
|
|
||||||
// Send a query to the device to see if it responds
|
// Send a query to the device to see if it responds
|
||||||
sendPacket(0x42);
|
sendPacket(0x42);
|
||||||
_commandSendTime = micros();
|
_timeoutTime = micros() + 5000000UL; // 5 second timeout
|
||||||
|
_awaitingResponse = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _loop(unsigned long currentMicros) override {
|
void _loop(unsigned long currentMicros) override {
|
||||||
// Check for incoming data on _serial, and update busy flag accordingly.
|
|
||||||
|
// Read responses from device
|
||||||
|
processIncoming();
|
||||||
|
|
||||||
|
// Check if a command sent to device has timed out. Allow 0.5 second for response
|
||||||
|
if (_awaitingResponse && (int32_t)(currentMicros - _timeoutTime) > 0) {
|
||||||
|
DIAG(F("DFPlayer device not responding on serial port"));
|
||||||
|
_deviceState = DEVSTATE_FAILED;
|
||||||
|
_awaitingResponse = false;
|
||||||
|
_playing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send any commands that need to go.
|
||||||
|
processOutgoing(currentMicros);
|
||||||
|
|
||||||
|
delayUntil(currentMicros + 10000); // Only enter every 10ms
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for incoming data on _serial, and update busy flag and other state accordingly
|
||||||
|
void processIncoming() {
|
||||||
// Expected message is in the form "7E 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"
|
||||||
|
bool ok = false;
|
||||||
while (_serial->available()) {
|
while (_serial->available()) {
|
||||||
int c = _serial->read();
|
int c = _serial->read();
|
||||||
if (c == 0x7E && _inputIndex == 0)
|
switch (_inputIndex) {
|
||||||
_inputIndex = 1;
|
case 0:
|
||||||
else if ((c==0xFF && _inputIndex==1)
|
if (c == 0x7E) ok = true;
|
||||||
|| (c==0x3D && _inputIndex==3)
|
break;
|
||||||
|| (_inputIndex >=4 && _inputIndex <= 8))
|
case 1:
|
||||||
_inputIndex++;
|
if (c == 0xFF) ok = true;
|
||||||
else if (c==0x06 && _inputIndex==2) {
|
break;
|
||||||
// Valid message prefix, so consider the device online
|
case 2:
|
||||||
|
if (c== 0x06) ok = true;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
_recvCMD = c; // CMD byte
|
||||||
|
ok = true;
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
switch (_recvCMD) {
|
||||||
|
case 0x42:
|
||||||
|
// Response to status query
|
||||||
|
_playing = (c != 0);
|
||||||
|
// Mark the device online and cancel timeout
|
||||||
if (_deviceState==DEVSTATE_INITIALISING) {
|
if (_deviceState==DEVSTATE_INITIALISING) {
|
||||||
_deviceState = DEVSTATE_NORMAL;
|
_deviceState = DEVSTATE_NORMAL;
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
_display();
|
_display();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
_inputIndex++;
|
_awaitingResponse = false;
|
||||||
} else if (c==0xEF && _inputIndex==9) {
|
break;
|
||||||
|
case 0x3d:
|
||||||
// End of play
|
// End of play
|
||||||
if (_playing) {
|
if (_playing) {
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
|
@ -144,35 +177,86 @@ protected:
|
||||||
#endif
|
#endif
|
||||||
_playing = false;
|
_playing = false;
|
||||||
}
|
}
|
||||||
_inputIndex = 0;
|
break;
|
||||||
} else
|
case 0x40:
|
||||||
_inputIndex = 0; // Unrecognised character sequence, start again!
|
// Error code
|
||||||
|
DIAG(F("DFPlayer: Error %d returned from device"), c);
|
||||||
|
_playing = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ok = true;
|
||||||
|
break;
|
||||||
|
case 4: case 5: case 7: case 8:
|
||||||
|
ok = true; // Skip over these bytes in message.
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
if (c==0xef) {
|
||||||
|
// Message finished
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ok)
|
||||||
|
_inputIndex++; // character as expected, so increment index
|
||||||
|
else
|
||||||
|
_inputIndex = 0; // otherwise reset.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send any commands that need to be sent
|
||||||
|
void processOutgoing(unsigned long currentMicros) {
|
||||||
|
|
||||||
|
// When two commands are sent in quick succession, the device will often fail to
|
||||||
|
// execute one. Testing has indicated that a delay of 100ms or more is required
|
||||||
|
// between successive commands to get reliable operation.
|
||||||
|
// If 100ms has elapsed since the last thing sent, then check if there's some output to do.
|
||||||
|
if (((int32_t)currentMicros - _commandSendTime) > 100000) {
|
||||||
|
if (_currentVolume > _requestedVolumeLevel) {
|
||||||
|
// Change volume before changing song if volume is reducing.
|
||||||
|
_currentVolume = _requestedVolumeLevel;
|
||||||
|
sendPacket(0x06, _currentVolume);
|
||||||
|
} else if (_requestedSong > 0) {
|
||||||
|
// Change song
|
||||||
|
sendPacket(0x03, _requestedSong);
|
||||||
|
_requestedSong = -1;
|
||||||
|
} else if (_requestedSong == 0) {
|
||||||
|
sendPacket(0x16); // Stop playing
|
||||||
|
_requestedSong = -1;
|
||||||
|
} else if (_currentVolume < _requestedVolumeLevel) {
|
||||||
|
// Change volume after changing song if volume is increasing.
|
||||||
|
_currentVolume = _requestedVolumeLevel;
|
||||||
|
sendPacket(0x06, _currentVolume);
|
||||||
|
} else if ((int32_t)currentMicros - _commandSendTime > 1000000) {
|
||||||
|
// Poll device every second that other commands aren't being sent,
|
||||||
|
// to check if it's still connected and responding.
|
||||||
|
sendPacket(0x42);
|
||||||
|
if (!_awaitingResponse) {
|
||||||
|
_timeoutTime = currentMicros + 5000000UL; // Timeout if no response within 5 seconds
|
||||||
|
_awaitingResponse = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
delayUntil(currentMicros + 10000); // Only enter every 10ms
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write with value 1 starts playing a song. The relative pin number is the file number.
|
// Write with value 1 starts playing a song. The relative pin number is the file number.
|
||||||
// Write with value 0 stops playing.
|
// Write with value 0 stops playing.
|
||||||
void _write(VPIN vpin, int value) override {
|
void _write(VPIN vpin, int value) override {
|
||||||
|
if (_deviceState == DEVSTATE_FAILED) return;
|
||||||
int pin = vpin - _firstVpin;
|
int pin = vpin - _firstVpin;
|
||||||
if (value) {
|
if (value) {
|
||||||
// Value 1, start playing
|
// Value 1, start playing
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("DFPlayer: Play %d"), pin+1);
|
DIAG(F("DFPlayer: Play %d"), pin+1);
|
||||||
#endif
|
#endif
|
||||||
sendPacket(0x03, pin+1);
|
_requestedSong = pin+1;
|
||||||
_playing = true;
|
_playing = true;
|
||||||
} else {
|
} else {
|
||||||
// Value 0, stop playing
|
// Value 0, stop playing
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("DFPlayer: Stop"));
|
DIAG(F("DFPlayer: Stop"));
|
||||||
#endif
|
#endif
|
||||||
sendPacket(0x16);
|
_requestedSong = 0; // No song
|
||||||
_playing = false;
|
_playing = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,16 +265,13 @@ protected:
|
||||||
// Volume may be specified as second parameter to writeAnalogue.
|
// Volume may be specified as second parameter to writeAnalogue.
|
||||||
// If value is zero, the player stops playing.
|
// If value is zero, the player stops playing.
|
||||||
// WriteAnalogue on second pin sets the output volume.
|
// WriteAnalogue on second pin sets the output volume.
|
||||||
// If starting a new file and setting volume, then avoid a short burst of loud noise by
|
|
||||||
// the following strategy:
|
|
||||||
// - If the volume is increasing, start playing the song before setting the volume,
|
|
||||||
// - If the volume is decreasing, decrease it and then start playing.
|
|
||||||
//
|
//
|
||||||
void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t=0) override {
|
void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t=0) override {
|
||||||
|
if (_deviceState == DEVSTATE_FAILED) return;
|
||||||
uint8_t pin = vpin - _firstVpin;
|
uint8_t pin = vpin - _firstVpin;
|
||||||
|
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("DFPlayer: VPIN:%d FileNo:%d Volume:%d"), vpin, value, volume);
|
DIAG(F("DFPlayer: VPIN:%u FileNo:%d Volume:%d"), vpin, value, volume);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Validate parameter.
|
// Validate parameter.
|
||||||
|
@ -199,37 +280,28 @@ protected:
|
||||||
if (pin == 0) {
|
if (pin == 0) {
|
||||||
// Play track
|
// Play track
|
||||||
if (value > 0) {
|
if (value > 0) {
|
||||||
if (volume != 0) {
|
if (volume > 0)
|
||||||
if (volume <= _lastVolumeLevel)
|
_requestedVolumeLevel = volume;
|
||||||
sendPacket(0x06, volume); // Set volume before starting
|
_requestedSong = value;
|
||||||
sendPacket(0x03, value); // Play track
|
|
||||||
_playing = true;
|
_playing = true;
|
||||||
if (volume > _lastVolumeLevel)
|
|
||||||
sendPacket(0x06, volume); // Set volume after starting
|
|
||||||
_lastVolumeLevel = volume;
|
|
||||||
} else {
|
} else {
|
||||||
// Volume not changed, just play
|
_requestedSong = 0; // stop playing
|
||||||
sendPacket(0x03, value);
|
|
||||||
_playing = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendPacket(0x16); // Stop play
|
|
||||||
_playing = false;
|
_playing = false;
|
||||||
}
|
}
|
||||||
} else if (pin == 1) {
|
} else if (pin == 1) {
|
||||||
// Set volume (0-30)
|
// Set volume (0-30)
|
||||||
sendPacket(0x06, value);
|
_requestedVolumeLevel = value;
|
||||||
_lastVolumeLevel = volume;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A read on any pin indicates whether the player is still playing.
|
// A read on any pin indicates whether the player is still playing.
|
||||||
int _read(VPIN) override {
|
int _read(VPIN) override {
|
||||||
|
if (_deviceState == DEVSTATE_FAILED) return false;
|
||||||
return _playing;
|
return _playing;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _display() override {
|
void _display() override {
|
||||||
DIAG(F("DFPlayer Configured on Vpins:%d-%d %S"), _firstVpin, _firstVpin+_nPins-1,
|
DIAG(F("DFPlayer Configured on Vpins:%u-%u %S"), _firstVpin, _firstVpin+_nPins-1,
|
||||||
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,7 +318,6 @@ private:
|
||||||
|
|
||||||
void sendPacket(uint8_t command, uint16_t arg = 0)
|
void sendPacket(uint8_t command, uint16_t arg = 0)
|
||||||
{
|
{
|
||||||
unsigned long currentMillis = millis();
|
|
||||||
uint8_t out[] = { 0x7E,
|
uint8_t out[] = { 0x7E,
|
||||||
0xFF,
|
0xFF,
|
||||||
06,
|
06,
|
||||||
|
@ -260,19 +331,10 @@ private:
|
||||||
|
|
||||||
setChecksum(out);
|
setChecksum(out);
|
||||||
|
|
||||||
// Check how long since the last command was sent.
|
// Output the command
|
||||||
// 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((uint8_t)0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now output the command
|
|
||||||
_serial->write(out, sizeof(out));
|
_serial->write(out, sizeof(out));
|
||||||
_commandSendTime = currentMillis;
|
|
||||||
|
_commandSendTime = micros();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t calcChecksum(uint8_t* packet)
|
uint16_t calcChecksum(uint8_t* packet)
|
||||||
|
|
|
@ -34,11 +34,16 @@
|
||||||
* device in use. There is no way for the device driver to sanity check pins are used for the
|
* device in use. There is no way for the device driver to sanity check pins are used for the
|
||||||
* correct purpose, however the EX-IOExpander device's pin map will prevent pins being used
|
* correct purpose, however the EX-IOExpander device's pin map will prevent pins being used
|
||||||
* incorrectly (eg. A6/7 on Nano cannot be used for digital input/output).
|
* incorrectly (eg. A6/7 on Nano cannot be used for digital input/output).
|
||||||
|
*
|
||||||
|
* The total number of pins cannot exceed 256 because of the communications packet format.
|
||||||
|
* The number of analogue inputs cannot exceed 16 because of a limit on the maximum
|
||||||
|
* I2C packet size of 32 bytes (in the Wire library).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef IO_EX_IOEXPANDER_H
|
#ifndef IO_EX_IOEXPANDER_H
|
||||||
#define IO_EX_IOEXPANDER_H
|
#define IO_EX_IOEXPANDER_H
|
||||||
|
|
||||||
|
#include "IODevice.h"
|
||||||
#include "I2CManager.h"
|
#include "I2CManager.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
#include "FSH.h"
|
#include "FSH.h"
|
||||||
|
@ -68,113 +73,206 @@ private:
|
||||||
// Constructor
|
// Constructor
|
||||||
EXIOExpander(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
|
EXIOExpander(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
|
||||||
_firstVpin = firstVpin;
|
_firstVpin = firstVpin;
|
||||||
|
// Number of pins cannot exceed 256 (1 byte) because of I2C message structure.
|
||||||
|
if (nPins > 256) nPins = 256;
|
||||||
_nPins = nPins;
|
_nPins = nPins;
|
||||||
_i2cAddress = i2cAddress;
|
_I2CAddress = i2cAddress;
|
||||||
addDevice(this);
|
addDevice(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _begin() {
|
void _begin() {
|
||||||
|
uint8_t status;
|
||||||
// Initialise EX-IOExander device
|
// Initialise EX-IOExander device
|
||||||
I2CManager.begin();
|
I2CManager.begin();
|
||||||
if (I2CManager.exists(_i2cAddress)) {
|
if (I2CManager.exists(_I2CAddress)) {
|
||||||
_command4Buffer[0] = EXIOINIT;
|
|
||||||
_command4Buffer[1] = _nPins;
|
|
||||||
_command4Buffer[2] = _firstVpin & 0xFF;
|
|
||||||
_command4Buffer[3] = _firstVpin >> 8;
|
|
||||||
// Send config, if EXIOPINS returned, we're good, setup pin buffers, otherwise go offline
|
// Send config, if EXIOPINS returned, we're good, setup pin buffers, otherwise go offline
|
||||||
I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command4Buffer, 4);
|
// NB The I2C calls here are done as blocking calls, as they're not time-critical
|
||||||
if (_receive3Buffer[0] == EXIOPINS) {
|
// during initialisation and the reads require waiting for a response anyway.
|
||||||
_numDigitalPins = _receive3Buffer[1];
|
// Hence we can allocate I/O buffers from the stack.
|
||||||
_numAnaloguePins = _receive3Buffer[2];
|
uint8_t receiveBuffer[3];
|
||||||
_digitalPinBytes = (_numDigitalPins + 7)/8;
|
uint8_t commandBuffer[4] = {EXIOINIT, (uint8_t)_nPins, (uint8_t)(_firstVpin & 0xFF), (uint8_t)(_firstVpin >> 8)};
|
||||||
|
status = I2CManager.read(_I2CAddress, receiveBuffer, sizeof(receiveBuffer), commandBuffer, sizeof(commandBuffer));
|
||||||
|
if (status == I2C_STATUS_OK) {
|
||||||
|
if (receiveBuffer[0] == EXIOPINS) {
|
||||||
|
_numDigitalPins = receiveBuffer[1];
|
||||||
|
_numAnaloguePins = receiveBuffer[2];
|
||||||
|
|
||||||
|
// See if we already have suitable buffers assigned
|
||||||
|
size_t digitalBytesNeeded = (_numDigitalPins + 7) / 8;
|
||||||
|
if (_digitalPinBytes < digitalBytesNeeded) {
|
||||||
|
// Not enough space, free any existing buffer and allocate a new one
|
||||||
|
if (_digitalPinBytes > 0) free(_digitalInputStates);
|
||||||
_digitalInputStates = (byte*) calloc(_digitalPinBytes, 1);
|
_digitalInputStates = (byte*) calloc(_digitalPinBytes, 1);
|
||||||
_analoguePinBytes = _numAnaloguePins * 2;
|
_digitalPinBytes = digitalBytesNeeded;
|
||||||
_analogueInputStates = (byte*) calloc(_analoguePinBytes, 1);
|
}
|
||||||
|
size_t analogueBytesNeeded = _numAnaloguePins * 2;
|
||||||
|
if (_analoguePinBytes < analogueBytesNeeded) {
|
||||||
|
// Free any existing buffers and allocate new ones.
|
||||||
|
if (_analoguePinBytes > 0) {
|
||||||
|
free(_analogueInputBuffer);
|
||||||
|
free(_analogueInputStates);
|
||||||
|
free(_analoguePinMap);
|
||||||
|
}
|
||||||
|
_analogueInputStates = (uint8_t*) calloc(analogueBytesNeeded, 1);
|
||||||
|
_analogueInputBuffer = (uint8_t*) calloc(analogueBytesNeeded, 1);
|
||||||
_analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1);
|
_analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1);
|
||||||
|
_analoguePinBytes = analogueBytesNeeded;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
DIAG(F("ERROR configuring EX-IOExpander device, I2C:%s"), _i2cAddress.toString());
|
DIAG(F("EX-IOExpander I2C:%s ERROR configuring device"), _I2CAddress.toString());
|
||||||
_deviceState = DEVSTATE_FAILED;
|
_deviceState = DEVSTATE_FAILED;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// We now need to retrieve the analogue pin map
|
// We now need to retrieve the analogue pin map
|
||||||
_command1Buffer[0] = EXIOINITA;
|
if (status == I2C_STATUS_OK) {
|
||||||
I2CManager.read(_i2cAddress, _analoguePinMap, _numAnaloguePins, _command1Buffer, 1);
|
commandBuffer[0] = EXIOINITA;
|
||||||
|
status = I2CManager.read(_I2CAddress, _analoguePinMap, _numAnaloguePins, commandBuffer, 1);
|
||||||
|
}
|
||||||
|
if (status == I2C_STATUS_OK) {
|
||||||
// Attempt to get version, if we don't get it, we don't care, don't go offline
|
// Attempt to get version, if we don't get it, we don't care, don't go offline
|
||||||
_command1Buffer[0] = EXIOVER;
|
uint8_t versionBuffer[3];
|
||||||
I2CManager.read(_i2cAddress, _versionBuffer, 3, _command1Buffer, 1);
|
commandBuffer[0] = EXIOVER;
|
||||||
_majorVer = _versionBuffer[0];
|
if (I2CManager.read(_I2CAddress, versionBuffer, sizeof(versionBuffer), commandBuffer, 1) == I2C_STATUS_OK) {
|
||||||
_minorVer = _versionBuffer[1];
|
_majorVer = versionBuffer[0];
|
||||||
_patchVer = _versionBuffer[2];
|
_minorVer = versionBuffer[1];
|
||||||
|
_patchVer = versionBuffer[2];
|
||||||
|
}
|
||||||
DIAG(F("EX-IOExpander device found, I2C:%s, Version v%d.%d.%d"),
|
DIAG(F("EX-IOExpander device found, I2C:%s, Version v%d.%d.%d"),
|
||||||
_i2cAddress.toString(), _versionBuffer[0], _versionBuffer[1], _versionBuffer[2]);
|
_I2CAddress.toString(), _majorVer, _minorVer, _patchVer);
|
||||||
|
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
_display();
|
_display();
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
if (status != I2C_STATUS_OK)
|
||||||
|
reportError(status);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
DIAG(F("EX-IOExpander device not found, I2C:%s"), _i2cAddress.toString());
|
DIAG(F("EX-IOExpander I2C:%s device not found"), _I2CAddress.toString());
|
||||||
_deviceState = DEVSTATE_FAILED;
|
_deviceState = DEVSTATE_FAILED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use
|
// Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if requested.
|
||||||
|
// Configuration isn't done frequently so we can use blocking I2C calls here, and so buffers can
|
||||||
|
// be allocated from the stack to reduce RAM allocation.
|
||||||
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override {
|
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override {
|
||||||
if (paramCount != 1) return false;
|
if (paramCount != 1) return false;
|
||||||
int pin = vpin - _firstVpin;
|
int pin = vpin - _firstVpin;
|
||||||
if (configType == CONFIGURE_INPUT) {
|
if (configType == CONFIGURE_INPUT) {
|
||||||
bool pullup = params[0];
|
uint8_t pullup = params[0];
|
||||||
_digitalOutBuffer[0] = EXIODPUP;
|
uint8_t outBuffer[] = {EXIODPUP, (uint8_t)pin, pullup};
|
||||||
_digitalOutBuffer[1] = pin;
|
uint8_t responseBuffer[1];
|
||||||
_digitalOutBuffer[2] = pullup;
|
uint8_t status = I2CManager.read(_I2CAddress, responseBuffer, sizeof(responseBuffer),
|
||||||
I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3);
|
outBuffer, sizeof(outBuffer));
|
||||||
if (_command1Buffer[0] == EXIORDY) {
|
if (status == I2C_STATUS_OK) {
|
||||||
|
if (responseBuffer[0] == EXIORDY) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
DIAG(F("Vpin %d cannot be used as a digital input pin"), (int)vpin);
|
DIAG(F("EXIOVpin %u cannot be used as a digital input pin"), (int)vpin);
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
reportError(status);
|
||||||
|
} else if (configType == CONFIGURE_ANALOGINPUT) {
|
||||||
|
// TODO: Consider moving code from _configureAnalogIn() to here and remove _configureAnalogIn
|
||||||
|
// from IODevice class definition. Not urgent, but each virtual function defined
|
||||||
|
// means increasing the RAM requirement of every HAL device driver, whether it's relevant
|
||||||
|
// to the driver or not.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Analogue input pin configuration, used to enable on EX-IOExpander device
|
// Analogue input pin configuration, used to enable an EX-IOExpander device.
|
||||||
|
// Use I2C blocking calls and allocate buffers from stack to save RAM.
|
||||||
int _configureAnalogIn(VPIN vpin) override {
|
int _configureAnalogIn(VPIN vpin) override {
|
||||||
int pin = vpin - _firstVpin;
|
int pin = vpin - _firstVpin;
|
||||||
_command2Buffer[0] = EXIOENAN;
|
uint8_t commandBuffer[] = {EXIOENAN, (uint8_t)pin};
|
||||||
_command2Buffer[1] = pin;
|
uint8_t responseBuffer[1];
|
||||||
I2CManager.read(_i2cAddress, _command1Buffer, 1, _command2Buffer, 2);
|
uint8_t status = I2CManager.read(_I2CAddress, responseBuffer, sizeof(responseBuffer),
|
||||||
if (_command1Buffer[0] == EXIORDY) {
|
commandBuffer, sizeof(commandBuffer));
|
||||||
|
if (status == I2C_STATUS_OK) {
|
||||||
|
if (responseBuffer[0] == EXIORDY) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
DIAG(F("Vpin %d cannot be used as an analogue input pin"), (int)vpin);
|
DIAG(F("EX-IOExpander: Vpin %u cannot be used as an analogue input pin"), (int)vpin);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
} else
|
||||||
|
reportError(status);
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads)
|
// Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads)
|
||||||
void _loop(unsigned long currentMicros) override {
|
void _loop(unsigned long currentMicros) override {
|
||||||
(void)currentMicros; // remove warning
|
if (_deviceState == DEVSTATE_FAILED) return; // If device failed, return
|
||||||
if (_deviceState == DEVSTATE_FAILED) return;
|
|
||||||
_command1Buffer[0] = EXIORDD;
|
// Request block is used for analogue and digital reads from the IOExpander, which are performed
|
||||||
I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1);
|
// on a cyclic basis. Writes are performed synchronously as and when requested.
|
||||||
_command1Buffer[0] = EXIORDAN;
|
|
||||||
I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1);
|
if (_readState != RDS_IDLE) {
|
||||||
|
if (_i2crb.isBusy()) return; // If I2C operation still in progress, return
|
||||||
|
|
||||||
|
uint8_t status = _i2crb.status;
|
||||||
|
if (status == I2C_STATUS_OK) { // If device request ok, read input data
|
||||||
|
|
||||||
|
// First check if we need to process received data
|
||||||
|
if (_readState == RDS_ANALOGUE) {
|
||||||
|
// Read of analogue values was in progress, so process received values
|
||||||
|
// Here we need to copy the values from input buffer to the analogue value array. We need to
|
||||||
|
// do this to avoid tearing of the values (i.e. one byte of a two-byte value being changed
|
||||||
|
// while the value is being read).
|
||||||
|
memcpy(_analogueInputStates, _analogueInputBuffer, _analoguePinBytes); // Copy I2C input buffer to states
|
||||||
|
|
||||||
|
} else if (_readState == RDS_DIGITAL) {
|
||||||
|
// Read of digital states was in progress, so process received values
|
||||||
|
// The received digital states are placed directly into the digital buffer on receipt,
|
||||||
|
// so don't need any further processing at this point (unless we want to check for
|
||||||
|
// changes and notify them to subscribers, to avoid the need for polling - see IO_GPIOBase.h).
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
reportError(status, false); // report eror but don't go offline.
|
||||||
|
|
||||||
|
_readState = RDS_IDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we're not doing anything now, check to see if a new input transfer is due.
|
||||||
|
if (_readState == RDS_IDLE) {
|
||||||
|
if (currentMicros - _lastDigitalRead > _digitalRefresh) { // Delay for digital read refresh
|
||||||
|
// Issue new read request for digital states. As the request is non-blocking, the buffer has to
|
||||||
|
// be allocated from heap (object state).
|
||||||
|
_readCommandBuffer[0] = EXIORDD;
|
||||||
|
I2CManager.read(_I2CAddress, _digitalInputStates, (_numDigitalPins+7)/8, _readCommandBuffer, 1, &_i2crb);
|
||||||
|
// non-blocking read
|
||||||
|
_lastDigitalRead = currentMicros;
|
||||||
|
_readState = RDS_DIGITAL;
|
||||||
|
} else if (currentMicros - _lastAnalogueRead > _analogueRefresh) { // Delay for analogue read refresh
|
||||||
|
// Issue new read for analogue input states
|
||||||
|
_readCommandBuffer[0] = EXIORDAN;
|
||||||
|
I2CManager.read(_I2CAddress, _analogueInputBuffer,
|
||||||
|
_numAnaloguePins * 2, _readCommandBuffer, 1, &_i2crb);
|
||||||
|
_lastAnalogueRead = currentMicros;
|
||||||
|
_readState = RDS_ANALOGUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtain the correct analogue input value, with reference to the analogue
|
||||||
|
// pin map.
|
||||||
// Obtain the correct analogue input value
|
// Obtain the correct analogue input value
|
||||||
int _readAnalogue(VPIN vpin) override {
|
int _readAnalogue(VPIN vpin) override {
|
||||||
if (_deviceState == DEVSTATE_FAILED) return 0;
|
if (_deviceState == DEVSTATE_FAILED) return 0;
|
||||||
int pin = vpin - _firstVpin;
|
int pin = vpin - _firstVpin;
|
||||||
uint8_t _pinLSBByte;
|
|
||||||
for (uint8_t aPin = 0; aPin < _numAnaloguePins; aPin++) {
|
for (uint8_t aPin = 0; aPin < _numAnaloguePins; aPin++) {
|
||||||
if (_analoguePinMap[aPin] == pin) {
|
if (_analoguePinMap[aPin] == pin) {
|
||||||
_pinLSBByte = aPin * 2;
|
uint8_t _pinLSBByte = aPin * 2;
|
||||||
}
|
|
||||||
}
|
|
||||||
uint8_t _pinMSBByte = _pinLSBByte + 1;
|
uint8_t _pinMSBByte = _pinLSBByte + 1;
|
||||||
return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte];
|
return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return -1; // pin not found in table
|
||||||
|
}
|
||||||
|
|
||||||
// Obtain the correct digital input value
|
// Obtain the correct digital input value
|
||||||
int _read(VPIN vpin) override {
|
int _read(VPIN vpin) override {
|
||||||
|
@ -185,63 +283,102 @@ private:
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write digital value. We could have an output buffer of states, that is periodically
|
||||||
|
// written to the device if there are any changes; this would reduce the I2C overhead
|
||||||
|
// if lots of output requests are being made. We could also cache the last value
|
||||||
|
// sent so that we don't write the same value over and over to the output.
|
||||||
|
// However, for the time being, we just write the current value (blocking I2C) to the
|
||||||
|
// IOExpander node. As it is a blocking request, we can use buffers allocated from
|
||||||
|
// the stack to save RAM allocation.
|
||||||
void _write(VPIN vpin, int value) override {
|
void _write(VPIN vpin, int value) override {
|
||||||
|
uint8_t digitalOutBuffer[3];
|
||||||
|
uint8_t responseBuffer[1];
|
||||||
if (_deviceState == DEVSTATE_FAILED) return;
|
if (_deviceState == DEVSTATE_FAILED) return;
|
||||||
int pin = vpin - _firstVpin;
|
int pin = vpin - _firstVpin;
|
||||||
_digitalOutBuffer[0] = EXIOWRD;
|
digitalOutBuffer[0] = EXIOWRD;
|
||||||
_digitalOutBuffer[1] = pin;
|
digitalOutBuffer[1] = pin;
|
||||||
_digitalOutBuffer[2] = value;
|
digitalOutBuffer[2] = value;
|
||||||
I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3);
|
uint8_t status = I2CManager.read(_I2CAddress, responseBuffer, 1, digitalOutBuffer, 3);
|
||||||
if (_command1Buffer[0] != EXIORDY) {
|
if (status != I2C_STATUS_OK) {
|
||||||
DIAG(F("Vpin %d cannot be used as a digital output pin"), (int)vpin);
|
reportError(status);
|
||||||
|
} else {
|
||||||
|
if (responseBuffer[0] != EXIORDY) {
|
||||||
|
DIAG(F("Vpin %u cannot be used as a digital output pin"), (int)vpin);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write analogue (integer) value. Write the parameters (blocking I2C) to the
|
||||||
|
// IOExpander node. As it is a blocking request, we can use buffers allocated from
|
||||||
|
// the stack to reduce RAM allocation.
|
||||||
void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override {
|
void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override {
|
||||||
|
uint8_t servoBuffer[7];
|
||||||
|
uint8_t responseBuffer[1];
|
||||||
|
|
||||||
if (_deviceState == DEVSTATE_FAILED) return;
|
if (_deviceState == DEVSTATE_FAILED) return;
|
||||||
int pin = vpin - _firstVpin;
|
int pin = vpin - _firstVpin;
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"),
|
DIAG(F("Servo: WriteAnalogue Vpin:%u Value:%d Profile:%d Duration:%d %S"),
|
||||||
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
|
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
|
||||||
#endif
|
#endif
|
||||||
_servoBuffer[0] = EXIOWRAN;
|
servoBuffer[0] = EXIOWRAN;
|
||||||
_servoBuffer[1] = pin;
|
servoBuffer[1] = pin;
|
||||||
_servoBuffer[2] = value & 0xFF;
|
servoBuffer[2] = value & 0xFF;
|
||||||
_servoBuffer[3] = value >> 8;
|
servoBuffer[3] = value >> 8;
|
||||||
_servoBuffer[4] = profile;
|
servoBuffer[4] = profile;
|
||||||
_servoBuffer[5] = duration & 0xFF;
|
servoBuffer[5] = duration & 0xFF;
|
||||||
_servoBuffer[6] = duration >> 8;
|
servoBuffer[6] = duration >> 8;
|
||||||
I2CManager.read(_i2cAddress, _command1Buffer, 1, _servoBuffer, 7);
|
uint8_t status = I2CManager.read(_I2CAddress, responseBuffer, 1, servoBuffer, 7);
|
||||||
if (_command1Buffer[0] != EXIORDY) {
|
if (status != I2C_STATUS_OK) {
|
||||||
DIAG(F("Vpin %d cannot be used as a servo/PWM pin"), (int)vpin);
|
DIAG(F("EX-IOExpander I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status));
|
||||||
|
_deviceState = DEVSTATE_FAILED;
|
||||||
|
} else {
|
||||||
|
if (responseBuffer[0] != EXIORDY) {
|
||||||
|
DIAG(F("Vpin %u cannot be used as a servo/PWM pin"), (int)vpin);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Display device information and status.
|
||||||
void _display() override {
|
void _display() override {
|
||||||
DIAG(F("EX-IOExpander I2C:%s v%d.%d.%d Vpins %d-%d %S"),
|
DIAG(F("EX-IOExpander I2C:%s v%d.%d.%d Vpins %u-%u %S"),
|
||||||
_i2cAddress.toString(), _majorVer, _minorVer, _patchVer,
|
_I2CAddress.toString(), _majorVer, _minorVer, _patchVer,
|
||||||
(int)_firstVpin, (int)_firstVpin+_nPins-1,
|
(int)_firstVpin, (int)_firstVpin+_nPins-1,
|
||||||
_deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F(""));
|
_deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
I2CAddress _i2cAddress;
|
// Helper function for error handling
|
||||||
|
void reportError(uint8_t status, bool fail=true) {
|
||||||
|
DIAG(F("EX-IOExpander I2C:%s Error:%d (%S)"), _I2CAddress.toString(),
|
||||||
|
status, I2CManager.getErrorMessage(status));
|
||||||
|
if (fail)
|
||||||
|
_deviceState = DEVSTATE_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t _numDigitalPins = 0;
|
uint8_t _numDigitalPins = 0;
|
||||||
uint8_t _numAnaloguePins = 0;
|
uint8_t _numAnaloguePins = 0;
|
||||||
byte _digitalOutBuffer[3];
|
|
||||||
uint8_t _versionBuffer[3];
|
|
||||||
uint8_t _majorVer = 0;
|
uint8_t _majorVer = 0;
|
||||||
uint8_t _minorVer = 0;
|
uint8_t _minorVer = 0;
|
||||||
uint8_t _patchVer = 0;
|
uint8_t _patchVer = 0;
|
||||||
byte* _digitalInputStates;
|
|
||||||
byte* _analogueInputStates;
|
uint8_t* _digitalInputStates;
|
||||||
uint8_t _digitalPinBytes = 0;
|
uint8_t* _analogueInputStates;
|
||||||
uint8_t _analoguePinBytes = 0;
|
uint8_t* _analogueInputBuffer; // buffer for I2C input transfers
|
||||||
byte _command1Buffer[1];
|
uint8_t _readCommandBuffer[1];
|
||||||
byte _command2Buffer[2];
|
|
||||||
byte _command4Buffer[4];
|
uint8_t _digitalPinBytes = 0; // Size of allocated memory buffer (may be longer than needed)
|
||||||
byte _receive3Buffer[3];
|
uint8_t _analoguePinBytes = 0; // Size of allocated memory buffers (may be longer than needed)
|
||||||
byte _servoBuffer[7];
|
|
||||||
uint8_t* _analoguePinMap;
|
uint8_t* _analoguePinMap;
|
||||||
|
I2CRB _i2crb;
|
||||||
|
|
||||||
|
enum {RDS_IDLE, RDS_DIGITAL, RDS_ANALOGUE}; // Read operation states
|
||||||
|
uint8_t _readState = RDS_IDLE;
|
||||||
|
|
||||||
|
unsigned long _lastDigitalRead = 0;
|
||||||
|
unsigned long _lastAnalogueRead = 0;
|
||||||
|
const unsigned long _digitalRefresh = 10000UL; // Delay refreshing digital inputs for 10ms
|
||||||
|
const unsigned long _analogueRefresh = 50000UL; // Delay refreshing analogue inputs for 50ms
|
||||||
|
|
||||||
// EX-IOExpander protocol flags
|
// EX-IOExpander protocol flags
|
||||||
enum {
|
enum {
|
||||||
|
|
|
@ -103,7 +103,7 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_
|
||||||
uint8_t stepsMSB = value >> 8;
|
uint8_t stepsMSB = value >> 8;
|
||||||
uint8_t stepsLSB = value & 0xFF;
|
uint8_t stepsLSB = value & 0xFF;
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("EX-Turntable WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"),
|
DIAG(F("EX-Turntable WriteAnalogue VPIN:%u Value:%d Activity:%d Duration:%d"),
|
||||||
vpin, value, activity, duration);
|
vpin, value, activity, duration);
|
||||||
DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"),
|
DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"),
|
||||||
_I2CAddress.toString(), stepsMSB, stepsLSB, activity);
|
_I2CAddress.toString(), stepsMSB, stepsLSB, activity);
|
||||||
|
@ -114,7 +114,7 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_
|
||||||
|
|
||||||
// Display Turnetable-EX device driver info.
|
// Display Turnetable-EX device driver info.
|
||||||
void EXTurntable::_display() {
|
void EXTurntable::_display() {
|
||||||
DIAG(F("EX-Turntable I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin,
|
DIAG(F("EX-Turntable I2C:%s Configured on Vpins:%u-%u %S"), _I2CAddress.toString(), (int)_firstVpin,
|
||||||
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,7 @@ protected:
|
||||||
void _write(VPIN vpin, int value) {
|
void _write(VPIN vpin, int value) {
|
||||||
int pin = vpin -_firstVpin;
|
int pin = vpin -_firstVpin;
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("IO_ExampleSerial::_write Pin:%d Value:%d"), (int)vpin, value);
|
DIAG(F("IO_ExampleSerial::_write VPIN:%u Value:%d"), (int)vpin, value);
|
||||||
#endif
|
#endif
|
||||||
// Send a command string over the serial line
|
// Send a command string over the serial line
|
||||||
_serial->print('#');
|
_serial->print('#');
|
||||||
|
@ -153,10 +153,10 @@ protected:
|
||||||
// Display information about the device, and perhaps its current condition (e.g. active, disabled etc).
|
// Display information about the device, and perhaps its current condition (e.g. active, disabled etc).
|
||||||
// Here we display the current values held for the pins.
|
// Here we display the current values held for the pins.
|
||||||
void _display() {
|
void _display() {
|
||||||
DIAG(F("IO_ExampleSerial Configured on VPins:%d-%d"), (int)_firstVpin,
|
DIAG(F("IO_ExampleSerial Configured on Vpins:%u-%u"), (int)_firstVpin,
|
||||||
(int)_firstVpin+_nPins-1);
|
(int)_firstVpin+_nPins-1);
|
||||||
for (int i=0; i<_nPins; i++)
|
for (int i=0; i<_nPins; i++)
|
||||||
DIAG(F(" VPin %2d: %d"), _firstVpin+i, _pinValues[i]);
|
DIAG(F(" VPin %2u: %d"), _firstVpin+i, _pinValues[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -196,7 +196,7 @@ void GPIOBase<T>::_loop(unsigned long currentMicros) {
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
void GPIOBase<T>::_display() {
|
void GPIOBase<T>::_display() {
|
||||||
DIAG(F("%S I2C:%s Configured on Vpins:%d-%d %S"), _deviceName, _I2CAddress.toString(),
|
DIAG(F("%S I2C:%s Configured on Vpins:%u-%u %S"), _deviceName, _I2CAddress.toString(),
|
||||||
_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -82,10 +82,10 @@ private:
|
||||||
public:
|
public:
|
||||||
// Static function to handle "HALDisplay::create(...)" calls.
|
// Static function to handle "HALDisplay::create(...)" calls.
|
||||||
static void create(I2CAddress i2cAddress, int width, int height) {
|
static void create(I2CAddress i2cAddress, int width, int height) {
|
||||||
/* if (checkNoOverlap(i2cAddress)) */ new HALDisplay(0, i2cAddress, width, height);
|
if (checkNoOverlap(0, 0, i2cAddress)) new HALDisplay(0, i2cAddress, width, height);
|
||||||
}
|
}
|
||||||
static void create(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) {
|
static void create(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) {
|
||||||
/* if (checkNoOverlap(i2cAddress)) */ new HALDisplay(displayNo, i2cAddress, width, height);
|
if (checkNoOverlap(0, 0, i2cAddress)) new HALDisplay(displayNo, i2cAddress, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
176
IO_HCSR04.h
176
IO_HCSR04.h
|
@ -30,7 +30,7 @@
|
||||||
*
|
*
|
||||||
* This driver polls the HC-SR04 by sending the trigger pulse and then measuring
|
* This driver polls the HC-SR04 by sending the trigger pulse and then measuring
|
||||||
* the length of the received pulse. If the calculated distance is less than
|
* the length of the received pulse. If the calculated distance is less than
|
||||||
* the threshold, the output state returned by a read() call changes to 1. If
|
* the threshold, the output _state returned by a read() call changes to 1. If
|
||||||
* the distance is greater than the threshold plus a hysteresis margin, the
|
* the distance is greater than the threshold plus a hysteresis margin, the
|
||||||
* output changes to 0. The device also supports readAnalogue(), which returns
|
* output changes to 0. The device also supports readAnalogue(), which returns
|
||||||
* the measured distance in cm, or 32767 if the distance exceeds the
|
* the measured distance in cm, or 32767 if the distance exceeds the
|
||||||
|
@ -48,6 +48,20 @@
|
||||||
* Note: The timing accuracy required for measuring the pulse length means that
|
* Note: The timing accuracy required for measuring the pulse length means that
|
||||||
* the pins have to be direct Arduino pins; GPIO pins on an IO Extender cannot
|
* the pins have to be direct Arduino pins; GPIO pins on an IO Extender cannot
|
||||||
* provide the required accuracy.
|
* provide the required accuracy.
|
||||||
|
*
|
||||||
|
* Example configuration:
|
||||||
|
* HCSR04::create(23000, 32, 33, 80, 85);
|
||||||
|
*
|
||||||
|
* Where 23000 is the VPIN allocated,
|
||||||
|
* 32 is the pin connected to the HCSR04 trigger terminal,
|
||||||
|
* 33 is the pin connected to the HCSR04 echo terminal,
|
||||||
|
* 80 is the distance in cm below which pin 23000 will be active,
|
||||||
|
* and 85 is the distance in cm above which pin 23000 will be inactive.
|
||||||
|
*
|
||||||
|
* Alternative configuration, which hogs the processor until the measurement is complete
|
||||||
|
* (old behaviour, more accurate but higher impact on other CS tasks):
|
||||||
|
* HCSR04::create(23000, 32, 33, 80, 85, HCSR04::LOOP);
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef IO_HCSR04_H
|
#ifndef IO_HCSR04_H
|
||||||
|
@ -61,38 +75,52 @@ private:
|
||||||
// pins must be arduino GPIO pins, not extender pins or HAL pins.
|
// pins must be arduino GPIO pins, not extender pins or HAL pins.
|
||||||
int _trigPin = -1;
|
int _trigPin = -1;
|
||||||
int _echoPin = -1;
|
int _echoPin = -1;
|
||||||
// Thresholds for setting active state in cm.
|
// Thresholds for setting active _state in cm.
|
||||||
uint8_t _onThreshold; // cm
|
uint8_t _onThreshold; // cm
|
||||||
uint8_t _offThreshold; // cm
|
uint8_t _offThreshold; // cm
|
||||||
// Last measured distance in cm.
|
// Last measured distance in cm.
|
||||||
uint16_t _distance;
|
uint16_t _distance;
|
||||||
// Active=1/inactive=0 state
|
// Active=1/inactive=0 _state
|
||||||
uint8_t _value = 0;
|
uint8_t _value = 0;
|
||||||
// Factor for calculating the distance (cm) from echo time (ms).
|
// Factor for calculating the distance (cm) from echo time (us).
|
||||||
// Based on a speed of sound of 345 metres/second.
|
// Based on a speed of sound of 345 metres/second.
|
||||||
const uint16_t factor = 58; // ms/cm
|
const uint16_t factor = 58; // us/cm
|
||||||
|
// Limit the time spent looping by dropping out when the expected
|
||||||
|
// worst case threshold value is greater than an arbitrary value.
|
||||||
|
const uint16_t maxPermittedLoopTime = 10 * factor; // max in us
|
||||||
|
unsigned long _startTime = 0;
|
||||||
|
unsigned long _maxTime = 0;
|
||||||
|
enum {DORMANT, MEASURING}; // _state values
|
||||||
|
uint8_t _state = DORMANT;
|
||||||
|
uint8_t _counter = 0;
|
||||||
|
uint16_t _options = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
enum Options {
|
||||||
|
LOOP = 1, // Option HCSR04::LOOP reinstates old behaviour, i.e. complete measurement in one loop entry.
|
||||||
|
};
|
||||||
|
|
||||||
// Static create function provides alternative way to create object
|
// Static create function provides alternative way to create object
|
||||||
static void create(VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) {
|
static void create(VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold, uint16_t options = 0) {
|
||||||
if (checkNoOverlap(vpin))
|
if (checkNoOverlap(vpin))
|
||||||
new HCSR04(vpin, trigPin, echoPin, onThreshold, offThreshold);
|
new HCSR04(vpin, trigPin, echoPin, onThreshold, offThreshold, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Constructor perfroms static initialisation of the device object
|
// Constructor performs static initialisation of the device object
|
||||||
HCSR04 (VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) {
|
HCSR04 (VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold, uint16_t options) {
|
||||||
_firstVpin = vpin;
|
_firstVpin = vpin;
|
||||||
_nPins = 1;
|
_nPins = 1;
|
||||||
_trigPin = trigPin;
|
_trigPin = trigPin;
|
||||||
_echoPin = echoPin;
|
_echoPin = echoPin;
|
||||||
_onThreshold = onThreshold;
|
_onThreshold = onThreshold;
|
||||||
_offThreshold = offThreshold;
|
_offThreshold = offThreshold;
|
||||||
|
_options = options;
|
||||||
addDevice(this);
|
addDevice(this);
|
||||||
}
|
}
|
||||||
// _begin function called to perform dynamic initialisation of the device
|
// _begin function called to perform dynamic initialisation of the device
|
||||||
void _begin() override {
|
void _begin() override {
|
||||||
|
_state = 0;
|
||||||
pinMode(_trigPin, OUTPUT);
|
pinMode(_trigPin, OUTPUT);
|
||||||
pinMode(_echoPin, INPUT);
|
pinMode(_echoPin, INPUT);
|
||||||
ArduinoPins::fastWriteDigital(_trigPin, 0);
|
ArduinoPins::fastWriteDigital(_trigPin, 0);
|
||||||
|
@ -112,76 +140,102 @@ protected:
|
||||||
return _distance;
|
return _distance;
|
||||||
}
|
}
|
||||||
|
|
||||||
// _loop function - read HC-SR04 once every 50 milliseconds.
|
// _loop function - read HC-SR04 once every 100 milliseconds.
|
||||||
void _loop(unsigned long currentMicros) override {
|
void _loop(unsigned long currentMicros) override {
|
||||||
read_HCSR04device();
|
unsigned long waitTime;
|
||||||
// Delay next loop entry until 50ms have elapsed.
|
switch(_state) {
|
||||||
delayUntil(currentMicros + 50000UL);
|
case DORMANT: // Issue pulse
|
||||||
}
|
// If receive pin is still set on from previous call, do nothing till next entry.
|
||||||
|
if (ArduinoPins::fastReadDigital(_echoPin)) return;
|
||||||
void _display() override {
|
|
||||||
DIAG(F("HCSR04 Configured on Vpin:%d TrigPin:%d EchoPin:%d On:%dcm Off:%dcm"),
|
|
||||||
_firstVpin, _trigPin, _echoPin, _onThreshold, _offThreshold);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
// This polls the HC-SR04 device by sending a pulse and measuring the duration of
|
|
||||||
// the pulse observed on the receive pin. In order to be kind to the rest of the CS
|
|
||||||
// software, no interrupts are used and interrupts are not disabled. The pulse duration
|
|
||||||
// is measured in a loop, using the micros() function. Therefore, interrupts from other
|
|
||||||
// sources may affect the result. However, interrupts response code in CS typically takes
|
|
||||||
// much less than the 58us frequency for the DCC interrupt, and 58us corresponds to only 1cm
|
|
||||||
// in the HC-SR04.
|
|
||||||
// To reduce chatter on the output, hysteresis is applied on reset: the output is set to 1 when the
|
|
||||||
// measured distance is less than the onThreshold, and is set to 0 if the measured distance is
|
|
||||||
// greater than the offThreshold.
|
|
||||||
//
|
|
||||||
void read_HCSR04device() {
|
|
||||||
// uint16 enough to time up to 65ms
|
|
||||||
uint16_t startTime, waitTime = 0, currentTime, maxTime;
|
|
||||||
|
|
||||||
// If receive pin is still set on from previous call, abort the read.
|
|
||||||
if (ArduinoPins::fastReadDigital(_echoPin))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Send 10us pulse to trigger transmitter
|
// Send 10us pulse to trigger transmitter
|
||||||
ArduinoPins::fastWriteDigital(_trigPin, 1);
|
ArduinoPins::fastWriteDigital(_trigPin, 1);
|
||||||
delayMicroseconds(10);
|
delayMicroseconds(10);
|
||||||
ArduinoPins::fastWriteDigital(_trigPin, 0);
|
ArduinoPins::fastWriteDigital(_trigPin, 0);
|
||||||
|
|
||||||
// Wait for receive pin to be set
|
// Wait, with timeout, for echo pin to become set.
|
||||||
startTime = currentTime = micros();
|
// Measured time delay is just under 500us, so
|
||||||
maxTime = factor * _offThreshold * 2;
|
// wait for max of 1000us.
|
||||||
|
_startTime = micros();
|
||||||
|
_maxTime = 1000;
|
||||||
|
|
||||||
while (!ArduinoPins::fastReadDigital(_echoPin)) {
|
while (!ArduinoPins::fastReadDigital(_echoPin)) {
|
||||||
// lastTime = currentTime;
|
// Not set yet, see if we've timed out.
|
||||||
currentTime = micros();
|
waitTime = micros() - _startTime;
|
||||||
waitTime = currentTime - startTime;
|
if (waitTime > _maxTime) {
|
||||||
if (waitTime > maxTime) {
|
// Timeout waiting for pulse start, abort the read and start again
|
||||||
// Timeout waiting for pulse start, abort the read
|
_state = DORMANT;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for receive pin to reset, and measure length of pulse
|
// Echo pulse started, so wait for echo pin to reset, and measure length of pulse
|
||||||
startTime = currentTime = micros();
|
_startTime = micros();
|
||||||
maxTime = factor * _offThreshold;
|
_maxTime = factor * _offThreshold;
|
||||||
while (ArduinoPins::fastReadDigital(_echoPin)) {
|
_state = MEASURING;
|
||||||
currentTime = micros();
|
// If maximum measurement time is high, then skip until next loop entry before
|
||||||
waitTime = currentTime - startTime;
|
// starting to look for pulse end.
|
||||||
|
// This gives better accuracy at shorter distance thresholds but without extending
|
||||||
|
// loop execution time for longer thresholds. If LOOP option is set on, then
|
||||||
|
// the entire measurement will be done in one loop entry, i.e. the code will fall
|
||||||
|
// through into the measuring phase.
|
||||||
|
if (!(_options & LOOP) && _maxTime > maxPermittedLoopTime) break;
|
||||||
|
/* fallthrough */
|
||||||
|
|
||||||
|
case MEASURING: // Check if echo pulse has finished
|
||||||
|
do {
|
||||||
|
waitTime = micros() - _startTime;
|
||||||
|
if (!ArduinoPins::fastReadDigital(_echoPin)) {
|
||||||
|
// Echo pulse completed; check if pulse length is below threshold and if so set value.
|
||||||
|
if (waitTime <= factor * _onThreshold) {
|
||||||
|
// Measured time is within the onThreshold, so value is one.
|
||||||
|
_value = 1;
|
||||||
|
// If the new distance value is less than the current, use it immediately.
|
||||||
|
// But if the new distance value is longer, then it may be erroneously long
|
||||||
|
// (because of extended loop times delays), so apply a delay to distance increases.
|
||||||
|
uint16_t estimatedDistance = waitTime / factor;
|
||||||
|
if (estimatedDistance < _distance)
|
||||||
|
_distance = estimatedDistance;
|
||||||
|
else
|
||||||
|
_distance += 1; // Just increase distance slowly.
|
||||||
|
_counter = 0;
|
||||||
|
//DIAG(F("HCSR04: Pulse Len=%l Distance=%d"), waitTime, _distance);
|
||||||
|
}
|
||||||
|
_state = DORMANT;
|
||||||
|
} else {
|
||||||
|
// Echo pulse hasn't finished, so check if maximum time has elapsed
|
||||||
// If pulse is too long then set return value to zero,
|
// If pulse is too long then set return value to zero,
|
||||||
// and finish without waiting for end of pulse.
|
// and finish without waiting for end of pulse.
|
||||||
if (waitTime > maxTime) {
|
if (waitTime > _maxTime) {
|
||||||
// Pulse length longer than maxTime, reset value.
|
// Pulse length longer than maxTime, value is provisionally zero.
|
||||||
|
// But don't change _value unless provisional value is zero for 10 consecutive measurements
|
||||||
|
if (_value == 1) {
|
||||||
|
if (++_counter >= 10) {
|
||||||
_value = 0;
|
_value = 0;
|
||||||
_distance = 32767;
|
_distance = 32767;
|
||||||
return;
|
_counter = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check if pulse length is below threshold, if so set value.
|
_state = DORMANT; // start again
|
||||||
//DIAG(F("HCSR04: Pulse Len=%l Distance=%d"), waitTime, distance);
|
}
|
||||||
_distance = waitTime / factor; // in centimetres
|
}
|
||||||
if (_distance < _onThreshold)
|
// If there's lots of time remaining before the expected completion time,
|
||||||
_value = 1;
|
// then exit and wait for next loop entry. Otherwise, loop until we finish.
|
||||||
|
// If option LOOP is set, then we loop until finished anyway.
|
||||||
|
uint32_t remainingTime = _maxTime - waitTime;
|
||||||
|
if (!(_options & LOOP) && remainingTime < maxPermittedLoopTime) return;
|
||||||
|
} while (_state == MEASURING) ;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Datasheet recommends a wait of at least 60ms between measurement cycles
|
||||||
|
if (_state == DORMANT)
|
||||||
|
delayUntil(currentMicros+60000UL); // wait 60ms till next measurement
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void _display() override {
|
||||||
|
DIAG(F("HCSR04 Configured on VPIN:%u TrigPin:%d EchoPin:%d On:%dcm Off:%dcm"),
|
||||||
|
_firstVpin, _trigPin, _echoPin, _onThreshold, _offThreshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -46,7 +46,7 @@ bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i
|
||||||
if (configType != CONFIGURE_SERVO) return false;
|
if (configType != CONFIGURE_SERVO) return false;
|
||||||
if (paramCount != 5) return false;
|
if (paramCount != 5) return false;
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("PCA9685 Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"),
|
DIAG(F("PCA9685 Configure VPIN:%u Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"),
|
||||||
vpin, params[0], params[1], params[2], params[3], params[4]);
|
vpin, params[0], params[1], params[2], params[3], params[4]);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -118,7 +118,7 @@ void PCA9685::_begin() {
|
||||||
// For this function, the configured profile is used.
|
// For this function, the configured profile is used.
|
||||||
void PCA9685::_write(VPIN vpin, int value) {
|
void PCA9685::_write(VPIN vpin, int value) {
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value);
|
DIAG(F("PCA9685 Write VPIN:%u Value:%d"), vpin, value);
|
||||||
#endif
|
#endif
|
||||||
int pin = vpin - _firstVpin;
|
int pin = vpin - _firstVpin;
|
||||||
if (value) value = 1;
|
if (value) value = 1;
|
||||||
|
@ -145,7 +145,7 @@ void PCA9685::_write(VPIN vpin, int value) {
|
||||||
//
|
//
|
||||||
void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) {
|
void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) {
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"),
|
DIAG(F("PCA9685 WriteAnalogue VPIN:%u Value:%d Profile:%d Duration:%d %S"),
|
||||||
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
|
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
|
||||||
#endif
|
#endif
|
||||||
if (_deviceState == DEVSTATE_FAILED) return;
|
if (_deviceState == DEVSTATE_FAILED) return;
|
||||||
|
@ -262,7 +262,7 @@ void PCA9685::writeDevice(uint8_t pin, int value) {
|
||||||
|
|
||||||
// Display details of this device.
|
// Display details of this device.
|
||||||
void PCA9685::_display() {
|
void PCA9685::_display() {
|
||||||
DIAG(F("PCA9685 I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin,
|
DIAG(F("PCA9685 I2C:%s Configured on Vpins:%u-%u %S"), _I2CAddress.toString(), (int)_firstVpin,
|
||||||
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -121,7 +121,7 @@ private:
|
||||||
void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override {
|
void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override {
|
||||||
(void)param1; (void)param2; // suppress compiler warning
|
(void)param1; (void)param2; // suppress compiler warning
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("PCA9685pwm WriteAnalogue Vpin:%d Value:%d %S"),
|
DIAG(F("PCA9685pwm WriteAnalogue VPIN:%u Value:%d %S"),
|
||||||
vpin, value, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
|
vpin, value, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
|
||||||
#endif
|
#endif
|
||||||
if (_deviceState == DEVSTATE_FAILED) return;
|
if (_deviceState == DEVSTATE_FAILED) return;
|
||||||
|
@ -134,7 +134,7 @@ private:
|
||||||
|
|
||||||
// Display details of this device.
|
// Display details of this device.
|
||||||
void _display() override {
|
void _display() override {
|
||||||
DIAG(F("PCA9685pwm I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin,
|
DIAG(F("PCA9685pwm I2C:%s Configured on Vpins:%u-%u %S"), _I2CAddress.toString(), (int)_firstVpin,
|
||||||
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -98,13 +98,14 @@ private:
|
||||||
|
|
||||||
void _write(VPIN vpin, int value) override {
|
void _write(VPIN vpin, int value) override {
|
||||||
if (vpin == _firstVpin + 1) {
|
if (vpin == _firstVpin + 1) {
|
||||||
byte _feedbackBuffer[2] = {RE_OP, value};
|
if (value != 0) value = 0x01;
|
||||||
|
byte _feedbackBuffer[2] = {RE_OP, (byte)value};
|
||||||
I2CManager.write(_I2CAddress, _feedbackBuffer, 2);
|
I2CManager.write(_I2CAddress, _feedbackBuffer, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _display() override {
|
void _display() override {
|
||||||
DIAG(F("Rotary Encoder I2C:%s v%d.%d.%d Configured on Vpin:%d-%d %S"), _I2CAddress.toString(), _majorVer, _minorVer, _patchVer,
|
DIAG(F("Rotary Encoder I2C:%s v%d.%d.%d Configured on VPIN:%u-%d %S"), _I2CAddress.toString(), _majorVer, _minorVer, _patchVer,
|
||||||
(int)_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
(int)_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
12
IO_Servo.h
12
IO_Servo.h
|
@ -98,7 +98,7 @@ private:
|
||||||
if (configType != CONFIGURE_SERVO) return false;
|
if (configType != CONFIGURE_SERVO) return false;
|
||||||
if (paramCount != 5) return false;
|
if (paramCount != 5) return false;
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("Servo: Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"),
|
DIAG(F("Servo: Configure VPIN:%u Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"),
|
||||||
vpin, params[0], params[1], params[2], params[3], params[4]);
|
vpin, params[0], params[1], params[2], params[3], params[4]);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -140,12 +140,12 @@ private:
|
||||||
// Get reference to slave device.
|
// Get reference to slave device.
|
||||||
_slaveDevice = findDevice(_firstSlavePin);
|
_slaveDevice = findDevice(_firstSlavePin);
|
||||||
if (!_slaveDevice) {
|
if (!_slaveDevice) {
|
||||||
DIAG(F("Servo: Slave device not found on pins %d-%d"),
|
DIAG(F("Servo: Slave device not found on Vpins %u-%u"),
|
||||||
_firstSlavePin, _firstSlavePin+_nPins-1);
|
_firstSlavePin, _firstSlavePin+_nPins-1);
|
||||||
_deviceState = DEVSTATE_FAILED;
|
_deviceState = DEVSTATE_FAILED;
|
||||||
}
|
}
|
||||||
if (_slaveDevice != findDevice(_firstSlavePin+_nPins-1)) {
|
if (_slaveDevice != findDevice(_firstSlavePin+_nPins-1)) {
|
||||||
DIAG(F("Servo: Slave device does not cover all pins %d-%d"),
|
DIAG(F("Servo: Slave device does not cover all Vpins %u-%u"),
|
||||||
_firstSlavePin, _firstSlavePin+_nPins-1);
|
_firstSlavePin, _firstSlavePin+_nPins-1);
|
||||||
_deviceState = DEVSTATE_FAILED;
|
_deviceState = DEVSTATE_FAILED;
|
||||||
}
|
}
|
||||||
|
@ -165,7 +165,7 @@ private:
|
||||||
void _write(VPIN vpin, int value) override {
|
void _write(VPIN vpin, int value) override {
|
||||||
if (_deviceState == DEVSTATE_FAILED) return;
|
if (_deviceState == DEVSTATE_FAILED) return;
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("Servo Write Vpin:%d Value:%d"), vpin, value);
|
DIAG(F("Servo Write VPIN:%u Value:%d"), vpin, value);
|
||||||
#endif
|
#endif
|
||||||
int pin = vpin - _firstVpin;
|
int pin = vpin - _firstVpin;
|
||||||
if (value) value = 1;
|
if (value) value = 1;
|
||||||
|
@ -193,7 +193,7 @@ private:
|
||||||
//
|
//
|
||||||
void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override {
|
void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override {
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"),
|
DIAG(F("Servo: WriteAnalogue VPIN:%u Value:%d Profile:%d Duration:%d %S"),
|
||||||
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
|
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
|
||||||
#endif
|
#endif
|
||||||
if (_deviceState == DEVSTATE_FAILED) return;
|
if (_deviceState == DEVSTATE_FAILED) return;
|
||||||
|
@ -288,7 +288,7 @@ private:
|
||||||
|
|
||||||
// Display details of this device.
|
// Display details of this device.
|
||||||
void _display() override {
|
void _display() override {
|
||||||
DIAG(F("Servo Configured on Vpins:%d-%d, slave pins:%d-%d %S"),
|
DIAG(F("Servo Configured on Vpins:%u-%u, slave pins:%d-%d %S"),
|
||||||
(int)_firstVpin, (int)_firstVpin+_nPins-1,
|
(int)_firstVpin, (int)_firstVpin+_nPins-1,
|
||||||
(int)_firstSlavePin, (int)_firstSlavePin+_nPins-1,
|
(int)_firstSlavePin, (int)_firstSlavePin+_nPins-1,
|
||||||
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||||
|
|
|
@ -124,7 +124,7 @@ protected:
|
||||||
|
|
||||||
// Display information about the device, and perhaps its current condition (e.g. active, disabled etc).
|
// Display information about the device, and perhaps its current condition (e.g. active, disabled etc).
|
||||||
void _display() {
|
void _display() {
|
||||||
DIAG(F("TouchKeypad Configured on VPins:%d-%d SCL=%d SDO=%d"), (int)_firstVpin,
|
DIAG(F("TouchKeypad Configured on Vpins:%u-%u SCL=%d SDO=%d"), (int)_firstVpin,
|
||||||
(int)_firstVpin+_nPins-1, _clockPin, _dataPin);
|
(int)_firstVpin+_nPins-1, _clockPin, _dataPin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -319,7 +319,7 @@ protected:
|
||||||
}
|
}
|
||||||
|
|
||||||
void _display() override {
|
void _display() override {
|
||||||
DIAG(F("VL53L0X I2C:%s Configured on Vpins:%d-%d On:%dmm Off:%dmm %S"),
|
DIAG(F("VL53L0X I2C:%s Configured on Vpins:%u-%u On:%dmm Off:%dmm %S"),
|
||||||
_I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold,
|
_I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold,
|
||||||
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,7 @@ public:
|
||||||
pinMode(_clockPin,OUTPUT);
|
pinMode(_clockPin,OUTPUT);
|
||||||
pinMode(_dataPin,_pinMap?INPUT_PULLUP:OUTPUT);
|
pinMode(_dataPin,_pinMap?INPUT_PULLUP:OUTPUT);
|
||||||
_display();
|
_display();
|
||||||
|
if (!_pinMap) _loopOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
// loop called by HAL supervisor
|
// loop called by HAL supervisor
|
||||||
|
@ -121,7 +122,7 @@ void _loopOutput() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _display() override {
|
void _display() override {
|
||||||
DIAG(F("IO_duinoNodes %SPUT Configured on VPins:%d-%d shift=%d"),
|
DIAG(F("IO_duinoNodes %SPUT Configured on Vpins:%u-%u shift=%d"),
|
||||||
_pinMap?F("IN"):F("OUT"),
|
_pinMap?F("IN"):F("OUT"),
|
||||||
(int)_firstVpin,
|
(int)_firstVpin,
|
||||||
(int)_firstVpin+_nPins-1, _nShiftBytes*8);
|
(int)_firstVpin+_nPins-1, _nShiftBytes*8);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
/*
|
/*
|
||||||
* © 2022 Paul M Antoine
|
* © 2022-2023 Paul M Antoine
|
||||||
* © 2021 Mike S
|
* © 2021 Mike S
|
||||||
* © 2021 Fred Decker
|
* © 2021 Fred Decker
|
||||||
* © 2020-2022 Harald Barth
|
* © 2020-2023 Harald Barth
|
||||||
* © 2020-2021 Chris Harlow
|
* © 2020-2021 Chris Harlow
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
|
@ -27,10 +27,6 @@
|
||||||
#include "DCCTimer.h"
|
#include "DCCTimer.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
|
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
|
||||||
#include "ESP32-fixes.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool MotorDriver::commonFaultPin=false;
|
bool MotorDriver::commonFaultPin=false;
|
||||||
|
|
||||||
volatile portreg_t shadowPORTA;
|
volatile portreg_t shadowPORTA;
|
||||||
|
@ -75,6 +71,23 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||||
dualSignal=true;
|
dualSignal=true;
|
||||||
getFastPin(F("SIG2"),signalPin2,fastSignalPin2);
|
getFastPin(F("SIG2"),signalPin2,fastSignalPin2);
|
||||||
pinMode(signalPin2, OUTPUT);
|
pinMode(signalPin2, OUTPUT);
|
||||||
|
|
||||||
|
fastSignalPin2.shadowinout = NULL;
|
||||||
|
if (HAVE_PORTA(fastSignalPin2.inout == &PORTA)) {
|
||||||
|
DIAG(F("Found PORTA pin %d"),signalPin2);
|
||||||
|
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
||||||
|
fastSignalPin2.inout = &shadowPORTA;
|
||||||
|
}
|
||||||
|
if (HAVE_PORTB(fastSignalPin2.inout == &PORTB)) {
|
||||||
|
DIAG(F("Found PORTB pin %d"),signalPin2);
|
||||||
|
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
||||||
|
fastSignalPin2.inout = &shadowPORTB;
|
||||||
|
}
|
||||||
|
if (HAVE_PORTC(fastSignalPin2.inout == &PORTC)) {
|
||||||
|
DIAG(F("Found PORTC pin %d"),signalPin2);
|
||||||
|
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
||||||
|
fastSignalPin2.inout = &shadowPORTC;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else dualSignal=false;
|
else dualSignal=false;
|
||||||
|
|
||||||
|
@ -123,8 +136,7 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||||
if (currentPin==UNUSED_PIN)
|
if (currentPin==UNUSED_PIN)
|
||||||
DIAG(F("** WARNING ** No current or short detection"));
|
DIAG(F("** WARNING ** No current or short detection"));
|
||||||
else {
|
else {
|
||||||
DIAG(F("CurrentPin=A%d, TripValue=%d"),
|
DIAG(F("Track %c, TripValue=%d"), trackLetter, rawCurrentTripValue);
|
||||||
currentPin-A0, rawCurrentTripValue);
|
|
||||||
|
|
||||||
// self testing diagnostic for the non-float converters... may be removed when happy
|
// self testing diagnostic for the non-float converters... may be removed when happy
|
||||||
// DIAG(F("senseFactorInternal=%d raw2mA(1000)=%d mA2Raw(1000)=%d"),
|
// DIAG(F("senseFactorInternal=%d raw2mA(1000)=%d mA2Raw(1000)=%d"),
|
||||||
|
@ -149,7 +161,7 @@ void MotorDriver::setPower(POWERMODE mode) {
|
||||||
// when switching a track On, we need to check the crrentOffset with the pin OFF
|
// when switching a track On, we need to check the crrentOffset with the pin OFF
|
||||||
if (powerMode==POWERMODE::OFF && currentPin!=UNUSED_PIN) {
|
if (powerMode==POWERMODE::OFF && currentPin!=UNUSED_PIN) {
|
||||||
senseOffset = ADCee::read(currentPin);
|
senseOffset = ADCee::read(currentPin);
|
||||||
DIAG(F("CurrentPin A%d sensOffset=%d"),currentPin-A0,senseOffset);
|
DIAG(F("Track %c sensOffset=%d"),trackLetter,senseOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
IODevice::write(powerPin,invertPower ? LOW : HIGH);
|
IODevice::write(powerPin,invertPower ? LOW : HIGH);
|
||||||
|
@ -197,7 +209,10 @@ int MotorDriver::getCurrentRaw(bool fromISR) {
|
||||||
(void)fromISR;
|
(void)fromISR;
|
||||||
if (currentPin==UNUSED_PIN) return 0;
|
if (currentPin==UNUSED_PIN) return 0;
|
||||||
int current;
|
int current;
|
||||||
current = ADCee::read(currentPin, fromISR)-senseOffset;
|
current = ADCee::read(currentPin, fromISR);
|
||||||
|
// here one can diag raw value
|
||||||
|
// if (fromISR == false) DIAG(F("%c: %d"), trackLetter, current);
|
||||||
|
current = current-senseOffset; // adjust with offset
|
||||||
if (current<0) current=0-current;
|
if (current<0) current=0-current;
|
||||||
if ((faultPin != UNUSED_PIN) && powerMode==POWERMODE::ON) {
|
if ((faultPin != UNUSED_PIN) && powerMode==POWERMODE::ON) {
|
||||||
if (invertFault && isLOW(fastFaultPin))
|
if (invertFault && isLOW(fastFaultPin))
|
||||||
|
@ -274,7 +289,7 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||||
f = taurustones[ (tSpeed-2)/2 ] ;
|
f = taurustones[ (tSpeed-2)/2 ] ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup
|
DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (tSpeed <= 1) brake = 255;
|
if (tSpeed <= 1) brake = 255;
|
||||||
|
@ -283,7 +298,7 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||||
if (invertBrake)
|
if (invertBrake)
|
||||||
brake=255-brake;
|
brake=255-brake;
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
#if defined(ARDUINO_ARCH_ESP32)
|
||||||
DCCEXanalogWrite(brakePin,brake);
|
DCCTimer::DCCEXanalogWrite(brakePin,brake);
|
||||||
#else
|
#else
|
||||||
analogWrite(brakePin,brake);
|
analogWrite(brakePin,brake);
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -182,11 +182,15 @@ class MotorDriver {
|
||||||
isProgTrack = on;
|
isProgTrack = on;
|
||||||
}
|
}
|
||||||
void checkPowerOverload(bool useProgLimit, byte trackno);
|
void checkPowerOverload(bool useProgLimit, byte trackno);
|
||||||
|
inline void setTrackLetter(char c) {
|
||||||
|
trackLetter = c;
|
||||||
|
};
|
||||||
#ifdef ANALOG_READ_INTERRUPT
|
#ifdef ANALOG_READ_INTERRUPT
|
||||||
bool sampleCurrentFromHW();
|
bool sampleCurrentFromHW();
|
||||||
void startCurrentFromHW();
|
void startCurrentFromHW();
|
||||||
#endif
|
#endif
|
||||||
private:
|
private:
|
||||||
|
char trackLetter = '?';
|
||||||
bool isProgTrack = false; // tells us if this is a prog track
|
bool isProgTrack = false; // tells us if this is a prog track
|
||||||
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
|
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
|
||||||
void getFastPin(const FSH* type,int pin, FASTPIN & result) {
|
void getFastPin(const FSH* type,int pin, FASTPIN & result) {
|
||||||
|
|
|
@ -73,12 +73,12 @@
|
||||||
#elif defined(ARDUINO_ARCH_ESP32)
|
#elif defined(ARDUINO_ARCH_ESP32)
|
||||||
// STANDARD shield on an ESPDUINO-32 (ESP32 in Uno form factor). The shield must be eiter the
|
// STANDARD shield on an ESPDUINO-32 (ESP32 in Uno form factor). The shield must be eiter the
|
||||||
// 3.3V compatible R3 version or it has to be modified to not supply more than 3.3V to the
|
// 3.3V compatible R3 version or it has to be modified to not supply more than 3.3V to the
|
||||||
// analog inputs. Here we use analog inputs A4 and A5 as A0 and A1 are wired in a way so that
|
// analog inputs. Here we use analog inputs A2 and A3 as A0 and A1 are wired in a way so that
|
||||||
// they are not useable at the same time as WiFi (what a bummer). The numbers below are the
|
// they are not useable at the same time as WiFi (what a bummer). The numbers below are the
|
||||||
// actual GPIO numbers. In comments the numbers the pins have on an Uno.
|
// actual GPIO numbers. In comments the numbers the pins have on an Uno.
|
||||||
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
|
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
|
||||||
new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 36/*A4*/, 0.70, 1500, UNUSED_PIN), \
|
new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 35/*A2*/, 0.70, 1500, UNUSED_PIN), \
|
||||||
new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 39/*A5*/, 0.70, 1500, UNUSED_PIN)
|
new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 34/*A3*/, 0.70, 1500, UNUSED_PIN)
|
||||||
|
|
||||||
#else
|
#else
|
||||||
// STANDARD shield on any Arduino Uno or Mega compatible with the original specification.
|
// STANDARD shield on any Arduino Uno or Mega compatible with the original specification.
|
||||||
|
|
|
@ -4,24 +4,37 @@ General points:
|
||||||
- Commands below have a single character opcode and parameters.
|
- Commands below have a single character opcode and parameters.
|
||||||
Even <JA> is actually read as <J A>
|
Even <JA> is actually read as <J A>
|
||||||
- Keyword parameters are shown in upper case but may be entered in mixed case.
|
- Keyword parameters are shown in upper case but may be entered in mixed case.
|
||||||
- value parameters are numeric.
|
- value parameters are decimal numeric (unless otherwise noted)
|
||||||
- [something] indicates its optional.
|
- [something] indicates its optional.
|
||||||
- Not all commands have a response, and not all responses come from the last commands that you have issued.
|
- Not all commands have a response, and broadcasts mean that not all responses come from the last commands that you have issued.
|
||||||
|
|
||||||
Startup status
|
Startup status
|
||||||
<s>
|
<s> Return status like
|
||||||
|
<iDCC-EX V-4.2.22 / MEGA / STANDARD_MOTOR_SHIELD G-devel-202302281422Z>
|
||||||
|
also returns defined turnout list:
|
||||||
|
<H id 1|0> 1=thrown
|
||||||
|
|
||||||
Track power management
|
Track power management. After power commands a power state is broadcast to all throttles.
|
||||||
<1>
|
|
||||||
<1 MAIN|PROG|JOIN>
|
<1> Power on all
|
||||||
<0>
|
<1 MAIN|PROG|JOIN> Power on MAIN or PROG track
|
||||||
<0 MAIN|PROG>
|
<1 JOIN> Power on MAIN and PROG track but send main track data on both.
|
||||||
|
<0> Power off all tracks
|
||||||
|
<0 MAIN|PROG> Power off main or prog track
|
||||||
|
|
||||||
|
Basic manual loco control
|
||||||
|
<t locoid speed direction> Throttle loco.
|
||||||
|
speed in JMRI-form (-1=ESTOP, 0=STOP, 1..126 = DCC speeds 2..127)
|
||||||
|
direction 1=forward, 0=reverse
|
||||||
|
For response see broadcast <l>
|
||||||
|
|
||||||
|
<F locoid function 1|0> Set loco function 1=ON, 0-OFF
|
||||||
|
For response see broadcast <l>
|
||||||
|
|
||||||
|
<!> emergency stop all locos
|
||||||
|
<T id 0|1|T|C> Control turnout id, 0=C=Closed, 1=T=Thrown
|
||||||
|
response broadcast <H id 0|1>
|
||||||
|
|
||||||
Basic manual control
|
|
||||||
<t cab speed direction>
|
|
||||||
<F cab function 1|0>
|
|
||||||
<!>
|
|
||||||
<T id 0|1|T|C>
|
|
||||||
|
|
||||||
DCC accessory control
|
DCC accessory control
|
||||||
<a address subaddress activate [onoff]>
|
<a address subaddress activate [onoff]>
|
||||||
|
@ -35,25 +48,33 @@ Note: Turnouts are best defined in myAutomation.h where a turnout description ca
|
||||||
<T id VPIN vpin>
|
<T id VPIN vpin>
|
||||||
<T id DCC addr subaddr>
|
<T id DCC addr subaddr>
|
||||||
<T id DCC linearaddr>
|
<T id DCC linearaddr>
|
||||||
|
Valid commands respond with <O>
|
||||||
|
|
||||||
|
Direct pin manipulation (replaces <Z commands, no predefinition required)
|
||||||
|
<z vpin> Set pin HIGH
|
||||||
|
<z -vpin> Set pin LOW
|
||||||
|
<z vpin value> Set pin analog value
|
||||||
|
<z vpin value profile> Set pin analog with profile
|
||||||
|
<z vpin value profile duration> set pin analog with profile and value
|
||||||
|
|
||||||
|
|
||||||
Outputs
|
Sensors (Used by JMRI, not required by EXRAIL)
|
||||||
<Z id activate>
|
<S id vpin pullup> define a sensor to be monitored.
|
||||||
<Z id vpin iflag>
|
Responses <Q id> and <q id> as sensor changes
|
||||||
|
|
||||||
Sensors
|
Decoder programming - main track
|
||||||
<S id vpin pullup>
|
<w cab cv value> POM write value to cv on loco
|
||||||
|
<b cab cv bit value> POM write bit to cv on loco
|
||||||
|
|
||||||
Decoder programming
|
Decoder Programming - prog track
|
||||||
<w cab cv value>
|
<W cabid> Clear consist and write new cab id (includes long/short settings)
|
||||||
<b cab cv bit value>
|
Responds <W cabid> or <W -1> for error
|
||||||
<W cabid>
|
<W cv value> Write value to cv
|
||||||
<W cv value>
|
|
||||||
<V cv value>
|
<V cv predictedValue> Read cv value, much faster if prediction is correct.
|
||||||
<V cv bit value>
|
<V cv bit predictedValue> Read CV bit
|
||||||
<R>
|
|
||||||
<R cv>
|
<R> Read drive-away loco id. (May be a consist id)
|
||||||
<B cv bit value>
|
|
||||||
<D ACK ON|OFF>
|
<D ACK ON|OFF>
|
||||||
<D ACK LIMIT|MIN|MAX|RETRY value>
|
<D ACK LIMIT|MIN|MAX|RETRY value>
|
||||||
<D PROGBOOST>
|
<D PROGBOOST>
|
||||||
|
@ -152,6 +173,11 @@ Obsolete commands/formats
|
||||||
<B cv bit value obsolete obsolete>
|
<B cv bit value obsolete obsolete>
|
||||||
<R cv obsolete obsolete>
|
<R cv obsolete obsolete>
|
||||||
<W cv value obsolete obsolete>
|
<W cv value obsolete obsolete>
|
||||||
|
<R cv> V command is much faster if prediction is correct.
|
||||||
|
<B cv bit value> V command is much faster if prediction is correct.
|
||||||
|
<Z id vpin active> (use <z) Define an output pin that JMRI can set by id
|
||||||
|
<Z id activate> (use <z) Activate an output pin by id
|
||||||
|
|
||||||
|
|
||||||
Broadcast responses
|
Broadcast responses
|
||||||
Note: broadcasts are sent to all throttles when appropriate (usually because something has changed)
|
Note: broadcasts are sent to all throttles when appropriate (usually because something has changed)
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#include "MotorDriver.h"
|
#include "MotorDriver.h"
|
||||||
#include "DCCTimer.h"
|
#include "DCCTimer.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
|
#include"CommandDistributor.h"
|
||||||
// Virtualised Motor shield multi-track hardware Interface
|
// Virtualised Motor shield multi-track hardware Interface
|
||||||
#define FOR_EACH_TRACK(t) for (byte t=0;t<=lastTrack;t++)
|
#define FOR_EACH_TRACK(t) for (byte t=0;t<=lastTrack;t++)
|
||||||
|
|
||||||
|
@ -128,6 +129,7 @@ void TrackManager::addTrack(byte t, MotorDriver* driver) {
|
||||||
track[t]=driver;
|
track[t]=driver;
|
||||||
if (driver) {
|
if (driver) {
|
||||||
track[t]->setPower(POWERMODE::OFF);
|
track[t]->setPower(POWERMODE::OFF);
|
||||||
|
track[t]->setTrackLetter('A'+t);
|
||||||
lastTrack=t;
|
lastTrack=t;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,6 +205,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||||
track[t]->setPower(POWERMODE::OFF);
|
track[t]->setPower(POWERMODE::OFF);
|
||||||
trackMode[t]=TRACK_MODE_OFF;
|
trackMode[t]=TRACK_MODE_OFF;
|
||||||
track[t]->makeProgTrack(false); // revoke prog track special handling
|
track[t]->makeProgTrack(false); // revoke prog track special handling
|
||||||
|
streamTrackState(NULL,t);
|
||||||
}
|
}
|
||||||
track[trackToSet]->makeProgTrack(true); // set for prog track special handling
|
track[trackToSet]->makeProgTrack(true); // set for prog track special handling
|
||||||
} else {
|
} else {
|
||||||
|
@ -210,6 +213,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||||
}
|
}
|
||||||
trackMode[trackToSet]=mode;
|
trackMode[trackToSet]=mode;
|
||||||
trackDCAddr[trackToSet]=dcAddr;
|
trackDCAddr[trackToSet]=dcAddr;
|
||||||
|
streamTrackState(NULL,trackToSet);
|
||||||
|
|
||||||
// When a track is switched, we must clear any side effects of its previous
|
// When a track is switched, we must clear any side effects of its previous
|
||||||
// state, otherwise trains run away or just dont move.
|
// state, otherwise trains run away or just dont move.
|
||||||
|
@ -290,36 +294,7 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
|
||||||
|
|
||||||
if (params==0) { // <=> List track assignments
|
if (params==0) { // <=> List track assignments
|
||||||
FOR_EACH_TRACK(t)
|
FOR_EACH_TRACK(t)
|
||||||
if (track[t]!=NULL) {
|
streamTrackState(stream,t);
|
||||||
StringFormatter::send(stream,F("<= %c "),'A'+t);
|
|
||||||
switch(trackMode[t]) {
|
|
||||||
case TRACK_MODE_MAIN:
|
|
||||||
StringFormatter::send(stream,F("MAIN"));
|
|
||||||
if (track[t]->trackPWM)
|
|
||||||
StringFormatter::send(stream,F("+"));
|
|
||||||
break;
|
|
||||||
case TRACK_MODE_PROG:
|
|
||||||
StringFormatter::send(stream,F("PROG"));
|
|
||||||
if (track[t]->trackPWM)
|
|
||||||
StringFormatter::send(stream,F("+"));
|
|
||||||
break;
|
|
||||||
case TRACK_MODE_OFF:
|
|
||||||
StringFormatter::send(stream,F("OFF"));
|
|
||||||
break;
|
|
||||||
case TRACK_MODE_EXT:
|
|
||||||
StringFormatter::send(stream,F("EXT"));
|
|
||||||
break;
|
|
||||||
case TRACK_MODE_DC:
|
|
||||||
StringFormatter::send(stream,F("DC %d"),trackDCAddr[t]);
|
|
||||||
break;
|
|
||||||
case TRACK_MODE_DCX:
|
|
||||||
StringFormatter::send(stream,F("DCX %d"),trackDCAddr[t]);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break; // unknown, dont care
|
|
||||||
}
|
|
||||||
StringFormatter::send(stream,F(">\n"));
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,6 +324,36 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TrackManager::streamTrackState(Print* stream, byte t) {
|
||||||
|
// null stream means send to commandDistributor for broadcast
|
||||||
|
if (track[t]==NULL) return;
|
||||||
|
auto format=F("");
|
||||||
|
switch(trackMode[t]) {
|
||||||
|
case TRACK_MODE_MAIN:
|
||||||
|
format=F("<= %c MAIN>\n");
|
||||||
|
break;
|
||||||
|
case TRACK_MODE_PROG:
|
||||||
|
format=F("<= %c PROG>\n");
|
||||||
|
break;
|
||||||
|
case TRACK_MODE_OFF:
|
||||||
|
format=F("<= %c OFF>\n");
|
||||||
|
break;
|
||||||
|
case TRACK_MODE_EXT:
|
||||||
|
format=F("<= %c EXT>\n");
|
||||||
|
break;
|
||||||
|
case TRACK_MODE_DC:
|
||||||
|
format=F("<= %c DC %d>\n");
|
||||||
|
break;
|
||||||
|
case TRACK_MODE_DCX:
|
||||||
|
format=F("<= %c DCX %d>\n");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break; // unknown, dont care
|
||||||
|
}
|
||||||
|
if (stream) StringFormatter::send(stream,format,'A'+t,trackDCAddr[t]);
|
||||||
|
else CommandDistributor::broadcastTrackState(format,'A'+t,trackDCAddr[t]);
|
||||||
|
}
|
||||||
|
|
||||||
byte TrackManager::nextCycleTrack=MAX_TRACKS;
|
byte TrackManager::nextCycleTrack=MAX_TRACKS;
|
||||||
|
|
||||||
void TrackManager::loop() {
|
void TrackManager::loop() {
|
||||||
|
|
|
@ -80,6 +80,8 @@ class TrackManager {
|
||||||
static void reportGauges(Print* stream);
|
static void reportGauges(Print* stream);
|
||||||
static void reportCurrent(Print* stream);
|
static void reportCurrent(Print* stream);
|
||||||
static void reportObsoleteCurrent(Print* stream);
|
static void reportObsoleteCurrent(Print* stream);
|
||||||
|
static void streamTrackState(Print* stream, byte t);
|
||||||
|
|
||||||
static int16_t joinRelay;
|
static int16_t joinRelay;
|
||||||
static bool progTrackSyncMain; // true when prog track is a siding switched to main
|
static bool progTrackSyncMain; // true when prog track is a siding switched to main
|
||||||
static bool progTrackBoosted; // true when prog track is not current limited
|
static bool progTrackBoosted; // true when prog track is not current limited
|
||||||
|
|
35
Turnouts.cpp
35
Turnouts.cpp
|
@ -110,49 +110,40 @@
|
||||||
/* static */ bool Turnout::setClosedStateOnly(uint16_t id, bool closeFlag) {
|
/* static */ bool Turnout::setClosedStateOnly(uint16_t id, bool closeFlag) {
|
||||||
Turnout *tt = get(id);
|
Turnout *tt = get(id);
|
||||||
if (!tt) return false;
|
if (!tt) return false;
|
||||||
tt->_turnoutData.closed = closeFlag;
|
|
||||||
|
|
||||||
// I know it says setClosedStateOnly, but we need to tell others
|
// I know it says setClosedStateOnly, but we need to tell others
|
||||||
// that the state has changed too.
|
// that the state has changed too. But we only broadcast if there
|
||||||
|
// really has been a change.
|
||||||
|
if (tt->_turnoutData.closed != closeFlag) {
|
||||||
|
tt->_turnoutData.closed = closeFlag;
|
||||||
|
CommandDistributor::broadcastTurnout(id, closeFlag);
|
||||||
|
}
|
||||||
#if defined(EXRAIL_ACTIVE)
|
#if defined(EXRAIL_ACTIVE)
|
||||||
RMFT2::turnoutEvent(id, closeFlag);
|
RMFT2::turnoutEvent(id, closeFlag);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
CommandDistributor::broadcastTurnout(id, closeFlag);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define DIAG_IO
|
||||||
// Static setClosed function is invoked from close(), throw() etc. to perform the
|
// Static setClosed function is invoked from close(), throw() etc. to perform the
|
||||||
// common parts of the turnout operation. Code which is specific to a turnout
|
// common parts of the turnout operation. Code which is specific to a turnout
|
||||||
// type should be placed in the virtual function setClosedInternal(bool) which is
|
// type should be placed in the virtual function setClosedInternal(bool) which is
|
||||||
// called from here.
|
// called from here.
|
||||||
/* static */ bool Turnout::setClosed(uint16_t id, bool closeFlag) {
|
/* static */ bool Turnout::setClosed(uint16_t id, bool closeFlag) {
|
||||||
#if defined(DIAG_IO)
|
#if defined(DIAG_IO)
|
||||||
if (closeFlag)
|
DIAG(F("Turnout(%d,%c)"), id, closeFlag ? 'c':'t');
|
||||||
DIAG(F("Turnout::close(%d)"), id);
|
|
||||||
else
|
|
||||||
DIAG(F("Turnout::throw(%d)"), id);
|
|
||||||
#endif
|
#endif
|
||||||
Turnout *tt = Turnout::get(id);
|
Turnout *tt = Turnout::get(id);
|
||||||
if (!tt) return false;
|
if (!tt) return false;
|
||||||
bool ok = tt->setClosedInternal(closeFlag);
|
bool ok = tt->setClosedInternal(closeFlag);
|
||||||
|
|
||||||
if (ok) {
|
if (ok) {
|
||||||
|
tt->setClosedStateOnly(id, closeFlag);
|
||||||
#ifndef DISABLE_EEPROM
|
#ifndef DISABLE_EEPROM
|
||||||
// Write byte containing new closed/thrown state to EEPROM if required. Note that eepromAddress
|
// Write byte containing new closed/thrown state to EEPROM if required. Note that eepromAddress
|
||||||
// is always zero for LCN turnouts.
|
// is always zero for LCN turnouts.
|
||||||
if (EEStore::eeStore->data.nTurnouts > 0 && tt->_eepromAddress > 0)
|
if (EEStore::eeStore->data.nTurnouts > 0 && tt->_eepromAddress > 0)
|
||||||
EEPROM.put(tt->_eepromAddress, tt->_turnoutData.flags);
|
EEPROM.put(tt->_eepromAddress, tt->_turnoutData.flags);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(EXRAIL_ACTIVE)
|
|
||||||
RMFT2::turnoutEvent(id, closeFlag);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Send message to JMRI etc.
|
|
||||||
CommandDistributor::broadcastTurnout(id, closeFlag);
|
|
||||||
}
|
}
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
@ -298,7 +289,6 @@
|
||||||
#ifndef IO_NO_HAL
|
#ifndef IO_NO_HAL
|
||||||
IODevice::writeAnalogue(_servoTurnoutData.vpin,
|
IODevice::writeAnalogue(_servoTurnoutData.vpin,
|
||||||
close ? _servoTurnoutData.closedPosition : _servoTurnoutData.thrownPosition, _servoTurnoutData.profile);
|
close ? _servoTurnoutData.closedPosition : _servoTurnoutData.thrownPosition, _servoTurnoutData.profile);
|
||||||
_turnoutData.closed = close;
|
|
||||||
#else
|
#else
|
||||||
(void)close; // avoid compiler warnings
|
(void)close; // avoid compiler warnings
|
||||||
#endif
|
#endif
|
||||||
|
@ -396,7 +386,6 @@
|
||||||
// and Close writes a 0.
|
// and Close writes a 0.
|
||||||
// RCN-213 specifies that Throw is 0 and Close is 1.
|
// RCN-213 specifies that Throw is 0 and Close is 1.
|
||||||
DCC::setAccessory(_dccTurnoutData.address, _dccTurnoutData.subAddress, close ^ !rcn213Compliant);
|
DCC::setAccessory(_dccTurnoutData.address, _dccTurnoutData.subAddress, close ^ !rcn213Compliant);
|
||||||
_turnoutData.closed = close;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -472,7 +461,6 @@
|
||||||
|
|
||||||
bool VpinTurnout::setClosedInternal(bool close) {
|
bool VpinTurnout::setClosedInternal(bool close) {
|
||||||
IODevice::write(_vpinTurnoutData.vpin, close);
|
IODevice::write(_vpinTurnoutData.vpin, close);
|
||||||
_turnoutData.closed = close;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -523,7 +511,10 @@
|
||||||
bool LCNTurnout::setClosedInternal(bool close) {
|
bool LCNTurnout::setClosedInternal(bool close) {
|
||||||
// Assume that the LCN command still uses 1 for throw and 0 for close...
|
// Assume that the LCN command still uses 1 for throw and 0 for close...
|
||||||
LCN::send('T', _turnoutData.id, !close);
|
LCN::send('T', _turnoutData.id, !close);
|
||||||
// The _turnoutData.closed flag should be updated by a message from the LCN master, later.
|
// The _turnoutData.closed flag should be updated by a message from the LCN master.
|
||||||
|
// but in this implementation it is updated in setClosedStateOnly() instead.
|
||||||
|
// If the LCN master updates this, setClosedStateOnly() and all setClosedInternal()
|
||||||
|
// have to be updated accordingly so that the closed flag is only set once.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -125,17 +125,18 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, con
|
||||||
wifiState = setup2( SSid, password, hostname, port, channel);
|
wifiState = setup2( SSid, password, hostname, port, channel);
|
||||||
|
|
||||||
if (wifiState == WIFI_NOAT) {
|
if (wifiState == WIFI_NOAT) {
|
||||||
DIAG(F("++ Wifi Setup NO AT ++"));
|
LCD(4, F("WiFi no AT chip"));
|
||||||
return wifiState;
|
return wifiState;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wifiState == WIFI_CONNECTED) {
|
if (wifiState == WIFI_CONNECTED) {
|
||||||
StringFormatter::send(wifiStream, F("ATE0\r\n")); // turn off the echo
|
StringFormatter::send(wifiStream, F("ATE0\r\n")); // turn off the echo
|
||||||
checkForOK(200, true);
|
checkForOK(200, true);
|
||||||
|
DIAG(F("WiFi CONNECTED"));
|
||||||
|
// LCD already shows IP
|
||||||
|
} else {
|
||||||
|
LCD(4,F("WiFi DISCON."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
DIAG(F("++ Wifi Setup %S ++"), wifiState == WIFI_CONNECTED ? F("CONNECTED") : F("DISCONNECTED"));
|
|
||||||
return wifiState;
|
return wifiState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -125,10 +125,10 @@ The configuration file for DCC-EX Command Station
|
||||||
// define LCD_DRIVER for I2C address 0x27, 16 cols, 2 rows
|
// define LCD_DRIVER for I2C address 0x27, 16 cols, 2 rows
|
||||||
// #define LCD_DRIVER 0x27,16,2
|
// #define LCD_DRIVER 0x27,16,2
|
||||||
|
|
||||||
//OR define OLED_DRIVER width,height in pixels (address auto detected)
|
//OR define OLED_DRIVER width,height[,address] in pixels (address auto detected if not supplied)
|
||||||
// 128x32 or 128x64 I2C SSD1306-based devices are supported.
|
// 128x32 or 128x64 I2C SSD1306-based devices are supported.
|
||||||
// Use 132,64 for a SH1106-based I2C device with a 128x64 display.
|
// Use 132,64 for a SH1106-based I2C device with a 128x64 display.
|
||||||
// #define OLED_DRIVER 128,32
|
// #define OLED_DRIVER 128,32,0x3c
|
||||||
|
|
||||||
// Define scroll mode as 0, 1 or 2
|
// Define scroll mode as 0, 1 or 2
|
||||||
// * #define SCROLLMODE 0 is scroll continuous (fill screen if poss),
|
// * #define SCROLLMODE 0 is scroll continuous (fill screen if poss),
|
||||||
|
|
169
config.h.txt
Normal file
169
config.h.txt
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
/**********************************************************************
|
||||||
|
|
||||||
|
Config.h
|
||||||
|
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||||
|
COPYRIGHT (c) 2020 Fred Decker
|
||||||
|
|
||||||
|
The configuration file for DCC++ EX Command Station
|
||||||
|
|
||||||
|
**********************************************************************/
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// NOTE: Before connecting these boards and selecting one in this software
|
||||||
|
// check the quick install guides!!! Some of these boards require a voltage
|
||||||
|
// generating resitor on the current sense pin of the device. Failure to select
|
||||||
|
// the correct resistor could damage the sense pin on your Arduino or destroy
|
||||||
|
// the device.
|
||||||
|
//
|
||||||
|
// DEFINE MOTOR_SHIELD_TYPE BELOW ACCORDING TO THE FOLLOWING TABLE:
|
||||||
|
//
|
||||||
|
// STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel
|
||||||
|
// POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track)
|
||||||
|
// FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection)
|
||||||
|
// FIREBOX_MK1 : The Firebox MK1
|
||||||
|
// FIREBOX_MK1S : The Firebox MK1S
|
||||||
|
// |
|
||||||
|
// +-----------------------v
|
||||||
|
//
|
||||||
|
// #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"),
|
||||||
|
// new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 0.488, 1500, UNUSED_PIN),
|
||||||
|
// new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 0.488, 1500, UNUSED_PIN)
|
||||||
|
|
||||||
|
#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// The IP port to talk to a WIFI or Ethernet shield.
|
||||||
|
//
|
||||||
|
#define IP_PORT 2560
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// NOTE: Only supported on Arduino Mega
|
||||||
|
// Set to false if you not even want it on the Arduino Mega
|
||||||
|
//
|
||||||
|
//#define ENABLE_WIFI true
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// DEFINE WiFi Parameters (only in effect if WIFI is on)
|
||||||
|
//
|
||||||
|
// If DONT_TOUCH_WIFI_CONF is set, all WIFI config will be done with
|
||||||
|
// the <+> commands and this sketch will not change anything over
|
||||||
|
// AT commands and the other WIFI_* defines below do not have any effect.
|
||||||
|
//#define DONT_TOUCH_WIFI_CONF
|
||||||
|
//
|
||||||
|
// WIFI_SSID is the network name IF you want to use your existing home network.
|
||||||
|
// Do NOT change this if you want to use the WiFi in Access Point (AP) mode.
|
||||||
|
//
|
||||||
|
// If you do NOT set the WIFI_SSID, the WiFi chip will first try
|
||||||
|
// to connect to the previously configured network and if that fails
|
||||||
|
// fall back to Access Point mode. The SSID of the AP will be
|
||||||
|
// automatically set to DCCEX_*.
|
||||||
|
//
|
||||||
|
// Your SSID may not conain ``"'' (double quote, ASCII 0x22).
|
||||||
|
#define WIFI_SSID "Your network name"
|
||||||
|
//
|
||||||
|
// WIFI_PASSWORD is the network password for your home network or if
|
||||||
|
// you want to change the password from default AP mode password
|
||||||
|
// to the AP password you want.
|
||||||
|
// Your password may not conain ``"'' (double quote, ASCII 0x22).
|
||||||
|
#define WIFI_PASSWORD "deadcafe"
|
||||||
|
//
|
||||||
|
// WIFI_HOSTNAME: You probably don't need to change this
|
||||||
|
#define WIFI_HOSTNAME "dccex"
|
||||||
|
//
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Wifi connect timeout in milliseconds. Default is 14000 (14 seconds). You only need
|
||||||
|
// to set this if you have an extremely slow Wifi router.
|
||||||
|
//
|
||||||
|
#define WIFI_CONNECT_TIMEOUT 14000
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// ENABLE_ETHERNET: Set to true if you have an Arduino Ethernet card (wired). This
|
||||||
|
// is not for Wifi. You will then need the Arduino Ethernet library as well
|
||||||
|
//
|
||||||
|
//#define ENABLE_ETHERNET true
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// DEFINE STATIC IP ADDRESS *OR* COMMENT OUT TO USE DHCP
|
||||||
|
//
|
||||||
|
//#define IP_ADDRESS { 192, 168, 1, 31 }
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// DEFINE MAC ADDRESS ARRAY FOR ETHERNET COMMUNICATIONS INTERFACE
|
||||||
|
//
|
||||||
|
// Uncomment to use with Ethernet Shields
|
||||||
|
//
|
||||||
|
// Ethernet Shields do not have have a MAC address in hardware. There may be one on
|
||||||
|
// a sticker on the Shield that you should use. Otherwise choose one of the ones below
|
||||||
|
// Be certain that no other device on your network has this same MAC address!
|
||||||
|
//
|
||||||
|
// 52:b8:8a:8e:ce:21
|
||||||
|
// e3:e9:73:e1:db:0d
|
||||||
|
// 54:2b:13:52:ac:0c
|
||||||
|
|
||||||
|
// NOTE: This is not used with ESP8266 WiFi modules.
|
||||||
|
|
||||||
|
//#define MAC_ADDRESS { 0x52, 0xB8, 0x8A, 0x8E, 0xCE, 0x21 } // MAC address of your networking card found on the sticker on your card or take one from above
|
||||||
|
|
||||||
|
//
|
||||||
|
// #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF }
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// DEFINE LCD SCREEN USAGE BY THE BASE STATION
|
||||||
|
//
|
||||||
|
// Note: This feature requires an I2C enabled LCD screen using a Hitachi HD44780
|
||||||
|
// controller and a PCF8574 based I2C 'backpack',
|
||||||
|
// OR an I2C Oled screen based on SSD1306 (128x64 or 128x32) controller,
|
||||||
|
// OR an I2C Oled screen based on SH1106 (132x64) controller.
|
||||||
|
// To enable, uncomment one of the lines below
|
||||||
|
|
||||||
|
// define LCD_DRIVER for I2C LCD address 0x3f,16 cols, 2 rows
|
||||||
|
//#define LCD_DRIVER {SubBus_4,0x27},20,4
|
||||||
|
|
||||||
|
//OR define OLED_DRIVER width,height in pixels (address auto detected)
|
||||||
|
#if defined(ARDUINO_ARCH_STM32)
|
||||||
|
#define OLED_DRIVER 0x3c, 128, 64
|
||||||
|
#else
|
||||||
|
#define OLED_DRIVER {SubBus_0,0x3c}, 128, 32
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define SCROLLMODE 1
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// DISABLE EEPROM
|
||||||
|
//
|
||||||
|
// If you do not need the EEPROM at all, you can disable all the code that saves
|
||||||
|
// data in the EEPROM. You might want to do that if you are in a Arduino UNO
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
#define DISABLE_EEPROM
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// DEFINE TURNOUTS/ACCESSORIES FOLLOW NORM RCN-213
|
||||||
|
//
|
||||||
|
// According to norm RCN-213 a DCC packet with a 1 is closed/straight
|
||||||
|
// and one with a 0 is thrown/diverging. In DCC++ Classic, and in previous
|
||||||
|
// versions of DCC++EX, a turnout throw command was implemented in the packet as
|
||||||
|
// '1' and a close command as '0'. The #define below makes the states
|
||||||
|
// match with the norm. But we don't want to cause havoc on existent layouts,
|
||||||
|
// so we define this only for new installations. If you don't want this,
|
||||||
|
// don't add it to your config.h.
|
||||||
|
//#define DCC_TURNOUTS_RCN_213
|
||||||
|
|
||||||
|
// The following #define likewise inverts the behaviour of the <a> command
|
||||||
|
// for triggering DCC Accessory Decoders, so that <a addr subaddr 0> generates a
|
||||||
|
// DCC packet with D=1 (close turnout) and <a addr subaddr 1> generates D=0
|
||||||
|
// (throw turnout).
|
||||||
|
//#define DCC_ACCESSORY_RCN_213
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
13
install_via_powershell.cmd
Normal file
13
install_via_powershell.cmd
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
@ECHO OFF
|
||||||
|
|
||||||
|
FOR /f "tokens=*" %%a IN ('powershell Get-ExecutionPolicy -Scope CurrentUser') DO SET PS_POLICY=%%a
|
||||||
|
|
||||||
|
IF NOT %PS_POLICY=="Bypass" (
|
||||||
|
powershell Set-ExecutionPolicy -Scope CurrentUser Bypass
|
||||||
|
)
|
||||||
|
|
||||||
|
powershell %~dp0%installer.ps1
|
||||||
|
|
||||||
|
IF NOT %PS_POLICY=="Bypass" (
|
||||||
|
powershell Set-ExecutionPolicy -Scope CurrentUser %PS_POLICY%
|
||||||
|
)
|
540
installer.ps1
Normal file
540
installer.ps1
Normal file
|
@ -0,0 +1,540 @@
|
||||||
|
<#
|
||||||
|
# © 2023 Peter Cole
|
||||||
|
#
|
||||||
|
# This file is part of EX-CommandStation
|
||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
#>
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
For script errors set ExecutionPolicy:
|
||||||
|
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass
|
||||||
|
############################################>
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
Optional command line parameters:
|
||||||
|
$buildDirectory - specify an existing directory rather than generating a new unique one
|
||||||
|
$configDirectory - specify a directory containing existing files as per $configFiles
|
||||||
|
############################################>
|
||||||
|
Param(
|
||||||
|
[Parameter()]
|
||||||
|
[String]$buildDirectory,
|
||||||
|
[Parameter()]
|
||||||
|
[String]$configDirectory
|
||||||
|
)
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
Define global parameters here such as known URLs etc.
|
||||||
|
############################################>
|
||||||
|
$installerVersion = "v0.0.8"
|
||||||
|
$configFiles = @("config.h", "myAutomation.h", "myHal.cpp", "mySetup.h")
|
||||||
|
$wifiBoards = @("arduino:avr:mega", "esp32:esp32:esp32")
|
||||||
|
$userDirectory = $env:USERPROFILE + "\"
|
||||||
|
$gitHubAPITags = "https://api.github.com/repos/DCC-EX/CommandStation-EX/git/refs/tags"
|
||||||
|
$gitHubURLPrefix = "https://github.com/DCC-EX/CommandStation-EX/archive/"
|
||||||
|
if ((Get-WmiObject win32_operatingsystem | Select-Object osarchitecture).osarchitecture -eq "64-bit") {
|
||||||
|
$arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip"
|
||||||
|
$arduinoCLIZip = $userDirectory + "Downloads\" + "arduino-cli_latest_Windows_64bit.zip"
|
||||||
|
} else {
|
||||||
|
$arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip"
|
||||||
|
$arduinoCLIZip = $userDirectory + "Downloads\" + "arduino-cli_latest_Windows_32bit.zip"
|
||||||
|
}
|
||||||
|
$arduinoCLIDirectory = $userDirectory + "arduino-cli"
|
||||||
|
$arduinoCLI = $arduinoCLIDirectory + "\arduino-cli.exe"
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
List of supported devices with FQBN in case clones used that aren't detected
|
||||||
|
############################################>
|
||||||
|
$supportedDevices = @(
|
||||||
|
@{
|
||||||
|
name = "Arduino Mega or Mega 2560"
|
||||||
|
fqbn = "arduino:avr:mega"
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
name = "Arduino Nano"
|
||||||
|
fqbn = "arduino:avr:nano"
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
name = "Arduino Uno"
|
||||||
|
fqbn = "arduino:avr:uno"
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
name = "ESP32 Dev Module"
|
||||||
|
fqbn = "esp32:esp32:esp32"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
List of supported displays
|
||||||
|
############################################>
|
||||||
|
$displayList = @(
|
||||||
|
@{
|
||||||
|
option = "LCD 16 columns x 2 rows"
|
||||||
|
configLine = "#define LCD_DRIVER 0x27,16,2"
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
option = "LCD 16 columns x 4 rows"
|
||||||
|
configLine = "#define LCD_DRIVER 0x27,16,4"
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
option = "OLED 128 x 32"
|
||||||
|
configLine = "#define OLED_DRIVER 128,32"
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
option = "OLED 128 x 64"
|
||||||
|
configLine = "#define OLED_DRIVER 128,64"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
Basics of config.h
|
||||||
|
############################################>
|
||||||
|
$configLines = @(
|
||||||
|
"/*",
|
||||||
|
"This config.h file was generated by the DCC-EX PowerShell installer $installerVersion",
|
||||||
|
"*/",
|
||||||
|
"",
|
||||||
|
"// Define standard motor shield",
|
||||||
|
"#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD",
|
||||||
|
""
|
||||||
|
)
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
Set default action for progress indicators, warnings, and errors
|
||||||
|
############################################>
|
||||||
|
$global:ProgressPreference = "SilentlyContinue"
|
||||||
|
$global:WarningPreference = "SilentlyContinue"
|
||||||
|
$global:ErrorActionPreference = "SilentlyContinue"
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
If $buildDirectory not provided, generate a new time/date stamp based directory to use
|
||||||
|
############################################>
|
||||||
|
if (!$PSBoundParameters.ContainsKey('buildDirectory')) {
|
||||||
|
$buildDate = Get-Date -Format 'yyyyMMdd-HHmmss'
|
||||||
|
$buildDirectory = $userDirectory + "EX-CommandStation-Installer\" + $buildDate
|
||||||
|
}
|
||||||
|
$commandStationDirectory = $buildDirectory + "\CommandStation-EX"
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
Write out intro message and prompt to continue
|
||||||
|
############################################>
|
||||||
|
@"
|
||||||
|
Welcome to the DCC-EX PowerShell installer for EX-CommandStation ($installerVersion)
|
||||||
|
|
||||||
|
Current installer options:
|
||||||
|
|
||||||
|
- EX-CommandStation will be built in $commandStationDirectory
|
||||||
|
- Arduino CLI will downloaded and extracted to $arduinoCLIDirectory
|
||||||
|
|
||||||
|
Before continuing, please ensure:
|
||||||
|
|
||||||
|
- Your computer is connected to the internet
|
||||||
|
- The device you wish to install EX-CommandStation on is connected to a USB port
|
||||||
|
|
||||||
|
This installer will obtain the Arduino CLI (if not already present), and then download and install your chosen version of EX-CommandStation
|
||||||
|
|
||||||
|
"@
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
Prompt user to confirm all is ready to proceed
|
||||||
|
############################################>
|
||||||
|
$confirmation = Read-Host "Enter 'Y' or 'y' then press <Enter> to confirm you are ready to proceed, any other key to exit"
|
||||||
|
if ($confirmation -ne "Y" -and $confirmation -ne "y") {
|
||||||
|
Exit
|
||||||
|
}
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
See if we have the Arduino CLI already, otherwise download and extract it
|
||||||
|
############################################>
|
||||||
|
if (!(Test-Path -PathType Leaf -Path $arduinoCLI)) {
|
||||||
|
if (!(Test-Path -PathType Container -Path $arduinoCLIDirectory)) {
|
||||||
|
try {
|
||||||
|
New-Item -ItemType Directory -Path $arduinoCLIDirectory | Out-Null
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Output "Arduino CLI does not exist and cannot create directory $arduinoCLIDirectory"
|
||||||
|
Exit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Output "`r`nDownloading and extracting Arduino CLI"
|
||||||
|
try {
|
||||||
|
Invoke-WebRequest -Uri $arduinoCLIURL -OutFile $arduinoCLIZip
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Output "Failed to download Arduino CLI"
|
||||||
|
Exit
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Expand-Archive -Path $arduinoCLIZip -DestinationPath $arduinoCLIDirectory -Force
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Output "Failed to extract Arduino CLI"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Output "`r`nArduino CLI already downloaded, ensuring it is up to date and you have a board connected"
|
||||||
|
}
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
Make sure Arduino CLI core index updated and list of boards populated
|
||||||
|
############################################>
|
||||||
|
# Need to do an initial board list to download everything first
|
||||||
|
try {
|
||||||
|
& $arduinoCLI core update-index | Out-Null
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Output "Failed to update Arduino CLI core index"
|
||||||
|
Exit
|
||||||
|
}
|
||||||
|
# Need to do an initial board list to download everything first
|
||||||
|
try {
|
||||||
|
& $arduinoCLI board list | Out-Null
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Output "Failed to update Arduino CLI board list"
|
||||||
|
Exit
|
||||||
|
}
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
Identify available board(s)
|
||||||
|
############################################>
|
||||||
|
try {
|
||||||
|
$boardList = & $arduinoCLI board list --format jsonmini | ConvertFrom-Json
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Output "Failed to obtain list of boards"
|
||||||
|
Exit
|
||||||
|
}
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
Get user to select board
|
||||||
|
############################################>
|
||||||
|
if ($boardList.count -eq 0) {
|
||||||
|
Write-Output "Could not find any attached devices, please ensure your device is plugged in to a USB port and Windows recognises it"
|
||||||
|
Exit
|
||||||
|
} else {
|
||||||
|
@"
|
||||||
|
|
||||||
|
Devices attached to COM ports:
|
||||||
|
------------------------------
|
||||||
|
"@
|
||||||
|
|
||||||
|
$boardSelect = 1
|
||||||
|
foreach ($board in $boardList) {
|
||||||
|
if ($board.matching_boards.name) {
|
||||||
|
$boardName = $board.matching_boards.name
|
||||||
|
} else {
|
||||||
|
$boardName = "Unknown device"
|
||||||
|
}
|
||||||
|
$port = $board.port.address
|
||||||
|
Write-Output "$boardSelect - $boardName on port $port"
|
||||||
|
$boardSelect++
|
||||||
|
}
|
||||||
|
Write-Output "$boardSelect - Exit"
|
||||||
|
$userSelection = 0
|
||||||
|
do {
|
||||||
|
[int]$userSelection = Read-Host "`r`nSelect the device to use from the list above"
|
||||||
|
} until (
|
||||||
|
(($userSelection -ge 1) -and ($userSelection -le ($boardList.count + 1)))
|
||||||
|
)
|
||||||
|
if ($userSelection -eq ($boardList.count + 1)) {
|
||||||
|
Write-Output "Exiting installer"
|
||||||
|
Exit
|
||||||
|
} else {
|
||||||
|
$selectedBoard = $userSelection - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
If the board is unknown, need to choose which one
|
||||||
|
############################################>
|
||||||
|
if ($null -eq $boardList[$selectedBoard].matching_boards.name) {
|
||||||
|
Write-Output "The device selected is unknown, these boards are supported:`r`n"
|
||||||
|
$deviceSelect = 1
|
||||||
|
foreach ($device in $supportedDevices) {
|
||||||
|
Write-Output "$deviceSelect - $($supportedDevices[$deviceSelect - 1].name)"
|
||||||
|
$deviceSelect++
|
||||||
|
}
|
||||||
|
Write-Output "$deviceSelect - Exit"
|
||||||
|
$userSelection = 0
|
||||||
|
do {
|
||||||
|
[int]$userSelection = Read-Host "Select the board type from the list above"
|
||||||
|
} until (
|
||||||
|
(($userSelection -ge 1) -and ($userSelection -le ($supportedDevices.count + 1)))
|
||||||
|
)
|
||||||
|
if ($userSelection -eq ($supportedDevices.count + 1)) {
|
||||||
|
Write-Output "Exiting installer"
|
||||||
|
Exit
|
||||||
|
} else {
|
||||||
|
$deviceName = $supportedDevices[$userSelection - 1].name
|
||||||
|
$deviceFQBN = $supportedDevices[$userSelection - 1].fqbn
|
||||||
|
$devicePort = $boardList[$selectedBoard].port.address
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$deviceName = $boardList[$selectedBoard].matching_boards.name
|
||||||
|
$deviceFQBN = $boardList[$selectedBoard].matching_boards.fqbn
|
||||||
|
$devicePort = $boardList[$selectedBoard].port.address
|
||||||
|
}
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
Get the list of tags
|
||||||
|
############################################>
|
||||||
|
try {
|
||||||
|
$gitHubTags = Invoke-RestMethod -Uri $gitHubAPITags
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Output "Failed to obtain list of available EX-CommandStation versions"
|
||||||
|
Exit
|
||||||
|
}
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
Get our GitHub tag list in a hash so we can sort by version numbers and extract just the ones we want
|
||||||
|
############################################>
|
||||||
|
$versionMatch = ".*?v(\d+)\.(\d+).(\d+)-(.*)"
|
||||||
|
$tagList = @{}
|
||||||
|
foreach ($tag in $gitHubTags) {
|
||||||
|
$tagHash = @{}
|
||||||
|
$tagHash["Ref"] = $tag.ref
|
||||||
|
$version = $tag.ref.split("/")[2]
|
||||||
|
$null = $version -match $versionMatch
|
||||||
|
$tagHash["Major"] = [int]$Matches[1]
|
||||||
|
$tagHash["Minor"] = [int]$Matches[2]
|
||||||
|
$tagHash["Patch"] = [int]$Matches[3]
|
||||||
|
$tagHash["Type"] = $Matches[4]
|
||||||
|
$tagList.Add($version, $tagHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
Get latest two Prod and Devel for user to select
|
||||||
|
############################################>
|
||||||
|
$userList = @{}
|
||||||
|
$prodCount = 1
|
||||||
|
$devCount = 1
|
||||||
|
$select = 1
|
||||||
|
foreach ($tag in $tagList.Keys | Sort-Object {$tagList[$_]["Major"]},{$tagList[$_]["Minor"]},{$tagList[$_]["Patch"]} -Descending) {
|
||||||
|
if (($tagList[$tag]["Type"] -eq "Prod") -and $prodCount -le 2) {
|
||||||
|
$userList[$select] = $tag
|
||||||
|
$select++
|
||||||
|
$prodCount++
|
||||||
|
} elseif (($tagList[$tag]["Type"] -eq "Devel") -and $devCount -le 2) {
|
||||||
|
$userList[$select] = $tag
|
||||||
|
$select++
|
||||||
|
$devCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
Display options for user to select and get the selection
|
||||||
|
############################################>
|
||||||
|
@"
|
||||||
|
|
||||||
|
Available EX-CommandStation versions:
|
||||||
|
-------------------------------------
|
||||||
|
"@
|
||||||
|
foreach ($selection in $userList.Keys | Sort-Object $selection) {
|
||||||
|
Write-Output "$selection - $($userList[$selection])"
|
||||||
|
}
|
||||||
|
Write-Output "5 - Exit"
|
||||||
|
$userSelection = 0
|
||||||
|
do {
|
||||||
|
[int]$userSelection = Read-Host "`r`nSelect the version to install from the list above (1 - 5)"
|
||||||
|
} until (
|
||||||
|
(($userSelection -ge 1) -and ($userSelection -le 5))
|
||||||
|
)
|
||||||
|
if ($userSelection -eq 5) {
|
||||||
|
Write-Output "Exiting installer"
|
||||||
|
Exit
|
||||||
|
} else {
|
||||||
|
$downloadURL = $gitHubURLPrefix + $tagList[$userList[$userSelection]]["Ref"] + ".zip"
|
||||||
|
}
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
Create build directory if it doesn't exist, or fail
|
||||||
|
############################################>
|
||||||
|
if (!(Test-Path -PathType Container -Path $buildDirectory)) {
|
||||||
|
try {
|
||||||
|
New-Item -ItemType Directory -Path $buildDirectory | Out-Null
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Output "Could not create build directory $buildDirectory"
|
||||||
|
Exit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
Download the chosen version to the build directory
|
||||||
|
############################################>
|
||||||
|
$downladFile = $buildDirectory + "\CommandStation-EX.zip"
|
||||||
|
Write-Output "Downloading and extracting $($userList[$userSelection])"
|
||||||
|
try {
|
||||||
|
Invoke-WebRequest -Uri $downloadURL -OutFile $downladFile
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Output "Error downloading EX-CommandStation zip file"
|
||||||
|
Exit
|
||||||
|
}
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
If folder exists, bail out and tell user
|
||||||
|
############################################>
|
||||||
|
if (Test-Path -PathType Container -Path "$buildDirectory\CommandStation-EX") {
|
||||||
|
Write-Output "EX-CommandStation directory already exists, please ensure you have copied any user files then delete manually: $buildDirectory\CommandStation-EX"
|
||||||
|
Exit
|
||||||
|
}
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
Extract and rename to CommandStation-EX to allow building
|
||||||
|
############################################>
|
||||||
|
try {
|
||||||
|
Expand-Archive -Path $downladFile -DestinationPath $buildDirectory -Force
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Output "Failed to extract EX-CommandStation zip file"
|
||||||
|
Exit
|
||||||
|
}
|
||||||
|
|
||||||
|
$folderName = $buildDirectory + "\CommandStation-EX-" + ($userList[$userSelection] -replace "^v", "")
|
||||||
|
try {
|
||||||
|
Rename-Item -Path $folderName -NewName $commandStationDirectory
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Output "Could not rename folder"
|
||||||
|
Exit
|
||||||
|
}
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
If config directory provided, copy files here
|
||||||
|
############################################>
|
||||||
|
if ($PSBoundParameters.ContainsKey('configDirectory')) {
|
||||||
|
if (Test-Path -PathType Container -Path $configDirectory) {
|
||||||
|
foreach ($file in $configFiles) {
|
||||||
|
if (Test-Path -PathType Leaf -Path "$configDirectory\$file") {
|
||||||
|
Copy-Item -Path "$configDirectory\$file" -Destination "$commandStationDirectory\$file"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Output "User provided configuration directory $configDirectory does not exist, skipping"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
If no config directory provided, prompt for display option
|
||||||
|
############################################>
|
||||||
|
Write-Output "`r`nIf you have an LCD or OLED display connected, you can configure it here`r`n"
|
||||||
|
Write-Output "1 - I have no display, skip this step"
|
||||||
|
$displaySelect = 2
|
||||||
|
foreach ($display in $displayList) {
|
||||||
|
Write-Output "$displaySelect - $($displayList[$displaySelect - 2].option)"
|
||||||
|
$displaySelect++
|
||||||
|
}
|
||||||
|
Write-Output "$($displayList.Count + 2) - Exit"
|
||||||
|
do {
|
||||||
|
[int]$displayChoice = Read-Host "`r`nSelect a display option"
|
||||||
|
} until (
|
||||||
|
($displayChoice -ge 1 -and $displayChoice -le ($displayList.Count + 2))
|
||||||
|
)
|
||||||
|
if ($displayChoice -eq ($displayList.Count + 2)) {
|
||||||
|
Exit
|
||||||
|
} elseif ($displayChoice -ge 2) {
|
||||||
|
$configLines+= "// Display configuration"
|
||||||
|
$configLines+= "$($displayList[$displayChoice - 2].configLine)"
|
||||||
|
$configLines+= "#define SCROLLMODE 1 // Alternate between pages"
|
||||||
|
}
|
||||||
|
<############################################
|
||||||
|
If device supports WiFi, prompt to configure
|
||||||
|
############################################>
|
||||||
|
if ($wifiBoards.Contains($deviceFQBN)) {
|
||||||
|
Write-Output "`r`nYour chosen board supports WiFi`r`n"
|
||||||
|
Write-Output "1 - I don't want WiFi, skip this step
|
||||||
|
2 - Configure my device as an access point I will connect to directly
|
||||||
|
3 - Configure my device to connect to my home WiFi network
|
||||||
|
4 - Exit"
|
||||||
|
do {
|
||||||
|
[int]$wifiChoice = Read-Host "`r`nSelect a WiFi option"
|
||||||
|
} until (
|
||||||
|
($wifiChoice -ge 1 -and $wifiChoice -le 4)
|
||||||
|
)
|
||||||
|
if ($wifiChoice -eq 4) {
|
||||||
|
Exit
|
||||||
|
} elseif ($wifiChoice -ne 1) {
|
||||||
|
$configLines+= ""
|
||||||
|
$configLines+= "// WiFi configuration"
|
||||||
|
$configLines+= "#define ENABLE_WIFI true"
|
||||||
|
$configLines+= "#define IP_PORT 2560"
|
||||||
|
$configLines+= "#define WIFI_HOSTNAME ""dccex"""
|
||||||
|
$configLines+= "#define WIFI_CHANNEL 1"
|
||||||
|
if ($wifiChoice -eq 2) {
|
||||||
|
$configLines+= "#define WIFI_SSID ""Your network name"""
|
||||||
|
$configLines+= "#define WIFI_PASSWORD ""Your network passwd"""
|
||||||
|
}
|
||||||
|
if ($wifiChoice -eq 3) {
|
||||||
|
$wifiSSID = Read-Host "Please enter the SSID of your home network here"
|
||||||
|
$wifiPassword = Read-Host "Please enter your home network WiFi password here"
|
||||||
|
$configLines+= "#define WIFI_SSID ""$($wifiSSID)"""
|
||||||
|
$configLines+= "#define WIFI_PASSWORD ""$($wifiPassword)"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
Write out config.h to a file here only if config directory not provided
|
||||||
|
############################################>
|
||||||
|
$configH = $commandStationDirectory + "\config.h"
|
||||||
|
try {
|
||||||
|
$configLines | Out-File -FilePath $configH -Encoding ascii
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Output "Error writing config file to $configH"
|
||||||
|
Exit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
Install core libraries for the platform
|
||||||
|
############################################>
|
||||||
|
$platformArray = $deviceFQBN.split(":")
|
||||||
|
$platform = $platformArray[0] + ":" + $platformArray[1]
|
||||||
|
try {
|
||||||
|
& $arduinoCLI core install $platform
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Output "Error install core libraries"
|
||||||
|
Exit
|
||||||
|
}
|
||||||
|
|
||||||
|
<############################################
|
||||||
|
Upload the sketch to the selected board
|
||||||
|
############################################>
|
||||||
|
#$arduinoCLI upload -b fqbn -p port $commandStationDirectory
|
||||||
|
Write-Output "Compiling and uploading to $deviceName on $devicePort"
|
||||||
|
try {
|
||||||
|
$output = & $arduinoCLI compile -b $deviceFQBN -u -t -p $devicePort $commandStationDirectory --format jsonmini | ConvertFrom-Json
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Output "Failed to compile"
|
||||||
|
Exit
|
||||||
|
}
|
||||||
|
if ($output.success -eq "True") {
|
||||||
|
Write-Output "`r`nCongratulations! DCC-EX EX-CommandStation $($userList[$userSelection]) has been installed on your $deviceName`r`n"
|
||||||
|
} else {
|
||||||
|
Write-Output "`r`nThere was an error installing $($userList[$userSelection]) on your $($deviceName), please take note of the errors provided:`r`n"
|
||||||
|
if ($null -ne $output.compiler_err) {
|
||||||
|
Write-Output "Compiler error: $($output.compiler_err)`r`n"
|
||||||
|
}
|
||||||
|
if ($null -ne $output.builder_result) {
|
||||||
|
Write-Output "Builder result: $($output.builder_result)`r`n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output "`r`nPress any key to exit the installer"
|
||||||
|
[void][System.Console]::ReadKey($true)
|
|
@ -69,10 +69,10 @@ else
|
||||||
# need to do this config better
|
# need to do this config better
|
||||||
cp -p config.example.h config.h
|
cp -p config.example.h config.h
|
||||||
fi
|
fi
|
||||||
need curl
|
|
||||||
if test -x "$ACLI" ; then
|
if test -x "$ACLI" ; then
|
||||||
: all well
|
: all well
|
||||||
else
|
else
|
||||||
|
need curl
|
||||||
curl "$ACLIINSTALL" > acliinstall.sh
|
curl "$ACLIINSTALL" > acliinstall.sh
|
||||||
chmod +x acliinstall.sh
|
chmod +x acliinstall.sh
|
||||||
./acliinstall.sh
|
./acliinstall.sh
|
||||||
|
|
|
@ -23,7 +23,9 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// This is the startup sequence, AKA SEQUENCE(0)
|
// This is the startup sequence,
|
||||||
|
AUTOSTART
|
||||||
|
POWERON // turn on track power
|
||||||
SENDLOCO(3,1) // send loco 3 off along route 1
|
SENDLOCO(3,1) // send loco 3 off along route 1
|
||||||
SENDLOCO(10,2) // send loco 10 off along route 2
|
SENDLOCO(10,2) // send loco 10 off along route 2
|
||||||
DONE // This just ends the startup thread, leaving 2 others running.
|
DONE // This just ends the startup thread, leaving 2 others running.
|
||||||
|
|
465
myHal.cpp.txt
Normal file
465
myHal.cpp.txt
Normal file
|
@ -0,0 +1,465 @@
|
||||||
|
#include "defines.h"
|
||||||
|
#include "IODevice.h"
|
||||||
|
|
||||||
|
#ifndef IO_NO_HAL
|
||||||
|
|
||||||
|
#include "IO_VL53L0X.h"
|
||||||
|
#include "IO_HCSR04.h"
|
||||||
|
#include "Sensors.h"
|
||||||
|
#include "Turnouts.h"
|
||||||
|
#include "IO_DFPlayer.h"
|
||||||
|
//#include "IO_Wire.h"
|
||||||
|
#include "IO_AnalogueInputs.h"
|
||||||
|
#if __has_include("IO_Servo.h")
|
||||||
|
#include "IO_Servo.h"
|
||||||
|
#include "IO_PCA9685pwm.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "IO_HALDisplay.h"
|
||||||
|
#include "LiquidCrystal_I2C.h"
|
||||||
|
|
||||||
|
#if __has_include("IO_CMRI.h")
|
||||||
|
#include "IO_CMRI.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//#include "IO_ExampleSerial.h"
|
||||||
|
|
||||||
|
//#include "IO_EXFastclock.h"
|
||||||
|
//#include "IO_EXTurntable.h"
|
||||||
|
|
||||||
|
#if __has_include("IO_ExternalEEPROM.h")
|
||||||
|
#include "IO_ExternalEEPROM.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __has_include("IO_Network.h")
|
||||||
|
#include "IO_Network.h"
|
||||||
|
#include "Net_RF24.h"
|
||||||
|
#include "Net_ENC28J60.h"
|
||||||
|
#include "Net_Ethernet.h"
|
||||||
|
#define NETWORK_PRESENT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "IO_TouchKeypad.h"
|
||||||
|
|
||||||
|
#define WIRE_TEST 0
|
||||||
|
#define TESTHARNESS 1
|
||||||
|
#define I2C_STRESS_TEST 0
|
||||||
|
#define I2C_SETCLOCK 0
|
||||||
|
|
||||||
|
#include "DCC.h"
|
||||||
|
|
||||||
|
|
||||||
|
#if 0 // Long Strings
|
||||||
|
#define s10 "0123456789"
|
||||||
|
#define s100 s10 s10 s10 s10 s10 s10 s10 s10 s10 s10
|
||||||
|
#define s1k s100 s100 s100 s100 s100 s100 s100 s100 s100 s100
|
||||||
|
#define s10k s1k s1k s1k s1k s1k s1k s1k s1k s1k s1k
|
||||||
|
#define s32k s10k s10k s10k s1k s1k
|
||||||
|
volatile const char PROGMEM ss1[] = s32k;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#if TESTHARNESS
|
||||||
|
|
||||||
|
// Function to be invoked by test harness
|
||||||
|
void myTest() {
|
||||||
|
// DIAG(F("VL53L0X #1 Test: dist=%d signal=%d ambient=%d value=%d"),
|
||||||
|
// IODevice::readAnalogue(5000),
|
||||||
|
// IODevice::readAnalogue(5001),
|
||||||
|
// IODevice::readAnalogue(5002),
|
||||||
|
// IODevice::read(5000));
|
||||||
|
// DIAG(F("VL53L0X #2 Test: dist=%d signal=%d ambient=%d value=%d"),
|
||||||
|
// IODevice::readAnalogue(5003),
|
||||||
|
// IODevice::readAnalogue(5004),
|
||||||
|
// IODevice::readAnalogue(5005),
|
||||||
|
// IODevice::read(5003));
|
||||||
|
// DIAG(F("HCSR04 Test: dist=%d value=%d"),
|
||||||
|
// IODevice::readAnalogue(2000),
|
||||||
|
// IODevice::read(2000));
|
||||||
|
// DIAG(F("ADS111x Test: %d %d %d %d %d"),
|
||||||
|
// IODevice::readAnalogue(4500),
|
||||||
|
// IODevice::readAnalogue(4501),
|
||||||
|
// IODevice::readAnalogue(4502),
|
||||||
|
// IODevice::readAnalogue(4503),
|
||||||
|
// IODevice::readAnalogue(A5)
|
||||||
|
// );
|
||||||
|
// DIAG(F("RF24 Test: 4000:%d 4002:%d"),
|
||||||
|
// IODevice::read(4000),
|
||||||
|
// IODevice::read(4002)
|
||||||
|
// );
|
||||||
|
DIAG(F("EXPANDER: 2212:%d 2213:%d 2214:%d"),
|
||||||
|
IODevice::readAnalogue(2212),
|
||||||
|
IODevice::readAnalogue(2213),
|
||||||
|
IODevice::readAnalogue(2214));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if I2C_STRESS_TEST
|
||||||
|
static bool initialised = false;
|
||||||
|
static uint8_t lastStatus = 0;
|
||||||
|
static const int nRBs = 3; // request blocks concurrently
|
||||||
|
static const int I2cTestPeriod = 1; // milliseconds
|
||||||
|
static I2CAddress testDevice = {SubBus_6, 0x27};
|
||||||
|
static I2CRB rb[nRBs];
|
||||||
|
static uint8_t readBuffer[nRBs*32]; // nRB x 32-byte input buffer
|
||||||
|
static uint8_t writeBuffer[nRBs]; // nRB x 1-byte output buffer
|
||||||
|
static unsigned long count = 0;
|
||||||
|
static unsigned long errors = 0;
|
||||||
|
static unsigned long lastOutput = millis();
|
||||||
|
|
||||||
|
void I2CTest() {
|
||||||
|
if (!initialised) {
|
||||||
|
// I2C Loading for stress test.
|
||||||
|
// Write value then read back 32 times
|
||||||
|
for (int i=0; i<nRBs; i++) {
|
||||||
|
writeBuffer[i] = (0xc5 ^ i ^ i<<3 ^ i<<6) & ~0x08; // bit corresponding to 08 is hard-wired low
|
||||||
|
rb[i].setRequestParams(testDevice, &readBuffer[i*32], 32,
|
||||||
|
&writeBuffer[i], 1);
|
||||||
|
I2CManager.queueRequest(&rb[i]);
|
||||||
|
}
|
||||||
|
initialised = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i=0; i<nRBs; i++) {
|
||||||
|
if (!rb[i].isBusy()) {
|
||||||
|
count++;
|
||||||
|
uint8_t status = rb[i].status;
|
||||||
|
if (status != lastStatus) {
|
||||||
|
DIAG(F("I2CTest: status=%d (%S)"),
|
||||||
|
(int)status, I2CManager.getErrorMessage(status));
|
||||||
|
lastStatus = status;
|
||||||
|
}
|
||||||
|
if (status == I2C_STATUS_OK) {
|
||||||
|
bool diff = false;
|
||||||
|
// Check contents of response
|
||||||
|
for (uint8_t j=0; j<32; j++) {
|
||||||
|
if (readBuffer[i*32+j] != writeBuffer[i]) {
|
||||||
|
DIAG(F("I2CTest: Received message mismatch, sent %2x rcvd %2x"),
|
||||||
|
writeBuffer[i], readBuffer[i*32+j]);
|
||||||
|
diff = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (diff) errors++;
|
||||||
|
} else
|
||||||
|
errors++;
|
||||||
|
I2CManager.queueRequest(&rb[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (millis() - lastOutput > 60000) { // 1 minute
|
||||||
|
DIAG(F("I2CTest: Count=%l Errors=%l"), count, errors);
|
||||||
|
count = errors = 0;
|
||||||
|
lastOutput = millis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void updateLocoScreen() {
|
||||||
|
for (int i=0; i<8; i++) {
|
||||||
|
if (DCC::speedTable[i].loco > 0) {
|
||||||
|
int speed = DCC::speedTable[i].speedCode;
|
||||||
|
char direction = (speed & 0x80) ? 'R' : 'F';
|
||||||
|
speed = speed & 0x7f;
|
||||||
|
if (speed > 0) speed = speed - 1;
|
||||||
|
SCREEN(3, i, F("Loco:%4d %3d %c"), DCC::speedTable[i].loco,
|
||||||
|
speed, direction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateTime() {
|
||||||
|
uint8_t buffer[20];
|
||||||
|
I2CAddress rtc = {SubBus_1, 0x68}; // Real-time clock I2C address
|
||||||
|
buffer[0] = 0;
|
||||||
|
|
||||||
|
// Set time - only needs to be done once if battery is ok.
|
||||||
|
static bool timeSet = false;
|
||||||
|
if (!timeSet) {
|
||||||
|
// I2CManager.read(rtc, buffer+1, sizeof(buffer)-1);
|
||||||
|
// uint8_t year = 23; // 2023
|
||||||
|
// uint8_t day = 2; // tuesday
|
||||||
|
// uint8_t date = 21; // 21st
|
||||||
|
// uint8_t month = 2; // feb
|
||||||
|
// uint8_t hours = 23; // xx:
|
||||||
|
// uint8_t minutes = 25; // :xx
|
||||||
|
// buffer[1] = 0; // seconds
|
||||||
|
// buffer[2] = ((minutes / 10) << 4) | (minutes % 10);
|
||||||
|
// buffer[3] = ((hours / 10) << 4) | (hours % 10);
|
||||||
|
// buffer[4] = day;
|
||||||
|
// buffer[5] = ((date/10) << 4) + date%10; // 24th
|
||||||
|
// buffer[6] = ((month/10) << 4) + month%10; // feb
|
||||||
|
// buffer[7] = ((year/10) << 4) + year%10; // xx23
|
||||||
|
// for (uint8_t i=8; i<sizeof(buffer); i++) buffer[i] = 0;
|
||||||
|
// I2CManager.write(rtc, buffer, sizeof(buffer));
|
||||||
|
timeSet = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t status = I2CManager.read(rtc, buffer+1, sizeof(buffer)-1, 1, 0);
|
||||||
|
if (status == I2C_STATUS_OK) {
|
||||||
|
uint8_t seconds10 = buffer[1] >> 4;
|
||||||
|
uint8_t seconds1 = buffer[1] & 0xf;
|
||||||
|
uint8_t minutes10 = buffer[2] >> 4;
|
||||||
|
uint8_t minutes1 = buffer[2] & 0xf;
|
||||||
|
uint8_t hours10 = buffer[3] >> 4;
|
||||||
|
uint8_t hours1 = buffer[3] & 0xf;
|
||||||
|
SCREEN(10, 0, F("Departures %d%d:%d%d:%d%d"),
|
||||||
|
hours10, hours1, minutes10, minutes1, seconds10, seconds1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void showCharacterSet() {
|
||||||
|
if (millis() < 3000) return;
|
||||||
|
const uint8_t lineLen = 20;
|
||||||
|
char buffer[lineLen+1];
|
||||||
|
static uint8_t nextChar = 0x20;
|
||||||
|
for (uint8_t row=0; row<8; row+=1) {
|
||||||
|
for (uint8_t col=0; col<lineLen; col++) {
|
||||||
|
buffer[col] = nextChar++;
|
||||||
|
buffer[++col] = ' ';
|
||||||
|
if (nextChar == 0) nextChar = 0x20; // check for wrap-around
|
||||||
|
}
|
||||||
|
buffer[lineLen] = '\0';
|
||||||
|
SCREEN(3, row, F("%s"), buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(ARDUINO_NUCLEO_F446RE)
|
||||||
|
HardwareSerial Serial3(PC11, PC10);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// HAL device initialisation
|
||||||
|
void halSetup() {
|
||||||
|
|
||||||
|
I2CManager.setTimeout(500); // microseconds
|
||||||
|
I2CManager.forceClock(400000);
|
||||||
|
|
||||||
|
HALDisplay<OLED>::create(10, {SubBus_5, 0x3c}, 132, 64); // SH1106
|
||||||
|
// UserAddin::create(updateLocoScreen, 1000);
|
||||||
|
// UserAddin::create(showCharacterSet, 5000);
|
||||||
|
// UserAddin::create(updateTime, 1000);
|
||||||
|
|
||||||
|
HALDisplay<OLED>::create(10, {SubBus_4, 0x3c}, 128, 32);
|
||||||
|
HALDisplay<OLED>::create(10, {SubBus_7, 0x3c}, 128, 32);
|
||||||
|
|
||||||
|
//HALDisplay<LiquidCrystal_I2C>::create(10, {SubBus_4, 0x27}, 20, 4);
|
||||||
|
|
||||||
|
// Draw double boxes with X O O X inside.
|
||||||
|
// SCREEN(3, 2, F("\xc9\xcd\xcd\xcd\xcb\xcd\xcd\xcd\xcb\xcd\xcd\xcd\xcb\xcd\xcd\xcd\xcb\xcd\xcd\xcd\xbb"));
|
||||||
|
// SCREEN(3, 3, F("\xba X \xba O \xba O \xba O \xba X \xba"));
|
||||||
|
// SCREEN(3, 4, F("\xcc\xcd\xcd\xcd\xce\xcd\xcd\xcd\xce\xcd\xcd\xcd\xce\xcd\xcd\xcd\xce\xcd\xcd\xcd\xb9"));
|
||||||
|
// SCREEN(3, 5, F("\xba X \xba O \xba O \xba O \xba X \xba"));
|
||||||
|
// SCREEN(3, 6, F("\xc8\xcd\xcd\xcd\xca\xcd\xcd\xcd\xca\xcd\xcd\xcd\xca\xcd\xcd\xcd\xca\xcd\xcd\xcd\xbc"));
|
||||||
|
|
||||||
|
// Draw single boxes with X O O X inside.
|
||||||
|
// SCREEN(3, 0, F("Summary Data:"));
|
||||||
|
// SCREEN(3, 1, F("\xda\xc4\xc4\xc4\xc2\xc4\xc4\xc4\xc2\xc4\xc4\xc4\xc2\xc4\xc4\xc4\xc2\xc4\xc4\xc4\xbf"));
|
||||||
|
// SCREEN(3, 2, F("\xb3 X \xb3 O \xb3 O \xb3 O \xb3 X \xb3"));
|
||||||
|
// SCREEN(3, 3, F("\xc3\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xb4"));
|
||||||
|
// SCREEN(3, 4, F("\xb3 X \xb3 O \xb3 O \xb3 O \xb3 X \xb3"));
|
||||||
|
// SCREEN(3, 5, F("\xc3\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xb4"));
|
||||||
|
// SCREEN(3, 6, F("\xb3 X \xb3 O \xb3 O \xb3 O \xb3 X \xb3"));
|
||||||
|
// SCREEN(3, 7, F("\xc0\xc4\xc4\xc4\xc1\xc4\xc4\xc4\xc1\xc4\xc4\xc4\xc1\xc4\xc4\xc4\xc1\xc4\xc4\xc4\xd9"));
|
||||||
|
|
||||||
|
// Blocks of different greyness
|
||||||
|
// SCREEN(3, 0, F("\xb0\xb0\xb0\xb0\xb1\xb1\xb1\xb1\xb2\xb2\xb2\xb2\xdb\xdb\xdb\xdb"));
|
||||||
|
// SCREEN(3, 1, F("\xb0\xb0\xb0\xb0\xb1\xb1\xb1\xb1\xb2\xb2\xb2\xb2\xdb\xdb\xdb\xdb"));
|
||||||
|
// SCREEN(3, 2, F("\xb0\xb0\xb0\xb0\xb1\xb1\xb1\xb1\xb2\xb2\xb2\xb2\xdb\xdb\xdb\xdb"));
|
||||||
|
|
||||||
|
// DCCEX logo
|
||||||
|
// SCREEN(3, 1, F("\xb0\xb0\x20\x20\x20\xb0\x20\x20\x20\xb0\x20\x20\x20\x20\xb0\xb0\xb0\x20\xb0\x20\xb0"));
|
||||||
|
// SCREEN(3, 2, F("\xb0\x20\xb0\x20\xb0\x20\xb0\x20\xb0\x20\xb0\x20\x20\x20\xb0\x20\x20\x20\xb0\x20\xb0"));
|
||||||
|
// SCREEN(3, 3, F("\xb0\x20\xb0\x20\xb0\x20\x20\x20\xb0\x20\x20\x20\xb0\x20\xb0\xb0\x20\x20\x20\xb0\x20"));
|
||||||
|
// SCREEN(3, 4, F("\xb0\x20\xb0\x20\xb0\x20\xb0\x20\xb0\x20\xb0\x20\x20\x20\xb0\x20\x20\x20\xb0\x20\xb0"));
|
||||||
|
// SCREEN(3, 5, F("\xb0\xb0\x20\x20\x20\xb0\x20\x20\x20\xb0\x20\x20\x20\x20\xb0\xb0\xb0\x20\xb0\x20\xb0"));
|
||||||
|
// SCREEN(3, 7, F("\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1"));
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// List versions of devices that respond to the version request
|
||||||
|
for (uint8_t address = 8; address<0x78; address++) {
|
||||||
|
uint8_t buffer[3];
|
||||||
|
uint8_t status = I2CManager.read(0x7c, buffer, sizeof(buffer), 1, address);
|
||||||
|
if (status == I2C_STATUS_OK) {
|
||||||
|
uint16_t manufacturer = ((uint16_t)buffer[0] << 4 ) | (buffer[1] >> 4);
|
||||||
|
uint16_t deviceID = ((uint16_t)(buffer[1] & 0x0f) << 5) | (buffer[2] >> 3);
|
||||||
|
uint16_t dieRevision = buffer[2] & 0x1f;
|
||||||
|
DIAG(F("Addr %s version: %x %x %x"), address.toString(), manufacturer, deviceID, dieRevision);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if I2C_STRESS_TEST
|
||||||
|
UserAddin::create(I2CTest, I2cTestPeriod);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if WIRE_TEST
|
||||||
|
// Test of Wire-I2CManager interface
|
||||||
|
Wire.begin();
|
||||||
|
Wire.setClock(400000);
|
||||||
|
Wire.beginTransmission(0x23);
|
||||||
|
Wire.print("Hello");
|
||||||
|
uint8_t status = Wire.endTransmission();
|
||||||
|
if (status==0) DIAG(F("Wire: device Found on 0x23"));
|
||||||
|
|
||||||
|
Wire.beginTransmission(0x23);
|
||||||
|
Wire.write(0xde);
|
||||||
|
Wire.endTransmission(false); // don't send stop
|
||||||
|
Wire.requestFrom(0x23, 1);
|
||||||
|
if (Wire.available()) {
|
||||||
|
DIAG(F("Wire: value=x%x"), Wire.read());
|
||||||
|
}
|
||||||
|
uint8_t st = I2CManager.write(0x33, 0, 0);
|
||||||
|
DIAG(F("I2CManager 0x33 st=%d \"%S\""), st,
|
||||||
|
I2CManager.getErrorMessage(st));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if I2C_SETCLOCK
|
||||||
|
// Test I2C clock changes
|
||||||
|
// Set up two I2C request blocks
|
||||||
|
I2CRB rb1, rb2;
|
||||||
|
uint8_t readBuff[32];
|
||||||
|
rb1.setRequestParams(0x23, readBuff, sizeof(readBuff), readBuff, sizeof(readBuff));
|
||||||
|
rb2.setRequestParams(0x23, readBuff, sizeof(readBuff), readBuff, sizeof(readBuff));
|
||||||
|
// First set clock to 400kHz and then issue requests
|
||||||
|
I2CManager.forceClock(400000);
|
||||||
|
I2CManager.queueRequest(&rb1);
|
||||||
|
I2CManager.queueRequest(&rb2);
|
||||||
|
// Wait a little to allow the first transaction to start
|
||||||
|
delayMicroseconds(2);
|
||||||
|
// ... then request a clock speed change
|
||||||
|
I2CManager.forceClock(100000);
|
||||||
|
DIAG(F("I2CClock: rb1 status=%d"), rb1.wait());
|
||||||
|
DIAG(F("I2CClock: rb2 status=%d"), rb2.wait());
|
||||||
|
// Reset clock speed
|
||||||
|
I2CManager.forceClock(400000);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
EXIOExpander::create(2200, 18, {SubBus_0, 0x65});
|
||||||
|
//UserAddin::create(myTest, 1000);
|
||||||
|
// ServoTurnout::create(2200, 2200, 400, 200, 0);
|
||||||
|
// ServoTurnout::create(2200, 2200, 400, 200, 0);
|
||||||
|
|
||||||
|
TouchKeypad::create(2300, 16, 25, 24);
|
||||||
|
|
||||||
|
// GPIO
|
||||||
|
PCF8574::create(800, 8, {SubBus_1, 0x23});
|
||||||
|
//PCF8574::create(808, 8, {SubBus_2, 0x27});
|
||||||
|
PCF8574::create(65000, 8, 0x27);
|
||||||
|
|
||||||
|
MCP23017::create(164,16,{SubBus_3, 0x20});
|
||||||
|
//MCP23017::create(180,16,{SubBus_0, 0x27});
|
||||||
|
Sensor::create(170, 170, 1); // Hall effect, enable pullup.
|
||||||
|
Sensor::create(171, 171, 1);
|
||||||
|
|
||||||
|
// PWM (LEDs and Servos)
|
||||||
|
// For servos, use default 50Hz pulses.
|
||||||
|
PCA9685::create(100, 16, {SubBus_1, 0x41});
|
||||||
|
// For LEDs, use 1kHz pulses.
|
||||||
|
PCA9685::create(116, 16, {SubBus_1, 0x40}, 1000);
|
||||||
|
|
||||||
|
// 4-pin Analogue Input Module
|
||||||
|
//ADS111x::create(4500, 4, 0x48);
|
||||||
|
|
||||||
|
// Laser Time-Of-Flight Sensors
|
||||||
|
VL53L0X::create(5000, 3, {SubBus_0, 0x60}, 300, 310, 46);
|
||||||
|
//VL53L0X::create(5003, 3, {SubBus_6, 0x61}, 300, 310, 47);
|
||||||
|
Sensor::create(5000, 5000, 0);
|
||||||
|
Sensor::create(5003, 5003, 0);
|
||||||
|
// Monitor reset digital on first TOF
|
||||||
|
//Sensor::create(46,46,0);
|
||||||
|
|
||||||
|
// // External 24C256 EEPROM (256kBytes) on I2C address 0x50.
|
||||||
|
// ExternalEEPROM::create({SubBus_0, 0x50}, 256);
|
||||||
|
|
||||||
|
// Play up to 10 sounds on pins 10000-10009. Player is connected to Serial1 or Serial2.
|
||||||
|
#if defined(HAVE_HWSERIAL1) && !defined(ARDUINO_ARCH_STM32)
|
||||||
|
DFPlayer::create(10000, 14, Serial1);
|
||||||
|
#elif defined(ARDUINO_ARCH_STM32)
|
||||||
|
DFPlayer::create(10000, 10, Serial3); // Pins PC11 (RX) and PC10 (TX)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Ultrasound echo device
|
||||||
|
HCSR04::create(2000, 32, 33, 80, 85 /*, HCSR04::LOOP */);
|
||||||
|
Sensor::create(2000, 2000, 0);
|
||||||
|
|
||||||
|
#if __has_include("IO_CMRI.h")
|
||||||
|
CMRIbus::create(0, Serial2, 115200, 50, 40); // 50ms cycle, pin 40 for DE/!RE pins
|
||||||
|
CMRInode::create(25000, 72, 0, 0, 'M'); // SMINI address 0
|
||||||
|
for (int pin=0; pin<24; pin++) {
|
||||||
|
Sensor::create(25000+pin, 25000+pin, 0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//CMRInode::create(25072, 72, 0, 13, 'M'); // SMINI address 13
|
||||||
|
//CMRInode::create(25144, 288, 0, 14, 'C', 144, 144); // CPNODE address 14
|
||||||
|
|
||||||
|
#ifdef NETWORK_PRESENT
|
||||||
|
// Define remote pins to be used. The range of remote pins is like a common data area shared
|
||||||
|
// between all nodes.
|
||||||
|
// For outputs, a write to a remote VPIN causes a message to be sent to another node, which then performs
|
||||||
|
// the write operation on the device VPIN that is local to that node.
|
||||||
|
// For inputs, the state of remote input VPIN is read on the node where it is connected, and then
|
||||||
|
// sent to other nodes in the system where the state is saved and processed. Updates are sent on change, and
|
||||||
|
// also periodically if no changes.
|
||||||
|
//
|
||||||
|
// Each definition is a triple of remote node, remote pin, indexed by relative pin. Up to 224 rpins can
|
||||||
|
// be configured (per node). This is to fit into a 32-byte packet.
|
||||||
|
REMOTEPINS rpins[] = {
|
||||||
|
{30,164,RPIN_IN} , //4000 Node 30, first MCP23017 pin, input
|
||||||
|
{30,165,RPIN_IN}, //4001 Node 30, second MCP23017 pin, input
|
||||||
|
{30,166,RPIN_OUT}, //4002 Node 30, third MCP23017 pin, output
|
||||||
|
{30,166,RPIN_OUT}, //4003 Node 30, fourth MCP23017 pin, output
|
||||||
|
{30,100,RPIN_INOUT}, //4004 Node 30, first PCA9685 servo pin
|
||||||
|
{30,101,RPIN_INOUT}, //4005 Node 30, second PCA9685 servo pin
|
||||||
|
{30,102,RPIN_INOUT}, //4006 Node 30, third PCA9685 servo pin
|
||||||
|
{30,103,RPIN_INOUT}, //4007 Node 30, fourth PCA9685 servo pin
|
||||||
|
{30,24,RPIN_IN}, //4008 Node 30, Arduino pin D24
|
||||||
|
{30,25,RPIN_IN}, //4009 Node 30, Arduino pin D25
|
||||||
|
{30,26,RPIN_IN}, //4010 Node 30, Arduino pin D26
|
||||||
|
{30,27,RPIN_IN}, //4011 Node 30, Arduino pin D27
|
||||||
|
{30,1000,RPIN_OUT}, //4012 Node 30, DFPlayer playing flag (when read) / Song selector (when written)
|
||||||
|
{30,5000,RPIN_IN}, //4013 Node 30, VL53L0X detect pin
|
||||||
|
{30,VPIN_NONE,0}, //4014 Node 30, spare
|
||||||
|
{30,VPIN_NONE,0}, //4015 Node 30, spare
|
||||||
|
|
||||||
|
{31,164,RPIN_IN} , //4016 Node 31, first MCP23017 pin, input
|
||||||
|
{31,165,RPIN_IN}, //4017 Node 31, second MCP23017 pin, input
|
||||||
|
{31,166,RPIN_OUT}, //4018 Node 31, third MCP23017 pin, output
|
||||||
|
{31,166,RPIN_OUT}, //4019 Node 31, fourth MCP23017 pin, output
|
||||||
|
{31,100,RPIN_INOUT}, //4020 Node 31, first PCA9685 servo pin
|
||||||
|
{31,101,RPIN_INOUT}, //4021 Node 31, second PCA9685 servo pin
|
||||||
|
{31,102,RPIN_INOUT}, //4022 Node 31, third PCA9685 servo pin
|
||||||
|
{31,103,RPIN_INOUT}, //4023 Node 31, fourth PCA9685 servo pin
|
||||||
|
{31,24,RPIN_IN}, //4024 Node 31, Arduino pin D24
|
||||||
|
{31,25,RPIN_IN}, //4025 Node 31, Arduino pin D25
|
||||||
|
{31,26,RPIN_IN}, //4026 Node 31, Arduino pin D26
|
||||||
|
{31,27,RPIN_IN}, //4027 Node 31, Arduino pin D27
|
||||||
|
{31,3,RPIN_IN}, //4028 Node 31, Arduino pin D3
|
||||||
|
{31,VPIN_NONE,0}, //4029 Node 31, spare
|
||||||
|
{31,VPIN_NONE,0}, //4030 Node 31, spare
|
||||||
|
{31,VPIN_NONE,0} //4031 Node 31, spare
|
||||||
|
};
|
||||||
|
// FirstVPIN, nPins, thisNode, pinDefs, CEPin, CSNPin
|
||||||
|
// Net_RF24 *rf24Driver = new Net_RF24(48, 49);
|
||||||
|
// Network<Net_RF24>::create(4000, NUMREMOTEPINS(rpins), NODE, rpins, rf24Driver);
|
||||||
|
#if NODE==30
|
||||||
|
//Net_ENC28J60 *encDriver = new Net_ENC28J60(49);
|
||||||
|
//Network<Net_ENC28J60>::create(4000, NUMREMOTEPINS(rpins), NODE, rpins, encDriver);
|
||||||
|
#elif NODE==31
|
||||||
|
Net_ENC28J60 *encDriver = new Net_ENC28J60(53);
|
||||||
|
Network<Net_ENC28J60>::create(4000, NUMREMOTEPINS(rpins), NODE, rpins, encDriver);
|
||||||
|
#else
|
||||||
|
Net_Ethernet *etherDriver = new Net_Ethernet();
|
||||||
|
Network<Net_Ethernet>::create(4000, NUMREMOTEPINS(rpins), NODE, rpins, etherDriver);
|
||||||
|
#endif
|
||||||
|
for (int i=0; i<=32; i++)
|
||||||
|
Sensor::create(4000+i, 4000+i, 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ARDUINO_ARCH_STM32
|
||||||
|
//PCF8574::create(1900, 8, 0x27);
|
||||||
|
Sensor::create(1900,100,1);
|
||||||
|
Sensor::create(1901,101,1);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif // IO_NO_HAL
|
|
@ -17,9 +17,11 @@
|
||||||
|
|
||||||
// Include devices you need.
|
// Include devices you need.
|
||||||
#include "IODevice.h"
|
#include "IODevice.h"
|
||||||
#include "IO_HCSR04.h" // Ultrasonic range sensor
|
//#include "IO_HALDisplay.h" // Auxiliary display devices (LCD/OLED)
|
||||||
#include "IO_VL53L0X.h" // Laser time-of-flight sensor
|
//#include "IO_HCSR04.h" // Ultrasonic range sensor
|
||||||
#include "IO_DFPlayer.h" // MP3 sound player
|
//#include "IO_VL53L0X.h" // Laser time-of-flight sensor
|
||||||
|
//#include "IO_DFPlayer.h" // MP3 sound player
|
||||||
|
//#include "IO_TouchKeypad.h // Touch keypad with 16 keys
|
||||||
//#include "IO_EXTurntable.h" // Turntable-EX turntable controller
|
//#include "IO_EXTurntable.h" // Turntable-EX turntable controller
|
||||||
//#include "IO_EXFastClock.h" // FastClock driver
|
//#include "IO_EXFastClock.h" // FastClock driver
|
||||||
|
|
||||||
|
@ -31,6 +33,61 @@
|
||||||
|
|
||||||
void halSetup() {
|
void halSetup() {
|
||||||
|
|
||||||
|
//=======================================================================
|
||||||
|
// The following directives define auxiliary display devices.
|
||||||
|
// These can be defined in addition to the system display (display
|
||||||
|
// number 0) that is defined in config.h.
|
||||||
|
// A write to a line which is beyond the length of the screen will overwrite
|
||||||
|
// the bottom line, unless the line number is 255 in which case the
|
||||||
|
// screen contents will scroll up before the text is written to the
|
||||||
|
// bottom line.
|
||||||
|
//=======================================================================
|
||||||
|
//
|
||||||
|
// Create a 128x32 OLED display device as display number 1
|
||||||
|
// (line 0 is written by EX-RAIL 'SCREEN(1, 0, "text")').
|
||||||
|
|
||||||
|
//HALDisplay<OLED>::create(1, 0x3d, 128, 32);
|
||||||
|
|
||||||
|
// Create a 20x4 LCD display device as display number 2
|
||||||
|
// (line 0 is written by EX-RAIL 'SCREEN(2, 0, "text")').
|
||||||
|
|
||||||
|
// HALDisplay<LiquidCrystal>(2, 0x27, 20, 4);
|
||||||
|
|
||||||
|
|
||||||
|
//=======================================================================
|
||||||
|
// User Add-ins
|
||||||
|
//=======================================================================
|
||||||
|
// User add-ins can be created when you want to do something that
|
||||||
|
// can't be done in EX-RAIL but does not merit a HAL driver. The
|
||||||
|
// user add-in is a C++ function that is executed periodically by the
|
||||||
|
// HAL subsystem.
|
||||||
|
|
||||||
|
// Example: The function will be executed once per second and will display,
|
||||||
|
// on screen #3, the first eight entries (assuming an 8-line display)
|
||||||
|
// from the loco speed table.
|
||||||
|
|
||||||
|
// Put the following block of code in myHal.cpp OUTSIDE of the
|
||||||
|
// halSetup() function:
|
||||||
|
//
|
||||||
|
// void updateLocoScreen() {
|
||||||
|
// for (int i=0; i<8; i++) {
|
||||||
|
// if (DCC::speedTable[i].loco > 0) {
|
||||||
|
// int speed = DCC::speedTable[i].speedCode;
|
||||||
|
// char direction = (speed & 0x80) ? 'R' : 'F';
|
||||||
|
// speed = speed & 0x7f;
|
||||||
|
// if (speed > 0) speed = speed - 1;
|
||||||
|
// SCREEN(3, i, F("Loco:%4d %3d %c"), DCC::speedTable[i].loco,
|
||||||
|
// speed, direction);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Put the following line INSIDE the halSetup() function:
|
||||||
|
//
|
||||||
|
// UserAddin::create(updateLocoScreen, 1000);
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
//=======================================================================
|
//=======================================================================
|
||||||
// The following directive defines a PCA9685 PWM Servo driver module.
|
// The following directive defines a PCA9685 PWM Servo driver module.
|
||||||
//=======================================================================
|
//=======================================================================
|
||||||
|
@ -176,6 +233,21 @@ void halSetup() {
|
||||||
// DFPlayer::create(10000, 10, Serial1);
|
// DFPlayer::create(10000, 10, Serial1);
|
||||||
|
|
||||||
|
|
||||||
|
//=======================================================================
|
||||||
|
// 16-pad capacitative touch key pad based on TP229 IC.
|
||||||
|
//=======================================================================
|
||||||
|
// Parameters below:
|
||||||
|
// 11000 = first VPIN allocated
|
||||||
|
// 16 = number of VPINs allocated
|
||||||
|
// 25 = local GPIO pin number for clock signal
|
||||||
|
// 24 = local GPIO pin number for data signal
|
||||||
|
//
|
||||||
|
// Pressing the key pads numbered 1-16 cause each of the nominated digital VPINs
|
||||||
|
// (11000-11015 in this case) to be activated.
|
||||||
|
|
||||||
|
// TouchKeypad::create(11000, 16, 25, 24);
|
||||||
|
|
||||||
|
|
||||||
//=======================================================================
|
//=======================================================================
|
||||||
// The following directive defines an EX-Turntable turntable instance.
|
// The following directive defines an EX-Turntable turntable instance.
|
||||||
//=======================================================================
|
//=======================================================================
|
||||||
|
|
|
@ -20,11 +20,11 @@ default_envs =
|
||||||
ESP32
|
ESP32
|
||||||
Nucleo-F411RE
|
Nucleo-F411RE
|
||||||
Nucleo-F446RE
|
Nucleo-F446RE
|
||||||
Teensy3.2
|
Teensy3_2
|
||||||
Teensy3.5
|
Teensy3_5
|
||||||
Teensy3.6
|
Teensy3_6
|
||||||
Teensy4.0
|
Teensy4_0
|
||||||
Teensy4.1
|
Teensy4_1
|
||||||
src_dir = .
|
src_dir = .
|
||||||
include_dir = .
|
include_dir = .
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ monitor_speed = 115200
|
||||||
monitor_echo = yes
|
monitor_echo = yes
|
||||||
build_flags = -std=c++17
|
build_flags = -std=c++17
|
||||||
|
|
||||||
[env:Arduino M0]
|
[env:Arduino-M0]
|
||||||
platform = atmelsam
|
platform = atmelsam
|
||||||
board = mzeroUSB
|
board = mzeroUSB
|
||||||
framework = arduino
|
framework = arduino
|
||||||
|
@ -192,7 +192,7 @@ build_flags = -std=c++17 -Os -g2 -Wunused-variable -DDIAG_LOOPTIMES ; -DDIAG_IO
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
monitor_echo = yes
|
monitor_echo = yes
|
||||||
|
|
||||||
[env:Teensy3.2]
|
[env:Teensy3_2]
|
||||||
platform = teensy
|
platform = teensy
|
||||||
board = teensy31
|
board = teensy31
|
||||||
framework = arduino
|
framework = arduino
|
||||||
|
@ -200,7 +200,7 @@ build_flags = -std=c++17 -Os -g2
|
||||||
lib_deps = ${env.lib_deps}
|
lib_deps = ${env.lib_deps}
|
||||||
lib_ignore = NativeEthernet
|
lib_ignore = NativeEthernet
|
||||||
|
|
||||||
[env:Teensy3.5]
|
[env:Teensy3_5]
|
||||||
platform = teensy
|
platform = teensy
|
||||||
board = teensy35
|
board = teensy35
|
||||||
framework = arduino
|
framework = arduino
|
||||||
|
@ -208,7 +208,7 @@ build_flags = -std=c++17 -Os -g2
|
||||||
lib_deps = ${env.lib_deps}
|
lib_deps = ${env.lib_deps}
|
||||||
lib_ignore = NativeEthernet
|
lib_ignore = NativeEthernet
|
||||||
|
|
||||||
[env:Teensy3.6]
|
[env:Teensy3_6]
|
||||||
platform = teensy
|
platform = teensy
|
||||||
board = teensy36
|
board = teensy36
|
||||||
framework = arduino
|
framework = arduino
|
||||||
|
@ -216,7 +216,7 @@ build_flags = -std=c++17 -Os -g2
|
||||||
lib_deps = ${env.lib_deps}
|
lib_deps = ${env.lib_deps}
|
||||||
lib_ignore = NativeEthernet
|
lib_ignore = NativeEthernet
|
||||||
|
|
||||||
[env:Teensy4.0]
|
[env:Teensy4_0]
|
||||||
platform = teensy
|
platform = teensy
|
||||||
board = teensy40
|
board = teensy40
|
||||||
framework = arduino
|
framework = arduino
|
||||||
|
@ -224,7 +224,7 @@ build_flags = -std=c++17 -Os -g2
|
||||||
lib_deps = ${env.lib_deps}
|
lib_deps = ${env.lib_deps}
|
||||||
lib_ignore = NativeEthernet
|
lib_ignore = NativeEthernet
|
||||||
|
|
||||||
[env:Teensy4.1]
|
[env:Teensy4_1]
|
||||||
platform = teensy
|
platform = teensy
|
||||||
board = teensy41
|
board = teensy41
|
||||||
framework = arduino
|
framework = arduino
|
||||||
|
|
39
version.h
39
version.h
|
@ -4,7 +4,40 @@
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
|
|
||||||
|
|
||||||
#define VERSION "4.2.20"
|
#define VERSION "4.2.45"
|
||||||
|
// 4.2.45 - Add ONCLOCKMINS to FastClock to allow hourly repeat events
|
||||||
|
// 4.2.44 - Add PowerShell installer EX-CommandStation-installer.exe
|
||||||
|
// 4.2.43 - Fix STM32 set right port mode bits for analog
|
||||||
|
// 4.2.42 - Added EXRAIL TURNOUTL Macro definition
|
||||||
|
// 4.2.41 - Move HAl startup to ASAP in setup()
|
||||||
|
// - Fix DNOU8 output pin setup to all LOW
|
||||||
|
// 4.2.40 - Automatically detect conflicting default I2C devices and disable
|
||||||
|
// 4.2.39 - DFplayer driver now polls device to detect failures and errors.
|
||||||
|
// 4.2.38 - Clean up compiler warning when IO_RotaryEncoder.h included
|
||||||
|
// 4.2.37 - Add new FLAGS HAL device for communications to/from EX-RAIL;
|
||||||
|
// - Fix diag display of high VPINs within IODevice class.
|
||||||
|
// 4.2.36 - do not broadcast a turnout state that has not changed
|
||||||
|
// - Use A2/A3 for current sensing on ESP32 + Motor Shield
|
||||||
|
// 4.2.35 - add <z> direct pin manipulation command
|
||||||
|
// 4.2.34 - Completely fix EX-IOExpander analogue inputs
|
||||||
|
// 4.2.33 - Fix EX-IOExpander non-working analogue inputs
|
||||||
|
// 4.2.32 - Fix LCD/Display bugfixes from 4.2.29
|
||||||
|
// 4.2.31 - Removes EXRAIL statup from top of file. (BREAKING CHANGE !!)
|
||||||
|
// Just add AUTOSTART to the top of your myAutomation.h to restore this function.
|
||||||
|
// 4.2.30 - Fixes/enhancements to EX-IOExpander device driver.
|
||||||
|
// 4.2.29 - Bugfix Scroll LCD without empty lines and consistent
|
||||||
|
// 4.2.28 - Reinstate use of timer11 in STM32 - remove HA mode.
|
||||||
|
// - Update IO_DFPlayer to work with MP3-TF-16P rev3.
|
||||||
|
// 4.2.27 - Bugfix LCD showed random characters in SCROLLMODE 2
|
||||||
|
// 4.2.26 - EX-IOExpander device driver enhancements
|
||||||
|
// - Enhance I2C error checking
|
||||||
|
// - Introduce delays to _loop to allow room for other I2C device comms
|
||||||
|
// - Improve analogue read reliability
|
||||||
|
// 4.2.25 - Bugfix SAMD21 Exrail odd byte boundary
|
||||||
|
// 4.2.24 - Bugfix Ethernet shield: Static IP now possible
|
||||||
|
// 4.2.23 - Bugfix signalpin2 was not set up in shadow port
|
||||||
|
// 4.2.22 - Implement broadcast of Track Manager changes
|
||||||
|
// 4.2.21 - Implement non-blocking I2C for EX-IOExpander device driver
|
||||||
// 4.2.20 - <JG> & <JI> commands for multi-track gauges
|
// 4.2.20 - <JG> & <JI> commands for multi-track gauges
|
||||||
// - Reinstate <c> but remember its a bit useless when TM involved.
|
// - Reinstate <c> but remember its a bit useless when TM involved.
|
||||||
// 4.2.19 - Bugfix for analog reading of track current sensor offset.
|
// 4.2.19 - Bugfix for analog reading of track current sensor offset.
|
||||||
|
@ -43,6 +76,10 @@
|
||||||
// 4.2.11 Exrail IFLOCO feature added
|
// 4.2.11 Exrail IFLOCO feature added
|
||||||
// 4.2.10 SIGNAL/SIGNALH bug fix as they were inverted
|
// 4.2.10 SIGNAL/SIGNALH bug fix as they were inverted
|
||||||
// IO_EXIOExpander.h input speed optimisation
|
// IO_EXIOExpander.h input speed optimisation
|
||||||
|
// ONCLOCK and ONCLOCKTIME command added to EXRAIL for EX-FastCLock
|
||||||
|
// <JC> Serial command added for EX-FastClock
|
||||||
|
// <jC> Broadcast added for EX-FastClock
|
||||||
|
// IO_EXFastClock.h added for I2C FastClock connection
|
||||||
// 4.2.9 duinoNodes support
|
// 4.2.9 duinoNodes support
|
||||||
// 4.2.8 HIGHMEM (EXRAIL support beyond 64kb)
|
// 4.2.8 HIGHMEM (EXRAIL support beyond 64kb)
|
||||||
// Withrottle connect/disconnect improvements
|
// Withrottle connect/disconnect improvements
|
||||||
|
|
Loading…
Reference in New Issue
Block a user