2020-07-03 18:35:02 +02:00
/*
* © 2020 , Chris Harlow . All rights reserved .
2020-08-01 00:35:22 +02:00
* © 2020 , Harald Barth
2020-07-03 18:35:02 +02:00
*
* This file is part of Asbelos DCC 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/>.
*/
2020-12-09 12:57:38 +01:00
# include "DIAG.h"
2020-05-24 17:07:16 +02:00
# include "DCC.h"
# include "DCCWaveform.h"
2020-10-04 21:20:13 +02:00
# include "EEStore.h"
2020-10-02 18:12:48 +02:00
# include "GITHUB_SHA.h"
# include "version.h"
2021-01-25 22:12:06 +01:00
# include "FSH.h"
2020-08-15 12:32:32 +02:00
2020-05-24 17:07:16 +02:00
// This module is responsible for converting API calls into
// messages to be sent to the waveform generator.
// It has no visibility of the hardware, timers, interrupts
// nor of the waveform issues such as preambles, start bits checksums or cutouts.
//
// Nor should it have to deal with JMRI responsess other than the OK/FAIL
// or cv value returned. I will move that back to the JMRI interface later
//
// The interface to the waveform generator is narrowed down to merely:
// Scheduling a message on the prog or main track using a function
// Obtaining ACKs from the prog track using a function
// There are no volatiles here.
2020-06-22 11:54:57 +02:00
const byte FN_GROUP_1 = 0x01 ;
const byte FN_GROUP_2 = 0x02 ;
const byte FN_GROUP_3 = 0x04 ;
const byte FN_GROUP_4 = 0x08 ;
const byte FN_GROUP_5 = 0x10 ;
2020-12-09 12:57:38 +01:00
FSH * DCC : : shieldName = NULL ;
2020-06-22 11:54:57 +02:00
2021-01-26 11:55:46 +01:00
void DCC : : begin ( const FSH * motorShieldName , MotorDriver * mainDriver , MotorDriver * progDriver ) {
shieldName = ( FSH * ) motorShieldName ;
2020-10-02 18:12:48 +02:00
DIAG ( F ( " <iDCC-EX V-%S / %S / %S G-%S> \n " ) , F ( VERSION ) , F ( ARDUINO_TYPE ) , shieldName , F ( GITHUB_SHA ) ) ;
2020-10-04 21:20:13 +02:00
// Load stuff from EEprom
( void ) EEPROM ; // tell compiler not to warn this is unused
EEStore : : init ( ) ;
2021-01-25 16:26:39 +01:00
DCCWaveform : : begin ( mainDriver , progDriver ) ;
2020-05-24 17:07:16 +02:00
}
void DCC : : setThrottle ( uint16_t cab , uint8_t tSpeed , bool tDirection ) {
2020-09-03 12:28:52 +02:00
byte speedCode = ( tSpeed & 0x7F ) + tDirection * 128 ;
2020-06-03 11:36:01 +02:00
setThrottle2 ( cab , speedCode ) ;
2020-05-24 17:07:16 +02:00
// retain speed for loco reminders
2020-06-03 11:36:01 +02:00
updateLocoReminder ( cab , speedCode ) ;
2020-05-24 17:07:16 +02:00
}
2020-06-03 11:36:01 +02:00
void DCC : : setThrottle2 ( uint16_t cab , byte speedCode ) {
2020-06-07 14:48:42 +02:00
2020-06-01 14:56:02 +02:00
uint8_t b [ 4 ] ;
2020-05-24 17:07:16 +02:00
uint8_t nB = 0 ;
2020-06-29 14:03:08 +02:00
// DIAG(F("\nsetSpeedInternal %d %x"),cab,speedCode);
2020-05-24 17:07:16 +02:00
if ( cab > 127 )
b [ nB + + ] = highByte ( cab ) | 0xC0 ; // convert train number into a two-byte address
b [ nB + + ] = lowByte ( cab ) ;
2020-06-01 14:56:02 +02:00
b [ nB + + ] = SET_SPEED ; // 128-step speed control byte
2020-06-03 11:36:01 +02:00
b [ nB + + ] = speedCode ; // for encoding see setThrottle
2020-06-07 14:48:42 +02:00
2020-05-24 17:07:16 +02:00
DCCWaveform : : mainTrack . schedulePacket ( b , nB , 0 ) ;
}
2020-06-22 11:54:57 +02:00
void DCC : : setFunctionInternal ( int cab , byte byte1 , byte byte2 ) {
2020-07-01 11:27:53 +02:00
// DIAG(F("\nsetFunctionInternal %d %x %x"),cab,byte1,byte2);
2020-06-01 14:56:02 +02:00
byte b [ 4 ] ;
2020-05-24 17:07:16 +02:00
byte nB = 0 ;
if ( cab > 127 )
b [ nB + + ] = highByte ( cab ) | 0xC0 ; // convert train number into a two-byte address
b [ nB + + ] = lowByte ( cab ) ;
2020-06-22 11:54:57 +02:00
if ( byte1 ! = 0 ) b [ nB + + ] = byte1 ;
2020-05-24 17:07:16 +02:00
b [ nB + + ] = byte2 ;
2020-06-22 11:54:57 +02:00
DCCWaveform : : mainTrack . schedulePacket ( b , nB , 3 ) ; // send packet 3 times
}
2020-06-27 16:36:32 +02:00
uint8_t DCC : : getThrottleSpeed ( int cab ) {
int reg = lookupSpeedTable ( cab ) ;
if ( reg < 0 ) return - 1 ;
return speedTable [ reg ] . speedCode & 0x7F ;
}
bool DCC : : getThrottleDirection ( int cab ) {
int reg = lookupSpeedTable ( cab ) ;
if ( reg < 0 ) return false ;
return ( speedTable [ reg ] . speedCode & 0x80 ) ! = 0 ;
}
2020-08-01 15:03:15 +02:00
// Set function to value on or off
2020-08-01 14:06:39 +02:00
void DCC : : setFn ( int cab , byte functionNumber , bool on ) {
2020-06-29 14:03:08 +02:00
if ( cab < = 0 | | functionNumber > 28 ) return ;
2020-06-22 11:54:57 +02:00
int reg = lookupSpeedTable ( cab ) ;
if ( reg < 0 ) return ;
2020-08-01 00:35:22 +02:00
2020-08-01 14:06:39 +02:00
// Take care of functions:
// Set state of function
2020-08-01 15:03:15 +02:00
unsigned long funcmask = ( 1UL < < functionNumber ) ;
2020-08-01 14:06:39 +02:00
if ( on ) {
speedTable [ reg ] . functions | = funcmask ;
} else {
speedTable [ reg ] . functions & = ~ funcmask ;
}
updateGroupflags ( speedTable [ reg ] . groupFlags , functionNumber ) ;
return ;
}
// Change function according to how button was pressed,
// typically in WiThrottle.
// Returns new state or -1 if nothing was changed.
int DCC : : changeFn ( int cab , byte functionNumber , bool pressed ) {
int funcstate = - 1 ;
if ( cab < = 0 | | functionNumber > 28 ) return funcstate ;
int reg = lookupSpeedTable ( cab ) ;
if ( reg < 0 ) return funcstate ;
2020-08-01 00:35:22 +02:00
// Take care of functions:
// Imitate how many command stations do it: Button press is
// toggle but for F2 where it is momentary
2020-08-01 15:03:15 +02:00
unsigned long funcmask = ( 1UL < < functionNumber ) ;
2020-08-01 00:35:22 +02:00
if ( functionNumber = = 2 ) {
// turn on F2 on press and off again at release of button
2020-08-01 14:06:39 +02:00
if ( pressed ) {
speedTable [ reg ] . functions | = funcmask ;
funcstate = 1 ;
} else {
speedTable [ reg ] . functions & = ~ funcmask ;
funcstate = 0 ;
}
2020-08-01 00:35:22 +02:00
} else {
2020-08-01 15:03:15 +02:00
// toggle function on press, ignore release
2020-08-01 14:06:39 +02:00
if ( pressed ) {
speedTable [ reg ] . functions ^ = funcmask ;
}
funcstate = speedTable [ reg ] . functions & funcmask ;
2020-08-01 00:35:22 +02:00
}
2020-08-01 14:06:39 +02:00
updateGroupflags ( speedTable [ reg ] . groupFlags , functionNumber ) ;
return funcstate ;
}
2020-10-12 23:18:55 +02:00
int DCC : : getFn ( int cab , byte functionNumber ) {
if ( cab < = 0 | | functionNumber > 28 ) return - 1 ; // unknown
int reg = lookupSpeedTable ( cab ) ;
if ( reg < 0 ) return - 1 ;
unsigned long funcmask = ( 1UL < < functionNumber ) ;
return ( speedTable [ reg ] . functions & funcmask ) ? 1 : 0 ;
}
2020-08-01 14:06:39 +02:00
// Set the group flag to say we have touched the particular group.
// A group will be reminded only if it has been touched.
void DCC : : updateGroupflags ( byte & flags , int functionNumber ) {
2020-08-01 00:35:22 +02:00
byte groupMask ;
if ( functionNumber < = 4 ) groupMask = FN_GROUP_1 ;
else if ( functionNumber < = 8 ) groupMask = FN_GROUP_2 ;
else if ( functionNumber < = 12 ) groupMask = FN_GROUP_3 ;
else if ( functionNumber < = 20 ) groupMask = FN_GROUP_4 ;
else groupMask = FN_GROUP_5 ;
2020-08-01 14:06:39 +02:00
flags | = groupMask ;
2020-05-24 17:07:16 +02:00
}
void DCC : : setAccessory ( int address , byte number , bool activate ) {
2020-06-23 21:00:34 +02:00
// use masks to detect wrong values and do nothing
2020-06-29 14:03:08 +02:00
if ( address ! = ( address & 511 ) )
2020-06-23 21:00:34 +02:00
return ;
2020-06-29 14:03:08 +02:00
if ( number ! = ( number & 3 ) )
2020-06-23 21:00:34 +02:00
return ;
2020-06-07 14:48:42 +02:00
byte b [ 2 ] ;
2020-05-24 17:07:16 +02:00
b [ 0 ] = address % 64 + 128 ; // first byte is of the form 10AAAAAA, where AAAAAA represent 6 least signifcant bits of accessory address
b [ 1 ] = ( ( ( ( address / 64 ) % 8 ) < < 4 ) + ( number % 4 < < 1 ) + activate % 2 ) ^ 0xF8 ; // second byte is of the form 1AAACDDD, where C should be 1, and the least significant D represent activate/deactivate
DCCWaveform : : mainTrack . schedulePacket ( b , 2 , 4 ) ; // Repeat the packet four times
}
void DCC : : writeCVByteMain ( int cab , int cv , byte bValue ) {
2020-06-07 14:48:42 +02:00
byte b [ 5 ] ;
2020-05-24 17:07:16 +02:00
byte nB = 0 ;
if ( cab > 127 )
b [ nB + + ] = highByte ( cab ) | 0xC0 ; // convert train number into a two-byte address
b [ nB + + ] = lowByte ( cab ) ;
2020-06-01 14:56:02 +02:00
b [ nB + + ] = cv1 ( WRITE_BYTE_MAIN , cv ) ; // any CV>1023 will become modulus(1024) due to bit-mask of 0x03
2020-05-24 17:07:16 +02:00
b [ nB + + ] = cv2 ( cv ) ;
b [ nB + + ] = bValue ;
DCCWaveform : : mainTrack . schedulePacket ( b , nB , 4 ) ;
}
void DCC : : writeCVBitMain ( int cab , int cv , byte bNum , bool bValue ) {
2020-06-07 14:48:42 +02:00
byte b [ 5 ] ;
2020-05-24 17:07:16 +02:00
byte nB = 0 ;
bValue = bValue % 2 ;
bNum = bNum % 8 ;
if ( cab > 127 )
b [ nB + + ] = highByte ( cab ) | 0xC0 ; // convert train number into a two-byte address
b [ nB + + ] = lowByte ( cab ) ;
2020-06-01 14:56:02 +02:00
b [ nB + + ] = cv1 ( WRITE_BIT_MAIN , cv ) ; // any CV>1023 will become modulus(1024) due to bit-mask of 0x03
2020-05-24 17:07:16 +02:00
b [ nB + + ] = cv2 ( cv ) ;
2020-06-07 14:48:42 +02:00
b [ nB + + ] = WRITE_BIT | ( bValue ? BIT_ON : BIT_OFF ) | bNum ;
2020-05-24 17:07:16 +02:00
DCCWaveform : : mainTrack . schedulePacket ( b , nB , 4 ) ;
}
2020-07-12 01:11:30 +02:00
void DCC : : setProgTrackSyncMain ( bool on ) {
DCCWaveform : : progTrackSyncMain = on ;
}
2020-09-27 13:03:46 +02:00
void DCC : : setProgTrackBoost ( bool on ) {
DCCWaveform : : progTrackBoosted = on ;
}
2020-05-24 17:07:16 +02:00
2020-12-09 12:57:38 +01:00
FSH * DCC : : getMotorShieldName ( ) {
2020-09-24 10:51:09 +02:00
return shieldName ;
}
2021-01-03 10:11:11 +01:00
const ackOp FLASH WRITE_BIT0_PROG [ ] = {
2020-06-07 14:48:42 +02:00
BASELINE ,
W0 , WACK ,
V0 , WACK , // validate bit is 0
ITC1 , // if acked, callback(1)
2020-09-08 21:39:54 +02:00
FAIL // callback (-1)
2020-06-07 14:48:42 +02:00
} ;
2021-01-03 10:11:11 +01:00
const ackOp FLASH WRITE_BIT1_PROG [ ] = {
2020-06-07 14:48:42 +02:00
BASELINE ,
W1 , WACK ,
V1 , WACK , // validate bit is 1
ITC1 , // if acked, callback(1)
2020-09-08 21:39:54 +02:00
FAIL // callback (-1)
2020-06-07 14:48:42 +02:00
} ;
2021-01-03 10:11:11 +01:00
const ackOp FLASH VERIFY_BIT0_PROG [ ] = {
2020-09-18 01:04:42 +02:00
BASELINE ,
V0 , WACK , // validate bit is 0
ITC0 , // if acked, callback(0)
V1 , WACK , // validate bit is 1
ITC1 ,
FAIL // callback (-1)
} ;
2021-01-03 10:11:11 +01:00
const ackOp FLASH VERIFY_BIT1_PROG [ ] = {
2020-09-18 01:04:42 +02:00
BASELINE ,
V1 , WACK , // validate bit is 1
ITC1 , // if acked, callback(1)
V0 , WACK ,
ITC0 ,
FAIL // callback (-1)
} ;
2020-06-07 14:48:42 +02:00
2021-01-03 10:11:11 +01:00
const ackOp FLASH READ_BIT_PROG [ ] = {
2020-06-07 14:48:42 +02:00
BASELINE ,
V1 , WACK , // validate bit is 1
ITC1 , // if acked, callback(1)
V0 , WACK , // validate bit is zero
ITC0 , // if acked callback 0
2020-09-08 21:39:54 +02:00
FAIL // bit not readable
} ;
2020-06-07 14:48:42 +02:00
2021-01-03 10:11:11 +01:00
const ackOp FLASH WRITE_BYTE_PROG [ ] = {
2020-06-07 14:48:42 +02:00
BASELINE ,
WB , WACK , // Write
VB , WACK , // validate byte
ITC1 , // if ok callback (1)
2020-09-08 21:39:54 +02:00
FAIL // callback (-1)
} ;
2020-06-07 14:48:42 +02:00
2021-01-03 10:11:11 +01:00
const ackOp FLASH VERIFY_BYTE_PROG [ ] = {
2020-09-18 01:04:42 +02:00
BASELINE ,
VB , WACK , // validate byte
ITCB , // if ok callback value
STARTMERGE , //clear bit and byte values ready for merge pass
// each bit is validated against 0 and the result inverted in MERGE
// this is because there tend to be more zeros in cv values than ones.
// There is no need for one validation as entire byte is validated at the end
V0 , WACK , MERGE , // read and merge first tested bit (7)
ITSKIP , // do small excursion if there was no ack
SETBIT , ( ackOp ) 7 ,
V1 , WACK , NAKFAIL , // test if there is an ack on the inverse of this bit (7)
SETBIT , ( ackOp ) 6 , // and abort whole test if not else continue with bit (6)
SKIPTARGET ,
V0 , WACK , MERGE , // read and merge second tested bit (6)
V0 , WACK , MERGE , // read and merge third tested bit (5) ...
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
VB , WACK , ITCB , // verify merged byte and return it if acked ok
FAIL } ;
2020-06-07 14:48:42 +02:00
2021-01-03 10:11:11 +01:00
const ackOp FLASH READ_CV_PROG [ ] = {
2020-06-07 14:48:42 +02:00
BASELINE ,
2020-06-07 17:29:53 +02:00
STARTMERGE , //clear bit and byte values ready for merge pass
2020-06-07 18:02:22 +02:00
// each bit is validated against 0 and the result inverted in MERGE
// this is because there tend to be more zeros in cv values than ones.
// There is no need for one validation as entire byte is validated at the end
2020-09-08 10:12:40 +02:00
V0 , WACK , MERGE , // read and merge first tested bit (7)
ITSKIP , // do small excursion if there was no ack
SETBIT , ( ackOp ) 7 ,
V1 , WACK , NAKFAIL , // test if there is an ack on the inverse of this bit (7)
SETBIT , ( ackOp ) 6 , // and abort whole test if not else continue with bit (6)
SKIPTARGET ,
V0 , WACK , MERGE , // read and merge second tested bit (6)
V0 , WACK , MERGE , // read and merge third tested bit (5) ...
2020-06-07 18:02:22 +02:00
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
2020-06-07 14:48:42 +02:00
VB , WACK , ITCB , // verify merged byte and return it if acked ok
2020-09-08 21:39:54 +02:00
FAIL } ; // verification failed
2020-06-07 14:48:42 +02:00
2021-01-03 10:11:11 +01:00
const ackOp FLASH LOCO_ID_PROG [ ] = {
2020-06-08 14:04:47 +02:00
BASELINE ,
2021-01-17 14:22:16 +01:00
SETCV , ( ackOp ) 1 ,
SETBIT , ( ackOp ) 7 ,
V0 , WACK , NAKFAIL , // test CV 1 bit 7 is a zero... NAK means no loco found
SETCV , ( ackOp ) 19 , // CV 19 is consist setting
SETBYTE , ( ackOp ) 0 ,
VB , WACK , ITSKIP , // ignore consist if cv19 is zero (no consist)
SETBYTE , ( ackOp ) 128 ,
VB , WACK , ITSKIP , // ignore consist if cv19 is 128 (no consist, direction bit set)
STARTMERGE , // Setup to read cv 19
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
VB , WACK , ITCB7 , // return 7 bits only, No_ACK means CV19 not supported so ignore it
SKIPTARGET , // continue here if CV 19 is zero or fails all validation
2020-06-08 14:04:47 +02:00
SETCV , ( ackOp ) 29 ,
SETBIT , ( ackOp ) 5 ,
V0 , WACK , ITSKIP , // Skip to SKIPTARGET if bit 5 of CV29 is zero
2021-01-17 14:22:16 +01:00
2020-06-08 14:04:47 +02:00
// Long locoid
SETCV , ( ackOp ) 17 , // CV 17 is part of locoid
STARTMERGE ,
V0 , WACK , MERGE , // read and merge bit 1 etc
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
VB , WACK , NAKFAIL , // verify merged byte and return -1 it if not acked ok
STASHLOCOID , // keep stashed cv 17 for later
// Read 2nd part from CV 18
SETCV , ( ackOp ) 18 ,
STARTMERGE ,
V0 , WACK , MERGE , // read and merge bit 1 etc
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
VB , WACK , NAKFAIL , // verify merged byte and return -1 it if not acked ok
COMBINELOCOID , // Combile byte with stash to make long locoid and callback
// ITSKIP Skips to here if CV 29 bit 5 was zero. so read CV 1 and return that
SKIPTARGET ,
SETCV , ( ackOp ) 1 ,
STARTMERGE ,
2021-01-17 14:22:16 +01:00
SETBIT , ( ackOp ) 6 , // skip over first bit as we know its a zero
2020-06-08 14:04:47 +02:00
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
V0 , WACK , MERGE ,
VB , WACK , ITCB , // verify merged byte and callback
2020-09-08 21:39:54 +02:00
FAIL
} ;
2020-06-08 14:04:47 +02:00
2021-01-26 12:54:51 +01:00
const ackOp FLASH SHORT_LOCO_ID_PROG [ ] = {
2021-01-18 11:06:46 +01:00
BASELINE ,
SETCV , ( ackOp ) 19 ,
SETBYTE , ( ackOp ) 0 ,
WB , WACK , // ignore router without cv19 support
// Turn off long address flag
SETCV , ( ackOp ) 29 ,
SETBIT , ( ackOp ) 5 ,
W0 , WACK , NAKFAIL ,
SETCV , ( ackOp ) 1 ,
SETBYTEL , // low byte of word
WB , WACK , NAKFAIL ,
VB , WACK , ITCB ,
FAIL
} ;
2021-01-26 12:54:51 +01:00
const ackOp FLASH LONG_LOCO_ID_PROG [ ] = {
2021-01-18 11:06:46 +01:00
BASELINE ,
// Clear consist CV 19
SETCV , ( ackOp ) 19 ,
SETBYTE , ( ackOp ) 0 ,
WB , WACK , // ignore router without cv19 support
// Turn on long address flag cv29 bit 5
SETCV , ( ackOp ) 29 ,
SETBIT , ( ackOp ) 5 ,
W1 , WACK , NAKFAIL ,
// Store high byte of address in cv 17
SETCV , ( ackOp ) 17 ,
SETBYTEH , // high byte of word
WB , WACK , NAKFAIL ,
VB , WACK , NAKFAIL ,
// store
SETCV , ( ackOp ) 18 ,
SETBYTEL , // low byte of word
WB , WACK , NAKFAIL ,
VB , WACK , ITC1 , // callback(1) means Ok
FAIL
} ;
2020-06-08 14:04:47 +02:00
2020-07-19 19:39:08 +02:00
// On the following prog-track functions blocking defaults to false.
// blocking=true forces the API to block, waiting for the response and invoke the callback BEFORE returning.
// During that wait, other parts of the system will be unresponsive.
// blocking =false means the callback will be called some time after the API returns (typically a few tenths of a second)
// but that would be very inconvenient in a Wifi situaltion where the stream becomes
// unuavailable immediately after the API rerturns.
void DCC : : writeCVByte ( int cv , byte byteValue , ACK_CALLBACK callback , bool blocking ) {
ackManagerSetup ( cv , byteValue , WRITE_BYTE_PROG , callback , blocking ) ;
2020-06-01 14:56:02 +02:00
}
2020-05-24 17:07:16 +02:00
2020-07-19 19:39:08 +02:00
void DCC : : writeCVBit ( int cv , byte bitNum , bool bitValue , ACK_CALLBACK callback , bool blocking ) {
2020-06-07 14:48:42 +02:00
if ( bitNum > = 8 ) callback ( - 1 ) ;
2020-07-19 19:39:08 +02:00
else ackManagerSetup ( cv , bitNum , bitValue ? WRITE_BIT1_PROG : WRITE_BIT0_PROG , callback , blocking ) ;
2020-06-03 10:42:17 +02:00
}
2020-05-24 17:07:16 +02:00
2020-09-18 01:04:42 +02:00
void DCC : : verifyCVByte ( int cv , byte byteValue , ACK_CALLBACK callback , bool blocking ) {
ackManagerSetup ( cv , byteValue , VERIFY_BYTE_PROG , callback , blocking ) ;
}
void DCC : : verifyCVBit ( int cv , byte bitNum , bool bitValue , ACK_CALLBACK callback , bool blocking ) {
if ( bitNum > = 8 ) callback ( - 1 ) ;
else ackManagerSetup ( cv , bitNum , bitValue ? VERIFY_BIT1_PROG : VERIFY_BIT0_PROG , callback , blocking ) ;
}
2020-05-24 17:07:16 +02:00
2020-07-19 19:39:08 +02:00
void DCC : : readCVBit ( int cv , byte bitNum , ACK_CALLBACK callback , bool blocking ) {
2020-06-07 14:48:42 +02:00
if ( bitNum > = 8 ) callback ( - 1 ) ;
2020-07-19 19:39:08 +02:00
else ackManagerSetup ( cv , bitNum , READ_BIT_PROG , callback , blocking ) ;
2020-06-07 14:48:42 +02:00
}
2020-07-19 19:39:08 +02:00
void DCC : : readCV ( int cv , ACK_CALLBACK callback , bool blocking ) {
ackManagerSetup ( cv , 0 , READ_CV_PROG , callback , blocking ) ;
2020-05-24 17:07:16 +02:00
}
2020-07-19 19:39:08 +02:00
void DCC : : getLocoId ( ACK_CALLBACK callback , bool blocking ) {
ackManagerSetup ( 0 , 0 , LOCO_ID_PROG , callback , blocking ) ;
2020-06-08 14:04:47 +02:00
}
2020-06-07 14:48:42 +02:00
2021-01-18 11:06:46 +01:00
void DCC : : setLocoId ( int id , ACK_CALLBACK callback , bool blocking ) {
if ( id < = 0 | | id > 9999 ) callback ( - 1 ) ;
if ( id < = 127 ) ackManagerSetup ( id , SHORT_LOCO_ID_PROG , callback , blocking ) ;
else ackManagerSetup ( id | 0xc000 , LONG_LOCO_ID_PROG , callback , blocking ) ;
}
2020-06-18 20:36:37 +02:00
void DCC : : forgetLoco ( int cab ) { // removes any speed reminders for this loco
2020-06-22 11:54:57 +02:00
int reg = lookupSpeedTable ( cab ) ;
if ( reg > = 0 ) speedTable [ reg ] . loco = 0 ;
2020-06-18 20:36:37 +02:00
}
void DCC : : forgetAllLocos ( ) { // removes all speed reminders
for ( int i = 0 ; i < MAX_LOCOS ; i + + ) speedTable [ i ] . loco = 0 ;
}
2020-06-22 11:54:57 +02:00
byte DCC : : loopStatus = 0 ;
2020-06-18 20:36:37 +02:00
2020-05-24 17:07:16 +02:00
void DCC : : loop ( ) {
2020-06-07 14:48:42 +02:00
DCCWaveform : : loop ( ) ; // power overload checks
2020-07-19 19:39:08 +02:00
ackManagerLoop ( false ) ; // maintain prog track ack manager
2020-06-22 11:54:57 +02:00
issueReminders ( ) ;
}
void DCC : : issueReminders ( ) {
// if the main track transmitter still has a pending packet, skip this time around.
2020-05-24 17:07:16 +02:00
if ( DCCWaveform : : mainTrack . packetPending ) return ;
2020-06-22 11:54:57 +02:00
// This loop searches for a loco in the speed table starting at nextLoco and cycling back around
for ( int reg = 0 ; reg < MAX_LOCOS ; reg + + ) {
int slot = reg + nextLoco ;
if ( slot > = MAX_LOCOS ) slot - = MAX_LOCOS ;
if ( speedTable [ slot ] . loco > 0 ) {
// have found the next loco to remind
// issueReminder will return true if this loco is completed (ie speed and functions)
if ( issueReminder ( slot ) ) nextLoco = slot + 1 ;
return ;
}
2020-05-24 17:07:16 +02:00
}
}
2020-06-22 11:54:57 +02:00
bool DCC : : issueReminder ( int reg ) {
2020-08-01 14:06:39 +02:00
unsigned long functions = speedTable [ reg ] . functions ;
2020-06-22 11:54:57 +02:00
int loco = speedTable [ reg ] . loco ;
byte flags = speedTable [ reg ] . groupFlags ;
switch ( loopStatus ) {
case 0 :
// DIAG(F("\nReminder %d speed %d"),loco,speedTable[reg].speedCode);
setThrottle2 ( loco , speedTable [ reg ] . speedCode ) ;
break ;
case 1 : // remind function group 1 (F0-F4)
if ( flags & FN_GROUP_1 )
2020-10-13 23:08:19 +02:00
setFunctionInternal ( loco , 0 , 128 | ( ( functions > > 1 ) & 0x0F ) | ( ( functions & 0x01 ) < < 4 ) ) ; // 100D DDDD
2020-06-22 11:54:57 +02:00
break ;
case 2 : // remind function group 2 F5-F8
if ( flags & FN_GROUP_2 )
2020-10-13 23:08:19 +02:00
setFunctionInternal ( loco , 0 , 176 | ( ( functions > > 5 ) & 0x0F ) ) ; // 1011 DDDD
2020-06-22 11:54:57 +02:00
break ;
case 3 : // remind function group 3 F9-F12
if ( flags & FN_GROUP_3 )
2020-10-13 23:08:19 +02:00
setFunctionInternal ( loco , 0 , 160 | ( ( functions > > 9 ) & 0x0F ) ) ; // 1010 DDDD
2020-06-22 11:54:57 +02:00
break ;
case 4 : // remind function group 4 F13-F20
if ( flags & FN_GROUP_4 )
setFunctionInternal ( loco , 222 , ( ( functions > > 13 ) & 0xFF ) ) ;
2020-07-05 22:24:54 +02:00
flags & = ~ FN_GROUP_4 ; // dont send them again
2020-06-22 11:54:57 +02:00
break ;
case 5 : // remind function group 5 F21-F28
if ( flags & FN_GROUP_5 )
setFunctionInternal ( loco , 223 , ( ( functions > > 21 ) & 0xFF ) ) ;
2020-07-05 22:24:54 +02:00
flags & = ~ FN_GROUP_5 ; // dont send them again
2020-06-22 11:54:57 +02:00
break ;
}
loopStatus + + ;
// if we reach status 6 then this loco is done so
// reset status to 0 for next loco and return true so caller
// moves on to next loco.
if ( loopStatus > 5 ) loopStatus = 0 ;
return loopStatus = = 0 ;
}
2020-05-24 17:07:16 +02:00
2020-06-03 10:42:17 +02:00
2020-05-24 17:07:16 +02:00
///// Private helper functions below here /////////////////////
2020-06-01 14:56:02 +02:00
2020-05-24 17:07:16 +02:00
byte DCC : : cv1 ( byte opcode , int cv ) {
cv - - ;
return ( highByte ( cv ) & ( byte ) 0x03 ) | opcode ;
}
byte DCC : : cv2 ( int cv ) {
cv - - ;
return lowByte ( cv ) ;
}
2020-06-22 11:54:57 +02:00
int DCC : : lookupSpeedTable ( int locoId ) {
2020-06-16 12:06:36 +02:00
// determine speed reg for this loco
2020-05-24 17:07:16 +02:00
int firstEmpty = MAX_LOCOS ;
2020-06-22 11:54:57 +02:00
int reg ;
2020-05-24 17:07:16 +02:00
for ( reg = 0 ; reg < MAX_LOCOS ; reg + + ) {
2020-06-22 11:54:57 +02:00
if ( speedTable [ reg ] . loco = = locoId ) break ;
2020-05-24 17:07:16 +02:00
if ( speedTable [ reg ] . loco = = 0 & & firstEmpty = = MAX_LOCOS ) firstEmpty = reg ;
}
if ( reg = = MAX_LOCOS ) reg = firstEmpty ;
if ( reg > = MAX_LOCOS ) {
DIAG ( F ( " \n Too many locos \n " ) ) ;
2020-06-22 11:54:57 +02:00
return - 1 ;
2020-05-24 17:07:16 +02:00
}
2020-06-22 11:54:57 +02:00
if ( reg = = firstEmpty ) {
speedTable [ reg ] . loco = locoId ;
2020-07-05 22:00:27 +02:00
speedTable [ reg ] . speedCode = 128 ; // default direction forward
2020-06-22 11:54:57 +02:00
speedTable [ reg ] . groupFlags = 0 ;
speedTable [ reg ] . functions = 0 ;
}
return reg ;
}
void DCC : : updateLocoReminder ( int loco , byte speedCode ) {
if ( loco = = 0 ) {
2020-09-03 12:28:52 +02:00
// broadcast stop/estop but dont change direction
for ( int reg = 0 ; reg < MAX_LOCOS ; reg + + ) {
speedTable [ reg ] . speedCode = ( speedTable [ reg ] . speedCode & 0x80 ) | ( speedCode & 0x7f ) ;
}
2020-06-22 11:54:57 +02:00
return ;
}
// determine speed reg for this loco
int reg = lookupSpeedTable ( loco ) ;
if ( reg > = 0 ) speedTable [ reg ] . speedCode = speedCode ;
2020-06-07 14:48:42 +02:00
}
2020-05-24 17:07:16 +02:00
DCC : : LOCO DCC : : speedTable [ MAX_LOCOS ] ;
2020-05-26 13:44:02 +02:00
int DCC : : nextLoco = 0 ;
2020-06-07 14:48:42 +02:00
//ACK MANAGER
ackOp const * DCC : : ackManagerProg ;
byte DCC : : ackManagerByte ;
2020-06-08 14:04:47 +02:00
byte DCC : : ackManagerStash ;
2021-01-18 11:06:46 +01:00
int DCC : : ackManagerWord ;
int DCC : : ackManagerCv ;
2020-06-07 14:48:42 +02:00
byte DCC : : ackManagerBitNum ;
bool DCC : : ackReceived ;
ACK_CALLBACK DCC : : ackManagerCallback ;
2020-07-19 19:39:08 +02:00
void DCC : : ackManagerSetup ( int cv , byte byteValueOrBitnum , ackOp const program [ ] , ACK_CALLBACK callback , bool blocking ) {
2020-06-07 14:48:42 +02:00
ackManagerCv = cv ;
ackManagerProg = program ;
2020-06-07 16:29:09 +02:00
ackManagerByte = byteValueOrBitnum ;
ackManagerBitNum = byteValueOrBitnum ;
2020-06-07 14:48:42 +02:00
ackManagerCallback = callback ;
2020-07-19 19:39:08 +02:00
if ( blocking ) ackManagerLoop ( blocking ) ;
2020-06-07 16:29:09 +02:00
}
2020-06-07 14:48:42 +02:00
2021-01-18 11:06:46 +01:00
void DCC : : ackManagerSetup ( int wordval , ackOp const program [ ] , ACK_CALLBACK callback , bool blocking ) {
ackManagerWord = wordval ;
ackManagerProg = program ;
ackManagerCallback = callback ;
if ( blocking ) ackManagerLoop ( blocking ) ;
}
2020-06-07 17:29:53 +02:00
const byte RESET_MIN = 8 ; // tuning of reset counter before sending message
2020-07-19 19:39:08 +02:00
// checkRessets return true if the caller should yield back to loop and try later.
2020-09-08 09:24:37 +02:00
bool DCC : : checkResets ( bool blocking , uint8_t numResets ) {
2020-07-19 19:39:08 +02:00
if ( blocking ) {
// must block waiting for restest to be issued
2020-09-08 09:24:37 +02:00
while ( DCCWaveform : : progTrack . sentResetsSincePacket < numResets ) ;
2020-07-19 19:39:08 +02:00
return false ; // caller need not yield
}
2020-09-08 09:24:37 +02:00
return DCCWaveform : : progTrack . sentResetsSincePacket < numResets ;
2020-07-19 19:39:08 +02:00
}
2020-06-07 14:48:42 +02:00
2020-07-19 19:39:08 +02:00
void DCC : : ackManagerLoop ( bool blocking ) {
while ( ackManagerProg ) {
2021-01-03 10:11:11 +01:00
byte opcode = GETFLASH ( ackManagerProg ) ;
2020-07-01 11:27:53 +02:00
2020-07-19 19:39:08 +02:00
// breaks from this switch will step to next prog entry
// returns from this switch will stay on same entry
// (typically waiting for a reset counter or ACK waiting, or when all finished.)
// if blocking then we must ONLY return AFTER callback issued
2020-06-07 14:48:42 +02:00
switch ( opcode ) {
2020-06-07 17:29:53 +02:00
case BASELINE :
2020-09-08 22:14:05 +02:00
if ( DCCWaveform : : progTrack . getPowerMode ( ) = = POWERMODE : : OFF ) {
2020-09-11 13:56:36 +02:00
if ( Diag : : ACK ) DIAG ( F ( " \n Auto Prog power on " ) ) ;
DCCWaveform : : progTrack . setPowerMode ( POWERMODE : : ON ) ;
DCCWaveform : : progTrack . sentResetsSincePacket = 0 ;
2020-09-08 22:14:05 +02:00
DCCWaveform : : progTrack . autoPowerOff = true ;
2020-09-25 13:37:30 +02:00
if ( ! blocking ) return ;
2020-09-08 22:14:05 +02:00
}
if ( checkResets ( blocking , DCCWaveform : : progTrack . autoPowerOff ? 20 : 3 ) ) return ;
2020-09-10 14:09:32 +02:00
DCCWaveform : : progTrack . setAckBaseline ( ) ;
2020-07-02 13:49:35 +02:00
break ;
2020-06-07 17:29:53 +02:00
case W0 : // write 0 bit
case W1 : // write 1 bit
2020-06-07 14:48:42 +02:00
{
2020-09-08 09:24:37 +02:00
if ( checkResets ( blocking , RESET_MIN ) ) return ;
2020-09-10 14:09:32 +02:00
if ( Diag : : ACK ) DIAG ( F ( " \n W%d cv=%d bit=%d " ) , opcode = = W1 , ackManagerCv , ackManagerBitNum ) ;
2020-06-07 16:29:09 +02:00
byte instruction = WRITE_BIT | ( opcode = = W1 ? BIT_ON : BIT_OFF ) | ackManagerBitNum ;
2020-06-07 14:48:42 +02:00
byte message [ ] = { cv1 ( BIT_MANIPULATE , ackManagerCv ) , cv2 ( ackManagerCv ) , instruction } ;
2020-07-11 10:35:57 +02:00
DCCWaveform : : progTrack . schedulePacket ( message , sizeof ( message ) , PROG_REPEATS ) ;
2020-09-10 14:09:32 +02:00
DCCWaveform : : progTrack . setAckPending ( ) ;
2020-07-02 13:49:35 +02:00
}
2020-06-07 14:48:42 +02:00
break ;
2020-06-07 17:29:53 +02:00
2020-06-07 14:48:42 +02:00
case WB : // write byte
{
2020-09-08 09:24:37 +02:00
if ( checkResets ( blocking , RESET_MIN ) ) return ;
2020-09-10 14:09:32 +02:00
if ( Diag : : ACK ) DIAG ( F ( " \n WB cv=%d value=%d " ) , ackManagerCv , ackManagerByte ) ;
2020-06-07 14:48:42 +02:00
byte message [ ] = { cv1 ( WRITE_BYTE , ackManagerCv ) , cv2 ( ackManagerCv ) , ackManagerByte } ;
2020-07-11 10:35:57 +02:00
DCCWaveform : : progTrack . schedulePacket ( message , sizeof ( message ) , PROG_REPEATS ) ;
2020-09-10 14:09:32 +02:00
DCCWaveform : : progTrack . setAckPending ( ) ;
2020-06-07 14:48:42 +02:00
}
break ;
2020-06-07 17:29:53 +02:00
2020-06-07 14:48:42 +02:00
case VB : // Issue validate Byte packet
{
2020-09-08 09:24:37 +02:00
if ( checkResets ( blocking , RESET_MIN ) ) return ;
2020-09-10 14:09:32 +02:00
if ( Diag : : ACK ) DIAG ( F ( " \n VB cv=%d value=%d " ) , ackManagerCv , ackManagerByte ) ;
2020-06-07 14:48:42 +02:00
byte message [ ] = { cv1 ( VERIFY_BYTE , ackManagerCv ) , cv2 ( ackManagerCv ) , ackManagerByte } ;
2020-07-11 10:35:57 +02:00
DCCWaveform : : progTrack . schedulePacket ( message , sizeof ( message ) , PROG_REPEATS ) ;
2020-09-10 14:09:32 +02:00
DCCWaveform : : progTrack . setAckPending ( ) ;
2020-06-07 14:48:42 +02:00
}
break ;
2020-06-07 17:29:53 +02:00
2020-06-07 14:48:42 +02:00
case V0 :
case V1 : // Issue validate bit=0 or bit=1 packet
{
2020-09-08 09:24:37 +02:00
if ( checkResets ( blocking , RESET_MIN ) ) return ;
2020-09-10 14:09:32 +02:00
if ( Diag : : ACK ) DIAG ( F ( " \n V%d cv=%d bit=%d " ) , opcode = = V1 , ackManagerCv , ackManagerBitNum ) ;
2020-06-07 16:29:09 +02:00
byte instruction = VERIFY_BIT | ( opcode = = V0 ? BIT_OFF : BIT_ON ) | ackManagerBitNum ;
2020-06-07 14:48:42 +02:00
byte message [ ] = { cv1 ( BIT_MANIPULATE , ackManagerCv ) , cv2 ( ackManagerCv ) , instruction } ;
2020-07-11 10:35:57 +02:00
DCCWaveform : : progTrack . schedulePacket ( message , sizeof ( message ) , PROG_REPEATS ) ;
2020-09-10 14:09:32 +02:00
DCCWaveform : : progTrack . setAckPending ( ) ;
2020-06-07 14:48:42 +02:00
}
break ;
2020-06-07 17:29:53 +02:00
2020-06-07 14:48:42 +02:00
case WACK : // wait for ack (or absence of ack)
2020-07-01 11:27:53 +02:00
{
2020-07-19 19:39:08 +02:00
byte ackState = 2 ; // keep polling
if ( blocking ) {
2020-09-10 14:09:32 +02:00
while ( ackState = = 2 ) ackState = DCCWaveform : : progTrack . getAck ( ) ;
2020-07-19 19:39:08 +02:00
}
else {
2020-09-10 14:09:32 +02:00
ackState = DCCWaveform : : progTrack . getAck ( ) ;
2020-07-19 19:39:08 +02:00
if ( ackState = = 2 ) return ; // keep polling
}
2020-07-02 13:49:35 +02:00
ackReceived = ackState = = 1 ;
break ; // we have a genuine ACK result
2020-07-01 11:27:53 +02:00
}
2020-06-07 14:48:42 +02:00
case ITC0 :
case ITC1 : // If True Callback(0 or 1) (if prevous WACK got an ACK)
if ( ackReceived ) {
2020-06-07 17:29:53 +02:00
ackManagerProg = NULL ; // all done now
2020-09-11 13:56:36 +02:00
callback ( opcode = = ITC0 ? 0 : 1 ) ;
2020-06-07 14:48:42 +02:00
return ;
}
break ;
2020-06-07 17:29:53 +02:00
2020-06-07 14:48:42 +02:00
case ITCB : // If True callback(byte)
if ( ackReceived ) {
2020-06-07 17:29:53 +02:00
ackManagerProg = NULL ; // all done now
2021-01-17 14:22:16 +01:00
callback ( ackManagerByte ) ;
return ;
}
break ;
case ITCB7 : // If True callback(byte & 0xF)
if ( ackReceived ) {
ackManagerProg = NULL ; // all done now
callback ( ackManagerByte & 0x7F ) ;
2020-06-07 14:48:42 +02:00
return ;
}
break ;
2020-06-07 17:29:53 +02:00
2020-06-08 14:04:47 +02:00
case NAKFAIL : // If nack callback(-1)
if ( ! ackReceived ) {
ackManagerProg = NULL ; // all done now
2020-09-11 13:56:36 +02:00
callback ( - 1 ) ;
2020-06-08 14:04:47 +02:00
return ;
}
break ;
2020-06-07 14:48:42 +02:00
case FAIL : // callback(-1)
ackManagerProg = NULL ;
2020-09-11 13:56:36 +02:00
callback ( - 1 ) ;
2020-06-07 14:48:42 +02:00
return ;
2020-06-07 17:29:53 +02:00
case STARTMERGE :
2020-06-07 16:29:09 +02:00
ackManagerBitNum = 7 ;
2020-06-07 14:48:42 +02:00
ackManagerByte = 0 ;
break ;
2020-06-07 17:29:53 +02:00
2020-06-07 18:02:22 +02:00
case MERGE : // Merge previous Validate zero wack response with byte value and update bit number (use for reading CV bytes)
2020-06-07 17:29:53 +02:00
ackManagerByte < < = 1 ;
2020-06-07 18:02:22 +02:00
// ackReceived means bit is zero.
if ( ! ackReceived ) ackManagerByte | = 1 ;
2020-06-07 17:29:53 +02:00
ackManagerBitNum - - ;
2020-06-07 14:48:42 +02:00
break ;
2020-06-08 14:04:47 +02:00
case SETBIT :
ackManagerProg + + ;
2021-01-03 10:11:11 +01:00
ackManagerBitNum = GETFLASH ( ackManagerProg ) ;
2020-06-08 14:04:47 +02:00
break ;
case SETCV :
ackManagerProg + + ;
2021-01-03 10:11:11 +01:00
ackManagerCv = GETFLASH ( ackManagerProg ) ;
2020-06-08 14:04:47 +02:00
break ;
2021-01-17 14:22:16 +01:00
case SETBYTE :
ackManagerProg + + ;
2021-01-25 22:12:06 +01:00
ackManagerByte = GETFLASH ( ackManagerProg ) ;
2021-01-17 14:22:16 +01:00
break ;
2021-01-18 11:06:46 +01:00
case SETBYTEH :
ackManagerByte = highByte ( ackManagerWord ) ;
break ;
case SETBYTEL :
ackManagerByte = lowByte ( ackManagerWord ) ;
break ;
2020-06-08 14:04:47 +02:00
case STASHLOCOID :
ackManagerStash = ackManagerByte ; // stash value from CV17
break ;
2020-06-07 17:29:53 +02:00
2020-06-08 14:04:47 +02:00
case COMBINELOCOID :
// ackManagerStash is cv17, ackManagerByte is CV 18
ackManagerProg = NULL ;
2020-07-01 11:27:53 +02:00
callback ( ackManagerByte + ( ( ackManagerStash - 192 ) < < 8 ) ) ;
2020-06-08 14:04:47 +02:00
return ;
case ITSKIP :
if ( ! ackReceived ) break ;
// SKIP opcodes until SKIPTARGET found
while ( opcode ! = SKIPTARGET ) {
ackManagerProg + + ;
2021-01-03 10:11:11 +01:00
opcode = GETFLASH ( ackManagerProg ) ;
2020-06-08 14:04:47 +02:00
opcode = pgm_read_byte_near ( ackManagerProg ) ;
2021-01-17 14:22:16 +01:00
// Jump over second byte of any 2-byte opcodes.
if ( opcode = = SETBIT | | opcode = = SETBYTE | | opcode = = SETCV ) ackManagerProg + + ;
2020-06-08 14:04:47 +02:00
}
break ;
case SKIPTARGET :
break ;
default :
2020-09-11 13:56:36 +02:00
DIAG ( F ( " \n !! ackOp %d FAULT!! " ) , opcode ) ;
2020-06-08 14:04:47 +02:00
ackManagerProg = NULL ;
2020-07-01 11:27:53 +02:00
callback ( - 1 ) ;
2020-06-08 14:04:47 +02:00
return ;
2020-06-07 17:29:53 +02:00
} // end of switch
2020-06-07 14:48:42 +02:00
ackManagerProg + + ;
}
}
2020-07-01 11:27:53 +02:00
void DCC : : callback ( int value ) {
2020-09-11 13:56:36 +02:00
if ( DCCWaveform : : progTrack . autoPowerOff ) {
if ( Diag : : ACK ) DIAG ( F ( " \n Auto Prog power off " ) ) ;
DCCWaveform : : progTrack . doAutoPowerOff ( ) ;
}
if ( Diag : : ACK ) DIAG ( F ( " \n Callback(%d) \n " ) , value ) ;
( ackManagerCallback ) ( value ) ;
2020-07-01 11:27:53 +02:00
}
2020-09-10 14:09:32 +02:00
void DCC : : displayCabList ( Print * stream ) {
int used = 0 ;
for ( int reg = 0 ; reg < MAX_LOCOS ; reg + + ) {
if ( speedTable [ reg ] . loco > 0 ) {
used + + ;
StringFormatter : : send ( stream , F ( " \n cab=%d, speed=%d, dir=%c " ) ,
speedTable [ reg ] . loco , speedTable [ reg ] . speedCode & 0x7f , ( speedTable [ reg ] . speedCode & 0x80 ) ? ' F ' : ' R ' ) ;
}
}
StringFormatter : : send ( stream , F ( " \n Used=%d, max=%d \n " ) , used , MAX_LOCOS ) ;
}