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

Compare commits

..

19 Commits

Author SHA1 Message Date
pmantoine
3868bb19ac More uint type fixes 2023-04-08 14:51:10 +08:00
pmantoine
7589917638 STM32 ADCee fix 2023-04-08 14:15:38 +08:00
Harald Barth
0d82370380 devel date 2023-04-07 20:46:20 +02:00
Harald Barth
ff6034dff2 curl only needed when downloading 2023-04-07 20:45:21 +02:00
Harald Barth
5d0de6b807 platformio wants this 2023-04-07 20:44:40 +02:00
pmantoine
c3d2e5b222 Fix to PIO build target names for Teensy 2023-04-07 13:59:49 +10:00
Asbelos
273f55b143 4.2.41 Hal setup and DNOU8 fix 2023-04-05 23:19:43 +01:00
peteGSX
86cb8f4666 Merge pull request #327 from DCC-EX:auto-disable-default-i2c
Added disable logic
2023-04-06 07:09:32 +10:00
peteGSX
9571088e1b Added disable logic 2023-04-06 07:03:59 +10:00
Neil McKechnie
1b4faa92cd Update IO_DFPlayer.h
Reinstate STOP command in place of PAUSE, as PAUSE was being reported differently to STOP in the status response.
2023-03-31 17:58:30 +01:00
Neil McKechnie
6fbaca7930 Update IO_DFPlayer.h
Ensure device goes off-line when not responding.
2023-03-31 16:50:18 +01:00
Neil McKechnie
6b535654f8 DFplayer driver now polls device to detect failures and errors.
Add cyclic (1-second) poll of DFplayer device to detect if it goes unresponsive.
2023-03-31 16:40:40 +01:00
peteGSX
2c943d250e Merge pull request #326 from DCC-EX:287-to-do-clean-up-rotary-encoder-device-driver-compile-time-warning
Cleaned up warning
2023-03-30 07:01:40 +10:00
peteGSX
89664eff9d Cleaned up warning 2023-03-30 06:54:18 +10:00
f8b054cf6a [ESP32] Use GPIO 35/A2 and 34/A3 for current sensing (#325)
* [ESP32] Use GPIO 35/A2 and 34/A3 for current sensing while used in combination with the standard Motor Shield
* Update version.h changelog
2023-03-27 10:44:47 -04:00
Neil McKechnie
d2a8aebd0f Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-03-27 13:08:19 +01:00
Neil McKechnie
86c3020672 Correct display of high VPIN numbers in diagnostic output.
No functional change.
VPINs are unsigned integers in the range 0-65535 (although the highest values are special, 65535=VPIN_NONE).  Values above 32767 were erroneously being displayed as negative.  This has been fixed, which is a pre-requisite for allowing VPINs above 32767 to be used.
2023-03-27 13:08:14 +01:00
Neil McKechnie
60ea7f081a Correct display of high VPIN numbers in diagnostic output.
No functional change.
VPINs are unsigned integers in the range 0-65535 (although the highest values are special, 65535=VPIN_NONE).  Values above 32767 were erroneously being displayed as negative.  This has been fixed, which is a pre-requisite for allowing VPINs above 32767 to be used.
2023-03-27 13:03:19 +01:00
Neil McKechnie
f348857ddb Add FLAGS device for EX-RAIL state communications. Improve VPIN display in messages.
FLAGS HAL device added to IODevice.h, which allows use of SET/RESET/<Z>/<T> to set and reset a VPIN state, and to allow <S>/IF/IFNOT/AT/WAITFOR/etc. to monitor the VPIN state.
Also, correct handling of VPINs above 32767 in DIAG calls within IODevice.cpp and IODevice.h.
2023-03-27 12:39:11 +01:00
28 changed files with 922 additions and 120 deletions

View File

@@ -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();

View File

@@ -967,7 +967,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)

View File

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

View File

@@ -237,7 +237,7 @@ int16_t ADCee::ADCmax() {
}
int ADCee::init(uint8_t pin) {
uint id = pin - A0;
uint8_t id = pin - A0;
int value = 0;
PinName stmpin = digitalPin[analogInputPin[id]];
uint32_t stmgpio = stmpin / 16; // 16-bits per GPIO port group on STM32
@@ -306,7 +306,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;

View File

@@ -198,7 +198,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 +208,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;
}

View File

@@ -1 +1 @@
#define GITHUB_SHA "devel-202303252126Z"
#define GITHUB_SHA "devel-202304071845Z"

View File

@@ -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);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////

View File

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

View File

@@ -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(""));
}

View File

@@ -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));
}

View File

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

View File

@@ -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(""));
}

View File

@@ -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]);
}

View File

@@ -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(""));
}

View File

@@ -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);
}

View File

@@ -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(""));
}

View File

@@ -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(""));
}

View File

@@ -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(""));
}

View File

@@ -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(""));

View File

@@ -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);
}

View File

@@ -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(""));
}

View File

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

View File

@@ -73,12 +73,12 @@
#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)
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)
#else
// STANDARD shield on any Arduino Uno or Mega compatible with the original specification.

169
config.h.txt Normal file
View 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
/////////////////////////////////////////////////////////////////////////////////////

View File

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

View File

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

View File

@@ -4,14 +4,22 @@
#include "StringFormatter.h"
#define VERSION "4.2.36"
#define VERSION "4.2.41"
// 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.