Merge branch 'AddOutputModeAddressing'

AddOutputModeAddressing:
  Fixed off-by-one (x4) error with DCC Accessory Output Mode Addressing. Made compatible with the DCC Spec for DCC Accessory Output Mode Addressing CV storage in CV 1 & 9. Its a bit wierd but...
  Added heaps of DEBUG PRINT to the Accessory Decoder section to follow/test the various test cases through the code and to figure out how to make this stuff work. Added more code to make the existing supported functions to be more selective about which packet bits patterns they take notice of as it was too broad previously Will remove some of the notifyCall-Back functions as some were not well conceived at the time and now need to go Testing is NOT complete as there were issues in JMRI that also need to be resolved in sync with this so we're not quite there yet..
  With eeprom_is_ready() for AVR-processors (#13)
  Fixed some bugs around DCC Accessory Output Mode Addressing and handling of edge cases Add function to set Accessory Decoder Address from next received Accessory Decoder command and new notify call-backs when a new address is set Added new notifyDccSigOutputState() with no OutputIndex parameter Bumped version but have NOT tagged the library as it needs more testing and checking for breakage
Added NmraDcc.h documentation changes and logic changes to correct a couple of issue that arose from Ken West performing a standard NMRA DCC Decoder Confirmance test. Added his test sketches and output reports
This commit is contained in:
Alex Shepherd
2017-12-24 16:53:22 +13:00
19 changed files with 18573 additions and 82 deletions

View File

@@ -23,6 +23,8 @@
// 2016-07-16 handle glitches on DCC line
// 2016-08-20 added ESP8266 support by Sven (littleyoda)
// 2017-01-19 added STM32F1 support by Franz-Peter
// 2017-11-29 Ken West (kgw4449@gmail.com):
// Minor fixes to pass NMRA Baseline Conformance Tests.
//
//------------------------------------------------------------------------
//
@@ -36,6 +38,9 @@
#include <avr/eeprom.h>
#endif
// Uncomment to print DEBUG messages
// #define DEBUG_PRINT
//------------------------------------------------------------------------
// DCC Receive Routine
//
@@ -80,6 +85,7 @@
//
//
//------------------------------------------------------------------------
#define MAX_ONEBITFULL 146
#define MAX_PRAEAMBEL 146
#define MAX_ONEBITHALF 82
@@ -230,6 +236,15 @@ typedef enum
}
DccRxWaitState ;
typedef enum
{
OPS_INS_RESERVED = 0,
OPS_INS_VERIFY_BYTE,
OPS_INS_BIT_MANIPULATION,
OPS_INS_WRITE_BYTE
}
OpsInstructionType;
struct DccRx_t
{
DccRxWaitState State ;
@@ -252,6 +267,8 @@ typedef struct
DCC_MSG LastMsg ;
uint8_t ExtIntNum;
uint8_t ExtIntPinNum;
int16_t myDccAddress; // Cached value of DCC Address from CVs
uint8_t inAccDecDCCAddrNextReceivedMode;
#ifdef DCC_DEBUG
uint8_t IntCount;
uint8_t TickCount;
@@ -475,10 +492,21 @@ void ExternalInterruptHandler(void)
SET_TP3;
}
else // Get next Byte
DccRx.State = WAIT_DATA ;
// KGW - Abort immediately if packet is too long.
if( DccRx.PacketBuf.Size == MAX_DCC_MESSAGE_LEN ) // Packet is too long - abort
{
DccRx.State = WAIT_PREAMBLE ;
bitMax = MAX_PRAEAMBEL;
bitMin = MIN_ONEBITFULL;
DccRx.BitCount = 0 ;
}
else
{
DccRx.State = WAIT_DATA ;
DccRx.BitCount = 0 ;
DccRx.TempByte = 0 ;
DccRx.BitCount = 0 ;
DccRx.TempByte = 0 ;
}
}
CLR_TP1;
CLR_TP3;
@@ -543,6 +571,15 @@ uint8_t readCV( unsigned int CV )
uint8_t writeCV( unsigned int CV, uint8_t Value)
{
switch( CV )
{
case CV_ACCESSORY_DECODER_ADDRESS_LSB: // Also same CV for CV_MULTIFUNCTION_PRIMARY_ADDRESS
case CV_ACCESSORY_DECODER_ADDRESS_MSB:
case CV_MULTIFUNCTION_EXTENDED_ADDRESS_MSB:
case CV_MULTIFUNCTION_EXTENDED_ADDRESS_LSB:
DccProcState.myDccAddress = -1; // Assume any CV Write Operation might change the Address
}
if( notifyCVWrite )
return notifyCVWrite( CV, Value ) ;
@@ -558,24 +595,30 @@ uint8_t writeCV( unsigned int CV, uint8_t Value)
uint16_t getMyAddr(void)
{
uint16_t Addr ;
uint8_t CV29Value ;
if( DccProcState.myDccAddress != -1 ) // See if we can return the cached value
return( DccProcState.myDccAddress );
CV29Value = readCV( CV_29_CONFIG ) ;
if( CV29Value & CV29_ACCESSORY_DECODER ) // Accessory Decoder?
Addr = ( readCV( CV_ACCESSORY_DECODER_ADDRESS_MSB ) << 6 ) | readCV( CV_ACCESSORY_DECODER_ADDRESS_LSB ) ;
if( CV29Value & CV29_ACCESSORY_DECODER ) // Accessory Decoder?
{
if( CV29Value & CV29_OUTPUT_ADDRESS_MODE )
DccProcState.myDccAddress = ( readCV( CV_ACCESSORY_DECODER_ADDRESS_MSB ) << 8 ) | readCV( CV_ACCESSORY_DECODER_ADDRESS_LSB ) - 1;
else
DccProcState.myDccAddress = ( ( readCV( CV_ACCESSORY_DECODER_ADDRESS_MSB ) & 0b00000111) << 6 ) | ( readCV( CV_ACCESSORY_DECODER_ADDRESS_LSB ) & 0b00111111) ;
}
else // Multi-Function Decoder?
{
if( CV29Value & CV29_EXT_ADDRESSING ) // Two Byte Address?
Addr = ( ( readCV( CV_MULTIFUNCTION_EXTENDED_ADDRESS_MSB ) - 192 ) << 8 ) | readCV( CV_MULTIFUNCTION_EXTENDED_ADDRESS_LSB ) ;
DccProcState.myDccAddress = ( ( readCV( CV_MULTIFUNCTION_EXTENDED_ADDRESS_MSB ) - 192 ) << 8 ) | readCV( CV_MULTIFUNCTION_EXTENDED_ADDRESS_LSB ) ;
else
Addr = readCV( 1 ) ;
DccProcState.myDccAddress = readCV( 1 ) ;
}
return Addr ;
return DccProcState.myDccAddress ;
}
void processDirectOpsOperation( uint8_t Cmd, uint16_t CVAddr, uint8_t Value )
@@ -802,12 +845,12 @@ void processMultiFunctionMessage( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t
case 0b11000000: // Feature Expansion Instruction
switch(Cmd & 0b00011111)
{
case 0B00011110:
case 0b00011110:
if( notifyDccFunc )
notifyDccFunc( Addr, AddrType, FN_13_20, Data1 ) ;
break;
case 0B00011111:
case 0b00011111:
if( notifyDccFunc )
notifyDccFunc( Addr, AddrType, FN_21_28, Data1 ) ;
break;
@@ -902,6 +945,24 @@ void clearDccProcState(uint8_t inServiceMode)
memset( &DccProcState.LastMsg, 0, sizeof( DCC_MSG ) ) ;
}
#ifdef DEBUG_PRINT
void SerialPrintPacketHex(const __FlashStringHelper *strLabel, DCC_MSG * pDccMsg)
{
Serial.print( strLabel );
for( uint8_t i = 0; i < pDccMsg->Size; i++ )
{
if( pDccMsg->Data[i] <= 9)
Serial.print('0');
Serial.print( pDccMsg->Data[i], HEX );
Serial.write( ' ' );
}
Serial.println();
}
#endif
void execDccProcessor( DCC_MSG * pDccMsg )
{
if( ( pDccMsg->Data[0] == 0 ) && ( pDccMsg->Data[1] == 0 ) )
@@ -965,61 +1026,238 @@ void execDccProcessor( DCC_MSG * pDccMsg )
if( DccProcState.Flags & FLAGS_DCC_ACCESSORY_DECODER )
{
uint16_t BoardAddress ;
uint8_t OutputAddress ;
uint8_t OutputIndex ;
uint16_t Address ;
uint16_t OutputAddress ;
uint8_t TurnoutPairIndex ;
#ifdef DEBUG_PRINT
SerialPrintPacketHex(F( "execDccProcessor: Accessory Decoder Command: "), pDccMsg);
#endif
BoardAddress = ( ( (~pDccMsg->Data[1]) & 0b01110000 ) << 2 ) | ( pDccMsg->Data[0] & 0b00111111 ) ;
// If we're filtering was it my board address Our or a broadcast address
if( ( DccProcState.Flags & FLAGS_MY_ADDRESS_ONLY ) && ( BoardAddress != getMyAddr() ) && ( BoardAddress != 511 ) )
return;
OutputAddress = pDccMsg->Data[1] & 0b00000111 ;
OutputIndex = OutputAddress >> 1;
Address = ( ( ( BoardAddress - 1 ) << 2 ) | OutputIndex ) + 1 ;
if( pDccMsg->Size == 6 && (pDccMsg->Data[2] & 0b11100000) == 0b11100000 && (pDccMsg->Data[1] & 0b00001111) == 0 )
#ifdef DEBUG_PRINT
Serial.print(F("execDccProcessor: Board Addr: "));
Serial.println(BoardAddress);
#endif
// First check for Legacy Accessory Decoder Configuration Variable Access Instruction
// as it's got a different format to the others
if((pDccMsg->Size == 5) && ((pDccMsg->Data[1] & 0b10001100) == 0b00001100))
{
// Accessory CV programming, program entire decoder
// Process only if it is our board address or a broadcast
// (even if we have more than one address on this decoder,
// CV programming is allowed only for address defined in CV1/CV9)
if( BoardAddress == getMyAddr() || BoardAddress == 511 )
#ifdef DEBUG_PRINT
Serial.println(F( "execDccProcessor: Legacy Accessory Decoder CV Access Command"));
#endif
// Check if this command is for our address or the broadcast address
if((BoardAddress != getMyAddr()) && ( OutputAddress < 511 ))
{
uint16_t CVAddr = ( ( ( pDccMsg->Data[2] & 0x03 ) << 8 ) | pDccMsg->Data[3] ) + 1 ;
uint8_t Value = pDccMsg->Data[4] ;
processDirectOpsOperation( pDccMsg->Data[2] & 0b00001100, CVAddr, Value ) ;
#ifdef DEBUG_PRINT
Serial.println(F("execDccProcessor: Board Address Not Matched"));
#endif
return;
}
uint16_t cvAddress = ((pDccMsg->Data[1] & 0b00000011) << 8) + pDccMsg->Data[2] + 1;
uint8_t cvValue = pDccMsg->Data[3];
#ifdef DEBUG_PRINT
Serial.print(F("execDccProcessor: CV: "));
Serial.print(cvAddress);
Serial.print(F(" Value: "));
Serial.println(cvValue);
#endif
if(validCV( cvAddress, 1 ))
writeCV(cvAddress, cvValue);
return;
}
else
{
if(pDccMsg->Data[1] & 0b10000000)
{
uint8_t direction = OutputAddress & 0x01;
uint8_t outputPower = (pDccMsg->Data[1] & 0b00001000) >> 3;
if( notifyDccAccState )
notifyDccAccState( Address, BoardAddress, OutputAddress, pDccMsg->Data[1] & 0b00001000 ) ;
if( notifyDccAccTurnoutBoard )
notifyDccAccTurnoutBoard( BoardAddress, OutputIndex, direction, outputPower );
if( notifyDccAccTurnoutOutput )
notifyDccAccTurnoutOutput( Address, direction, outputPower );
}
else
{
if( notifyDccSigState )
notifyDccSigState( Address, OutputIndex, pDccMsg->Data[2] ) ;
}
}
TurnoutPairIndex = (pDccMsg->Data[1] & 0b00000110) >> 1;
OutputAddress = (((BoardAddress - 1) << 2 ) | TurnoutPairIndex) + 1 ;
if( DccProcState.inAccDecDCCAddrNextReceivedMode)
{
if( DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE )
{
#ifdef DEBUG_PRINT
Serial.print(F("execDccProcessor: Set Output Addr: "));
Serial.println(OutputAddress);
#endif
uint16_t storedOutputAddress = OutputAddress + 1; // The value stored in CV1 & 9 for Output Addressing Mode is + 1
writeCV(CV_ACCESSORY_DECODER_ADDRESS_LSB, (uint8_t)(storedOutputAddress % 256));
writeCV(CV_ACCESSORY_DECODER_ADDRESS_MSB, (uint8_t)(storedOutputAddress / 256));
if( notifyDccAccOutputAddrSet )
notifyDccAccOutputAddrSet(OutputAddress);
}
else
{
#ifdef DEBUG_PRINT
Serial.print(F("execDccProcessor: Set Board Addr: "));
Serial.println(BoardAddress);
#endif
writeCV(CV_ACCESSORY_DECODER_ADDRESS_LSB, (uint8_t)(BoardAddress % 64));
writeCV(CV_ACCESSORY_DECODER_ADDRESS_MSB, (uint8_t)(BoardAddress / 64));
if( notifyDccAccBoardAddrSet )
notifyDccAccBoardAddrSet(BoardAddress);
}
DccProcState.inAccDecDCCAddrNextReceivedMode = 0; // Reset the mode now that we have set the address
}
// If we're filtering addresses, does the address match our address or is it a broadcast address? If NOT then return
if( DccProcState.Flags & FLAGS_MY_ADDRESS_ONLY )
{
if( ( DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE ) && ( OutputAddress != getMyAddr() ) && ( OutputAddress < 2045 ) )
return;
else if( ( BoardAddress != getMyAddr() ) && ( BoardAddress < 511 ) )
return;
#ifdef DEBUG_PRINT
Serial.println(F("execDccProcessor: Address Matched"));
#endif
}
if((pDccMsg->Size == 4) && ((pDccMsg->Data[1] & 0b10001001) == 1)) // Extended Accessory Decoder Control Packet Format
{
uint8_t state = pDccMsg->Data[2] & 0b00011111;
#ifdef DEBUG_PRINT
Serial.print(F("execDccProcessor: Output Addr: "));
Serial.print(OutputAddress);
Serial.print(F(" Extended State: "));
Serial.println(state);
#endif
if( notifyDccSigOutputState )
notifyDccSigOutputState(OutputAddress, state);
}
else if(pDccMsg->Size == 3) // Basic Accessory Decoder Packet Format
{
uint8_t direction = pDccMsg->Data[1] & 0b00000001;
uint8_t outputPower = (pDccMsg->Data[1] & 0b00001000) >> 3;
if( DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE )
{
#ifdef DEBUG_PRINT
Serial.print(F("execDccProcessor: Output Addr: "));
Serial.print(OutputAddress);
Serial.print(F(" Turnout Dir: "));
Serial.print(direction);
Serial.print(F(" Output Power: "));
Serial.println(outputPower);
#endif
if( notifyDccAccTurnoutOutput )
notifyDccAccTurnoutOutput( OutputAddress, direction, outputPower );
}
else
{
#ifdef DEBUG_PRINT
Serial.print(F("execDccProcessor: Turnout Pair Index: "));
Serial.print(TurnoutPairIndex);
Serial.print(F(" Dir: "));
Serial.print(direction);
Serial.print(F(" Output Power: "));
Serial.println(outputPower);
#endif
if( notifyDccAccTurnoutBoard )
notifyDccAccTurnoutBoard( BoardAddress, TurnoutPairIndex, direction, outputPower );
}
}
else if(pDccMsg->Size == 6) // Accessory Decoder OPS Mode Programming
{
#ifdef DEBUG_PRINT
Serial.println(F("execDccProcessor: OPS Mode CV Programming Command"));
#endif
// Check for unsupported OPS Mode Addressing mode
if(((pDccMsg->Data[1] & 0b10001001) != 1) && ((pDccMsg->Data[1] & 0b10001111) != 0x80))
{
#ifdef DEBUG_PRINT
Serial.println(F("execDccProcessor: Unsupported OPS Mode CV Addressing Mode"));
#endif
return;
}
// Check if this command is for our address or the broadcast address
if(DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE)
{
#ifdef DEBUG_PRINT
Serial.print(F("execDccProcessor: Check Output Address: "));
Serial.println(OutputAddress);
#endif
if((OutputAddress != getMyAddr()) && ( OutputAddress < 2045 ))
{
#ifdef DEBUG_PRINT
Serial.println(F("execDccProcessor: Output Address Not Matched"));
#endif
return;
}
}
else
{
#ifdef DEBUG_PRINT
Serial.print(F("execDccProcessor: Check Board Address: "));
Serial.println(BoardAddress);
#endif
if((BoardAddress != getMyAddr()) && ( BoardAddress < 511 ))
{
#ifdef DEBUG_PRINT
Serial.println(F("execDccProcessor: Board Address Not Matched"));
#endif
return;
}
}
uint16_t cvAddress = ((pDccMsg->Data[2] & 0b00000011) << 8) + pDccMsg->Data[3] + 1;
uint8_t cvValue = pDccMsg->Data[4];
OpsInstructionType insType = (OpsInstructionType)((pDccMsg->Data[2] & 0b00001100) >> 2) ;
#ifdef DEBUG_PRINT
Serial.print(F("execDccProcessor: OPS Mode Instruction: "));
Serial.println(insType);
#endif
switch(insType)
{
case OPS_INS_RESERVED:
case OPS_INS_VERIFY_BYTE:
#ifdef DEBUG_PRINT
Serial.print(F("execDccProcessor: Unsupported OPS Mode Instruction: "));
Serial.println(insType);
#endif
break; // We only support Write Byte or Bit Manipulation
case OPS_INS_WRITE_BYTE:
#ifdef DEBUG_PRINT
Serial.print(F("execDccProcessor: CV: "));
Serial.print(cvAddress);
Serial.print(F(" Value: "));
Serial.println(cvValue);
#endif
if(validCV( cvAddress, 1 ))
writeCV(cvAddress, cvValue);
break;
// 111CDBBB
// Where BBB represents the bit position within the CV,
// D contains the value of the bit to be verified or written,
// and C describes whether the operation is a verify bit or a write bit operation.
// C = "1" WRITE BIT
// C = "0" VERIFY BIT
case OPS_INS_BIT_MANIPULATION:
// Make sure its a Write Bit Manipulation
if((cvValue & 0b00010000) && validCV(cvAddress, 1 ))
{
uint8_t currentValue = readCV(cvAddress);
uint8_t newValueMask = 1 << (cvValue & 0b00000111);
if(cvValue & 0b00001000)
writeCV(cvAddress, cvValue | newValueMask);
else
writeCV(cvAddress, cvValue & ~newValueMask);
}
break;
}
}
}
}
@@ -1082,11 +1320,13 @@ void NmraDcc::init( uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, ui
DccProcState.Flags = Flags ;
DccProcState.OpsModeAddressBaseCV = OpsModeAddressBaseCV ;
DccProcState.myDccAddress = -1;
DccProcState.inAccDecDCCAddrNextReceivedMode = 0;
// Set the Bits that control Multifunction or Accessory behaviour
// and if the Accessory decoder optionally handles Output Addressing
uint8_t cv29Mask = Flags & (CV29_ACCESSORY_DECODER | CV29_OUTPUT_ADDRESS_MODE) ; // peal off the top two bits
writeCV( CV_29_CONFIG, ( readCV( CV_29_CONFIG ) & ~cv29Mask ) | Flags ) ;
uint8_t cv29Mask = CV29_ACCESSORY_DECODER | CV29_OUTPUT_ADDRESS_MODE ; // peal off the top two bits
writeCV( CV_29_CONFIG, ( readCV( CV_29_CONFIG ) & ~cv29Mask ) | (Flags & ~FLAGS_MY_ADDRESS_ONLY) ) ; // KGW: Don't write bit 0 to CV.
writeCV( 7, VersionId ) ;
writeCV( 8, ManufacturerId ) ;
@@ -1143,6 +1383,11 @@ uint8_t NmraDcc::getBitCount(void)
}
#endif
void NmraDcc::setAccDecDCCAddrNextReceived(uint8_t enable)
{
DccProcState.inAccDecDCCAddrNextReceivedMode = enable;
}
uint8_t NmraDcc::process()
{
if( DccProcState.inServiceMode )