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?
If( timer < 75 ) then oneBit ; else zeroBit ;. I tried about everything that I can think of to get it to work.