mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-29 10:23:45 +02:00
Compare commits
84 Commits
lewduino
...
v4.2.14-De
Author | SHA1 | Date | |
---|---|---|---|
|
95945eab4c | ||
|
638682f05c | ||
|
212bf8d80e | ||
|
290d878063 | ||
|
2a7588b1b5 | ||
|
be33bafa66 | ||
|
6cc66e26c1 | ||
|
c91d66549c | ||
|
9e5d780c14 | ||
|
2c0886bc2f | ||
|
762742b4af | ||
|
88b572a148 | ||
|
fcf16c1367 | ||
|
c69b8d85c8 | ||
|
006c85e6ae | ||
|
4f233de726 | ||
|
4acf46db54 | ||
|
20b3e9064c | ||
|
459904e5dd | ||
|
878549d538 | ||
|
7f4e3d9cea | ||
|
f2aeb4069f | ||
|
aaf25d5426 | ||
|
fb9170ab8b | ||
|
286bdc3c4d | ||
|
cd46d3c9e0 | ||
|
fb36bd1380 | ||
|
b62c4da04d | ||
|
8fac20a451 | ||
|
1be382a6ed | ||
|
1f433d0c17 | ||
|
046e62a8b3 | ||
|
a2c7c7d12a | ||
|
9b36bdcf46 | ||
|
a8646a2f32 | ||
|
22e20f9092 | ||
|
873d470f86 | ||
|
ff7260b9bc | ||
|
de4954ca3e | ||
|
c26f53e1fa | ||
|
e48a40fafb | ||
|
5c120efa16 | ||
|
9abcfb9e4f | ||
|
e01893bcf1 | ||
|
6eff836473 | ||
|
402e16727c | ||
|
658fca2601 | ||
|
3fccf6a484 | ||
|
ace9c1642a | ||
|
ec4dfb8c1e | ||
|
6482a421b4 | ||
|
d02c6b1f61 | ||
|
94c8dafeb2 | ||
|
322cb3db54 | ||
|
ffdf023de6 | ||
|
8f32ae712f | ||
|
eea1396997 | ||
|
b1bd28273d | ||
|
0be25f6e7f | ||
|
71ce913712 | ||
|
70845b4932 | ||
|
c44fb0ac44 | ||
|
1c7103c21e | ||
|
5170147e3e | ||
|
2ad08029a4 | ||
|
25b3250345 | ||
|
3973996344 | ||
|
943494385f | ||
|
c8fea3a4a7 | ||
|
a480a5a3d2 | ||
|
070daa37dc | ||
|
75f1a8f43a | ||
|
8ecb408da7 | ||
|
3862f7250d | ||
|
785b515f9e | ||
|
9699a44081 | ||
|
cb9a8bb7a6 | ||
|
1d5897d2d2 | ||
|
7bc0433197 | ||
|
9104956009 | ||
|
af4d8d4075 | ||
|
06945bb114 | ||
|
2d27cb052d | ||
|
8cbcf5df32 |
@@ -29,6 +29,11 @@
|
||||
#include "DCCWaveform.h"
|
||||
#include "DCC.h"
|
||||
#include "TrackManager.h"
|
||||
#include "StringFormatter.h"
|
||||
|
||||
// variables to hold clock time
|
||||
int16_t lastclocktime;
|
||||
int8_t lastclockrate;
|
||||
|
||||
|
||||
#if WIFI_ON || ETHERNET_ON || defined(SERIAL1_COMMANDS) || defined(SERIAL2_COMMANDS) || defined(SERIAL3_COMMANDS)
|
||||
@@ -155,6 +160,50 @@ void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastClockTime(int16_t time, int8_t rate) {
|
||||
// The JMRI clock command is of the form : PFT65871<;>4
|
||||
// The CS broadcast is of the form "<jC mmmm nn" where mmmm is time minutes and dd speed
|
||||
// The string below contains serial and Withrottle protocols which should
|
||||
// be safe for both types.
|
||||
broadcastReply(COMMAND_TYPE, F("<jC %d %d>\n"),time, rate);
|
||||
#ifdef CD_HANDLE_RING
|
||||
broadcastReply(WITHROTTLE_TYPE, F("PFT%d<;>%d\n"), time*60, rate);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandDistributor::setClockTime(int16_t clocktime, int8_t clockrate, byte opt) {
|
||||
// opt - case 1 save the latest time if changed
|
||||
// case 2 broadcast the time when requested
|
||||
// case 3 display latest time
|
||||
switch (opt)
|
||||
{
|
||||
case 1:
|
||||
if (clocktime != lastclocktime){
|
||||
if (Diag::CMD) {
|
||||
DIAG(F("Clock Command Received"));
|
||||
DIAG(F("Received Clock Time is: %d at rate: %d"), clocktime, clockrate);
|
||||
}
|
||||
LCD(6,F("Clk Time:%d Sp %d"), clocktime, clockrate);
|
||||
// look for an event for this time
|
||||
RMFT2::clockEvent(clocktime,1);
|
||||
// Now tell everyone else what the time is.
|
||||
CommandDistributor::broadcastClockTime(clocktime, clockrate);
|
||||
lastclocktime = clocktime;
|
||||
lastclockrate = clockrate;
|
||||
}
|
||||
return;
|
||||
|
||||
case 2:
|
||||
CommandDistributor::broadcastClockTime(lastclocktime, lastclockrate);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int16_t CommandDistributor::retClockTime() {
|
||||
return lastclocktime;
|
||||
}
|
||||
|
||||
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);
|
||||
|
@@ -25,6 +25,7 @@
|
||||
#include "RingStream.h"
|
||||
#include "StringBuffer.h"
|
||||
#include "defines.h"
|
||||
#include "EXRAIL2.h"
|
||||
|
||||
#if WIFI_ON | ETHERNET_ON
|
||||
// Command Distributor must handle a RingStream of clients
|
||||
@@ -45,10 +46,14 @@ public :
|
||||
static void broadcastLoco(byte slot);
|
||||
static void broadcastSensor(int16_t id, bool value);
|
||||
static void broadcastTurnout(int16_t id, bool isClosed);
|
||||
static void broadcastClockTime(int16_t time, int8_t rate);
|
||||
static void setClockTime(int16_t time, int8_t rate, byte opt);
|
||||
static int16_t retClockTime();
|
||||
static void broadcastPower();
|
||||
static void broadcastText(const FSH * msg);
|
||||
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
|
||||
static void forget(byte clientId);
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -99,6 +99,9 @@ void setup()
|
||||
// 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();
|
||||
// Responsibility 3: Start the DCC engine.
|
||||
// Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s)
|
||||
// Standard supported devices have pre-configured macros but custome hardware installations require
|
||||
|
@@ -510,6 +510,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
|
||||
case 's': // <s>
|
||||
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||
CommandDistributor::broadcastPower(); // <s> is the only "get power status" command we have
|
||||
Turnout::printAll(stream); //send all Turnout states
|
||||
Output::printAll(stream); //send all Output states
|
||||
Sensor::printAll(stream); //send all Sensor states
|
||||
@@ -570,9 +571,19 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
|
||||
case 'J' : // throttle info access
|
||||
{
|
||||
if ((params<1) | (params>2)) break; // <J>
|
||||
if ((params<1) | (params>3)) break; // <J>
|
||||
//if ((params<1) | (params>2)) break; // <J>
|
||||
int16_t id=(params==2)?p[1]:0;
|
||||
switch(p[0]) {
|
||||
case HASH_KEYWORD_C: // <JC mmmm nn> sets time and speed
|
||||
if (params==1) { // <JC> returns latest time
|
||||
int16_t x = CommandDistributor::retClockTime();
|
||||
StringFormatter::send(stream, F("<jC %d>\n"), x);
|
||||
return;
|
||||
}
|
||||
CommandDistributor::setClockTime(p[1], p[2], 1);
|
||||
return;
|
||||
|
||||
case HASH_KEYWORD_A: // <JA> returns automations/routes
|
||||
StringFormatter::send(stream, F("<jA"));
|
||||
if (params==1) {// <JA>
|
||||
@@ -616,14 +627,17 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
else { // <JT id>
|
||||
Turnout * t=Turnout::get(id);
|
||||
if (!t || t->isHidden()) StringFormatter::send(stream, F(" %d X"),id);
|
||||
else StringFormatter::send(stream, F(" %d %c \"%S\""),
|
||||
id,t->isThrown()?'T':'C',
|
||||
else {
|
||||
const FSH *tdesc = NULL;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
RMFT2::getTurnoutDescription(id)
|
||||
#else
|
||||
F("")
|
||||
#endif
|
||||
);
|
||||
tdesc = RMFT2::getTurnoutDescription(id);
|
||||
#endif
|
||||
if (tdesc == NULL)
|
||||
tdesc = F("");
|
||||
StringFormatter::send(stream, F(" %d %c \"%S\""),
|
||||
id,t->isThrown()?'T':'C',
|
||||
tdesc);
|
||||
}
|
||||
}
|
||||
StringFormatter::send(stream, F(">\n"));
|
||||
return;
|
||||
|
14
DCCTimer.h
14
DCCTimer.h
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2022-2023 Paul M. Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021-2022 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
@@ -102,9 +102,14 @@ private:
|
||||
// that an offset can be initialized.
|
||||
class ADCee {
|
||||
public:
|
||||
// init does add the pin to the list of scanned pins (if this
|
||||
// begin is called for any setup that must be done before
|
||||
// **init** can be called. On some architectures this involves ADC
|
||||
// initialisation and clock routing, sampling times etc.
|
||||
static void begin();
|
||||
// init adds the pin to the list of scanned pins (if this
|
||||
// platform's implementation scans pins) and returns the first
|
||||
// read value. It is called before the regular scan is started.
|
||||
// read value (which is why it required begin to have been called first!)
|
||||
// It must be called before the regular scan is started.
|
||||
static int init(uint8_t pin);
|
||||
// read does read the pin value from the scanned cache or directly
|
||||
// if this is a platform that does not scan. fromISR is a hint if
|
||||
@@ -117,9 +122,6 @@ private:
|
||||
// On platforms that scan, it is called from waveform ISR
|
||||
// only on a regular basis.
|
||||
static void scan();
|
||||
// begin is called for any setup that must be done before
|
||||
// scan can be called.
|
||||
static void begin();
|
||||
// bit array of used pins (max 16)
|
||||
static uint16_t usedpins;
|
||||
// cached analog values (malloc:ed to actual number of ADC channels)
|
||||
|
@@ -168,23 +168,6 @@ int ADCee::init(uint8_t pin) {
|
||||
if (id > NUM_ADC_INPUTS)
|
||||
return -1023;
|
||||
|
||||
// Dummy read using Arduino library
|
||||
analogReadResolution(12);
|
||||
value = analogRead(pin);
|
||||
|
||||
// Reconfigure ADC
|
||||
ADC->CTRLA.bit.ENABLE = 0; // disable ADC
|
||||
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
|
||||
|
||||
ADC->CTRLB.reg &= 0b1111100011001111; // mask PRESCALER and RESSEL bits
|
||||
ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 16
|
||||
ADC_CTRLB_RESSEL_12BIT; // Result 12 bits, 10 bits possible
|
||||
ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time
|
||||
ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0
|
||||
ADC->SAMPCTRL.reg = 0x00ul; // sampling Time Length = 0
|
||||
ADC->CTRLA.bit.ENABLE = 1; // enable ADC
|
||||
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
|
||||
|
||||
// Permanently configure SAMD IO MUX for that pin
|
||||
pinPeripheral(pin, PIO_ANALOG);
|
||||
ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[pin].ulADCChannelNumber; // Selection for the positive ADC input
|
||||
@@ -205,9 +188,11 @@ int ADCee::init(uint8_t pin) {
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
int16_t ADCee::ADCmax() {
|
||||
return 4095;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
|
||||
*/
|
||||
|
@@ -31,14 +31,18 @@
|
||||
#include "DCCTimer.h"
|
||||
|
||||
#if defined(ARDUINO_NUCLEO_F411RE)
|
||||
// STM32F411RE doesn't have Serial1 defined by default
|
||||
// Nucleo-64 boards don't have Serial1 defined by default
|
||||
HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE
|
||||
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
|
||||
// via the debugger on the Nucleo-64 STM32F411RE. It is therefore unavailable
|
||||
// for other DCC-EX uses like WiFi, DFPlayer, etc.
|
||||
// Let's define Serial6 as an additional serial port (the only other option for the F411RE)
|
||||
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
|
||||
// Let's define Serial6 as an additional serial port (the only other option for the Nucleo-64s)
|
||||
HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE
|
||||
#elif defined(ARDUINO_BLAH_F412ZG) || defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||
#elif defined(ARDUINO_NUCLEO_F446RE)
|
||||
// Nucleo-64 boards don't have Serial1 defined by default
|
||||
HardwareSerial Serial1(PA10, PB6); // Rx=PA10, Tx=PB6 -- CN10 pins 33 and 17 - 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.
|
||||
#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
|
||||
#else
|
||||
@@ -92,7 +96,7 @@ void DCCTimer::clearPWM() {
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
volatile uint32_t *serno1 = (volatile uint32_t *)0x1FFF7A10;
|
||||
volatile uint32_t *serno2 = (volatile uint32_t *)0x1FFF7A14;
|
||||
volatile uint32_t *serno3 = (volatile uint32_t *)0x1FFF7A18;
|
||||
// volatile uint32_t *serno3 = (volatile uint32_t *)0x1FFF7A18;
|
||||
|
||||
volatile uint32_t m1 = *serno1;
|
||||
volatile uint32_t m2 = *serno2;
|
||||
@@ -127,31 +131,148 @@ void DCCTimer::reset() {
|
||||
while(true) {};
|
||||
}
|
||||
|
||||
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
|
||||
|
||||
// TODO: may need to use uint32_t on STMF4xx variants with > 16 analog inputs!
|
||||
uint16_t ADCee::usedpins = 0;
|
||||
int * ADCee::analogvals = NULL;
|
||||
uint32_t * analogchans = NULL;
|
||||
bool adc1configured = false;
|
||||
|
||||
int16_t ADCee::ADCmax() {
|
||||
return 4095;
|
||||
}
|
||||
|
||||
int ADCee::init(uint8_t pin) {
|
||||
return analogRead(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
|
||||
uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC channel (only valid for ADC1!)
|
||||
GPIO_TypeDef * gpioBase;
|
||||
|
||||
// Port config - find which port we're on and power it up
|
||||
switch(stmgpio) {
|
||||
case 0x00:
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //Power up PORTA
|
||||
gpioBase = GPIOA;
|
||||
break;
|
||||
case 0x01:
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; //Power up PORTB
|
||||
gpioBase = GPIOB;
|
||||
break;
|
||||
case 0x02:
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; //Power up PORTC
|
||||
gpioBase = GPIOC;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set pin mux mode to analog input
|
||||
gpioBase->MODER |= (0b011 << (stmpin << 1)); // Set pin mux to analog mode
|
||||
|
||||
// Set the sampling rate for that analog input
|
||||
if (adcchan < 10)
|
||||
ADC1->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
|
||||
else
|
||||
ADC1->SMPR1 |= (0b111 << ((adcchan - 10) * 3)); // Channel sampling rate 480 cycles
|
||||
|
||||
// Read the inital ADC value for this analog input
|
||||
ADC1->SQR3 = adcchan; // 1st conversion in regular sequence
|
||||
ADC1->CR2 |= (1 << 30); // Start 1st conversion SWSTART
|
||||
while(!(ADC1->SR & (1 << 1))); // Wait until conversion is complete
|
||||
value = ADC1->DR; // Read value from register
|
||||
|
||||
if (analogvals == NULL)
|
||||
{
|
||||
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
|
||||
analogchans = (uint32_t *)calloc(NUM_ADC_INPUTS+1, sizeof(uint32_t));
|
||||
}
|
||||
analogvals[id] = value; // Store sampled value
|
||||
analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin
|
||||
usedpins |= (1 << id); // This pin is now ready
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
|
||||
*/
|
||||
int ADCee::read(uint8_t pin, bool fromISR) {
|
||||
int current;
|
||||
if (!fromISR) noInterrupts();
|
||||
current = analogRead(pin);
|
||||
if (!fromISR) interrupts();
|
||||
return current;
|
||||
uint8_t id = pin - A0;
|
||||
// Was this pin initialised yet?
|
||||
if ((usedpins & (1<<id) ) == 0)
|
||||
return -1023;
|
||||
// We do not need to check (analogvals == NULL)
|
||||
// because usedpins would still be 0 in that case
|
||||
return analogvals[id];
|
||||
}
|
||||
|
||||
/*
|
||||
* Scan function that is called from interrupt
|
||||
*/
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
void ADCee::scan() {
|
||||
static uint id = 0; // id and mask are the same thing but it is faster to
|
||||
static uint16_t mask = 1; // increment and shift instead to calculate mask from id
|
||||
static bool waiting = false;
|
||||
|
||||
if (waiting) {
|
||||
// look if we have a result
|
||||
if (!(ADC1->SR & (1 << 1)))
|
||||
return; // no result, continue to wait
|
||||
// found value
|
||||
analogvals[id] = ADC1->DR;
|
||||
// advance at least one track
|
||||
// for scope debug TrackManager::track[1]->setBrake(0);
|
||||
waiting = false;
|
||||
id++;
|
||||
mask = mask << 1;
|
||||
if (id == NUM_ADC_INPUTS+1) {
|
||||
id = 0;
|
||||
mask = 1;
|
||||
}
|
||||
}
|
||||
if (!waiting) {
|
||||
if (usedpins == 0) // otherwise we would loop forever
|
||||
return;
|
||||
// look for a valid track to sample or until we are around
|
||||
while (true) {
|
||||
if (mask & usedpins) {
|
||||
// start new ADC aquire on id
|
||||
ADC1->SQR3 = analogchans[id]; //1st conversion in regular sequence
|
||||
ADC1->CR2 |= (1 << 30); //Start 1st conversion SWSTART
|
||||
// for scope debug TrackManager::track[1]->setBrake(1);
|
||||
waiting = true;
|
||||
return;
|
||||
}
|
||||
id++;
|
||||
mask = mask << 1;
|
||||
if (id == NUM_ADC_INPUTS+1) {
|
||||
id = 0;
|
||||
mask = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
|
||||
void ADCee::begin() {
|
||||
noInterrupts();
|
||||
//ADC1 config sequence
|
||||
// TODO: currently defaults to ADC1, may need more to handle other members of STM32F4xx family
|
||||
RCC->APB2ENR |= (1 << 8); //Enable ADC1 clock (Bit8)
|
||||
// Set ADC prescaler - DIV8 ~ 40ms, DIV6 ~ 30ms, DIV4 ~ 20ms, DIV2 ~ 11ms
|
||||
ADC->CCR = (0 << 16); // Set prescaler 0=DIV2, 1=DIV4, 2=DIV6, 3=DIV8
|
||||
ADC1->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8)
|
||||
ADC1->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00)
|
||||
ADC1->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion
|
||||
ADC1->CR2 &= ~(1 << 1); //Single conversion
|
||||
ADC1->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0
|
||||
ADC1->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC1->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC1->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC1->CR2 |= (1 << 0); // Switch on ADC1
|
||||
interrupts();
|
||||
}
|
||||
#endif
|
@@ -62,7 +62,6 @@ const bool signalTransform[]={
|
||||
/* WAVE_PENDING (should not happen) -> */ LOW};
|
||||
|
||||
void DCCWaveform::begin() {
|
||||
ADCee::begin();
|
||||
DCCTimer::begin(DCCWaveform::interruptHandler);
|
||||
}
|
||||
|
||||
|
52
EXRAIL2.cpp
52
EXRAIL2.cpp
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021-2022 Harald Barth
|
||||
* © 2021-2023 Harald Barth
|
||||
* © 2020-2022 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -91,6 +91,8 @@ LookList * RMFT2::onDeactivateLookup=NULL;
|
||||
LookList * RMFT2::onRedLookup=NULL;
|
||||
LookList * RMFT2::onAmberLookup=NULL;
|
||||
LookList * RMFT2::onGreenLookup=NULL;
|
||||
LookList * RMFT2::onChangeLookup=NULL;
|
||||
LookList * RMFT2::onClockLookup=NULL;
|
||||
|
||||
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
|
||||
#define SKIPOP progCounter+=3
|
||||
@@ -173,6 +175,9 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
onRedLookup=LookListLoader(OPCODE_ONRED);
|
||||
onAmberLookup=LookListLoader(OPCODE_ONAMBER);
|
||||
onGreenLookup=LookListLoader(OPCODE_ONGREEN);
|
||||
onChangeLookup=LookListLoader(OPCODE_ONCHANGE);
|
||||
onClockLookup=LookListLoader(OPCODE_ONTIME);
|
||||
|
||||
|
||||
// Second pass startup, define any turnouts or servos, set signals red
|
||||
// add sequences onRoutines to the lookups
|
||||
@@ -742,9 +747,17 @@ void RMFT2::loop2() {
|
||||
skipIf=IODevice::readAnalogue(operand)>=(int)(getOperand(1));
|
||||
break;
|
||||
|
||||
case OPCODE_IFLOCO: // do if the loco is the active one
|
||||
skipIf=loco!=(uint16_t)operand; // bad luck if someone enters negative loco numbers into EXRAIL
|
||||
break;
|
||||
|
||||
case OPCODE_IFNOT: // do next operand if sensor not set
|
||||
skipIf=readSensor(operand);
|
||||
break;
|
||||
|
||||
case OPCODE_IFRE: // do next operand if rotary encoder != position
|
||||
skipIf=IODevice::readAnalogue(operand)!=(int)(getOperand(1));
|
||||
break;
|
||||
|
||||
case OPCODE_IFRANDOM: // do block on random percentage
|
||||
skipIf=(uint8_t)micros() >= operand * 255/100;
|
||||
@@ -968,6 +981,8 @@ void RMFT2::loop2() {
|
||||
case OPCODE_ONRED:
|
||||
case OPCODE_ONAMBER:
|
||||
case OPCODE_ONGREEN:
|
||||
case OPCODE_ONCHANGE:
|
||||
case OPCODE_ONTIME:
|
||||
|
||||
break;
|
||||
|
||||
@@ -1069,11 +1084,23 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
|
||||
// Manage invert (HIGH on) pins
|
||||
bool aHigh=sigid & ACTIVE_HIGH_SIGNAL_FLAG;
|
||||
|
||||
|
||||
// set the three pins
|
||||
if (redpin) IODevice::write(redpin,(rag==SIGNAL_RED || rag==SIMAMBER)^aHigh);
|
||||
if (amberpin) IODevice::write(amberpin,(rag==SIGNAL_AMBER)^aHigh);
|
||||
if (greenpin) IODevice::write(greenpin,(rag==SIGNAL_GREEN || rag==SIMAMBER)^aHigh);
|
||||
if (redpin) {
|
||||
bool redval=(rag==SIGNAL_RED || rag==SIMAMBER);
|
||||
if (!aHigh) redval=!redval;
|
||||
IODevice::write(redpin,redval);
|
||||
}
|
||||
if (amberpin) {
|
||||
bool amberval=(rag==SIGNAL_AMBER);
|
||||
if (!aHigh) amberval=!amberval;
|
||||
IODevice::write(amberpin,amberval);
|
||||
}
|
||||
if (greenpin) {
|
||||
bool greenval=(rag==SIGNAL_GREEN || rag==SIMAMBER);
|
||||
if (!aHigh) greenval=!greenval;
|
||||
IODevice::write(greenpin,greenval);
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ bool RMFT2::isSignal(int16_t id,char rag) {
|
||||
@@ -1094,7 +1121,19 @@ void RMFT2::activateEvent(int16_t addr, bool activate) {
|
||||
if (activate) handleEvent(F("ACTIVATE"),onActivateLookup,addr);
|
||||
else handleEvent(F("DEACTIVATE"),onDeactivateLookup,addr);
|
||||
}
|
||||
|
||||
|
||||
void RMFT2::changeEvent(int16_t vpin, bool change) {
|
||||
// Hunt for an ONCHANGE for this sensor
|
||||
if (change) handleEvent(F("CHANGE"),onChangeLookup,vpin);
|
||||
}
|
||||
|
||||
void RMFT2::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);
|
||||
}
|
||||
|
||||
void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
|
||||
int pc= handlers->find(id);
|
||||
if (pc<0) return;
|
||||
@@ -1209,4 +1248,3 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
12
EXRAIL2.h
12
EXRAIL2.h
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020-2022 Chris Harlow
|
||||
* © 2023 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -54,6 +55,9 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
OPCODE_ENDTASK,OPCODE_ENDEXRAIL,
|
||||
OPCODE_SET_TRACK,
|
||||
OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN,
|
||||
OPCODE_ONCHANGE,
|
||||
OPCODE_ONCLOCKTIME,
|
||||
OPCODE_ONTIME,
|
||||
|
||||
// OPcodes below this point are skip-nesting IF operations
|
||||
// placed here so that they may be skipped as a group
|
||||
@@ -64,7 +68,9 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
OPCODE_IFTIMEOUT,
|
||||
OPCODE_IF,OPCODE_IFNOT,
|
||||
OPCODE_IFRANDOM,OPCODE_IFRESERVE,
|
||||
OPCODE_IFCLOSED,OPCODE_IFTHROWN
|
||||
OPCODE_IFCLOSED,OPCODE_IFTHROWN,
|
||||
OPCODE_IFRE,
|
||||
OPCODE_IFLOCO
|
||||
};
|
||||
|
||||
enum thrunger: byte {
|
||||
@@ -113,6 +119,8 @@ class LookList {
|
||||
static void createNewTask(int route, uint16_t cab);
|
||||
static void turnoutEvent(int16_t id, bool closed);
|
||||
static void activateEvent(int16_t addr, bool active);
|
||||
static void changeEvent(int16_t id, bool change);
|
||||
static void clockEvent(int16_t clocktime, bool change);
|
||||
static const int16_t SERVO_SIGNAL_FLAG=0x4000;
|
||||
static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000;
|
||||
static const int16_t DCC_SIGNAL_FLAG=0x1000;
|
||||
@@ -169,6 +177,8 @@ private:
|
||||
static LookList * onRedLookup;
|
||||
static LookList * onAmberLookup;
|
||||
static LookList * onGreenLookup;
|
||||
static LookList * onChangeLookup;
|
||||
static LookList * onClockLookup;
|
||||
|
||||
// Local variables - exist for each instance/task
|
||||
RMFT2 *next; // loop chain
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2021-2022 Chris Harlow
|
||||
* © 2020,2021 Chris Harlow. All rights reserved.
|
||||
* © 2020-2022 Chris Harlow. All rights reserved.
|
||||
* © 2023 Harald Barth
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
@@ -65,6 +65,7 @@
|
||||
#undef IFCLOSED
|
||||
#undef IFGREEN
|
||||
#undef IFGTE
|
||||
#undef IFLOCO
|
||||
#undef IFLT
|
||||
#undef IFNOT
|
||||
#undef IFRANDOM
|
||||
@@ -72,6 +73,7 @@
|
||||
#undef IFRESERVE
|
||||
#undef IFTHROWN
|
||||
#undef IFTIMEOUT
|
||||
#undef IFRE
|
||||
#undef INVERT_DIRECTION
|
||||
#undef JOIN
|
||||
#undef KILLALL
|
||||
@@ -85,9 +87,12 @@
|
||||
#undef ONDEACTIVATE
|
||||
#undef ONDEACTIVATEL
|
||||
#undef ONCLOSE
|
||||
#undef ONTIME
|
||||
#undef ONCLOCKTIME
|
||||
#undef ONGREEN
|
||||
#undef ONRED
|
||||
#undef ONTHROW
|
||||
#undef ONCHANGE
|
||||
#undef PARSE
|
||||
#undef PAUSE
|
||||
#undef PIN_TURNOUT
|
||||
@@ -178,6 +183,7 @@
|
||||
#define IFCLOSED(turnout_id)
|
||||
#define IFGREEN(signal_id)
|
||||
#define IFGTE(sensor_id,value)
|
||||
#define IFLOCO(loco_id)
|
||||
#define IFLT(sensor_id,value)
|
||||
#define IFNOT(sensor_id)
|
||||
#define IFRANDOM(percent)
|
||||
@@ -185,6 +191,7 @@
|
||||
#define IFTHROWN(turnout_id)
|
||||
#define IFRESERVE(block)
|
||||
#define IFTIMEOUT
|
||||
#define IFRE(sensor_id,value)
|
||||
#define INVERT_DIRECTION
|
||||
#define JOIN
|
||||
#define KILLALL
|
||||
@@ -195,12 +202,15 @@
|
||||
#define ONACTIVATE(addr,subaddr)
|
||||
#define ONACTIVATEL(linear)
|
||||
#define ONAMBER(signal_id)
|
||||
#define ONTIME(value)
|
||||
#define ONCLOCKTIME(hours,mins)
|
||||
#define ONDEACTIVATE(addr,subaddr)
|
||||
#define ONDEACTIVATEL(linear)
|
||||
#define ONCLOSE(turnout_id)
|
||||
#define ONGREEN(signal_id)
|
||||
#define ONRED(signal_id)
|
||||
#define ONTHROW(turnout_id)
|
||||
#define ONCHANGE(sensor_id)
|
||||
#define PAUSE
|
||||
#define PIN_TURNOUT(id,pin,description...)
|
||||
#define PRINT(msg)
|
||||
|
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020-2022 Chris Harlow
|
||||
* © 2023 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -55,6 +56,10 @@
|
||||
// helper macro for turnout description as HIDDEN
|
||||
#define HIDDEN "\x01"
|
||||
|
||||
// helper macro to strip leading zeros off time inputs
|
||||
// (10#mins)%100)
|
||||
#define STRIP_ZERO(value) 10##value%100
|
||||
|
||||
// Pass 1 Implements aliases
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ALIAS
|
||||
@@ -278,6 +283,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id),
|
||||
#define IFGREEN(signal_id) OPCODE_IFGREEN,V(signal_id),
|
||||
#define IFGTE(sensor_id,value) OPCODE_IFGTE,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define IFLOCO(loco_id) OPCODE_IFLOCO,V(loco_id),
|
||||
#define IFLT(sensor_id,value) OPCODE_IFLT,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define IFNOT(sensor_id) OPCODE_IFNOT,V(sensor_id),
|
||||
#define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent),
|
||||
@@ -285,6 +291,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
|
||||
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),
|
||||
#define IFTIMEOUT OPCODE_IFTIMEOUT,0,0,
|
||||
#define IFRE(sensor_id,value) OPCODE_IFRE,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0,
|
||||
#define JOIN OPCODE_JOIN,0,0,
|
||||
#define KILLALL OPCODE_KILLALL,0,0,
|
||||
@@ -296,11 +303,14 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
|
||||
#define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id),
|
||||
#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 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),
|
||||
#define ONRED(signal_id) OPCODE_ONRED,V(signal_id),
|
||||
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
|
||||
#define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id),
|
||||
#define PAUSE OPCODE_PAUSE,0,0,
|
||||
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
|
||||
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
|
||||
|
@@ -1 +1 @@
|
||||
#define GITHUB_SHA "devel-202212051450Z"
|
||||
#define GITHUB_SHA "devel-202301290750Z"
|
||||
|
@@ -161,6 +161,8 @@ public:
|
||||
// once the GPIO port concerned has been read.
|
||||
void setGPIOInterruptPin(int16_t pinNumber);
|
||||
|
||||
// Method to check if pins will overlap before creating new device.
|
||||
static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0);
|
||||
|
||||
protected:
|
||||
|
||||
@@ -234,9 +236,6 @@ protected:
|
||||
// pin low if an input changes state.
|
||||
int16_t _gpioInterruptPin = -1;
|
||||
|
||||
// Method to check if pins will overlap before creating new device.
|
||||
static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0);
|
||||
|
||||
// Static support function for subclass creation
|
||||
static void addDevice(IODevice *newDevice);
|
||||
|
||||
@@ -408,5 +407,8 @@ private:
|
||||
#include "IO_MCP23008.h"
|
||||
#include "IO_MCP23017.h"
|
||||
#include "IO_PCF8574.h"
|
||||
#include "IO_duinoNodes.h"
|
||||
#include "IO_EXIOExpander.h"
|
||||
|
||||
|
||||
#endif // iodevice_h
|
||||
|
128
IO_EXFastclock.h
Normal file
128
IO_EXFastclock.h
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* © 2022, Colin Murdoch. 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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The IO_EXFastclock device driver is used to interface the standalone fast clock and receive time data.
|
||||
*
|
||||
* The EX-fastClock code lives in a separate repo (https://github.com/DCC-EX/EX-Fastclock) and contains the clock logic.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef IO_EXFastclock_h
|
||||
#define IO_EXFastclock_h
|
||||
|
||||
|
||||
#include "IODevice.h"
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
#include "EXRAIL2.h"
|
||||
#include "CommandDistributor.h"
|
||||
|
||||
bool FAST_CLOCK_EXISTS = true;
|
||||
|
||||
class EXFastClock : public IODevice {
|
||||
public:
|
||||
// Constructor
|
||||
EXFastClock(uint8_t I2CAddress){
|
||||
_I2CAddress = I2CAddress;
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
static void create(uint8_t _I2CAddress) {
|
||||
|
||||
DIAG(F("Checking for Clock"));
|
||||
// Start by assuming we will find the clock
|
||||
// Check if specified I2C address is responding (blocking operation)
|
||||
// Returns I2C_STATUS_OK (0) if OK, or error code.
|
||||
uint8_t _checkforclock = I2CManager.checkAddress(_I2CAddress);
|
||||
DIAG(F("Clock check result - %d"), _checkforclock);
|
||||
// XXXX change thistosave2 bytes
|
||||
if (_checkforclock == 0) {
|
||||
FAST_CLOCK_EXISTS = true;
|
||||
//DIAG(F("I2C Fast Clock found at x%x"), _I2CAddress);
|
||||
new EXFastClock(_I2CAddress);
|
||||
}
|
||||
else {
|
||||
FAST_CLOCK_EXISTS = false;
|
||||
//DIAG(F("No Fast Clock found"));
|
||||
LCD(6,F("CLOCK NOT FOUND"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t _I2CAddress;
|
||||
|
||||
|
||||
// Initialisation of Fastclock
|
||||
void _begin() override {
|
||||
|
||||
if (FAST_CLOCK_EXISTS == true) {
|
||||
I2CManager.begin();
|
||||
if (I2CManager.exists(_I2CAddress)) {
|
||||
_deviceState = DEVSTATE_NORMAL;
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
} else {
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
//LCD(6,F("CLOCK NOT FOUND"));
|
||||
DIAG(F("Fast Clock Not Found at address %d"), _I2CAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Processing loop to obtain clock time
|
||||
|
||||
void _loop(unsigned long currentMicros) override{
|
||||
|
||||
if (FAST_CLOCK_EXISTS==true) {
|
||||
uint8_t readBuffer[3];
|
||||
byte a,b;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
I2CManager.read(_I2CAddress, readBuffer, 3);
|
||||
// XXXX change this to save a few bytes
|
||||
a = readBuffer[0];
|
||||
b = readBuffer[1];
|
||||
//_clocktime = (a << 8) + b;
|
||||
//_clockrate = readBuffer[2];
|
||||
|
||||
CommandDistributor::setClockTime(((a << 8) + b), readBuffer[2], 1);
|
||||
//setClockTime(int16_t clocktime, int8_t clockrate, byte opt);
|
||||
|
||||
// As the minimum clock increment is 2 seconds delay a bit - say 1 sec.
|
||||
// Clock interval is 60/ clockspeed i.e 60/b seconds
|
||||
delayUntil(currentMicros + ((60/b) * 1000000));
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Display EX-FastClock device driver info.
|
||||
void _display() {
|
||||
DIAG(F("FastCLock on I2C:x%x - %S"), _I2CAddress, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
213
IO_EXIOExpander.h
Normal file
213
IO_EXIOExpander.h
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* © 2021, Peter Cole. All rights reserved.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The IO_EXIOExpander.h device driver integrates with one or more EX-IOExpander devices.
|
||||
* This device driver will configure the device on startup, along with
|
||||
* interacting with the device for all input/output duties.
|
||||
*
|
||||
* To create EX-IOExpander devices, these are defined in myHal.cpp:
|
||||
* (Note the device driver is included by default)
|
||||
*
|
||||
* void halSetup() {
|
||||
* // EXIOExpander::create(vpin, num_vpins, i2c_address, digitalPinCount, analoguePinCount);
|
||||
* EXIOExpander::create(800, 18, 0x65, 12, 8);
|
||||
* }
|
||||
*
|
||||
* Note when defining the number of digital and analogue pins, there is no way to sanity check
|
||||
* this from the device driver, and it is up to the user to define the correct values here.
|
||||
*
|
||||
* All pins available on the EX-IOExpander device must be accounted for.
|
||||
*
|
||||
* Vpins are allocated to digital pins first, and then analogue pins, so digital pins will
|
||||
* populate the first part of the specified vpin range, with the analogue pins populating the
|
||||
* last part of the vpin range.
|
||||
* Eg. for a default Nano, 800 - 811 are digital (D2 - D13), 812 to 817 are analogue (A0 - A3, A6/A7).
|
||||
*/
|
||||
|
||||
#ifndef IO_EX_IOEXPANDER_H
|
||||
#define IO_EX_IOEXPANDER_H
|
||||
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
#include "FSH.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* IODevice subclass for EX-IOExpander.
|
||||
*/
|
||||
class EXIOExpander : public IODevice {
|
||||
public:
|
||||
static void create(VPIN vpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) {
|
||||
if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress, numDigitalPins, numAnaloguePins);
|
||||
}
|
||||
|
||||
private:
|
||||
// Constructor
|
||||
EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_i2cAddress = i2cAddress;
|
||||
_numDigitalPins = numDigitalPins;
|
||||
_numAnaloguePins = numAnaloguePins;
|
||||
_digitalPinBytes = (numDigitalPins+7)/8;
|
||||
_analoguePinBytes = numAnaloguePins * 2;
|
||||
_digitalInputStates=(byte*) calloc(_digitalPinBytes,1);
|
||||
_analogueInputStates=(byte*) calloc(_analoguePinBytes,1);
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
void _begin() {
|
||||
// Initialise EX-IOExander device
|
||||
I2CManager.begin();
|
||||
if (I2CManager.exists(_i2cAddress)) {
|
||||
_digitalOutBuffer[0] = EXIOINIT;
|
||||
_digitalOutBuffer[1] = _numDigitalPins;
|
||||
_digitalOutBuffer[2] = _numAnaloguePins;
|
||||
// Send config, if EXIORDY returned, we're good, otherwise go offline
|
||||
I2CManager.read(_i2cAddress, _commandBuffer, 1, _digitalOutBuffer, 3);
|
||||
if (_commandBuffer[0] != EXIORDY) {
|
||||
DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress);
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
return;
|
||||
}
|
||||
// Attempt to get version, if we don't get it, we don't care, don't go offline
|
||||
// Using digital in buffer in reverse to save RAM
|
||||
_commandBuffer[0] = EXIOVER;
|
||||
I2CManager.read(_i2cAddress, _versionBuffer, 3, _commandBuffer, 1);
|
||||
_majorVer = _versionBuffer[0];
|
||||
_minorVer = _versionBuffer[1];
|
||||
_patchVer = _versionBuffer[2];
|
||||
DIAG(F("EX-IOExpander device found, I2C:x%x, Version v%d.%d.%d"),
|
||||
_i2cAddress, _versionBuffer[0], _versionBuffer[1], _versionBuffer[2]);
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
} else {
|
||||
DIAG(F("EX-IOExpander device not found, I2C:x%x"), _i2cAddress);
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override {
|
||||
if (configType != CONFIGURE_INPUT) return false;
|
||||
if (paramCount != 1) return false;
|
||||
if (vpin >= _firstVpin + _numDigitalPins) {
|
||||
DIAG(F("EX-IOExpander ERROR: Vpin %d is an analogue pin, cannot use as a digital pin"), vpin);
|
||||
return false;
|
||||
}
|
||||
bool pullup = params[0];
|
||||
int pin = vpin - _firstVpin;
|
||||
_digitalOutBuffer[0] = EXIODPUP;
|
||||
_digitalOutBuffer[1] = pin;
|
||||
_digitalOutBuffer[2] = pullup;
|
||||
I2CManager.write(_i2cAddress, _digitalOutBuffer, 3);
|
||||
return true;
|
||||
}
|
||||
|
||||
// We only use this to detect incorrect use of analogue pins
|
||||
int _configureAnalogIn(VPIN vpin) override {
|
||||
if (vpin < _firstVpin + _numDigitalPins) {
|
||||
DIAG(F("EX-IOExpander ERROR: Vpin %d is a digital pin, cannot use as an analogue pin"), vpin);
|
||||
return false;
|
||||
}
|
||||
int pin = vpin - _firstVpin;
|
||||
_analogueOutBuffer[0] = EXIOENAN;
|
||||
_analogueOutBuffer[1] = pin;
|
||||
I2CManager.write(_i2cAddress, _analogueOutBuffer, 2);
|
||||
return true;
|
||||
}
|
||||
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
(void)currentMicros; // remove warning
|
||||
_commandBuffer[0] = EXIORDD;
|
||||
I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _commandBuffer, 1);
|
||||
_commandBuffer[0] = EXIORDAN;
|
||||
I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _commandBuffer, 1);
|
||||
}
|
||||
|
||||
int _readAnalogue(VPIN vpin) override {
|
||||
if (vpin < _firstVpin + _numDigitalPins) return false;
|
||||
int pin = vpin - _firstVpin - _numDigitalPins;
|
||||
uint8_t _pinLSBByte = pin * 2;
|
||||
uint8_t _pinMSBByte = _pinLSBByte + 1;
|
||||
return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte];
|
||||
}
|
||||
|
||||
int _read(VPIN vpin) override {
|
||||
if (vpin >= _firstVpin + _numDigitalPins) return false;
|
||||
int pin = vpin - _firstVpin;
|
||||
uint8_t pinByte = pin / 8;
|
||||
bool value = _digitalInputStates[pinByte] >> (pin - pinByte * 8);
|
||||
return value;
|
||||
}
|
||||
|
||||
void _write(VPIN vpin, int value) override {
|
||||
if (vpin >= _firstVpin + _numDigitalPins) return;
|
||||
int pin = vpin - _firstVpin;
|
||||
_digitalOutBuffer[0] = EXIOWRD;
|
||||
_digitalOutBuffer[1] = pin;
|
||||
_digitalOutBuffer[2] = value;
|
||||
I2CManager.write(_i2cAddress, _digitalOutBuffer, 3);
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
int _firstAnalogue, _lastAnalogue;
|
||||
if (_numAnaloguePins == 0) {
|
||||
_firstAnalogue = 0;
|
||||
_lastAnalogue = 0;
|
||||
} else {
|
||||
_firstAnalogue = _firstVpin + _numDigitalPins;
|
||||
_lastAnalogue = _firstVpin + _nPins - 1;
|
||||
}
|
||||
DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d: %d Digital Vpins %d-%d, %d Analogue Vpins %d-%d %S"),
|
||||
_i2cAddress, _majorVer, _minorVer, _patchVer,
|
||||
_numDigitalPins, _firstVpin, _firstVpin + _numDigitalPins - 1,
|
||||
_numAnaloguePins, _firstAnalogue, _lastAnalogue,
|
||||
_deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
uint8_t _i2cAddress;
|
||||
uint8_t _numDigitalPins;
|
||||
uint8_t _numAnaloguePins;
|
||||
byte _analogueOutBuffer[2];
|
||||
byte _digitalOutBuffer[3];
|
||||
uint8_t _versionBuffer[3];
|
||||
uint8_t _majorVer = 0;
|
||||
uint8_t _minorVer = 0;
|
||||
uint8_t _patchVer = 0;
|
||||
byte* _digitalInputStates;
|
||||
byte* _analogueInputStates;
|
||||
uint8_t _digitalPinBytes = 0;
|
||||
uint8_t _analoguePinBytes = 0;
|
||||
byte _commandBuffer[1];
|
||||
|
||||
enum {
|
||||
EXIOINIT = 0xE0, // Flag to initialise setup procedure
|
||||
EXIORDY = 0xE1, // Flag we have completed setup procedure, also for EX-IO to ACK setup
|
||||
EXIODPUP = 0xE2, // Flag we're sending digital pin pullup configuration
|
||||
EXIOVER = 0xE3, // Flag to get version
|
||||
EXIORDAN = 0xE4, // Flag to read an analogue input
|
||||
EXIOWRD = 0xE5, // Flag for digital write
|
||||
EXIORDD = 0xE6, // Flag to read digital input
|
||||
EXIOENAN = 0xE7, // Flag eo enable an analogue pin
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
@@ -47,7 +47,7 @@ EXTurntable::EXTurntable(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
// Initialisation of TurntableEX
|
||||
// Initialisation of EXTurntable
|
||||
void EXTurntable::_begin() {
|
||||
I2CManager.begin();
|
||||
I2CManager.setClock(1000000);
|
||||
@@ -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("TurntableEX WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"),
|
||||
DIAG(F("EX-Turntable WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"),
|
||||
vpin, value, activity, duration);
|
||||
DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"),
|
||||
_I2CAddress, 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("TurntableEX I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin,
|
||||
DIAG(F("EX-Turntable I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin,
|
||||
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
|
127
IO_RotaryEncoder.h
Normal file
127
IO_RotaryEncoder.h
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* © 2022, Peter Cole. All rights reserved.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The IO_RotaryEncoder device driver is used to receive positions from a rotary encoder connected to an Arduino via I2C.
|
||||
*
|
||||
* There is separate code required for the Arduino the rotary encoder is connected to, which is located here:
|
||||
* https://github.com/peteGSX-Projects/dcc-ex-rotary-encoder
|
||||
*
|
||||
* This device driver receives the rotary encoder position when the rotary encoder button is pushed, and these positions
|
||||
* can be tested in EX-RAIL with:
|
||||
* ONCHANGE(vpin) - flag when the rotary encoder position has changed from the previous position
|
||||
* IFRE(vpin, position) - test to see if specified rotary encoder position has been received
|
||||
*
|
||||
* Further to this, feedback can be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin.
|
||||
* A SET(vpin) will flag that a turntable (or anything else) is in motion, and a RESET(vpin) that the motion has finished.
|
||||
*
|
||||
* Refer to the documentation for further information including the valid activities and examples.
|
||||
*/
|
||||
|
||||
#ifndef IO_ROTARYENCODER_H
|
||||
#define IO_ROTARYENCODER_H
|
||||
|
||||
#include "EXRAIL2.h"
|
||||
#include "IODevice.h"
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
class RotaryEncoder : public IODevice {
|
||||
public:
|
||||
// Constructor
|
||||
RotaryEncoder(VPIN firstVpin, int nPins, uint8_t I2CAddress){
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_I2CAddress = I2CAddress;
|
||||
addDevice(this);
|
||||
}
|
||||
static void create(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
|
||||
if (checkNoOverlap(firstVpin, nPins, I2CAddress)) new RotaryEncoder(firstVpin, nPins, I2CAddress);
|
||||
}
|
||||
|
||||
private:
|
||||
// Initiate the device
|
||||
void _begin() {
|
||||
I2CManager.begin();
|
||||
if (I2CManager.exists(_I2CAddress)) {
|
||||
byte _getVersion[1] = {RE_VER};
|
||||
I2CManager.read(_I2CAddress, _versionBuffer, 3, _getVersion, 1);
|
||||
_majorVer = _versionBuffer[0];
|
||||
_minorVer = _versionBuffer[1];
|
||||
_patchVer = _versionBuffer[2];
|
||||
_buffer[0] = RE_OP;
|
||||
I2CManager.write(_I2CAddress, _buffer, 1);
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
} else {
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
I2CManager.read(_I2CAddress, _buffer, 1);
|
||||
_position = _buffer[0];
|
||||
// This here needs to have a change check, ie. position is a different value.
|
||||
#if defined(EXRAIL_ACTIVE)
|
||||
if (_position != _previousPosition) {
|
||||
_previousPosition = _position;
|
||||
RMFT2::changeEvent(_firstVpin,1);
|
||||
} else {
|
||||
RMFT2::changeEvent(_firstVpin,0);
|
||||
}
|
||||
#endif
|
||||
delayUntil(currentMicros + 100000);
|
||||
}
|
||||
|
||||
// Device specific read function
|
||||
int _readAnalogue(VPIN vpin) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return 0;
|
||||
return _position;
|
||||
}
|
||||
|
||||
void _write(VPIN vpin, int value) override {
|
||||
if (vpin == _firstVpin + 1) {
|
||||
byte _feedbackBuffer[2] = {RE_OP, value};
|
||||
I2CManager.write(_I2CAddress, _feedbackBuffer, 2);
|
||||
}
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("Rotary Encoder I2C:x%x v%d.%d.%d Configured on Vpin:%d-%d %S"), _I2CAddress, _majorVer, _minorVer, _patchVer,
|
||||
(int)_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
uint8_t _I2CAddress;
|
||||
int8_t _position;
|
||||
int8_t _previousPosition = 0;
|
||||
uint8_t _versionBuffer[3];
|
||||
uint8_t _buffer[1];
|
||||
uint8_t _majorVer = 0;
|
||||
uint8_t _minorVer = 0;
|
||||
uint8_t _patchVer = 0;
|
||||
|
||||
enum {
|
||||
RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device
|
||||
RE_OP = 0xA1, // Flag for normal operation
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif
|
172
IO_duinoNodes.h
Normal file
172
IO_duinoNodes.h
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* © 2022, Chris Harlow. All rights reserved.
|
||||
* Based on original by: Robin Simonds, Beagle Bay Inc
|
||||
*
|
||||
* This file is part of DCC-EX API
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
#ifndef IO_duinoNodes_h
|
||||
#define IO_duinoNodes_h
|
||||
#include <Arduino.h>
|
||||
#include "defines.h"
|
||||
#include "IODevice.h"
|
||||
|
||||
#define DN_PIN_MASK(bit) (0x80>>(bit%8))
|
||||
#define DN_GET_BIT(x) (_pinValues[(x)/8] & DN_PIN_MASK((x)) )
|
||||
#define DN_SET_BIT(x) _pinValues[(x)/8] |= DN_PIN_MASK((x))
|
||||
#define DN_CLR_BIT(x) _pinValues[(x)/8] &= ~DN_PIN_MASK((x))
|
||||
|
||||
|
||||
|
||||
class IO_duinoNodes : public IODevice {
|
||||
|
||||
public:
|
||||
IO_duinoNodes(VPIN firstVpin, int nPins,
|
||||
byte clockPin, byte latchPin, byte dataPin,
|
||||
const byte* pinmap) :
|
||||
IODevice(firstVpin, nPins) {
|
||||
|
||||
_latchPin=latchPin;
|
||||
_clockPin=clockPin;
|
||||
_dataPin=dataPin;
|
||||
_pinMap=pinmap;
|
||||
_nShiftBytes=(nPins+7)/8; // rounded up to multiples of 8 bits
|
||||
_pinValues=(byte*) calloc(_nShiftBytes,1);
|
||||
// Connect to HAL so my _write, _read and _loop will be called as required.
|
||||
IODevice::addDevice(this);
|
||||
}
|
||||
|
||||
// Called by HAL to start handling this device
|
||||
void _begin() override {
|
||||
_deviceState = DEVSTATE_NORMAL;
|
||||
pinMode(_latchPin,OUTPUT);
|
||||
pinMode(_clockPin,OUTPUT);
|
||||
pinMode(_dataPin,_pinMap?INPUT_PULLUP:OUTPUT);
|
||||
_display();
|
||||
}
|
||||
|
||||
// loop called by HAL supervisor
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
if (_pinMap) _loopInput(currentMicros);
|
||||
else if (_xmitPending) _loopOutput();
|
||||
}
|
||||
|
||||
void _loopInput(unsigned long currentMicros) {
|
||||
|
||||
if (currentMicros-_prevMicros < POLL_MICROS) return; // Nothing to do
|
||||
_prevMicros=currentMicros;
|
||||
|
||||
//set latch to HIGH to freeze & store parallel data
|
||||
ArduinoPins::fastWriteDigital(_latchPin, HIGH);
|
||||
delayMicroseconds(1);
|
||||
//set latch to LOW to enable the data to be transmitted serially
|
||||
ArduinoPins::fastWriteDigital(_latchPin, LOW);
|
||||
|
||||
// stream in the bitmap using mapping order provided at constructor
|
||||
for (int xmitByte=0;xmitByte<_nShiftBytes; xmitByte++) {
|
||||
byte newByte=0;
|
||||
for (int xmitBit=0;xmitBit<8; xmitBit++) {
|
||||
ArduinoPins::fastWriteDigital(_clockPin, LOW);
|
||||
delayMicroseconds(1);
|
||||
bool data = ArduinoPins::fastReadDigital(_dataPin);
|
||||
byte map=_pinMap[xmitBit];
|
||||
if (data) newByte |= map;
|
||||
else newByte &= ~map;
|
||||
ArduinoPins::fastWriteDigital(_clockPin, HIGH);
|
||||
delayMicroseconds(1);
|
||||
}
|
||||
_pinValues[xmitByte]=newByte;
|
||||
// DIAG(F("DIN %x=%x"),xmitByte, newByte);
|
||||
}
|
||||
}
|
||||
|
||||
void _loopOutput() {
|
||||
// stream out the bitmap (highest pin first)
|
||||
_xmitPending=false;
|
||||
ArduinoPins::fastWriteDigital(_latchPin, LOW);
|
||||
for (int xmitBit=_nShiftBytes*8 -1; xmitBit>=0; xmitBit--) {
|
||||
ArduinoPins::fastWriteDigital(_dataPin,DN_GET_BIT(xmitBit));
|
||||
ArduinoPins::fastWriteDigital(_clockPin,HIGH);
|
||||
ArduinoPins::fastWriteDigital(_clockPin,LOW);
|
||||
}
|
||||
ArduinoPins::fastWriteDigital(_latchPin, HIGH);
|
||||
}
|
||||
|
||||
int _read(VPIN vpin) override {
|
||||
int pin=vpin - _firstVpin;
|
||||
bool b=DN_GET_BIT(pin);
|
||||
return b?1:0;
|
||||
}
|
||||
|
||||
void _write(VPIN vpin, int value) override {
|
||||
int pin = vpin - _firstVpin;
|
||||
bool oldval=DN_GET_BIT(pin);
|
||||
bool newval=value!=0;
|
||||
if (newval==oldval) return; // no change
|
||||
if (newval) DN_SET_BIT(pin);
|
||||
else DN_CLR_BIT(pin);
|
||||
_xmitPending=true; // shift register will be sent on next _loop()
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("IO_duinoNodes %SPUT Configured on VPins:%d-%d shift=%d"),
|
||||
_pinMap?F("IN"):F("OUT"),
|
||||
(int)_firstVpin,
|
||||
(int)_firstVpin+_nPins-1, _nShiftBytes*8);
|
||||
}
|
||||
|
||||
private:
|
||||
static const unsigned long POLL_MICROS=100000; // 10 / S
|
||||
unsigned long _prevMicros;
|
||||
int _nShiftBytes=0;
|
||||
VPIN _latchPin,_clockPin,_dataPin;
|
||||
byte* _pinValues;
|
||||
bool _xmitPending; // Only relevant in output mode
|
||||
const byte* _pinMap; // NULL in output mode
|
||||
};
|
||||
|
||||
class IO_DNIN8 {
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
|
||||
{
|
||||
// input arrives as board pin 0,7,6,5,1,2,3,4
|
||||
static const byte pinmap[8]={0x80,0x01,0x02,0x04,0x40,0x20,0x10,0x08};
|
||||
if (IODevice::checkNoOverlap(firstVpin,nPins))
|
||||
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class IO_DNIN8K {
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
|
||||
{
|
||||
// input arrives as board pin 0, 1, 2, 3, 4, 5, 6, 7
|
||||
static const byte pinmap[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
|
||||
if (IODevice::checkNoOverlap(firstVpin,nPins))
|
||||
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap);
|
||||
}
|
||||
};
|
||||
|
||||
class IO_DNOU8 {
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
|
||||
{
|
||||
if (IODevice::checkNoOverlap(firstVpin,nPins))
|
||||
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,NULL);
|
||||
}
|
||||
|
||||
};
|
||||
#endif
|
39
Release_Notes/duinoNodes.md
Normal file
39
Release_Notes/duinoNodes.md
Normal file
@@ -0,0 +1,39 @@
|
||||
Using Lew's Duino Gear boards:
|
||||
|
||||
1. DNIN8 Input
|
||||
This is a shift-register implementation of a digital input collector.
|
||||
Multiple DNIN8 may be connected in sequence but it is IMPORTANT that the software
|
||||
configuratuion correctly represents the number of boards connected otherwise the results will be meaningless.
|
||||
|
||||
Use in myAnimation.h
|
||||
|
||||
HAL(IO_DNIN8, firstVpin, numPins, clockPin, latchPin, dataPin)
|
||||
e.g.
|
||||
HAL(IO_DNIN8, 400, 16, 40, 42, 44)
|
||||
|
||||
OR Use in myHal.cpp
|
||||
IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin)
|
||||
|
||||
|
||||
|
||||
This will create virtaul pins 400-415 using two DNIN8 boards connected in sequence.
|
||||
Vpins 400-407 will be on the first board (closest to the CS) and 408-415 on the second.
|
||||
|
||||
Note: 16 pins uses two boards. You may specify a non-multiple-of-8 pins but this will be rounded up to a multiple of 8 and you must connect ONLY the number of boards that this takes.
|
||||
|
||||
This example uses Arduino GPIO pins 40,42,44 as these are conveniently side-by-side on a Mega which is easier when you are using a 3 strand cable.
|
||||
|
||||
The DNIN8K module works the same but you must use DNIN8K in the HAL setup instead of DNIN8. NO you cant mix 8 and 8k versions in the same string of boards but you can create another string of boards.
|
||||
|
||||
|
||||
DNOU8 works the same way,
|
||||
Use in myAnimation.h
|
||||
|
||||
HAL(IO_DNOU8, firstVpin, numPins, clockPin, latchPin, dataPin)
|
||||
e.g.
|
||||
HAL(IO_DNIN8, 450, 16, 45, 47, 49)
|
||||
|
||||
OR Use in myHal.cpp
|
||||
IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin)
|
||||
|
||||
This creates a string of input pins 450-465. Note the clock/latch/data pins must be different to any DNIN8/k pins.
|
@@ -525,7 +525,7 @@ void WiThrottle::sendTurnouts(Print* stream) {
|
||||
}
|
||||
void WiThrottle::sendRoster(Print* stream) {
|
||||
rosterSent=true;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount);
|
||||
for (int16_t r=0;r<RMFT2::rosterNameCount;r++) {
|
||||
int16_t cabid=GETHIGHFLASHW(RMFT2::rosterIdList,r*2);
|
||||
@@ -533,11 +533,13 @@ void WiThrottle::sendRoster(Print* stream) {
|
||||
RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L');
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
#else
|
||||
(void)stream; // remove warning
|
||||
#endif
|
||||
}
|
||||
void WiThrottle::sendRoutes(Print* stream) {
|
||||
routesSent=true;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL"));
|
||||
// first pass automations
|
||||
for (int ix=0;;ix+=2) {
|
||||
@@ -554,7 +556,8 @@ void WiThrottle::sendRoutes(Print* stream) {
|
||||
StringFormatter::send(stream,F("]\\[R%d}|{%S}|{2"),id,desc);
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
|
||||
#else
|
||||
(void)stream; // remove warning
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -609,4 +612,4 @@ void WiThrottle::sendFunctions(Print* stream, byte loco) {
|
||||
int fstate=DCC::getFn(locoid,fKey);
|
||||
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),myLocos[loco].throttle,LorS(locoid),locoid,fstate,fKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -224,4 +224,8 @@ The configuration file for DCC-EX Command Station
|
||||
//
|
||||
//#define SERIAL_BT_COMMANDS
|
||||
|
||||
|
||||
// FastClock Enabler
|
||||
// To build the FastClock code into the CS please uncomment the line below
|
||||
//#define USEFASTCLOCK
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@@ -20,7 +20,8 @@
|
||||
#include "IO_HCSR04.h" // Ultrasonic range sensor
|
||||
#include "IO_VL53L0X.h" // Laser time-of-flight sensor
|
||||
#include "IO_DFPlayer.h" // MP3 sound player
|
||||
|
||||
//#include "IO_EXTurntable.h" // Turntable-EX turntable controller
|
||||
//#include "IO_EXFastClock.h" // FastClock driver
|
||||
|
||||
//==========================================================================
|
||||
// The function halSetup() is invoked from CS if it exists within the build.
|
||||
@@ -160,6 +161,65 @@ void halSetup() {
|
||||
// DFPlayer::create(10000, 10, Serial1);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines an EX-Turntable turntable instance.
|
||||
//=======================================================================
|
||||
// EXTurntable::create(VPIN, Number of VPINs, I2C Address)
|
||||
//
|
||||
// The parameters are:
|
||||
// VPIN=600
|
||||
// Number of VPINs=1 (Note there is no reason to change this)
|
||||
// I2C address=0x60
|
||||
//
|
||||
// Note that the I2C address is defined in the EX-Turntable code, and 0x60 is the default.
|
||||
|
||||
//EXTurntable::create(600, 1, 0x60);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines an EX-IOExpander instance.
|
||||
//=======================================================================
|
||||
// EXIOExpander::create(VPIN, Number of VPINs, I2C Address, Digital pin count, Analogue pin count)
|
||||
//
|
||||
// The parameters are:
|
||||
// VPIN=an available Vpin
|
||||
// Number of VPINs=Digital pin count + Analogue pin count (must match device in use as per documentation)
|
||||
// I2C address=an available I2C address (default 0x65)
|
||||
//
|
||||
// Note that the I2C address is defined in the EX-IOExpander code, and 0x65 is the default.
|
||||
// The first example is for an Arduino Nano with the default pin allocations.
|
||||
// The second example is for an Arduino Uno using all pins as digital only.
|
||||
|
||||
//EXIOExpander::create(800, 18, 0x65, 12, 6);
|
||||
//EXIOExpander::create(820, 16, 0x66, 16, 0);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines a rotary encoder instance.
|
||||
//=======================================================================
|
||||
// The parameters are:
|
||||
// firstVpin = First available Vpin to allocate
|
||||
// numPins= Number of Vpins to allocate, can be either 1 or 2
|
||||
// i2cAddress = Available I2C address (default 0x70)
|
||||
|
||||
//RotaryEncoder::create(firstVpin, numPins, i2cAddress);
|
||||
//RotaryEncoder::create(700, 1, 0x70);
|
||||
//RotaryEncoder::create(701, 2, 0x71);
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines an EX-FastClock instance.
|
||||
//=======================================================================
|
||||
// EXFastCLock::create(I2C Address)
|
||||
//
|
||||
// The parameters are:
|
||||
//
|
||||
// I2C address=0x55 (decimal 85)
|
||||
//
|
||||
// Note that the I2C address is defined in the EX-FastClock code, and 0x55 is the default.
|
||||
|
||||
|
||||
// EXFastClock::create(0x55);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@@ -19,6 +19,7 @@ default_envs =
|
||||
samd21-zero-usb
|
||||
ESP32
|
||||
Nucleo-F411RE
|
||||
Nucleo-F446RE
|
||||
Teensy3.2
|
||||
Teensy3.5
|
||||
Teensy3.6
|
||||
@@ -50,19 +51,6 @@ monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -std=c++17
|
||||
|
||||
; Firebox disabled for now
|
||||
; [env:samc21-firebox]
|
||||
; platform = atmelsam
|
||||
; board = firebox
|
||||
; framework = arduino
|
||||
; upload_protocol = atmel-ice
|
||||
; lib_deps =
|
||||
; ${env.lib_deps}
|
||||
; SparkFun External EEPROM Arduino Library
|
||||
;monitor_speed = 115200
|
||||
;monitor_echo = yes
|
||||
;build_flags = -std=c++17
|
||||
|
||||
[env:mega2560-debug]
|
||||
platform = atmelavr
|
||||
board = megaatmega2560
|
||||
@@ -109,9 +97,6 @@ lib_deps =
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
; Example, but v12 does generate bigger binaries
|
||||
; platform_packages = toolchain-atmelavr@symlink:///opt/avr-gcc-12.1.0-x64-linux
|
||||
; Should make binaries smaller
|
||||
build_flags = -mcall-prologues
|
||||
|
||||
[env:mega328]
|
||||
@@ -160,7 +145,6 @@ lib_deps =
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
; Should make binaries smaller
|
||||
build_flags = -mcall-prologues
|
||||
|
||||
[env:nano]
|
||||
@@ -184,7 +168,16 @@ platform = ststm32
|
||||
board = nucleo_f411re
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
[env:Nucleo-F446RE]
|
||||
platform = ststm32
|
||||
board = nucleo_f446re
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
@@ -226,4 +219,5 @@ board = teensy41
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore =
|
||||
lib_ignore =
|
||||
|
||||
|
@@ -4,7 +4,14 @@
|
||||
#include "StringFormatter.h"
|
||||
|
||||
|
||||
#define VERSION "4.2.8pre1"
|
||||
#define VERSION "4.2.14"
|
||||
// 4.2.14 STM32F4xx fast ADC read implementation
|
||||
// 4.2.13 Broadcast power for <s> again
|
||||
// 4.2.12 Bugfix for issue #299 TurnoutDescription NULL
|
||||
// 4.2.11 Exrail IFLOCO feature added
|
||||
// 4.2.10 SIGNAL/SIGNALH bug fix as they were inverted
|
||||
// IO_EXIOExpander.h input speed optimisation
|
||||
// 4.2.9 duinoNodes support
|
||||
// 4.2.8 HIGHMEM (EXRAIL support beyond 64kb)
|
||||
// Withrottle connect/disconnect improvements
|
||||
// Report BOARD_TYPE if provided by compiler
|
||||
|
Reference in New Issue
Block a user