This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Receiving DMX512 data stream

Parts of this discussion started on this thread: i-am-trying-to-do-dmx512-protocol

I am using SDK 15.2.0 on an nRF52810.  My test board is based off of a Laird BL651 which contains a real (not simulated) nRF52810.  I'm using the Segger IDE.  My code is based off of the ble_app_uart_pca10040e_s112 example.  I made a copy of app_uart_fifo.c and modified to use the nrfx_uarte drivers directly.

I am trying to receive a DMX512 data stream. The DMX controller sends a packet of up to 513 bytes of data at 250KBaud and precedes each packet with a UART BREAK (holds the line low for 90us).  In my test case the controller is only sending 65 bytes of data.

I initiate the read using: nrfx_uarte_rx(&app_uart_inst, dmx_rx_buffer, BUF_READ_SIZE );  where BUF_READ_SIZE  is 513.  Here is my event handler:

static void uart_event_handler(const nrfx_uarte_event_t * p_event, void* p_context) 
{
    app_uart_evt_t app_uart_event; 
    uint32_t err_code;
 
    switch (p_event->type)
    {
        case NRFX_UARTE_EVT_RX_DONE:
            {
                //never reached??
                NRF_LOG_DEBUG("DMX data in:");
                NRF_LOG_HEXDUMP_DEBUG(dmx_rx_buffer, p_event->data.rxtx.bytes);
                (void)nrfx_uarte_rx(&app_uart_inst, dmx_rx_buffer, BUF_READ_SIZE);
            }
            break;

        case NRFX_UARTE_EVT_ERROR:
            app_uart_event.evt_type                 = APP_UART_COMMUNICATION_ERROR;
            app_uart_event.data.error_communication = p_event->data.error.error_mask;

            if (app_uart_event.data.error_communication & UARTE_ERRORSRC_BREAK_Msk)
            {
                size_t amount = nrf_uarte_rx_amount_get(app_uart_inst.p_reg);
                NRF_LOG_DEBUG("DMX err: %d %d %d %d", amount, p_event->data.rxtx.bytes,  dmx_rx_buffer[1],  dmx_rx_buffer[57]);
                (void)nrfx_uarte_rx(&app_uart_inst, dmx_rx_buffer, BUF_READ_SIZE);
            }
            break;
            
        default:
            break;
    }
}

The good news is I'm capturing the data reliably.  The bad news is I can't tell how much data was received.  The EVT_RX_DONE handler never gets called unless I reduce BUF_READ_SIZE from 513 to 65 or less.  The EVT_ERROR handler is correctly detecting BREAK.  I checked it with a scope and the handler gets called consistently 60us after the start of the BREAK which is good.  The handler is also seeing a FRAMING error which is expected.  It doesn't appear to be missing any of the BREAK events.

Once in the EVT_ERROR handler I can't find the amount of data received before the error occurred. 

Also,  if I stop the code and check the 513 byte buffer contents, I can see the correct 65 bytes of DMX data,  but the 65 bytes of data gets replicated throughout the 513 byte buffer, so I see my data at locations 0-64, but also at 65-129, 130-195, ... .

is there a way to find the number of bytes received before the BREAK and can I stop the data from filling the rest of the buffer after the BREAK is received?

Parents
  • Here is an update.  The data in the buffer looks like I expect it to which is good,  but I still can't tell how many bytes were written.

    I tried adding a call to nrfx_uarte_rx_abort in the error handler, but didn't get the results I was hoping for.  The documentation says an RX_DONE event will be generated, but the event handler sees the event. 

    Calling nrfx_uarte_rx_abort has some undesired side effects too.  I sometimes get an extra ERROR event on the first character when I call nrfx_uarte_rx to do the next read.

    Except for the not knowing how many bytes were sent, the code I added in the first post seems to be working fine.

  • Maybe the error is real, and the uart needs to be stopped after the abort before re-priming for the next message packet.

    Why does the DMX controller need to send a Break anyway? When the receiver needs to stop and process an incoming packet, usually it works better to attach the Rx input to a timer input in addition to the Uart, such that when no transition occurs within some specified interval an end Rx condition can be triggered. That timer monitoring can be initiated on the first Rx edge transition.

  • The DMX controller sends a Break because that is how DMX is defined.  It is a pretty old standard. Here is a link if you are interested: DMX.  Basicly the signal goes low for 88us-1sec, then high for about 10us, then you get up to 513 bytes of data at 250Kbaud with two stop bits, but delays can occur between bytes as long as the whole packet takes less than 1 second, but then next packet can start right away if it wants to.  Typically a packet take about 22ms, but it can be much quicker if not much data is sent. Most UARTs detect the initial low as a Framing error or as a Break, and handle the remaining incoming data just fine. The nRF52810 seems to be handling the data just fine too, except I haven't found a way to determine how many bytes just got sent.  If I had a dedicated micro, I could just put it in a tight loop and count the characters as they come in, but the nRF52810 has other things to do, so that is not an option.

    Wouldn't a timer input that detects a low signal for say 60us, have the same issue I'm currently having?  The UART would still cause a framing or break error.  Also the re-priming you mentioned could be a big issue too since there might only be 40us before the data gets sent.  I can loose an occasional packet, but loosing too many would affect the performance too much.

    Questions for you:

    • You said something about re-priming the uart.  Do you have any information on how much time is required after an abort before data can be received again?
    • Regarding the missing RX_DONE event, in the code from my first post, I replaced the call to nrfx_uarte_rx() with a call to nrfx_uarte_rx_abort so that when the BREAK is handled the RX stops.  Should that cause an NRFX_UARTE_EVT_RX_DONE event that gets seen by the handler?  (It doesn't currently)
    • Is there any other way to find the number of bytes received?

    My backup plan it to fill the buffer with known data and scan it after the data has been received to see what has changed, but that's a terribly brute force approach and may not work.  Plus I have to deal with issues like what if the data sent matches my preset values.  

  • The goal is to actually not use the Uart to detect the break, since the message frequency is so slow. Instead turn off the Uart: The UARTE receiver will be in its lowest activity level, and consume the least amount of energy, when it is stopped, i.e. before it is started via STARTRX or after it has been stopped via STOPRX and the RXTO event has been generated

    Instead monitor the Rx line with a timer or simple port event edge detection to locate the end-of-break, as with most RS485 systems, and link that to the STARTRX of the Uart using PPI. That way the break is detected and a hardware-triggered STARTRX command is sent to the Uart. The PPI sampling is at 16MHz, so 10uSecs is plenty of time for timer or port to tell the Uart to start without using interrupts or waking the cpu. The end-of-packet is then detected by the same timer which is now running in time-since-last-char mode, which generates an interrupt on timeout - or PPI-triggered event - and thence issues the STOPRX as we know the FIFO buffer is empty. I haven't tried this on this specific device, but it works well on other systems.

    If you do want use the Uart to detect the break, I think you have to do a FLUSHRX after the STOPRX generated by nrfx_uarte_rx_abort(), otherwise the DMA doesn't finish transferring pending data from the RX FIFO and thence generate the RX_DONE you are hoping for. Adding the flush might fix the issue.

Reply
  • The goal is to actually not use the Uart to detect the break, since the message frequency is so slow. Instead turn off the Uart: The UARTE receiver will be in its lowest activity level, and consume the least amount of energy, when it is stopped, i.e. before it is started via STARTRX or after it has been stopped via STOPRX and the RXTO event has been generated

    Instead monitor the Rx line with a timer or simple port event edge detection to locate the end-of-break, as with most RS485 systems, and link that to the STARTRX of the Uart using PPI. That way the break is detected and a hardware-triggered STARTRX command is sent to the Uart. The PPI sampling is at 16MHz, so 10uSecs is plenty of time for timer or port to tell the Uart to start without using interrupts or waking the cpu. The end-of-packet is then detected by the same timer which is now running in time-since-last-char mode, which generates an interrupt on timeout - or PPI-triggered event - and thence issues the STOPRX as we know the FIFO buffer is empty. I haven't tried this on this specific device, but it works well on other systems.

    If you do want use the Uart to detect the break, I think you have to do a FLUSHRX after the STOPRX generated by nrfx_uarte_rx_abort(), otherwise the DMA doesn't finish transferring pending data from the RX FIFO and thence generate the RX_DONE you are hoping for. Adding the flush might fix the issue.

Children
  • I tried FLUSHRX in the error handler, but got the same results.

    It will probably take me a while to figure out how to setup PPI to trigger on a 60us low signal. Any hints would be appreciated.

  • I take it you separately triggered first the STOPRX and then the FLUSHRX? Both would be required .. there were some posts regarding PPI, such as this question. I won't have time to look at testing this for a while, but could try later in the week if needed.

  • I tried a stoprx followed by a flushrx and I get the same result.

    I set up the PPI to find the break event, checked it with a scope and it looks like I want.  However, I'm still not seeing the expected rx count in the uarte handler.  Here is my PPI setup, maybe you can spot a problem or a better way?

        // Break begins on High to low transition, reset and start timer
        NRF_PPI->CH[0].EEP = (uint32_t)&NRF_GPIOTE->EVENTS_IN[0];
        NRF_PPI->CH[0].TEP = (uint32_t)&NRF_TIMER1->TASKS_START;
        NRF_PPI->FORK[0].TEP = (uint32_t)&NRF_TIMER1-> TASKS_CLEAR;
        
        // Stop count on low to high transition, only count when line is low
        NRF_PPI->CH[1].EEP = (uint32_t)&NRF_GPIOTE->EVENTS_IN[1];
        NRF_PPI->CH[1].TEP = (uint32_t)&NRF_TIMER1->TASKS_CAPTURE[0];
        NRF_PPI->FORK[1].TEP = (uint32_t)&NRF_TIMER1->TASKS_STOP;
        
        // StopRX when count matches compare
        NRF_PPI->CH[2].EEP = (uint32_t)&NRF_TIMER1->EVENTS_COMPARE[1]; 
        NRF_PPI->CH[2].TEP = (uint32_t)&NRF_UARTE0->TASKS_STOPRX;
        NRF_PPI->FORK[2].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[2];  // for scope
    //    NRF_PPI->FORK[2].TEP = (uint32_t)&NRF_UARTE0->TASKS_FLUSHRX;
        
        // Timer 1, sends event when count reaches 50us
        NRF_TIMER1->MODE = (uint32_t)TIMER_MODE_MODE_Timer<<TIMER_MODE_MODE_Pos; // Timer mode
        NRF_TIMER1->BITMODE = (uint32_t)TIMER_BITMODE_BITMODE_24Bit<<TIMER_BITMODE_BITMODE_Pos; // 32 bits resolution
        NRF_TIMER1->PRESCALER = (uint32_t)0; // Prescaler = 0
        NRF_TIMER1->CC[1] =  (uint32_t)800;
    
        // Event on GPIO high to low
        NRF_GPIOTE->CONFIG[0] = GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos | 
                                             GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos | 
                                             YELLOWWIRE << GPIOTE_CONFIG_PSEL_Pos | 
                                             GPIOTE_CONFIG_OUTINIT_Low << GPIOTE_CONFIG_OUTINIT_Pos;
    
        // Event on GPIO low to high
        NRF_GPIOTE->CONFIG[1] = GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos | 
                                             GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos | 
                                             YELLOWWIRE << GPIOTE_CONFIG_PSEL_Pos | 
                                             GPIOTE_CONFIG_OUTINIT_Low << GPIOTE_CONFIG_OUTINIT_Pos;
    
    #if 1 // For scoping, toggle gpio on break detected
        NRF_GPIOTE->CONFIG[2] = GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos | 
                                             GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos | 
                                             ORANGEWIRE << GPIOTE_CONFIG_PSEL_Pos | 
                                             GPIOTE_CONFIG_OUTINIT_Low << GPIOTE_CONFIG_OUTINIT_Pos;
    #endif
    
                                                 
    
        NRF_TIMER1->TASKS_START = (uint32_t)1; // start the timer
    
        NRF_PPI->CHENSET = (uint32_t)(1<<0); //Enable PPI channel0
        NRF_PPI->CHENSET = (uint32_t)(1<<1); //Enable PPI channel1
        NRF_PPI->CHENSET = (uint32_t)(1<<2); //Enable PPI channel2
    

    I tied the uart rx line to a gpio to use with the PPI.

    I'm expecting when I tell the uart to receive 513 bytes and then send it 65 bytes followed by a break that the uart handler will get called with an NRFX_UARTE_EVT_RX_DONE event and have a count of 65.  But that isn't happening.

Related