0

I am trying to let an Attiny85 decode NMRA DCC signal. I have unfortunately made a mistake in PCB design and I have not used the correct external interrupt pin (PB2) for this purpose. Than I could simply use the NMRAdcc library and be done with it.

I tried to improvise using the pin change interrupt along side timer 1. But for reasons that are beyond me, I cannot get the job done.

The goal

I need a falling OR rising ISR and count the time between ISR intervals. I need to determen whether I fetched a ONE bit (2x 58us = 116us) or a ZERO bit (2x 100us = 200us). And I need to send every bit to the packet decoding software.

The attiny85 runs on the 8MHz internal oscilator.

The problem

Obviously it does not work. It is a custom PCB design, and it is too small to cut traces and resolder a wire. Besides using the wrong external ISR pin, there are no design flaws. The hardware is fine. The pin change ISR fires atleast.

I only have 2 LEDs to debug. Which makes life difficult. With software simulations I could atleast verify that the DCC packet processing seems to work.

What have I tried

Besides extensive LED examining and software simulation (calling the ISR (as function) from code with some delayMicrosecond() instructions, I tried to set up pin change ISR on PB4 and have timer 1 run in continous incrementing mode.

To set the pin change ISR:

void setupPinChangeInterrupt()
{
    PCMSK |= (1 << PCINT4);     // Enable interrupt on PB4
    GIMSK |= (1 << PCIE);       // Enable Pin Change Interrupts
}

And set timer 1

void setupTimer1()
{
    TCCR1 = 0;             // Reset control register
    TCNT1 = 0;             // Reset counter
    TCCR1 = (1 << CS12);   // set prescaler
}

Within the pin change ISR I run these lines

volatile uint8_t deltaBuffer[BUFFER_SIZE];
volatile uint8_t bufferIndex = 0;
volatile bool bufferFull = false;
const int ONE_BIT  = 116 ;
const int ZERO_BIT = 200 ;
ISR(PCINT0_vect)
{
    static uint8_t lastPinState = 1;

    uint8_t pinState = (PINB & (1 << PB4)) ? 1 : 0;                     // and mask all of PORTB with PB4

    if (lastPinState == 1 && pinState == 0)
    {
        uint8_t timerVal = TCNT1;                                       // get value of timer1
        TCNT1 = 0;                                                      // reset the counter

        deltaBuffer[ bufferIndex ] = timerVal ;                         // DEBUGGING 
        if( ++ bufferIndex == BUFFER_SIZE ) {  bufferFull = true ;}     // DEBUGGING 

        if( timerVal > (ONE_BIT - 30) && timerVal < (ONE_BIT + 30 ))
        {
            dccBitHandler( 1 ) ;                                        // sends bit to DCC decoding software
        }
        else if( timerVal > (ZERO_BIT - 30) && timerVal < (ZERO_BIT + 30 ))
        {
            dccBitHandler( 0 ) ;                                        // sends bit to DCC decoding software
        }
    }

    lastPinState = pinState;
}

I tried several pre-scalers, and several values. With the LEDs I could confirm that the ISR yields one and zero bits. With the LED I could print out timer values binary, so I could actually see the actual timer values. But I suspect that bits are missed somehow.

I used to fill a large array and when it is full, it would blink the LED using dirty delays to verify values.

To debug the DCC message parsing, I turned the pinchange ISR into a function, and call the function using delayMicroseconds()

//         static uint8_t dir = 1 ; dir^=1;
//         for( int i = 0 ; i < 16 ; i ++ )
//         {
//             ISRsimu() ;
//             /*dccBitHandler( 1 ) ;*/ delayMicroseconds(  58 ) ; // preamble
//         }
//         ISRsimu() ; /*dccBitHandler( 0 ) ;*/ delayMicroseconds( 100 ) ;

// //  Paket:   1 0  A  A -  A  A  A  A |  1    A   A   A - D  A  A R
// //  Adresse: 1 0 A7 A6 - A5 A4 A3 A2 |  1 /A10 /A9 /A8 - 0 A1 A0 0

// //  Paket:   1 0  A  A -  A  A  A  A |  0 A A A - 0 A A 1   EXT
// //  Adresse: 1 0 A7 A6 - A5 A4 A3 A2 |  0 /A10 /A9 /A8 - – A1 A0 – EXT
   
//         ISRsimu() ;/*dccBitHandler( 1 ) ; */ delayMicroseconds(  58 ) ;  //  1    
//         ISRsimu() ;/*dccBitHandler( 0 ) ; */ delayMicroseconds( 100 ) ;  //  0
//         ISRsimu() ;/*dccBitHandler( 0 ) ; */ delayMicroseconds( 100 ) ;  // A7
//         ISRsimu() ;/*dccBitHandler( 0 ) ; */ delayMicroseconds( 100 ) ;  // A6
//         ISRsimu() ;/*dccBitHandler( 0 ) ; */ delayMicroseconds( 100 ) ;  // A5
//         ISRsimu() ;/*dccBitHandler( 0 ) ; */ delayMicroseconds( 100 ) ;  // A4
//         ISRsimu() ;/*dccBitHandler( 0 ) ; */ delayMicroseconds( 100 ) ;  // A3
//         ISRsimu() ;/*dccBitHandler( 0 ) ; */ delayMicroseconds( 100 ) ;  // A2

//         ISRsimu() ; /*dccBitHandler( 0 ) ;*/  delayMicroseconds( 100 ) ;

//         ISRsimu() ; /*dccBitHandler( 0 ) ;   */ delayMicroseconds( 100 ) ; // X 0 = normal, 1 dcc ext   10111100
//         ISRsimu() ; /*dccBitHandler( 1 ) ;   */ delayMicroseconds(  58 ) ; // /A10 
//         ISRsimu() ; /*dccBitHandler( 1 ) ;   */ delayMicroseconds(  58 ) ; // /A9
//         ISRsimu() ; /*dccBitHandler( 1 ) ;   */ delayMicroseconds(  58 ) ; // /A8
//         ISRsimu() ; /*dccBitHandler( dir ) ; */ delayMicroseconds( dir ? 58 : 100 ) ; // D
//         ISRsimu() ; /*dccBitHandler( 0 ) ;   */ delayMicroseconds( 100 ) ; //  A1
//         ISRsimu() ; /*dccBitHandler( 1 ) ;   */ delayMicroseconds(  58 ) ; //  A0
//         ISRsimu() ; /*dccBitHandler( 0 ) ;   */ delayMicroseconds( 100 ) ; //   R

//         ISRsimu() ; /*dccBitHandler( 0 ) ;*/    delayMicroseconds( 100 ) ;

//         ISRsimu() ; /*dccBitHandler( 1 ^ 0 ) ;  */     delayMicroseconds( 58 ) ;           // 1
//         ISRsimu() ; /*dccBitHandler( 0 ^ 1 ) ;  */     delayMicroseconds( 58 ) ;           // 1
//         ISRsimu() ; /*dccBitHandler( 0 ^ 1 ) ;  */     delayMicroseconds( 58 ) ;           // 1
//         ISRsimu() ; /*dccBitHandler( 0 ^ 1 ) ;  */     delayMicroseconds( 58 ) ;           // 1
//         ISRsimu() ; /*dccBitHandler( 0 ^ dir ) ;*/     delayMicroseconds( dir ? 58 : 100 ) ; // dir
//         ISRsimu() ; /*dccBitHandler( 0 ^ 0 ) ;  */     delayMicroseconds( 100 ) ;          // 0
//         ISRsimu() ; /*dccBitHandler( 0 ^ 1 ) ;  */     delayMicroseconds( 58 ) ;           // 1
//         ISRsimu() ; /*dccBitHandler( 0 ^ 0 ) ;  */     delayMicroseconds( 100 ) ;          // 0

//         ISRsimu() ; /* dccBitHandler( 1 ) ;   */       delayMicroseconds( 58 ) ;           // 1
//     }

And this yielded to correct parsed messages. To decode the protocol, I used this self-written library code.

volatile uint8_t    dccPacket[6];
volatile uint8_t*   dccPtr = dccPacket;
volatile uint8_t    bitMask = 0x80;
volatile bool       packetReceived = false;
volatile uint8_t    preAmbleCount = 0;
volatile uint8_t    preambleReceived = 0;
volatile uint8_t    processing = 0;
volatile uint8_t    packetLength = 0;

void dccBitHandler( uint8_t bitValue )
{
    static uint8_t bitCounter = 0;

    if( bitValue == 1 ) { if( ++ preAmbleCount >= 14 ) preambleReceived = 1 ; }
    else                {        preAmbleCount = 0 ; }

    if( preambleReceived == 1 && bitValue == 0 )
    {
        dccPtr           = dccPacket;
        *dccPtr          = 0;
        packetLength     = 1;
        bitCounter       = 0;
        bitMask          = 0x80;
        preambleReceived = 0 ;
        processing       = 1 ;
        return ;
    }

    if( processing )
    {
        if( ++ bitCounter == 9 )
        {
            bitCounter = 0;

            if (bitValue == 0)
            {
                bitMask = 0x80;
                dccPtr++;
                *dccPtr = 0;
                packetLength++;
            }
            else
            {
                packetReceived   = true ;
                preambleReceived =    0 ;
                processing       =    0 ;
            }
        }
        else
        {
            if (bitValue == 1) {
                *dccPtr |= bitMask;
            }
            bitMask >>= 1;
        }
    }
}


void updateDcc()
{
    if (!packetReceived) return;
    packetReceived = false;

    // for( int i = 0 ; i < 3 ; i ++ ) {preampleFound();delay(300);preampleFound();delay(300);}  
    // delay(3000);
    // doBleeps( dccPacket[0] ) ;
    // delay(3000);
    // doBleeps( dccPacket[1] ) ;
    // delay(3000);
    // doBleeps( dccPacket[2] ) ;
    // for( int i = 0 ; i < 3 ; i ++ ) {preampleFound();delay(300);preampleFound();delay(300);}  

    
    if( (dccPacket[0] & 0xC0) != 0x80 ) return ;  // for our decoder, we check if the packet is for accessory. even before checkusm
    if( packetLength < 2 )              return ;  // dito for message length.
    
    uint8_t checksum = 0;
    for (uint8_t i = 0; i < packetLength - 1; i++) // checksum is GOEAN
    {
        checksum ^= dccPacket[i];
    }
    
    if (checksum != dccPacket[packetLength-1])
    {
        return; 
    }
    
    // TODO: parse valid packet here
    // Determine the address and whether it's an extended DCC message

//  Paket:   1 0  A  A -  A  A  A  A |  1    A   A   A - D  A  A R
//  Adresse: 1 0 A7 A6 - A5 A4 A3 A2 |  1 /A10 /A9 /A8 - 0 A1 A0 0

//  Paket:   1 0  A  A -  A  A  A  A |  0   A   A   A  - 0  A  A 1 EXT
//  Adresse: 1 0 A7 A6 - A5 A4 A3 A2 |  0 /A10 /A9 /A8 - – A1 A0 – EXT
           // 10000000                  0 1 1 1  001 00000000



    uint8_t  A1_A0     =  dccPacket[1] & 0x06 ;
    uint8_t  A7_A2     =  dccPacket[0] & 0x3F ;
    uint8_t  A10_A9_A8 = (dccPacket[1] ^ 0xFF) & 0x70 ; // A10,A9,A8

    uint16_t address =                      // address is GOEAN
        (A1_A0     >> 1 ) 
      | (A7_A2     << 2 ) 
      | (A10_A9_A8 << 4 ) ;

    uint8_t  isExtended = (dccPacket[1] >> 7) ;  // EXT is GOEAN
    if( isExtended && notifyExtendedDcc )
    {
        uint8_t value = dccPacket[2] ;
        notifyExtendedDcc( address, value ) ;
        return ;
    }

    uint8_t pow =  dccPacket[1] & 0x01 ;            // CONV. is GOEAN
    uint8_t dir = (dccPacket[1] >> 3 ) & 0x01 ;

    // for( int i = 0 ; i < address ; i ++ ) {preampleFound();delay(300);preampleFound();delay(300);}
    // delay(1000);
    // for( int i = 0 ; i < dir ; i ++ ) {preampleFound();delay(300);preampleFound();delay(300);}

    if( notifyConventionalDcc ) notifyConventionalDcc( address, pow, dir ) ;

   return ;
}
// 00000001 10111100 00000000

(GOEAN means "good one" as in 'is verified') I think I did the parsing okay, really. But at this point I am unsure of anything. I have came up with creative and elaborate software tests, but it seems I am now dead in the water.

What am I missing here?

4
  • Not exactly sure what is connected to what from your question, but as long as you have the DCC digital signal coming into one of your gpio pins, then you should be able to set a pin change interrupt on that pin and then set a timer to 1/2 of the diff between a 0 and 1 bit time. Then when you wake up from the timer, look at the signal again and if it changed then you have a 1, otherwise you have a 0. Make sense? Commented Jul 8 at 20:36
  • The DCC signal comes in on PB4 It is posted in the source code. What you are saying I also tried. If( timer < 75 ) then oneBit ; else zeroBit ; . I tried about everything that I can think of to get it to work. Commented Jul 9 at 7:58
  • Maybe start with the simplest possible code: light the red LED when you receive a valid a 1 bit and the green LED when you get a valid 0. Maybe also clear the LED on a timer overflow. Now run a signal into the pin and see what you see. This will tell you a lot. Remember SIMPLEST POSSIBLE CODE to do just this. :) Commented Jul 22 at 13:58
  • It is also easier for people to help you if you post complete and ready to compile code that is the smallest possible version of the program that demonstrates the issue. You should explicitly include (1) what you expect the code to do, and (2) what it does. Commented Jul 22 at 14:06

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.