DCC LocoNet interface issues on transmit

We are working on building some components for LocoNet DCC control of a model train layout.

Background on this project is it is an block occupancy detector.
The idea behind it is that we are using current sensors (ZMCT103C) along with comparators (LM393) to compare against a reference voltage set using a potentiometer.
This allows us to convert the analog signals of the current detector against a threshold voltage to output a digital signal.
Then in order to support far more sensors than we have digital pins available on the Arduino we are using parallel-to-serial shift registers daisy chained.
The code samples the values values read in through the shift registers.
Overall the actual sensors and everything are working great.
In the serial monitor we can see perfectly as occupancy happens on a block and it is working well on even an idling locomotive that uses minimal current.

The issues we are having are during the transmit to the LocoNet of the sensor changes.
Note we are also seeing this same issue when we are trying anything of the sample programs for sending data to the LocoNet.

We are using a breakout board for connecting to the LocoNet for both receive and transmit (See link below).
We're using the LocoNet library for the Arduino (See link below).

We tried out the breakout board with some simple examples.
For receiving LocoNet packets it is working perfectly.
We can see messages coming through and can run a simple monitor.

For transmitting though we are having issues.
Any time we attempt to transmit it "hangs".
Interesting thing is that as soon as we unplug the LocoNet cable then the it stops hanging and continues.
It seems to me that there is something about the way that the LocoNet library is doing the backoff logic for getting a turn on the network.

Essentially when we hit this line in our code "LocoNet.reportSensor( address, value == sensorOccupied );" it never returns from the function call until we unplug the LocoNet cable.
So I'm assuming that within the library there is some blocking logic (Backoff/retry logic) where it is basically just waiting.

We've tried it on both an Uno and a Nano.

We made sure that we uncommented the code for RX_INVERTED (Both LN_SW_UART_RX_INVERTED and LN_SW_UART_TX_INVERTED are uncommented).

We've tried both doing an Loconet.init both with allowing the default TX pin to be used or a specific one that we choose.

We also tried removing JMRI from the network in case that was causing issues.

We tried connecting the LocoNet cable into the booster, base station or daisy chained to another device but still always say the same issues.
Note that during the time when our transmit is "hanging" other devices on the layout are all still working without issue.

Does anyone have any thoughts on what we might be missing?
Any help is greatly appreciated.

Breakout board being used:

LocoNet Library being used:

Post your code here (NO links) in code tags so we can see.

The following is the current code (Still in prototyping, not complete yet).
The line where it hangs is "LocoNet.reportSensor( address, value == sensorOccupied );"
Any help is of course appreciated.
I'm hoping there is just something simple that I've overlooked.
Note that we've also tried several of the sample programs that are included with the LocoNet library and all of those hit the same problem such that as soon as any function that does a transmit is called then it hangs.

#include <LocoNet.h>
#include <EEPROM.h>

// Pins: Communication
// TX: library defaults to 6 and we can change it
// RX: Library forces it to be 8
#define pinTX 7

// Pins: Device setup and control
#define pinLED 13
#define pinButton 12

// Pins: Sensor reading
// SR pin 1 -> load -> D2
// SR pin 15 -> clockEnable -> D3
// SR pin 7 -> dataIn -> D4
// SR pin 2 -> clockIn -> D5
#define pinLoad 2 
#define pinClockEnable 3
#define pinDataIn 4
#define pinClockIn 5

// Control sampling collection from sensors
#define sensorOccupied 1
#define sensorNotOccupied 0
#define sampleMax 50
#define sampleThreshold 5
#define sensorInterval 2000
#define sensorCount 8
int sampleCount;
int sensorSample[sensorCount];
int sensorValue[sensorCount];
int sensorPreviousValue[sensorCount];
unsigned long sensorPreviousTime[sensorCount];

// Device addresses and communciation
uint16_t addressDecoder;
static LnBuf LnTxBuffer ;

// Device state and control
#define serialBaud 9600
#define loopDelay 10
#define defaultAddress 1999
#define useDefaultAddress 1
int state;
int firstPass;

void setup() {

  // Perform all initiatlization and setup for the device

  Serial.begin(serialBaud);

  Serial.println("Setup Started");

  // Control pins for the device
  pinMode(pinLED, OUTPUT);
  pinMode(pinButton, INPUT_PULLUP);
  
  // Shift register pins
  pinMode(pinLoad, OUTPUT);
  pinMode(pinClockEnable, OUTPUT);
  pinMode(pinDataIn, INPUT);
  pinMode(pinClockIn, OUTPUT);
  
  // First initialize the LocoNet interface, specifying the TX Pin
  LocoNet.init(pinTX);
  

  // Initialize a LocoNet packet buffer to buffer bytes from the PC 
  initLnBuf(&LnTxBuffer) ;

  // Initialize sensor values
  initSensors();

  // Get the current address for the device
  readAddress();

  // Go into regular operating state
  setState(0);
  firstPass = 1;
  outputLED();

  Serial.println("Setup Complete");

}

void loop() {      

  // Main loop of the device that will be called repeatidly as long as
  // the dvice is running.

  if (firstPass == 1) {
    sendAllsensors();
  }
  firstPass = 0;

  handleButton();

  handleMessagesRecieved();
 
  if (state == 0) {
    readSensors();
  }
  
  outputLED();

  delay(loopDelay);
}

void handleButton(){

  // Check if the programming button is being pushed and set the 
  // next state that it should be toggLED to
  if(!digitalRead(pinButton))
  {
    switch (state)
      {
        case 0:
          setState(1);      
          break;
        case 1:
          setState(0);
          break;
        }
  }

}

void handleMessagesRecieved(){

  // Check for any received LocoNet packets
  lnMsg *LnPacket = LocoNet.receive();
  if( LnPacket )
  {
    //printRXpacket(LnPacket);
    
    // Global power on
    if ( LnPacket -> data[0] == 0x83 ) {
      sendAllsensors();
    }
    
    // Programming
    if ( LnPacket -> data[0] == 0xB0 )
    {
      byte address_received = LnPacket -> data[1];

      switch (state)
      {               
        case 1:        
          // We are in programming state for the the decoder address
          // Capture the address that has been sent and store it
          Serial.println("Action : - Received B0 - Storing address");
          addressDecoder = address_received;
          writeAddress(0, addressDecoder);          
          setState(0);
          break;
       }    
    }
  }


}

void initSensors() {

  // Setup the initial values for all sensors.

  for (int i = 0; i<sensorCount; i++) {
    sensorSample[i] = 0;
    sensorValue[i] = sensorNotOccupied;
    sensorPreviousValue[i] = sensorNotOccupied;
    sensorPreviousTime[i] = 0;
  }

  sampleCount = 0;

}

void outputLED() {

  // Based on the state that the decvice is in control the LED

  int lamp;

  // Choose the speicfic LED settings for the state that the device is in
  switch (state) {
    case 0:
      lamp = 400;
      break;
    case 1:
      lamp = 400;
      break;
  }

  if (state != 0) {
    digitalWrite(pinLED, HIGH);
    delay (lamp/2);
    digitalWrite(pinLED, LOW);
    delay (lamp/2);
  } else
  {
    digitalWrite(pinLED, LOW);
  }
}

void printState()
{
  switch (state) {
    case 0:
      Serial.println("State 0 - Normal operation");
      break;
    case 1:
      Serial.println("State 1 - Decoder Address to be acquired");
      break;
  }
  
}

void setState( int newState ) {

  // Change the device from one state to another

  Serial.print("State Change : ");
  Serial.print(state);
  Serial.print(" -> ");
  Serial.println(newState);

  state = newState;

  printState();

}

void printAddress() {
  Serial.print("Address decoder : ");
  Serial.println(addressDecoder);
}

void readAddress() {

  // Read the device address informaiton from the EPROM

  if (useDefaultAddress == 0) {
    addressDecoder = EEPROM.read(0);
  } else {
    addressDecoder = defaultAddress;
  }

  printAddress();

}

void writeAddress(int slot, uint16_t address) {

  // Update the device address in the EPROM.
  // Check first if the value actaully needs to be updated so that we can limit
  // effect of too many EPROM writes over the lifetime of the device

  if ( address != EEPROM.read(slot) ) {
    EEPROM.write(slot, address);
  }
  readAddress();
}

void printRXpacket(lnMsg *LnPacket) {

  // Print out a message that has been recieved from the LocoNet

  uint8_t Length = getLnMsgSize( LnPacket ) ;  
  Serial.print("RX: ");
  for( uint8_t Index = 0; Index < Length; Index++ )
  {
    Serial.print(LnPacket->data[ Index ], HEX);
    Serial.print("  ");
    } 
  Serial.println();
}


void sendSensorMessage(uint16_t address, int value) {

  // Send out an invidiual sensor status to the LocoNet

  if ( value == 0 ) {
    Serial.println("Sensor: " + (String)address + " : Occupied");
  } else {
    Serial.println("Sensor: " + (String)address + " : Unoccupied");
  }
  
  LocoNet.reportSensor( address, value == sensorOccupied );

}

void sendAllsensors()
{

  // Go through all sensors and send their status out to the LocoNet
  Serial.println("Send all sensors");

  for (int i = 0; i<sensorCount; i++) {
    sendSensorMessage((addressDecoder + i), !sensorValue[i]); 
  }
  
}

void sendSensor(int sensorNumber) {
  
  // Send an individual sensor status out to the LocoNet.
  // It will only send if the previous time it was sent it had a different value.
  // It will also only send if a threshold time value for the sending has been reached to avoid
  // exsesive sending of messages.

  unsigned long currentTime = millis();

  if ((sensorValue[sensorNumber] != sensorPreviousValue[sensorNumber]) && (currentTime - sensorPreviousTime[sensorNumber] >= sensorInterval))
  {

    sensorPreviousValue[sensorNumber] = sensorValue[sensorNumber];
    sensorPreviousTime[sensorNumber] = currentTime;
   
    sendSensorMessage((addressDecoder + sensorNumber), !sensorValue[sensorNumber]);
    
  }   
  
}

void readSensors() {

  // Read all sensors and send out messages

  // Read from shift register for all sensors
  // Use sampling to decide when to change the actual sensor value


  // Get a sample of the state of all sensors via the shift register
  digitalWrite(pinLoad, LOW);
  delayMicroseconds(5);
  digitalWrite(pinLoad, HIGH);
  delayMicroseconds(5);

  digitalWrite(pinClockIn, HIGH);
  digitalWrite(pinClockEnable, LOW);
  byte incoming1 = shiftIn(pinDataIn, pinClockIn, LSBFIRST);
  //byte incoming2 = shiftIn(pinDataIn, pinClockIn, LSBFIRST);
  //byte incoming3 = shiftIn(pinDataIn, pinClockIn, LSBFIRST); 
  digitalWrite(pinClockEnable, HIGH);

  // Sample counter is increased so that we can tell if we are at a point where we want to 
  // determine the sensor value
  sampleCount = sampleCount + 1;

  // Each invidivual sensor is mapped from a specific bit on a specific input byte.
  // We invert the sesnor value and add the value such that we count the number of times that the
  // sensor sees activity
  //sensorSample[0] = sensorSample[0] + !bitRead(incoming1,0);
  sensorSample[1] = sensorSample[1] + !bitRead(incoming1,1);
  //sensorSample[2] = sensorSample[2] + !bitRead(incoming1,2);
  //sensorSample[3] = sensorSample[3] + !bitRead(incoming1,3);
  //sensorSample[4] = sensorSample[4] + !bitRead(incoming1,4);
  //sensorSample[5] = sensorSample[5] + !bitRead(incoming1,5);
  //sensorSample[6] = sensorSample[6] + !bitRead(incoming1,6);
  //sensorSample[7] = sensorSample[7] + !bitRead(incoming1,7);
  //sensorSample[8] = sensorSample[8] + !bitRead(incoming2,0);
  //sensorSample[9] = sensorSample[9] + !bitRead(incoming2,1);   
  //sensorSample[10] = sensorSample[10] + !bitRead(incoming2,2);
  //sensorSample[11] = sensorSample[11] + !bitRead(incoming2,3);
  //sensorSample[12] = sensorSample[12] + !bitRead(incoming2,4);
  //sensorSample[13] = sensorSample[13] + !bitRead(incoming2,5);
  //sensorSample[14] = sensorSample[14] + !bitRead(incoming2,6);
  //sensorSample[15] = sensorSample[15] + !bitRead(incoming2,7);
  //sensorSample[16] = sensorSample[16] + !bitRead(incoming3,0);
  //sensorSample[17] = sensorSample[17] + !bitRead(incoming3,1);
  //sensorSample[18] = sensorSample[18] + !bitRead(incoming3,2);
  //sensorSample[19] = sensorSample[19] + !bitRead(incoming3,3);
  //sensorSample[20] = sensorSample[20] + !bitRead(incoming3,4);
  //sensorSample[21] = sensorSample[21] + !bitRead(incoming3,5);
  //sensorSample[22] = sensorSample[22] + !bitRead(incoming3,6);
  //sensorSample[23] = sensorSample[23] + !bitRead(incoming3,7);

  // If we are at a point where we have done enough samples then we make a decision for each
  // sensor to set the current value
  if ( sampleCount >= sampleMax ) {

    // Check each sensor to see if the samples have passed the threhold or not
    for (int i=0; i < sensorCount; i++) {
      if ( sensorSample[i] >= sampleThreshold ) {
        sensorValue[i] = sensorOccupied;        
      } else {
        sensorValue[i] = sensorNotOccupied;
      }
      sensorSample[i] = 0;
    }

    sampleCount = 0;

    // Send out sensor deltas
    for ( int i=0; i < sensorCount; i++) {
      sendSensor (i);
    }

  }  

}




Do you know how to look at the public members of the class? If so, try adding some debug prints for some obvious members like anything to do with status or stats.

I'll attempt to do that.
I'll take a look through the library to see which public attributes might make most sense to print to the console.

Here is the list. For future ref, do a verify, then highlight the name in the include statement then right click and select goto definition. A new tab will open (READ ONLY) where you can look at the class info.

I would print the results of the following:
available
getstats - may require a buffer of type LnBufStats
getStatusStr

Thanks for the information.
Very appreciated.
I'm new to digging into the details within libraries so still figuring out my best ways to debug and get observability into what is going on.
I'll take a look through those details you sent and try to see if I can get some insights to what might be happening.

Bit of a mystery re getStats, I can't yet find the structure for it. Now will check Library.

1 Like

It looks like LnBufStats is just a pointer. I see some code that messes with it but let's for now just print the other stuff.
BTW, have you tried any of the samples in the library (15 of them) and which of them is closest to yout sketch?

I tried out the samples.
They also seem to hit the same issue.
They successfully work on any receive logic form the network but as soon as they do a transmit they hit something that is blocking and hangs until I disconnect the network cable.
It is a bit of a mystery.

Ok, that narrows it down. Have you added the serial prints yet? I am curious about available and some others. Put them in immedietly before the failing line of code, and end after the last serial print, do a Serial.flush() then add a delay(3000). Post any results in Serial Monitor.

I will have to try that.
I unfortunately won't be able to test it out today but will try that.
I'm glad that I've got your curiosity going :slight_smile:

Add one more Serial.println after the suspect statement printing the status that your code is THROWING away.
What I mean is add a temporary int as the return value to the failing statement and print it out.

int returnCode = LocoNet.reportSensor(address, value == sensorOccupied);
Serial.println(returnCode);

Possible returned values are

typedef enum
{
	LN_CD_BACKOFF = 0,
	LN_PRIO_BACKOFF,
	LN_NETWORK_BUSY,
	LN_DONE,
	LN_COLLISION,
	LN_UNKNOWN_ERROR,
	LN_RETRY_ERROR
} LN_STATUS;

Sorry to tell you this, but that is not good programming on display there.

I'll give that a try capturing the return status of the function call.
Currently it isn't reaching the point though when the print will happen since it hangs within the call.
As tests on this though what I will try is:

  1. with the cable unplugged (which doesn't hang) what is the return status of the call
  2. with the cable plugged in (which hangs) after a period of time unplug the cable (which stops the hanging) and see what the return status of the call is (Might be the same as test 1, or might be something different)
  3. With the cable plugged in (which hangs) let it sit there for a long time and see if it eventually returns after the logic in the library has determined there is a time out (I've waited approximately a minute before and didn't get a timeout) and see what the return status of the call is (Maybe it will give one of the error codes that you found).

Have you looked at the library code that is causing the hang? I am not sure what it is doing, but it is low level and seems to be playing with the hardware. Has this EVER worked? If not you likely have a subtle wiring issue.

I took a look through it.
Unfortunately it is as you said getting pretty low level there as it is working with the network protocol.
This was our first experiments with a LocoNet connection for transmitting so we didn't have it working previously except for receiving.
I'm also wondering if there is something else about the actual other devices on the network that are in someway interfering with it.
Essentially LocoNet is a single wire communication protocol.
I don't know too much about it but the way I understand from the reading is that devices on the network basically need to get a lock on the network and can then transmit a message and release the lock.
I was thinking that maybe something on the network was locking us out but we see messages moving from various different devices without issue.

I'll look back at the wiring.
Famous last words are "The wiring is correct" only to realize a subtle mistake :slight_smile:

Thanks for your help so far on this it is appreciated.
When I have a chance to get back onto the device I'll do some of those print statements and also grab the return code.
Hopefully I can figure this out and then do more cleanup of the code and move out of my PoC phase. :slight_smile:

I just looked at the little LocoNet Interface Breakout Board. I have an observation and a question. I hope they don't cost much, because you can buy and make those easily, in fact questionable if needed.
Q1 are all the voltages 5V?
Q2 SUPER IMPORTANT, are ALL ALL ALL the grounds connected TOGETHER?

Here is the code that is (maybe) causing the hang.

	uint8_t AddrH = ((--Address >> 8) & 0x0F) | OPC_INPUT_REP_CB;
	uint8_t AddrL = (Address >> 1) & 0x7F;
	if (Address % 2)
		AddrH |= OPC_INPUT_REP_SW;

	if (State)
		AddrH |= OPC_INPUT_REP_HI;

	return send(OPC_INPUT_REP, AddrL, AddrH);

If the other prints don't reveal anything and the wiring checks out, then we may need to modify a local copy of the library. I can't see anyway to avoid moving on to the send function.