mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-29 02:13:45 +02:00
Compare commits
65 Commits
v4.2.36-De
...
devel-sabe
Author | SHA1 | Date | |
---|---|---|---|
|
9d953c70b8 | ||
|
efdbfcb030 | ||
|
33306219c8 | ||
|
d857c4f2e4 | ||
|
70fae16ab3 | ||
|
f465020e93 | ||
|
235bcc9ff0 | ||
|
d2cc60812d | ||
|
75f274e3b7 | ||
|
ff53b90034 | ||
|
1daa0a9ba9 | ||
|
294b9693c5 | ||
|
16e44eb11a | ||
|
4bad334875 | ||
|
32491854ff | ||
|
3e95372816 | ||
|
05b0fc3d2e | ||
|
bb7cdc5422 | ||
|
6199cecd42 | ||
|
1aae0aed0a | ||
|
1d29be9de6 | ||
|
bfa33a9df7 | ||
|
49c0a1a55a | ||
|
9b7d1ae858 | ||
|
c11d8f6359 | ||
|
3868bb19ac | ||
|
7589917638 | ||
|
e7d9626a72 | ||
|
fe035f4096 | ||
|
4d236446b0 | ||
|
a100d709ce | ||
|
0d82370380 | ||
|
ff6034dff2 | ||
|
5d0de6b807 | ||
|
2e518fcac2 | ||
|
be273454bc | ||
|
751b46b6bb | ||
|
91bc9df44e | ||
|
72ceb63913 | ||
|
d2c7e7fb8d | ||
|
61db37c7ea | ||
|
3a3071f35b | ||
|
ef3d36ae25 | ||
|
c3d2e5b222 | ||
|
9f212c27bf | ||
|
de06c0ae3e | ||
|
273f55b143 | ||
|
86cb8f4666 | ||
|
9571088e1b | ||
|
18a992bf08 | ||
|
de6e91a778 | ||
|
b18df1405c | ||
|
305e0902f4 | ||
|
95c1b1da31 | ||
|
1b4faa92cd | ||
|
6fbaca7930 | ||
|
6b535654f8 | ||
|
2c943d250e | ||
|
89664eff9d | ||
f8b054cf6a | |||
|
d2a8aebd0f | ||
|
86c3020672 | ||
|
60ea7f081a | ||
|
f348857ddb | ||
|
31ecba08d8 |
@@ -2,6 +2,7 @@
|
||||
* © 2022 Harald Barth
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2020 Gregor Baues
|
||||
* © 2022 Colin Murdoch
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -204,6 +205,39 @@ int16_t CommandDistributor::retClockTime() {
|
||||
void CommandDistributor::broadcastLoco(byte slot) {
|
||||
DCC::LOCO * sp=&DCC::speedTable[slot];
|
||||
broadcastReply(COMMAND_TYPE, F("<l %d %d %d %l>\n"), sp->loco,slot,sp->speedCode,sp->functions);
|
||||
#ifdef SABERTOOTH
|
||||
if (Serial2 && sp->loco == SABERTOOTH) {
|
||||
static uint8_t rampingmode = 0;
|
||||
bool direction = (sp->speedCode & 0x80) !=0; // true for forward
|
||||
int32_t speed = sp->speedCode & 0x7f;
|
||||
if (speed == 1) { // emergency stop
|
||||
if (rampingmode != 1) {
|
||||
rampingmode = 1;
|
||||
Serial2.print("R1: 0\r\n");
|
||||
Serial2.print("R2: 0\r\n");
|
||||
}
|
||||
Serial2.print("MD: 0\r\n");
|
||||
} else {
|
||||
if (speed != 0) {
|
||||
// speed is here 2 to 127
|
||||
speed = (speed - 1) * 1625 / 100;
|
||||
speed = speed * (direction ? 1 : -1);
|
||||
// speed is here -2047 to 2047
|
||||
}
|
||||
if (rampingmode != 2) {
|
||||
rampingmode = 2;
|
||||
Serial2.print("R1: 2047\r\n");
|
||||
Serial2.print("R2: 2047\r\n");
|
||||
}
|
||||
Serial2.print("M1: ");
|
||||
Serial2.print(speed);
|
||||
Serial2.print("\r\n");
|
||||
Serial2.print("M2: ");
|
||||
Serial2.print(speed);
|
||||
Serial2.print("\r\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef CD_HANDLE_RING
|
||||
WiThrottle::markForBroadcast(sp->loco);
|
||||
#endif
|
||||
|
@@ -2,6 +2,8 @@
|
||||
* © 2022 Harald Barth
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2020 Gregor Baues
|
||||
* © 2022 Colin Murdoch
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
|
@@ -75,6 +75,9 @@ void setup()
|
||||
|
||||
DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com"));
|
||||
|
||||
// Initialise HAL layer before reading EEprom or setting up MotorDrivers
|
||||
IODevice::begin();
|
||||
|
||||
DISPLAY_START (
|
||||
// This block is still executed for DIAGS if display not in use
|
||||
LCD(0,F("DCC-EX v%S"),F(VERSION));
|
||||
@@ -96,10 +99,7 @@ void setup()
|
||||
#if ETHERNET_ON
|
||||
EthernetInterface::setup();
|
||||
#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,
|
||||
// let's make sure to initialise the ADCee class!
|
||||
ADCee::begin();
|
||||
|
@@ -7,6 +7,7 @@
|
||||
* © 2020-2021 M Steve Todd
|
||||
* © 2020-2021 Fred Decker
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2022 Colin Murdoch
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -967,7 +968,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
break;
|
||||
|
||||
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;
|
||||
|
||||
#if !defined(IO_NO_HAL)
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2022-2023 Paul M. Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021-2022 Harald Barth
|
||||
* © 2021-2023 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -62,6 +62,9 @@ class DCCTimer {
|
||||
static bool isPWMPin(byte pin);
|
||||
static void setPWM(byte pin, bool high);
|
||||
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
|
||||
// by estimation or inspection, that may be used by other
|
||||
// called subroutines. Must be called with interrupts disabled.
|
||||
|
@@ -150,6 +150,45 @@ int DCCTimer::freeMemory() {
|
||||
void DCCTimer::reset() {
|
||||
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) {
|
||||
pinMode(pin, ANALOG);
|
||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||
|
@@ -162,7 +162,7 @@ uint16_t ADCee::usedpins = 0;
|
||||
int * ADCee::analogvals = NULL;
|
||||
|
||||
int ADCee::init(uint8_t pin) {
|
||||
uint id = pin - A0;
|
||||
uint8_t id = pin - A0;
|
||||
int value = 0;
|
||||
|
||||
if (id > NUM_ADC_INPUTS)
|
||||
@@ -210,7 +210,7 @@ int ADCee::read(uint8_t pin, bool fromISR) {
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
void ADCee::scan() {
|
||||
static uint id = 0; // id and mask are the same thing but it is faster to
|
||||
static 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 bool waiting = false;
|
||||
|
||||
|
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* © 2023 Neil McKechnie
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2022-23 Paul M. Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Harald Barth
|
||||
* © 2021, 2023 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* © 2021 Chris Harlow
|
||||
* © 2021 David Cutting
|
||||
@@ -43,11 +43,18 @@ HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14
|
||||
HardwareSerial Serial1(PA10, PB6); // Rx=PA10 (D2), Tx=PB6 (D10) -- CN10 pins 17 and 9 - F446RE
|
||||
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
|
||||
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
|
||||
// NB: USART3 and USART6 are available but as yet undefined
|
||||
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||
// Nucleo-144 boards don't have Serial1 defined by default
|
||||
HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- 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
|
||||
#warning Serial1 not defined
|
||||
#error STM32 board selected is not yet explicitly supported - so Serial1 peripheral is not defined
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -227,6 +234,9 @@ void DCCTimer::reset() {
|
||||
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
|
||||
|
||||
// TODO: may need to use uint32_t on STMF4xx variants with > 16 analog inputs!
|
||||
#if defined(ARDUINO_NUCLEO_F446RE) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||
#warning STM32 board selected not fully supported - only use ADC1 inputs 0-15 for current sensing!
|
||||
#endif
|
||||
uint16_t ADCee::usedpins = 0;
|
||||
int * ADCee::analogvals = NULL;
|
||||
uint32_t * analogchans = NULL;
|
||||
@@ -237,10 +247,13 @@ int16_t ADCee::ADCmax() {
|
||||
}
|
||||
|
||||
int ADCee::init(uint8_t pin) {
|
||||
uint id = pin - A0;
|
||||
|
||||
int value = 0;
|
||||
PinName stmpin = digitalPin[analogInputPin[id]];
|
||||
uint32_t stmgpio = stmpin / 16; // 16-bits per GPIO port group on STM32
|
||||
PinName stmpin = analogInputToPinName(pin);
|
||||
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!)
|
||||
GPIO_TypeDef * gpioBase;
|
||||
|
||||
@@ -258,12 +271,20 @@ int ADCee::init(uint8_t pin) {
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; //Power up PORTC
|
||||
gpioBase = GPIOC;
|
||||
break;
|
||||
default:
|
||||
return -1023; // some silly value as error
|
||||
}
|
||||
|
||||
// Set pin mux mode to analog input
|
||||
gpioBase->MODER |= (0b011 << (stmpin << 1)); // Set pin mux to analog mode
|
||||
// Set pin mux mode to analog input, the 32 bit port mode register has 2 bits per pin
|
||||
gpioBase->MODER |= (0b011 << (STM_PIN(stmpin) << 1)); // Set pin mux to analog mode (binary 11)
|
||||
|
||||
// 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)
|
||||
ADC1->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
|
||||
else
|
||||
@@ -275,8 +296,12 @@ int ADCee::init(uint8_t pin) {
|
||||
while(!(ADC1->SR & (1 << 1))); // Wait until conversion is complete
|
||||
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));
|
||||
analogchans = (uint32_t *)calloc(NUM_ADC_INPUTS+1, sizeof(uint32_t));
|
||||
}
|
||||
@@ -291,7 +316,7 @@ int ADCee::init(uint8_t pin) {
|
||||
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
|
||||
*/
|
||||
int ADCee::read(uint8_t pin, bool fromISR) {
|
||||
uint8_t id = pin - A0;
|
||||
uint8_t id = pin - PNUM_ANALOG_BASE;
|
||||
// Was this pin initialised yet?
|
||||
if ((usedpins & (1<<id) ) == 0)
|
||||
return -1023;
|
||||
@@ -306,7 +331,7 @@ int ADCee::read(uint8_t pin, bool fromISR) {
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
void ADCee::scan() {
|
||||
static uint id = 0; // id and mask are the same thing but it is faster to
|
||||
static 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 bool waiting = false;
|
||||
|
||||
@@ -368,4 +393,4 @@ void ADCee::begin() {
|
||||
ADC1->CR2 |= (1 << 0); // Switch on ADC1
|
||||
interrupts();
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
@@ -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.
17
EXRAIL2.cpp
17
EXRAIL2.cpp
@@ -2,6 +2,7 @@
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021-2023 Harald Barth
|
||||
* © 2020-2023 Chris Harlow
|
||||
* © 2022 Colin Murdoch
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -198,7 +199,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
case OPCODE_IFNOT: {
|
||||
int16_t pin = (int16_t)operand;
|
||||
if (pin<0) pin = -pin;
|
||||
DIAG(F("EXRAIL input vpin %d"),pin);
|
||||
DIAG(F("EXRAIL input VPIN %u"),pin);
|
||||
IODevice::configureInput((VPIN)pin,true);
|
||||
break;
|
||||
}
|
||||
@@ -208,7 +209,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
case OPCODE_IFGTE:
|
||||
case OPCODE_IFLT:
|
||||
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);
|
||||
break;
|
||||
}
|
||||
@@ -265,16 +266,17 @@ void RMFT2::setTurnoutHiddenState(Turnout * t) {
|
||||
char RMFT2::getRouteType(int16_t id) {
|
||||
for (int16_t i=0;;i+=2) {
|
||||
int16_t rid= GETHIGHFLASHW(routeIdList,i);
|
||||
if (rid==id) return 'R';
|
||||
if (rid==0) break;
|
||||
if (rid==id) return 'R';
|
||||
}
|
||||
for (int16_t i=0;;i+=2) {
|
||||
int16_t rid= GETHIGHFLASHW(automationIdList,i);
|
||||
if (rid==id) return 'A';
|
||||
if (rid==0) break;
|
||||
if (rid==id) return 'A';
|
||||
}
|
||||
return 'X';
|
||||
}
|
||||
}
|
||||
|
||||
// This filter intercepts <> commands to do the following:
|
||||
// - Implement RMFT specific commands/diagnostics
|
||||
// - Reject/modify JMRI commands that would interfere with RMFT processing
|
||||
@@ -1129,7 +1131,10 @@ void RMFT2::clockEvent(int16_t clocktime, bool change) {
|
||||
// Hunt for an ONTIME for this time
|
||||
if (Diag::CMD)
|
||||
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) {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020-2022 Chris Harlow
|
||||
* © 2022 Colin Murdoch
|
||||
* © 2023 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
|
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* © 2020-2022 Chris Harlow. All rights reserved.
|
||||
* © 2022 Colin Murdoch
|
||||
* © 2023 Harald Barth
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -91,6 +92,7 @@
|
||||
#undef ONCLOSE
|
||||
#undef ONTIME
|
||||
#undef ONCLOCKTIME
|
||||
#undef ONCLOCKMINS
|
||||
#undef ONGREEN
|
||||
#undef ONRED
|
||||
#undef ONTHROW
|
||||
@@ -134,6 +136,7 @@
|
||||
#undef STOP
|
||||
#undef THROW
|
||||
#undef TURNOUT
|
||||
#undef TURNOUTL
|
||||
#undef UNJOIN
|
||||
#undef UNLATCH
|
||||
#undef VIRTUAL_SIGNAL
|
||||
@@ -208,6 +211,7 @@
|
||||
#define ONAMBER(signal_id)
|
||||
#define ONTIME(value)
|
||||
#define ONCLOCKTIME(hours,mins)
|
||||
#define ONCLOCKMINS(mins)
|
||||
#define ONDEACTIVATE(addr,subaddr)
|
||||
#define ONDEACTIVATEL(linear)
|
||||
#define ONCLOSE(turnout_id)
|
||||
@@ -254,6 +258,7 @@
|
||||
#define STOP
|
||||
#define THROW(id)
|
||||
#define TURNOUT(id,addr,subaddr,description...)
|
||||
#define TURNOUTL(id,addr,description...)
|
||||
#define UNJOIN
|
||||
#define UNLATCH(sensor_id)
|
||||
#define VIRTUAL_SIGNAL(id)
|
||||
|
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020-2022 Chris Harlow
|
||||
* © 2022 Colin Murdoch
|
||||
* © 2023 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -316,6 +317,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
|
||||
#define ONTIME(value) OPCODE_ONTIME,V(value),
|
||||
#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 ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3),
|
||||
#define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id),
|
||||
@@ -361,6 +363,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define STOP OPCODE_SPEED,V(0),
|
||||
#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 TURNOUTL(id,addr,description...) TURNOUT(id,(addr-1)/4+1,(addr-1)%4, description)
|
||||
#define UNJOIN OPCODE_UNJOIN,0,0,
|
||||
#define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id),
|
||||
#define VIRTUAL_SIGNAL(id)
|
||||
|
@@ -1 +1 @@
|
||||
#define GITHUB_SHA "devel-202303252126Z"
|
||||
#define GITHUB_SHA "devel-202305022149Z"
|
||||
|
52
IODevice.cpp
52
IODevice.cpp
@@ -63,15 +63,31 @@ void IODevice::begin() {
|
||||
if (exrailHalSetup)
|
||||
exrailHalSetup();
|
||||
|
||||
// Predefine two PCA9685 modules 0x40-0x41
|
||||
// Predefine two PCA9685 modules 0x40-0x41 if no conflicts
|
||||
// Allocates 32 pins 100-131
|
||||
PCA9685::create(100, 16, 0x40);
|
||||
PCA9685::create(116, 16, 0x41);
|
||||
if (checkNoOverlap(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);
|
||||
} 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
|
||||
MCP23017::create(164, 16, 0x20);
|
||||
MCP23017::create(180, 16, 0x21);
|
||||
if (checkNoOverlap(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);
|
||||
} else {
|
||||
DIAG(F("Default MCP23017 at I2C 0x21 disabled due to configured user device"));
|
||||
}
|
||||
}
|
||||
|
||||
// reset() function to reinitialise all devices
|
||||
@@ -169,7 +185,7 @@ bool IODevice::hasCallback(VPIN vpin) {
|
||||
|
||||
// Display (to diagnostics) details of the device.
|
||||
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(""));
|
||||
}
|
||||
|
||||
@@ -179,7 +195,7 @@ bool IODevice::configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i
|
||||
IODevice *dev = findDevice(vpin);
|
||||
if (dev) return dev->_configure(vpin, configType, paramCount, params);
|
||||
#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
|
||||
return false;
|
||||
}
|
||||
@@ -191,7 +207,7 @@ int IODevice::read(VPIN vpin) {
|
||||
return dev->_read(vpin);
|
||||
}
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("IODevice::read(): Vpin %d not found!"), (int)vpin);
|
||||
DIAG(F("IODevice::read(): VPIN %u not found!"), (int)vpin);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
@@ -203,7 +219,7 @@ int IODevice::readAnalogue(VPIN vpin) {
|
||||
return dev->_readAnalogue(vpin);
|
||||
}
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("IODevice::readAnalogue(): Vpin %d not found!"), (int)vpin);
|
||||
DIAG(F("IODevice::readAnalogue(): VPIN %u not found!"), (int)vpin);
|
||||
#endif
|
||||
return -1023;
|
||||
}
|
||||
@@ -213,7 +229,7 @@ int IODevice::configureAnalogIn(VPIN vpin) {
|
||||
return dev->_configureAnalogIn(vpin);
|
||||
}
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("IODevice::configureAnalogIn(): Vpin %d not found!"), (int)vpin);
|
||||
DIAG(F("IODevice::configureAnalogIn(): VPIN %u not found!"), (int)vpin);
|
||||
#endif
|
||||
return -1023;
|
||||
}
|
||||
@@ -227,7 +243,7 @@ void IODevice::write(VPIN vpin, int value) {
|
||||
return;
|
||||
}
|
||||
#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
|
||||
}
|
||||
|
||||
@@ -246,7 +262,7 @@ void IODevice::writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t para
|
||||
return;
|
||||
}
|
||||
#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
|
||||
}
|
||||
|
||||
@@ -314,9 +330,11 @@ IODevice *IODevice::findDeviceFollowing(VPIN vpin) {
|
||||
|
||||
// Private helper function to check for vpin overlap. Run during setup only.
|
||||
// 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) {
|
||||
#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
|
||||
VPIN lastPin=firstPin+nPins-1;
|
||||
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;
|
||||
bool noOverlap= firstPin>lastDevPin || lastPin<firstDevPin;
|
||||
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);
|
||||
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[]) {
|
||||
if (configType!=CONFIGURE_INPUT || nParams!=1 || pin >= NUM_DIGITAL_PINS) return false;
|
||||
#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
|
||||
pinMode(pin, p[0] ? INPUT_PULLUP : INPUT);
|
||||
return true;
|
||||
@@ -528,7 +546,7 @@ int ArduinoPins::_configureAnalogIn(VPIN vpin) {
|
||||
}
|
||||
|
||||
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_MCP23017.h"
|
||||
#include "IO_PCF8574.h"
|
||||
|
@@ -119,7 +119,7 @@ private:
|
||||
case STATE_GETVALUE:
|
||||
_value[_currentPin] = ((uint16_t)_inBuffer[0] << 8) + (uint16_t)_inBuffer[1];
|
||||
#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
|
||||
|
||||
// Move to next pin
|
||||
@@ -142,7 +142,7 @@ private:
|
||||
}
|
||||
|
||||
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(""));
|
||||
}
|
||||
|
||||
|
@@ -62,7 +62,7 @@ void DCCAccessoryDecoder::_write(VPIN id, int state) {
|
||||
|
||||
void DCCAccessoryDecoder::_display() {
|
||||
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));
|
||||
}
|
||||
|
||||
|
165
IO_DFPlayer.h
165
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
|
||||
*
|
||||
@@ -33,10 +33,13 @@
|
||||
* and Serialn is the name of the Serial port connected to the DFPlayer (e.g. Serial1).
|
||||
*
|
||||
* Example:
|
||||
* In mySetup function within mySetup.cpp:
|
||||
* In halSetup function within myHal.cpp:
|
||||
* 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-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.
|
||||
@@ -61,6 +64,10 @@
|
||||
* card (as listed by the DIR command in Windows). This may not match the order of the files
|
||||
* as displayed by Windows File Manager, which sorts the file names. It is suggested that
|
||||
* files be copied into an empty SDcard in the desired order, one at a time.
|
||||
*
|
||||
* 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
|
||||
@@ -74,7 +81,10 @@ private:
|
||||
HardwareSerial *_serial;
|
||||
bool _playing = false;
|
||||
uint8_t _inputIndex = 0;
|
||||
unsigned long _commandSendTime; // Allows timeout processing
|
||||
unsigned long _commandSendTime; // Time (us) that last transmit took place.
|
||||
unsigned long _timeoutTime;
|
||||
uint8_t _recvCMD; // Last received command code byte
|
||||
bool _awaitingResponse = false;
|
||||
uint8_t _requestedVolumeLevel = MAXVOLUME;
|
||||
uint8_t _currentVolume = MAXVOLUME;
|
||||
int _requestedSong = -1; // -1=none, 0=stop, >0=file number
|
||||
@@ -102,80 +112,137 @@ protected:
|
||||
|
||||
// Send a query to the device to see if it responds
|
||||
sendPacket(0x42);
|
||||
_commandSendTime = micros();
|
||||
_timeoutTime = micros() + 5000000UL; // 5 second timeout
|
||||
_awaitingResponse = true;
|
||||
}
|
||||
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
// Check for incoming data on _serial, and update busy flag accordingly.
|
||||
// Expected message is in the form "7E FF 06 3D xx xx xx xx xx EF"
|
||||
while (_serial->available()) {
|
||||
int c = _serial->read();
|
||||
if (c == 0x7E && _inputIndex == 0)
|
||||
_inputIndex = 1;
|
||||
else if ((c==0xFF && _inputIndex==1)
|
||||
|| (c==0x3D && _inputIndex==3)
|
||||
|| (_inputIndex >=4 && _inputIndex <= 8))
|
||||
_inputIndex++;
|
||||
else if (c==0x06 && _inputIndex==2) {
|
||||
// Valid message prefix, so consider the device online
|
||||
if (_deviceState==DEVSTATE_INITIALISING) {
|
||||
_deviceState = DEVSTATE_NORMAL;
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
}
|
||||
_inputIndex++;
|
||||
} else if (c==0xEF && _inputIndex==9) {
|
||||
// End of play
|
||||
if (_playing) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DFPlayer: Finished"));
|
||||
#endif
|
||||
_playing = false;
|
||||
}
|
||||
_inputIndex = 0;
|
||||
} else
|
||||
_inputIndex = 0; // Unrecognised character sequence, start again!
|
||||
}
|
||||
|
||||
// Check if the initial prompt to device has timed out. Allow 5 seconds
|
||||
if (_deviceState == DEVSTATE_INITIALISING && currentMicros - _commandSendTime > 5000000UL) {
|
||||
// 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"
|
||||
bool ok = false;
|
||||
while (_serial->available()) {
|
||||
int c = _serial->read();
|
||||
switch (_inputIndex) {
|
||||
case 0:
|
||||
if (c == 0x7E) ok = true;
|
||||
break;
|
||||
case 1:
|
||||
if (c == 0xFF) ok = true;
|
||||
break;
|
||||
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) {
|
||||
_deviceState = DEVSTATE_NORMAL;
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
}
|
||||
_awaitingResponse = false;
|
||||
break;
|
||||
case 0x3d:
|
||||
// End of play
|
||||
if (_playing) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DFPlayer: Finished"));
|
||||
#endif
|
||||
_playing = false;
|
||||
}
|
||||
break;
|
||||
case 0x40:
|
||||
// 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 (currentMicros - _commandSendTime > 100000UL) {
|
||||
if (((int32_t)currentMicros - _commandSendTime) > 100000) {
|
||||
if (_currentVolume > _requestedVolumeLevel) {
|
||||
// Change volume before changing song if volume is reducing.
|
||||
_currentVolume = _requestedVolumeLevel;
|
||||
sendPacket(0x06, _currentVolume);
|
||||
_commandSendTime = currentMicros;
|
||||
} else if (_requestedSong > 0) {
|
||||
// Change song
|
||||
sendPacket(0x03, _requestedSong);
|
||||
_requestedSong = -1;
|
||||
_commandSendTime = currentMicros;
|
||||
} else if (_requestedSong == 0) {
|
||||
sendPacket(0x0e); // Pause playing
|
||||
sendPacket(0x16); // Stop playing
|
||||
_requestedSong = -1;
|
||||
_commandSendTime = currentMicros;
|
||||
} else if (_currentVolume < _requestedVolumeLevel) {
|
||||
// Change volume after changing song if volume is increasing.
|
||||
_currentVolume = _requestedVolumeLevel;
|
||||
sendPacket(0x06, _currentVolume);
|
||||
_commandSendTime = currentMicros;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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 0 stops playing.
|
||||
void _write(VPIN vpin, int value) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
int pin = vpin - _firstVpin;
|
||||
if (value) {
|
||||
// Value 1, start playing
|
||||
@@ -200,10 +267,11 @@ protected:
|
||||
// WriteAnalogue on second pin sets the output volume.
|
||||
//
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t=0) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
uint8_t pin = vpin - _firstVpin;
|
||||
|
||||
#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
|
||||
|
||||
// Validate parameter.
|
||||
@@ -228,11 +296,12 @@ protected:
|
||||
|
||||
// A read on any pin indicates whether the player is still playing.
|
||||
int _read(VPIN) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return false;
|
||||
return _playing;
|
||||
}
|
||||
|
||||
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(""));
|
||||
}
|
||||
|
||||
@@ -264,6 +333,8 @@ private:
|
||||
|
||||
// Output the command
|
||||
_serial->write(out, sizeof(out));
|
||||
|
||||
_commandSendTime = micros();
|
||||
}
|
||||
|
||||
uint16_t calcChecksum(uint8_t* packet)
|
||||
|
@@ -103,7 +103,7 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_
|
||||
uint8_t stepsMSB = value >> 8;
|
||||
uint8_t stepsLSB = value & 0xFF;
|
||||
#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);
|
||||
DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"),
|
||||
_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.
|
||||
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(""));
|
||||
}
|
||||
|
||||
|
@@ -84,7 +84,7 @@ protected:
|
||||
void _write(VPIN vpin, int value) {
|
||||
int pin = vpin -_firstVpin;
|
||||
#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
|
||||
// Send a command string over the serial line
|
||||
_serial->print('#');
|
||||
@@ -153,10 +153,10 @@ protected:
|
||||
// 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.
|
||||
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);
|
||||
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>
|
||||
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(""));
|
||||
}
|
||||
|
||||
|
@@ -234,7 +234,7 @@ protected:
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("HCSR04 Configured on Vpin:%d TrigPin:%d EchoPin:%d On:%dcm Off:%dcm"),
|
||||
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 (paramCount != 5) return false;
|
||||
#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]);
|
||||
#endif
|
||||
|
||||
@@ -118,7 +118,7 @@ void PCA9685::_begin() {
|
||||
// For this function, the configured profile is used.
|
||||
void PCA9685::_write(VPIN vpin, int value) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value);
|
||||
DIAG(F("PCA9685 Write VPIN:%u Value:%d"), vpin, value);
|
||||
#endif
|
||||
int pin = vpin - _firstVpin;
|
||||
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) {
|
||||
#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(""));
|
||||
#endif
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
@@ -262,7 +262,7 @@ void PCA9685::writeDevice(uint8_t pin, int value) {
|
||||
|
||||
// Display details of this device.
|
||||
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(""));
|
||||
}
|
||||
|
||||
|
@@ -121,7 +121,7 @@ private:
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override {
|
||||
(void)param1; (void)param2; // suppress compiler warning
|
||||
#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(""));
|
||||
#endif
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
@@ -134,7 +134,7 @@ private:
|
||||
|
||||
// Display details of this device.
|
||||
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(""));
|
||||
}
|
||||
|
||||
|
@@ -98,13 +98,14 @@ private:
|
||||
|
||||
void _write(VPIN vpin, int value) override {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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(""));
|
||||
}
|
||||
|
||||
|
12
IO_Servo.h
12
IO_Servo.h
@@ -98,7 +98,7 @@ private:
|
||||
if (configType != CONFIGURE_SERVO) return false;
|
||||
if (paramCount != 5) return false;
|
||||
#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]);
|
||||
#endif
|
||||
|
||||
@@ -140,12 +140,12 @@ private:
|
||||
// Get reference to slave device.
|
||||
_slaveDevice = findDevice(_firstSlavePin);
|
||||
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);
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
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);
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
@@ -165,7 +165,7 @@ private:
|
||||
void _write(VPIN vpin, int value) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("Servo Write Vpin:%d Value:%d"), vpin, value);
|
||||
DIAG(F("Servo Write VPIN:%u Value:%d"), vpin, value);
|
||||
#endif
|
||||
int pin = vpin - _firstVpin;
|
||||
if (value) value = 1;
|
||||
@@ -193,7 +193,7 @@ private:
|
||||
//
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override {
|
||||
#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(""));
|
||||
#endif
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
@@ -288,7 +288,7 @@ private:
|
||||
|
||||
// Display details of this device.
|
||||
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)_firstSlavePin, (int)_firstSlavePin+_nPins-1,
|
||||
(_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).
|
||||
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);
|
||||
}
|
||||
|
||||
|
@@ -319,7 +319,7 @@ protected:
|
||||
}
|
||||
|
||||
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,
|
||||
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
@@ -55,6 +55,7 @@ public:
|
||||
pinMode(_clockPin,OUTPUT);
|
||||
pinMode(_dataPin,_pinMap?INPUT_PULLUP:OUTPUT);
|
||||
_display();
|
||||
if (!_pinMap) _loopOutput();
|
||||
}
|
||||
|
||||
// loop called by HAL supervisor
|
||||
@@ -121,7 +122,7 @@ void _loopOutput() {
|
||||
}
|
||||
|
||||
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"),
|
||||
(int)_firstVpin,
|
||||
(int)_firstVpin+_nPins-1, _nShiftBytes*8);
|
||||
|
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2022-2023 Paul M Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2022 Harald Barth
|
||||
* © 2020-2023 Harald Barth
|
||||
* © 2020-2021 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -27,10 +27,6 @@
|
||||
#include "DCCTimer.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#include "ESP32-fixes.h"
|
||||
#endif
|
||||
|
||||
bool MotorDriver::commonFaultPin=false;
|
||||
|
||||
volatile portreg_t shadowPORTA;
|
||||
@@ -38,7 +34,7 @@ volatile portreg_t shadowPORTB;
|
||||
volatile portreg_t shadowPORTC;
|
||||
|
||||
MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||
byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin) {
|
||||
byte current_pin, float sense_factor, unsigned int trip_milliamps, int8_t fault_pin) {
|
||||
powerPin=power_pin;
|
||||
invertPower=power_pin < 0;
|
||||
if (invertPower) {
|
||||
@@ -112,6 +108,9 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||
|
||||
faultPin=fault_pin;
|
||||
if (faultPin != UNUSED_PIN) {
|
||||
invertFault=fault_pin < 0;
|
||||
faultPin=invertFault ? 0-fault_pin : fault_pin;
|
||||
DIAG(F("Fault pin = %d invert %d"), faultPin, invertFault);
|
||||
getFastPin(F("FAULT"),faultPin, 1 /*input*/, fastFaultPin);
|
||||
pinMode(faultPin, INPUT);
|
||||
}
|
||||
@@ -210,10 +209,17 @@ int MotorDriver::getCurrentRaw(bool fromISR) {
|
||||
(void)fromISR;
|
||||
if (currentPin==UNUSED_PIN) return 0;
|
||||
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 ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && powerMode==POWERMODE::ON)
|
||||
if ((faultPin != UNUSED_PIN) && powerMode==POWERMODE::ON) {
|
||||
if (invertFault && isLOW(fastFaultPin))
|
||||
return (current == 0 ? -1 : -current);
|
||||
if (!invertFault && !isLOW(fastFaultPin))
|
||||
return (current == 0 ? -1 : -current);
|
||||
}
|
||||
return current;
|
||||
|
||||
}
|
||||
@@ -283,7 +289,7 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||
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
|
||||
if (tSpeed <= 1) brake = 255;
|
||||
@@ -292,7 +298,7 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||
if (invertBrake)
|
||||
brake=255-brake;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
DCCEXanalogWrite(brakePin,brake);
|
||||
DCCTimer::DCCEXanalogWrite(brakePin,brake);
|
||||
#else
|
||||
analogWrite(brakePin,brake);
|
||||
#endif
|
||||
|
@@ -112,7 +112,7 @@ class MotorDriver {
|
||||
public:
|
||||
|
||||
MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||
byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
|
||||
byte current_pin, float senseFactor, unsigned int tripMilliamps, int8_t fault_pin);
|
||||
void setPower( POWERMODE mode);
|
||||
POWERMODE getPower() { return powerMode;}
|
||||
// as the port registers can be shadowed to get syncronized DCC signals
|
||||
@@ -202,6 +202,7 @@ class MotorDriver {
|
||||
bool dualSignal; // true to use signalPin2
|
||||
bool invertBrake; // brake pin passed as negative means pin is inverted
|
||||
bool invertPower; // power pin passed as negative means pin is inverted
|
||||
bool invertFault; // fault pin passed as negative means pin is inverted
|
||||
|
||||
// Raw to milliamp conversion factors avoiding float data types.
|
||||
// Milliamps=rawADCreading * sensefactorInternal / senseScale
|
||||
|
@@ -73,12 +73,16 @@
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
// 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
|
||||
// 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
|
||||
// actual GPIO numbers. In comments the numbers the pins have on an Uno.
|
||||
#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(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 39/*A5*/, 0.70, 1500, UNUSED_PIN)
|
||||
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
|
||||
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*/, 34/*A3*/, 0.70, 1500, UNUSED_PIN)
|
||||
|
||||
#define EX8874_SHIELD F("EX8874"),\
|
||||
new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 35/*A2*/, 1.17, 5000, -36 /*-A4*/), \
|
||||
new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 34/*A3*/, 1.17, 5000, -39 /*-A5*/)
|
||||
|
||||
#else
|
||||
// STANDARD shield on any Arduino Uno or Mega compatible with the original specification.
|
||||
@@ -116,6 +120,10 @@
|
||||
new MotorDriver(2, 7, UNUSED_PIN, -9, A0, 10, 2500, 6), \
|
||||
new MotorDriver(4, 8, UNUSED_PIN, -10, A1, 10, 2500, 12)
|
||||
|
||||
#define EX8874_SHIELD F("EX8874"), \
|
||||
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 4.86, 5000, -A4), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 4.86, 5000, -A5)
|
||||
|
||||
// Firebox Mk1
|
||||
#define FIREBOX_MK1 F("FIREBOX_MK1"), \
|
||||
new MotorDriver(3, 6, 7, UNUSED_PIN, A5, 9.766, 5500, UNUSED_PIN), \
|
||||
|
@@ -87,6 +87,9 @@ void SerialManager::init() {
|
||||
delay(1000);
|
||||
}
|
||||
#endif
|
||||
#ifdef SABERTOOTH
|
||||
Serial2.begin(9600, SERIAL_8N1, 16, 17); // GPIO 16 RXD2; GPIO 17 TXD2 on ESP32
|
||||
#endif
|
||||
}
|
||||
|
||||
void SerialManager::broadcast(char * stringBuffer) {
|
||||
|
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
|
||||
cp -p config.example.h config.h
|
||||
fi
|
||||
need curl
|
||||
if test -x "$ACLI" ; then
|
||||
: all well
|
||||
else
|
||||
need curl
|
||||
curl "$ACLIINSTALL" > acliinstall.sh
|
||||
chmod +x acliinstall.sh
|
||||
./acliinstall.sh
|
||||
|
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
|
@@ -20,11 +20,11 @@ default_envs =
|
||||
ESP32
|
||||
Nucleo-F411RE
|
||||
Nucleo-F446RE
|
||||
Teensy3.2
|
||||
Teensy3.5
|
||||
Teensy3.6
|
||||
Teensy4.0
|
||||
Teensy4.1
|
||||
Teensy3_2
|
||||
Teensy3_5
|
||||
Teensy3_6
|
||||
Teensy4_0
|
||||
Teensy4_1
|
||||
src_dir = .
|
||||
include_dir = .
|
||||
|
||||
@@ -53,7 +53,7 @@ monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -std=c++17
|
||||
|
||||
[env:Arduino M0]
|
||||
[env:Arduino-M0]
|
||||
platform = atmelsam
|
||||
board = mzeroUSB
|
||||
framework = arduino
|
||||
@@ -192,7 +192,7 @@ build_flags = -std=c++17 -Os -g2 -Wunused-variable -DDIAG_LOOPTIMES ; -DDIAG_IO
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
[env:Teensy3.2]
|
||||
[env:Teensy3_2]
|
||||
platform = teensy
|
||||
board = teensy31
|
||||
framework = arduino
|
||||
@@ -200,7 +200,7 @@ build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
[env:Teensy3.5]
|
||||
[env:Teensy3_5]
|
||||
platform = teensy
|
||||
board = teensy35
|
||||
framework = arduino
|
||||
@@ -208,7 +208,7 @@ build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
[env:Teensy3.6]
|
||||
[env:Teensy3_6]
|
||||
platform = teensy
|
||||
board = teensy36
|
||||
framework = arduino
|
||||
@@ -216,7 +216,7 @@ build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
[env:Teensy4.0]
|
||||
[env:Teensy4_0]
|
||||
platform = teensy
|
||||
board = teensy40
|
||||
framework = arduino
|
||||
@@ -224,7 +224,7 @@ build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
[env:Teensy4.1]
|
||||
[env:Teensy4_1]
|
||||
platform = teensy
|
||||
board = teensy41
|
||||
framework = arduino
|
||||
|
22
version.h
22
version.h
@@ -4,14 +4,28 @@
|
||||
#include "StringFormatter.h"
|
||||
|
||||
|
||||
#define VERSION "4.2.36"
|
||||
#define VERSION "4.2.47"
|
||||
// 4.2.47 - Correct response to <JA 0>
|
||||
// 4.2.46 - Support boards with inverted fault pin
|
||||
// 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.
|
||||
// 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.
|
||||
@@ -64,6 +78,10 @@
|
||||
// 4.2.11 Exrail IFLOCO feature added
|
||||
// 4.2.10 SIGNAL/SIGNALH bug fix as they were inverted
|
||||
// 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.8 HIGHMEM (EXRAIL support beyond 64kb)
|
||||
// Withrottle connect/disconnect improvements
|
||||
|
Reference in New Issue
Block a user