//------------------------------------------------------------------------ // // Model Railroading with Arduino - NmraDcc.cpp // // Copyright (c) 2008 - 2105 Alex Shepherd // // This source file is subject of the GNU general public license 2, // that is available at the world-wide-web at // http://www.gnu.org/licenses/gpl.txt // //------------------------------------------------------------------------ // // file: NmraDcc.cpp // author: Alex Shepherd // webpage: http://mrrwa.org/ // history: 2008-03-20 Initial Version // 2011-06-26 Migrated into Arduino library from OpenDCC codebase // 2014 Added getAddr to NmraDcc Geoff Bunza // 2015-11-06 Martin Pischky (martin@pischky.de): // Experimental Version to support 14 speed steps // and new signature of notifyDccSpeed and notifyDccFunc // //------------------------------------------------------------------------ // // purpose: Provide a simplified interface to decode NMRA DCC packets // and build DCC Mobile and Stationary Decoders // //------------------------------------------------------------------------ #include "NmraDcc.h" #include //------------------------------------------------------------------------ // DCC Receive Routine // // Howto: uses two interrupts: a rising edge in DCC polarity triggers INTx // in INTx handler, Timer0 CompareB with a delay of 80us is started. // On Timer0 CompareB Match the level of DCC is evaluated and // parsed. // // |<-----116us----->| // // DCC 1: _________XXXXXXXXX_________XXXXXXXXX_________ // ^-INTx // |----87us--->| // ^Timer-INT: reads zero // // DCC 0: _________XXXXXXXXXXXXXXXXXX__________________ // ^-INTx // |----------->| // ^Timer-INT: reads one // //------------------------------------------------------------------------ // The Timer0 prescaler is hard-coded in wiring.c #define TIMER_PRESCALER 64 // We will use a time period of 80us and not 87us as this gives us a bit more time to do other stuff #define DCC_BIT_SAMPLE_PERIOD (F_CPU * 70L / TIMER_PRESCALER / 1000000L) #if (DCC_BIT_SAMPLE_PERIOD > 254) #error DCC_BIT_SAMPLE_PERIOD too big, use either larger prescaler or slower processor #endif #if (DCC_BIT_SAMPLE_PERIOD < 8) #error DCC_BIT_SAMPLE_PERIOD too small, use either smaller prescaler or faster processor #endif typedef enum { WAIT_PREAMBLE = 0, WAIT_START_BIT, WAIT_DATA, WAIT_END_BIT } DccRxWaitState ; struct DccRx_t { DccRxWaitState State ; uint8_t DataReady ; uint8_t BitCount ; uint8_t TempByte ; DCC_MSG PacketBuf; DCC_MSG PacketCopy; } DccRx ; typedef struct { uint8_t Flags ; uint8_t OpsModeAddressBaseCV ; uint8_t inServiceMode ; long LastServiceModeMillis ; uint8_t PageRegister ; // Used for Paged Operations in Service Mode Programming uint8_t DuplicateCount ; DCC_MSG LastMsg ; uint8_t ExtIntNum; uint8_t ExtIntPinNum; #ifdef DCC_DEBUG uint8_t IntCount; uint8_t TickCount; #endif } DCC_PROCESSOR_STATE ; DCC_PROCESSOR_STATE DccProcState ; void ExternalInterruptHandler(void) { OCR0B = TCNT0 + DCC_BIT_SAMPLE_PERIOD ; #if defined(TIMSK0) TIMSK0 |= (1< 10 ) DccRx.State = WAIT_START_BIT ; } else DccRx.BitCount = 0 ; break; case WAIT_START_BIT: if( !DccBitVal ) { DccRx.State = WAIT_DATA ; DccRx.PacketBuf.Size = 0; DccRx.PacketBuf.PreambleBits = 0; for(uint8_t i = 0; i< MAX_DCC_MESSAGE_LEN; i++ ) DccRx.PacketBuf.Data[i] = 0; // We now have 1 too many PreambleBits so decrement before copying DccRx.PacketBuf.PreambleBits = DccRx.BitCount - 1 ; DccRx.BitCount = 0 ; DccRx.TempByte = 0 ; } break; case WAIT_DATA: DccRx.TempByte = ( DccRx.TempByte << 1 ) ; if( DccBitVal ) DccRx.TempByte |= 1 ; if( DccRx.BitCount == 8 ) { if( DccRx.PacketBuf.Size == MAX_DCC_MESSAGE_LEN ) // Packet is too long - abort { DccRx.State = WAIT_PREAMBLE ; DccRx.BitCount = 0 ; } else { DccRx.State = WAIT_END_BIT ; DccRx.PacketBuf.Data[ DccRx.PacketBuf.Size++ ] = DccRx.TempByte ; } } break; case WAIT_END_BIT: if( DccBitVal ) // End of packet? { DccRx.State = WAIT_PREAMBLE ; DccRx.PacketCopy = DccRx.PacketBuf ; DccRx.DataReady = 1 ; } else // Get next Byte DccRx.State = WAIT_DATA ; DccRx.BitCount = 0 ; DccRx.TempByte = 0 ; } } void ackCV(void) { if( notifyCVAck ) notifyCVAck() ; } uint8_t validCV( uint16_t CV, uint8_t Writable ) { if( notifyCVResetFactoryDefault && (CV == CV_MANUFACTURER_ID ) && Writable ) notifyCVResetFactoryDefault(); if( notifyCVValid ) return notifyCVValid( CV, Writable ) ; uint8_t Valid = 1 ; if( CV > E2END ) Valid = 0 ; if( Writable && ( ( CV ==CV_VERSION_ID ) || (CV == CV_MANUFACTURER_ID ) ) ) Valid = 0 ; return Valid ; } uint8_t readCV( uint16_t CV ) { uint8_t Value ; if( notifyCVRead ) return notifyCVRead( CV ) ; Value = eeprom_read_byte( (uint8_t*) CV ) ; return Value ; } uint8_t writeCV( uint16_t CV, uint8_t Value) { if( notifyCVWrite ) return notifyCVWrite( CV, Value ) ; if( eeprom_read_byte( (uint8_t*) CV ) != Value ) { eeprom_write_byte( (uint8_t*) CV, Value ) ; if( notifyCVChange ) notifyCVChange( CV, Value) ; } return eeprom_read_byte( (uint8_t*) CV ) ; } uint16_t getMyAddr(void) { uint16_t Addr ; uint8_t CV29Value ; 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 ) ; 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 ) ; else Addr = readCV( 1 ) ; } return Addr ; } void processDirectOpsOperation( uint8_t Cmd, uint16_t CVAddr, uint8_t Value ) { // is it a Byte Operation if( Cmd & 0x04 ) { // Perform the Write Operation if( Cmd & 0x08 ) { if( validCV( CVAddr, 1 ) ) { if( writeCV( CVAddr, Value ) == Value ) ackCV(); } } else // Perform the Verify Operation { if( validCV( CVAddr, 0 ) ) { if( readCV( CVAddr ) == Value ) ackCV(); } } } // Perform the Bit-Wise Operation else { uint8_t BitMask = (1 << (Value & 0x07) ) ; uint8_t BitValue = Value & 0x08 ; uint8_t BitWrite = Value & 0x10 ; uint8_t tempValue = readCV( CVAddr ) ; // Read the Current CV Value // Perform the Bit Write Operation if( BitWrite ) { if( validCV( CVAddr, 1 ) ) { if( BitValue ) tempValue |= BitMask ; // Turn the Bit On else tempValue &= ~BitMask ; // Turn the Bit Off if( writeCV( CVAddr, tempValue ) == tempValue ) ackCV() ; } } // Perform the Bit Verify Operation else { if( validCV( CVAddr, 0 ) ) { if( BitValue ) { if( tempValue & BitMask ) ackCV() ; } else { if( !( tempValue & BitMask) ) ackCV() ; } } } } } #ifdef NMRA_DCC_PROCESS_MULTIFUNCTION void processMultiFunctionMessage( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t Cmd, uint8_t Data1, uint8_t Data2 ) { uint8_t speed ; uint16_t CVAddr ; DCC_DIRECTION dir ; DCC_SPEED_STEPS speedSteps ; uint8_t CmdMasked = Cmd & 0b11100000 ; // If we are an Accessory Decoder if( DccProcState.Flags & FLAGS_DCC_ACCESSORY_DECODER ) { // and this isn't an Ops Mode Write or we are NOT faking the Multifunction Ops mode address in CV 33+34 or // it's not our fake address, then return if( ( CmdMasked != 0b11100000 ) || ( DccProcState.OpsModeAddressBaseCV == 0 ) ) return ; uint16_t FakeOpsAddr = readCV( DccProcState.OpsModeAddressBaseCV ) | ( readCV( DccProcState.OpsModeAddressBaseCV + 1 ) << 8 ) ; uint16_t OpsAddr = Addr & 0x3FFF ; if( OpsAddr != FakeOpsAddr ) return ; } // We are looking for FLAGS_MY_ADDRESS_ONLY but it does not match and it is not a Broadcast Address then return else if( ( DccProcState.Flags & FLAGS_MY_ADDRESS_ONLY ) && ( Addr != getMyAddr() ) && ( Addr != 0 ) ) return ; switch( CmdMasked ) { case 0b00000000: // Decoder Control switch( Cmd & 0b00001110 ) { case 0b00000000: if( notifyDccReset && ( Cmd & 0b00000001 ) ) // Hard Reset if( notifyDccReset) notifyDccReset( 1 ) ; break ; case 0b00000010: // Factory Test break ; case 0b00000110: // Set Decoder Flags break ; case 0b00001010: // Set Advanced Addressing break ; case 0b00001110: // Decoder Acknowledgment break ; default: // Reserved ; } break ; case 0b00100000: // Advanced Operations switch( Cmd & 0b00011111 ) { case 0b00011111: if( notifyDccSpeed ) { switch( Data1 & 0b01111111 ) { case 0b00000000: // 0=STOP speed = 1 ; // => 1 break ; case 0b00000001: // 1=EMERGENCY_STOP speed = 0 ; // => 0 break ; default: // 2..127 speed = (Data1 & 0b01111111) ; } dir = (DCC_DIRECTION) ((Data1 & 0b10000000) >> 7) ; notifyDccSpeed( Addr, AddrType, speed, dir, SPEED_STEP_128 ) ; } } break; case 0b01000000: case 0b01100000: //TODO should we cache this info in DCC_PROCESSOR_STATE.Flags ? #ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE speedSteps = (readCV( CV_29_CONFIG ) & CV29_F0_LOCATION) ? SPEED_STEP_28 : SPEED_STEP_14 ; #else speedSteps = SPEED_STEP_28 ; #endif if( notifyDccSpeed ) { switch( Cmd & 0b00011111 ) { case 0b00000000: // 0 0000 = STOP case 0b00010000: // 1 0000 = STOP speed = 1 ; // => 1 break ; case 0b00000001: // 0 0001 = EMERGENCY STOP case 0b00010001: // 1 0001 = EMERGENCY STOP speed = 0 ; // => 0 break ; default: #ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE if( speedSteps == SPEED_STEP_14 ) { speed = (Cmd & 0b00001111) ; // => 2..15 } else { #endif speed = (((Cmd & 0b00001111) << 1 ) | ((Cmd & 0b00010000) >> 4)) - 2 ; // => 2..29 #ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE } #endif } dir = (DCC_DIRECTION) ((Cmd & 0b00100000) >> 5) ; notifyDccSpeed( Addr, AddrType, speed, dir, speedSteps ) ; } if( notifyDccSpeedRaw ) notifyDccSpeedRaw(Addr, AddrType, Cmd ); #ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE if( notifyDccFunc && (speedSteps == SPEED_STEP_14) ) { // function light is controlled by this package uint8_t fn0 = (Cmd & 0b00010000) ; notifyDccFunc( Addr, AddrType, FN_0, fn0 ) ; } break; #endif case 0b10000000: // Function Group 0..4 if( notifyDccFunc ) { // function light is controlled by this package (28 or 128 speed steps) notifyDccFunc( Addr, AddrType, FN_0_4, Cmd & 0b00011111 ) ; } break; case 0b10100000: // Function Group 5..8 if( notifyDccFunc) { if (Cmd & 0b00010000 ) notifyDccFunc( Addr, AddrType, FN_5_8, Cmd & 0b00001111 ) ; else notifyDccFunc( Addr, AddrType, FN_9_12, Cmd & 0b00001111 ) ; } break; case 0b11000000: // Feature Expansion Instruction switch(Cmd & 0b00011111) { case 0B00011110: if( notifyDccFunc ) notifyDccFunc( Addr, AddrType, FN_13_20, Data1 ) ; break; case 0B00011111: if( notifyDccFunc ) notifyDccFunc( Addr, AddrType, FN_21_28, Data1 ) ; break; } break; case 0b11100000: // CV Access CVAddr = ( ( ( Cmd & 0x03 ) << 8 ) | Data1 ) + 1 ; processDirectOpsOperation( Cmd, CVAddr, Data2 ) ; break; } } #endif #ifdef NMRA_DCC_PROCESS_SERVICEMODE void processServiceModeOperation( DCC_MSG * pDccMsg ) { uint16_t CVAddr ; uint8_t Value ; if( pDccMsg->Size == 3) // 3 Byte Packets are for Address Only, Register and Paged Mode { uint8_t RegisterAddr ; RegisterAddr = pDccMsg->Data[0] & 0x07 ; Value = pDccMsg->Data[1] ; if( RegisterAddr == 5 ) { DccProcState.PageRegister = Value ; ackCV(); } else { if( RegisterAddr == 4 ) CVAddr = CV_29_CONFIG ; else if( ( RegisterAddr <= 3 ) && ( DccProcState.PageRegister > 0 ) ) CVAddr = ( ( DccProcState.PageRegister - 1 ) * 4 ) + RegisterAddr + 1 ; else CVAddr = RegisterAddr + 1 ; if( pDccMsg->Data[0] & 0x08 ) // Perform the Write Operation { if( validCV( CVAddr, 1 ) ) { if( writeCV( CVAddr, Value ) == Value ) ackCV(); } } else // Perform the Verify Operation { if( validCV( CVAddr, 0 ) ) { if( readCV( CVAddr ) == Value ) ackCV(); } } } } else if( pDccMsg->Size == 4) // 4 Byte Packets are for Direct Byte & Bit Mode { CVAddr = ( ( ( pDccMsg->Data[0] & 0x03 ) << 8 ) | pDccMsg->Data[1] ) + 1 ; Value = pDccMsg->Data[2] ; processDirectOpsOperation( pDccMsg->Data[0] & 0b00001100, CVAddr, Value ) ; } } #endif void resetServiceModeTimer(uint8_t inServiceMode) { // Set the Service Mode DccProcState.inServiceMode = inServiceMode ; DccProcState.LastServiceModeMillis = inServiceMode ? millis() : 0 ; } void clearDccProcState(uint8_t inServiceMode) { resetServiceModeTimer( inServiceMode ) ; // Set the Page Register to it's default of 1 only on the first Reset DccProcState.PageRegister = 1 ; // Clear the LastMsg buffer and DuplicateCount in preparation for possible CV programming DccProcState.DuplicateCount = 0 ; memset( &DccProcState.LastMsg, 0, sizeof( DCC_MSG ) ) ; } void execDccProcessor( DCC_MSG * pDccMsg ) { if( ( pDccMsg->Data[0] == 0 ) && ( pDccMsg->Data[1] == 0 ) ) { if( notifyDccReset ) notifyDccReset( 0 ) ; #ifdef NMRA_DCC_PROCESS_SERVICEMODE // If this is the first Reset then perform some one-shot actions as we maybe about to enter service mode if( DccProcState.inServiceMode ) resetServiceModeTimer( 1 ) ; else clearDccProcState( 1 ); #endif } else { #ifdef NMRA_DCC_PROCESS_SERVICEMODE if( DccProcState.inServiceMode && ( pDccMsg->Data[0] >= 112 ) && ( pDccMsg->Data[0] < 128 ) ) { resetServiceModeTimer( 1 ) ; if( memcmp( pDccMsg, &DccProcState.LastMsg, sizeof( DCC_MSG ) ) ) { DccProcState.DuplicateCount = 0 ; memcpy( &DccProcState.LastMsg, pDccMsg, sizeof( DCC_MSG ) ) ; } // Wait until you see 2 identicle packets before acting on a Service Mode Packet else { DccProcState.DuplicateCount++ ; processServiceModeOperation( pDccMsg ) ; } } else { if( DccProcState.inServiceMode ) clearDccProcState( 0 ); #endif // Idle Packet if( ( pDccMsg->Data[0] == 0b11111111 ) && ( pDccMsg->Data[1] == 0 ) ) { if( notifyDccIdle ) notifyDccIdle() ; } #ifdef NMRA_DCC_PROCESS_MULTIFUNCTION // Multi Function Decoders (7-bit address) else if( pDccMsg->Data[0] < 128 ) processMultiFunctionMessage( pDccMsg->Data[0], DCC_ADDR_SHORT, pDccMsg->Data[1], pDccMsg->Data[2], pDccMsg->Data[3] ) ; // Basic Accessory Decoders (9-bit) & Extended Accessory Decoders (11-bit) else if( pDccMsg->Data[0] < 192 ) #else else if( ( pDccMsg->Data[0] >= 128 ) && ( pDccMsg->Data[0] < 192 ) ) #endif { if( DccProcState.Flags & FLAGS_DCC_ACCESSORY_DECODER ) { uint16_t BoardAddress ; uint8_t OutputAddress ; uint8_t OutputIndex ; uint16_t Address ; 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->Data[1] & 0b10000000) { if( notifyDccAccState ) notifyDccAccState( Address, BoardAddress, OutputAddress, pDccMsg->Data[1] & 0b00001000 ) ; } else { if( notifyDccSigState ) notifyDccSigState( Address, OutputIndex, pDccMsg->Data[2] ) ; } } } #ifdef NMRA_DCC_PROCESS_MULTIFUNCTION // Multi Function Decoders (14-bit address) else if( pDccMsg->Data[0] < 232 ) { uint16_t Address ; Address = ( ( pDccMsg->Data[0] - 192 ) << 8 ) | pDccMsg->Data[1]; //TODO should we convert Address to 1 .. 10239 ? processMultiFunctionMessage( Address, DCC_ADDR_LONG, pDccMsg->Data[2], pDccMsg->Data[3], pDccMsg->Data[4] ) ; } #endif #ifdef NMRA_DCC_PROCESS_SERVICEMODE } #endif } } NmraDcc::NmraDcc() { } void NmraDcc::pin( uint8_t ExtIntNum, uint8_t ExtIntPinNum, uint8_t EnablePullup) { DccProcState.ExtIntNum = ExtIntNum; DccProcState.ExtIntPinNum = ExtIntPinNum; pinMode( ExtIntPinNum, INPUT ); if( EnablePullup ) digitalWrite(ExtIntPinNum, HIGH); } void NmraDcc::init( uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, uint8_t OpsModeAddressBaseCV ) { // Clear all the static member variables memset( &DccRx, 0, sizeof( DccRx) ); #ifdef TCCR0A // Change Timer0 Waveform Generation Mode from Fast PWM back to Normal Mode TCCR0A &= ~((1< 20L ) { clearDccProcState( 0 ) ; } } if( DccRx.DataReady ) { // We need to do this check with interrupts disabled cli(); Msg = DccRx.PacketCopy ; DccRx.DataReady = 0 ; sei(); uint8_t xorValue = 0 ; for(uint8_t i = 0; i < DccRx.PacketCopy.Size; i++) xorValue ^= DccRx.PacketCopy.Data[i]; if(xorValue) return 0 ; else { if( notifyDccMsg ) notifyDccMsg( &Msg ); execDccProcessor( &Msg ); } return 1 ; } return 0 ; };