Usart not able to receive DMX data relyably

I am running on a SAM4e16E at 96mhz. DMX data arrives at 250kbaud and I have the interrupt priority of the RXBUF set to MAX_SYSCALL_INTERRUPT_PRIORITY. What happens is the RXBUF interrupt arrives late and then again quickly causing the data to be captured twice missing the first data byte. An thoughts on how to speed up the RXBUF response? I have set the INTERRUPT_PRIORITY to less than MAX and freeRTOS stops running.
I am not sure how to post my interrupt code correctly?

void ConfigureFieldBusUsart(uint32_t IER)
{
  Pdc *FbPdc;                            // Pointer to Fieldbus PDC register base.

  usart_disable_interrupt(FieldbusUsart, ALL_INTERRUPT_MASK); 	// Disable all the interrupts.
  usart_enable_tx(FieldbusUsart);
  usart_enable_rx(FieldbusUsart);
  usart_enable_interrupt(FieldbusUsart,IER);
  NVIC_ClearPendingIRQ(FIELDBUS_IRQn);
  NVIC_SetPriority(FIELDBUS_IRQn, configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY); // was +5
  NVIC_EnableIRQ(FIELDBUS_IRQn);
  FbPdc = usart_get_pdc_base(FieldbusUsart); //Get USART PDC base address and enable rec and transmitter.
  pdc_enable_transfer(FbPdc, PERIPH_PTCR_TXTEN);
}

Hi Larry,

There are many possible reasons for late arrival of interrupt. We will need more information from you to know the possible cause.
Can you help to share the following information?

  • Your USART RX interrupt handler implementation. You may post the interrupt handler function just like the ConfigureFieldBusUsart you shared.
  • Can you share your application code which calls FreeRTOS APIs and interact with the USART interrupt handler?
  • Can describe your USART activity with connected device?
  • Is there any other loading or interrupt enabled in your system?

Also, do you process error bytes like overflow, underflow, frame and parity errors in your isr? A frequent cause of trouble is sloppy treatment of those (eg clearing the interrupt source only if no error occurred).

I will point out that 250k baud means a character about every 40 microseconds, and at 96 MHz, that is less than 4000 cycles per character. This means that the ISR needs to be written fairly efficiently (and the OEM drivers are often written to be generic, and not focused on efficiency). Also, it means you need to be very careful with critical sections (which disable interrupts) in your code. The FreeRTOS code base uses design rules that keeps its own critical sections short, but if your code (or other code you are using) isn’t as careful, it isn’t hard to break that timing.

Thanks for your comments. I have included my RX interrupt code. Using a scope I can see the interrupt occurs several usec after the stopbits and sometimes it is delayed into the next character. I commented out all extra tasks and things did not really change. I am using other UARTs but they aren’t doing anything. I only use Critical sections in 2 places in my code and they are in sections that are not being called.

static inline void SaveRdmData( uint8_t data, uint16_t SlotNumber )
{
  RdmSlot[SlotNumber] = data;
}
static inline void SaveDmxData( uint8_t data, uint16_t SlotNumber )
{
  TmpSlotData[SlotNumber] = data;
}

void Fieldbus_Handler( void )
{
  uint8_t data;
  uint32_t status;
  BaseType_t xHigherPriorityTaskWoken = pdFALSE;
  uint32_t ST;

  status = usart_get_status(FieldbusUsart);
  ResetStatus();
  if (status & US_CSR_RXRDY)  // Receiving interrupt
    data = FieldbusUsart->US_RHR & 0xFF;// US_RHR_RXCHR_Msk;

	// Transmitting interrupt.
  if (FbTransmitting)
  {
    if (status & US_CSR_ENDTX) // Transmit continuously.
    {
      ledSetState(eLED_COMMS_TX,LED_FLASH);
      FbTransmitting = false;
      usart_disable_interrupt(FieldbusUsart, US_IDR_ENDTX);
      ResetStatus();
      RdmTxState = txTurnOffRts;
      tc_stop(RDM_TX_TIMER,RDM_TX_TIMER_CHANNEL);
      tc_write_rc( RDM_TX_TIMER, RDM_TX_TIMER_CHANNEL, RdmTxTurnOffDelay);
      tc_start(RDM_TX_TIMER,RDM_TX_TIMER_CHANNEL);
      return;
    }
  }

  /********************************************************
  ** First BREAK initiates the sequence, starts the timer
  ** Second BREAK ends the sequence, stops the timer
  ********************************************************/
  if ( status & US_CSR_RXBRK )
  {
    if (!StartOfBreak) // test for Break
    {
      StartOfBreak = true;
      BreakReceived = false;
      StopResponseDelay();
      StartRxBreakTimer();
      ValidNSC   = false;
      ValidRDM   = false;
      ValidDUB   = false;
      SlotNumber = 0;
      return;
    }
    else
    {
      BreakReceived = ValidateRxBreakTime();
      tc_stop( FIELDBUS_TIMER, FIELDBUS_TIMER_CHANNEL );
      if (BreakReceived)
        StartOfBreak = false;
      else
        StartRxBreakTimer();
      return;
    }
  } // NOT a Break

  if (status & (US_CSR_OVRE | US_CSR_FRAME | US_CSR_PARE  | US_CSR_RXBRK));
  {  // bad data clear everything and wait for next break sequence
    StartOfBreak = false;
    BreakReceived = false;
    tc_stop(FIELDBUS_TIMER,FIELDBUS_TIMER_CHANNEL);
    return;
  }

  if ( BreakReceived ) // we have a valid character
  {
    if ( ValidNSC )
    {
      SaveDmxData(data,SlotNumber++);
      if (SlotNumber >= Dmx.LastSlot) // if we reached the last Slot wait for next break sequence
      {
        StartOfBreak = false;
        BreakReceived = false;
        TransferTmpDmxData();
        xSemaphoreGiveFromISR(DmxSemaphore,&xHigherPriorityTaskWoken);
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
        return;
      }
    } // ValidNSC

    else if (ValidRDM)
    {
      SaveRdmData(data,SlotNumber++);
      if (SlotNumber > 2 )
      {
        if (SlotNumber==RdmData.ML+2)
        {
          StartOfBreak = false;
          BreakReceived = false;
          xSemaphoreGiveFromISR(RdmSemaphore,&xHigherPriorityTaskWoken);
          portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
          return;
        }
      }
    }
    else if (ValidDUB) // only valid during loopback test
    {
      SaveRdmData(data,SlotNumber++); // save data until tested by loopback function
    }
    else
    {
      ValidNSC = (data==E120_SC_DMX); // we've got a good DMX NULL Start Code
      ValidRDM = (data==E120_SC_RDM); // we've got a good RDM Alternate Start Code
      ValidDUB = pdFALSE;
      SlotNumber = 0;      // Reset Slot number to 0
      if (ValidRDM)
        SaveRdmData(data,SlotNumber++);
      else
      {
        SaveDmxData(data,SlotNumber++); // Save the NULL Start Code
      }
    }
  } // Break was NOT Received
  else if (EnableDUB)
  {
    if (data==0xFE) // Disc_Unique_Branch response header
    {
      ValidDUB = true;
      BreakReceived = true;
      SlotNumber    = 0;      // Reset Slot number to
      SaveRdmData(data,SlotNumber++);
    }
  }
}

(adding CODE markup would help readability, use the </> button)

Into the next characters shouldn’t be a problem unless it is a VERY poorly designed serial port, normally you have until the next character is completed, or longer if you have a fifo (and a fifo is VERY desirable at these speeds).

Your code calls a few external functions, but hopefully those are quick. I presume Fieldbus_Handler is the direct ISR entry point, and not something that a lower level handler is routing to.

You may need to figure out where the delay to respond is coming from. Perhaps use a GPIO on a scope to see when the ISR actually starts and end. Maybe add another to the critical section code to verify that you aren’t getting into a too long critical section.

Does the serial port not have a FIFO that can be enabled? I would expect that a serial port designed for this sort of high speed traffic to be using a FIFO to handle the data.

I was looking for the “Code” button but did know it. I will use it in the future.
The uC is a SAM4e16e. It does not have a fifo to my own surprise. Yes, Fieldbus_Handler is the direct ISR entry point. Because I need to handle the “Break” character I can’t use the DMA functions that I am sure would be faster.

You can use a cycle counter to determine the number of cycles actually used in the isr. Along with the calculations provided by Richard, that should tell you if you need to manually streamline your isr. You may want to experiment with compiler optimizations if your problem is there.

I asked about the ISR entry, because Cortex-M generally name the ISR entries as devicename_Handler, and I didn’t see a “FieldBus” device listed on the part.

It is a bit surprising not to have a Fifo, they must be expecting the DMA channel to be used to not need it. You might still be able to use the DMA and detect break if you can enable just some of the interrupt bits.

It is also possible that they just don’t intend for the serial port to run at that fast of a baud rate.

If it is a critical section causing problems, it might be possible to put the ISR at a higher priority (lower value) than MAX_SYSCAL_INTERRUPT_PRIORITY and then have it trigger a second isr (for some unused device) that is below that level to signal the semaphore. That is a trick I use at times when I have a very critical timing ISR.

SAM4e16e USART comes with pretty easy to use dedicated PDC (Peripheral DMA Controller). Using it usually eliminates the need of a FIFO. Also running at 96 MHz CPU clock 250 kBd shouldn’t be a problem. I’m running a USART at higher baud rates with less CPU clock. But with PDC, of course.
I’d propose get rid of the driver middleware, use the manual and write your own USART driver. Or ask the net for a better driver with PDC. I’m sure there are some good open source drivers.

1 Like

Thank you all for your help and suggestions. The root problem was the GCC compiler optimizer set to anything above -o0 caused hardware faults when I was indirectly storing “float” variables into an array. That was why I turned of optimizing. The fix appears to declare every “float” as a “volatile”, then the optimizer left them alone. The Rx interrupt is working as expected and we are again moving forward.

1 Like

That sounds like a strange error, and one that I haven’t seen before. I have no problem using GCC to manipulate float data on an M4 processor.

What was the “hardware fault” that you were getting?
Are you using the M4F port for your program, and was GCC set up correctly to use an M4 with float?

I am not able to tell what the fault is actually caused by the HW_FAULT exception is taken when I try to use a float. I don’t know what a M4F port is. I am using a default project for the SAM4E16E from microchip/atmel. I am using the Microchip IDE. Where can I learn if my GCC is setup correctly?

To find out what the actual fault was, you can install a fault vector and look to see where the fault occurred.

To see if you are using the M4F port, your project should be using the port.c and portmacro.h in the portable/gcc/ARM_M4F directory. If you can’t easily tell by looking at your project, you may need to ask Microchip how to confirm this. If they alter FreeRTOS for use in their IDE, they need to supply support for using it.

As to GCC being setup correctly, that again would be a Microchip question. One thing to make sure is that in your project options you correctly indicate everywhere needed that you are using floating point

Is it that the compiler was optimizing a variable because it did not see the context in which it was being manipulated? If so, it has nothing to do with the type of the variable. In any case, this is a good resource from ARM for debugging faults - https://www.keil.com/appnotes/files/apnt209.pdf.

1 Like