UART_DRV_ReadDataBlocking seems to allow FIFO data to be lost

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

UART_DRV_ReadDataBlocking seems to allow FIFO data to be lost

Jump to solution
2,408 Views
dave408
Senior Contributor II

I'm not 100% sure about this.  However, I have been running my code that is expecting a 17 byte Modbus RTU packet, and it very rarely works.

 

I have a MQX Lite task running the following Modbus FSM:

 

77201_77201.pngpastedImage_0.png

 

In each the Reception and Control and Waiting states, I read in data using UART_DRV_ReadDataBlocking and pass 1ms for the timeout.  I'm using 1ms as a rough estimate of the 750us interbyte delay required by the Modbus spec for baud rates > 19200 (I am currently using 38400).

 

If I send a command that is <= 8 bytes, the command is handled properly the majority of the time.  However, when I send a command that is 18 bytes, it almost never works.  My code always detects the interpacket delay (1750us) after receiving 8 bytes.

 

Just as an extra test, I have kept reading the RX buffer with UART_DRV_ReadDataBlocking, and the remaining 10 bytes that I am expecting are never read out of the FIFO.  The end result is that my modbus receive buffer has all 0s after the 8th byte and then the CRC check fails.

 

I'm wondering if I have missed something or am misusing the function.  I know that I have been told that UART_DRV_SendDataBlocking is poorly named and doesn't actually block until the data goes out of the UART TX pin.  I don't see anything in UART_DRV_ReadDataBlocking that suggests the same.

 

I went ahead and modified my FSM to do the following: keep trying to read bytes and stuff them into a buffer when successful:

 

case Receiving: {     while( true) {         int ret = UART_DRV_ReceiveDataBlocking( _rs485_instance, &rx, 1, INTERBYTE_DELAY); // INTERBYTE_DELAY is 1         if( kStatus_UART_Success == ret) {             rxbuff[index++] = rx;         }         if( index == 17) {             int i = 0;             i += 2;         }     } ...

 

Two observations when I run this:

1.  I never hit my breakpoint inside of if(index==18), because I never read enough data from the RX FIFO

2.  I checked rxbuff and it only has 8 bytes of data (indicated by the variable "index")

 

I then changed up the code a little and in the Receiving state, rather than reading in one byte at a time, I read in the remaining 16 bytes since I knew they were coming.  When I did this, I was able to read in all of the data sent by the host:

 

case Receiving: {     while( true) {         // read in the next 16 bytes since we know they are coming         int ret = UART_DRV_ReceiveDataBlocking( _rs485_instance, &rxbuff[1], 16, 1000);         if( kStatus_UART_Success == ret) {             int j = 0;         }     }     ...

 

I'd love to hear any suggestions as to what might cause this behavior!

Labels (1)
Tags (5)
1 Solution
1,300 Views
mjbcswitzerland
Specialist V

Dave

I have used MODBUS RTU on various Kinetis parts in many products, where the RTU reception is detailed in the following two documents:

- utasker.com/docs/MODBUS/uTasker_MODBUS.PDF see page 27

- utasker.com/docs/uTasker/uTaskerUART.PDF see page 14

I would suggest the simplest and most accurate method is to use the Rx without FIFO operation so that you get an interrupt per character to control the interspace recognition. For example, add a callback to the standard interrupt that puts the received character to a buffer.

I use this callback to retrigger a 1.5T timer

When this timer fires I then start a 2.5T timer

If there is a character received before this fires it is invalid and the complete frame should be discarded (this is missing from your diagram but is specified by RTU).

If the 2.5T timer fires (3.5T in total) it is a valid frame termination and then your task can simply be scheduled to handle the message in the buffer.

For Kinetis projects I tend to use PITs for accurate timers (accepted 750us for >= 19'200 Baud or more accurate if preferred for > 19'200 Baud) since these usually allow 4 RTUs to operate in parallel. When semi-duplex RS485 is involved I also use the same PIT for RTS timing (descriptions also in the docs) for devices (like KL or KE) which don't do RS485 timing in HW.

Basically I wouldn't use FIFOs since this can greatly obscure timing and I would avoid doing any low level protol operation at the task level. The operation is very simple for an interrup callback and you would need a very high priority task to get close to its accuracy, which would be a waste of resources.

Below I have pasted extracts of the UART rx callback an timer handlers as reference.

Regards

Mark

// The tty driver calls this routine each time that it has received a character
// The inter-character space is monitored to detect an RTU frame termination
//
extern void fnRetrigger_T1_5_monitor(QUEUE_HANDLE Channel)
{
    unsigned char ucMODBUSport = fnMapMODBUSport(Channel);
    if (iModbusSerialState[ucMODBUSport] == MODBUS_CHARACTER_TERMINATING) { // if a character is received after a space of T1.5 but before T3.5 it represents a corrupted reception
        iModbusSerialState[ucMODBUSport] = MODBUS_FRAME_NOT_OK;          // inter-character gap violation (between 1.5 and 3.5 characters)
        fnFlush(SerialHandle[ucMODBUSport], FLUSH_RX);
        fnInterruptMessage(MODBUS_TASK, (unsigned char)(EVENT_INCOMPLETE_FRAME_RECEIVED - ucMODBUSport)); // schedule task to handle statistics of invalid character gaps
        return;
    }
    fnConfigureInterrupt((void *)&timer_setup_1_5[ucMODBUSport]);        // retrigger / start T1.5 timer
}

// An inter-character space of greater than 1.5 characters has been detected
// Now start the T3.5 timer to detect whether it is an end of frame or if it is an illegal gap
//
static void fnTimer_T_1_5_fired_0(ucMODBUSport)
{
    iModbusSerialState[ucMODBUSport] = MODBUS_CHARACTER_TERMINATING;
    fnConfigureInterrupt((void *)&timer_setup_3_5[ucMODBUSport]);        // start T3.5 timer
}


// An inter-character space of greater than 3.5 characters has been detected - this is the end of a valid RTU frame
//
static void fnTimerT3_5_fired(unsigned char ucMODBUSport)
{
    if (iModbusSerialState[ucMODBUSport] != MODBUS_FRAME_NOT_OK) {
        iModbusSerialState[ucMODBUSport] = MODBUS_FRAME_OK;              // 3.5 characters of idle - this is the end of a valid frame
        rxFrameLength[ucMODBUSport] = fnMsgs(SerialHandle[ucMODBUSport]);// save the length of the frame
        fnInterruptMessage(MODBUS_TASK, (unsigned char)(EVENT_VALID_FRAME_RECEIVED - ucMODBUSport)); // schedule task to handle valid RTU frame on this MODBUS port
    }
    else {
        fnFlush(SerialHandle[ucMODBUSport], FLUSH_RX);
        iModbusSerialState[ucMODBUSport] = MODBUS_IDLE;
        fnInterruptMessage(MODBUS_TASK, (unsigned char)(EVENT_BAD_FRAME_TERMINATED - ucMODBUSport)); // schedule task to handle statistics of bad frame terminations
    }
}

View solution in original post

10 Replies
1,300 Views
dave408
Senior Contributor II

With the multibyte read "success" in mind, I'm going to try out a solution / workaround.  My next plan is to replace the single byte read function with a UART_RCFIFO_REG check.  If > 0, then I'll read in that number of bytes immediately, and set the timeout to (N * intercharacter delay).  I'll post up the results.

0 Kudos
1,300 Views
dave408
Senior Contributor II

This approach didn't work.  UART_RCFIFO_REG reported 7 bytes, I read in 7 bytes, and then when I read UART_RCFIFO_REG afterward, there was nothing else in the FIFO. 

0 Kudos
1,300 Views
dave408
Senior Contributor II

Although Mark has presented me with some really great suggestions, I'd still like to hear from others what could cause the problems that I am seeing.  I understand that timing will be compromised with my approach, but that point aside, it just doesn't seem reasonable that the SDK functions are not capable of pulling in one byte at a time and keeping up with the incoming stream.  (or maybe it's something else instead that causes the loss of data!)

0 Kudos
1,300 Views
mjbcswitzerland
Specialist V

Hi Dave

In order to study the details I would also experiment without using MQX - that is, take it back to a main forever loop to test the code and avoid blocking functions.

You need to understand the UART HW and the workings/charateristics of the library IO operations and then things should fall in to place.

Since you presently have the operation in a pre-emptive environment there could be other not so obvious behaviors leading to complications - priorities, preemption of code etc. Once the raw basics are fully understood and contained the next layer of behavioral dependenacy can be added for final analysis.

Regards

Mark

0 Kudos
1,300 Views
dave408
Senior Contributor II

Well, this is progress.  But now I'm back to studying your code snippets.  I made a dumb mistake which is now really obvious.  I disabled the RX FIFO and then ended up using the RX callback (which is essentially the RX ISR) to handle one byte at a time.  I implemented the Modbus FSM in the ISR.  And it looks like it works!  My 17 byte command was received properly.  The problem -- because I evaluate the interbyte and interpacket delays only on incoming data, this means I can't detect the end of one packet until I start transmitting the next.  :smileyhappy:

So I need to have at least a separate timer that can signal at T3.5 to allow the packet to go through.  At a glance, it looks like you're signaling at T1.5 and T3.5.

The other problem I'm having that I'll have to tackle later is that I can send my buffer to the message queue, but my task doesn't ever receive it.  :smileysad:

0 Kudos
1,299 Views
dave408
Senior Contributor II

I agree with you completely, and will have to pursue that as a parallel project while I continue with my current effort.  Regarding that, I am experimenting with the next lowest-level implementation, which is to not handle any of the timing or packet assembling in a task.  I now have the RX callback enabled, disabled the FIFO, and am assembling the packet in the callback instead.  I just have to be able to get the resulting packet copied to the task so it can perform the processing.  I hope this will eventually work, because since I'm committed to using MQX, I think the RX callback is the closest I can get to a baremetal ISR.

Thanks for your suggestions, and I'm going to start building a baremetal project now.

0 Kudos
1,301 Views
mjbcswitzerland
Specialist V

Dave

I have used MODBUS RTU on various Kinetis parts in many products, where the RTU reception is detailed in the following two documents:

- utasker.com/docs/MODBUS/uTasker_MODBUS.PDF see page 27

- utasker.com/docs/uTasker/uTaskerUART.PDF see page 14

I would suggest the simplest and most accurate method is to use the Rx without FIFO operation so that you get an interrupt per character to control the interspace recognition. For example, add a callback to the standard interrupt that puts the received character to a buffer.

I use this callback to retrigger a 1.5T timer

When this timer fires I then start a 2.5T timer

If there is a character received before this fires it is invalid and the complete frame should be discarded (this is missing from your diagram but is specified by RTU).

If the 2.5T timer fires (3.5T in total) it is a valid frame termination and then your task can simply be scheduled to handle the message in the buffer.

For Kinetis projects I tend to use PITs for accurate timers (accepted 750us for >= 19'200 Baud or more accurate if preferred for > 19'200 Baud) since these usually allow 4 RTUs to operate in parallel. When semi-duplex RS485 is involved I also use the same PIT for RTS timing (descriptions also in the docs) for devices (like KL or KE) which don't do RS485 timing in HW.

Basically I wouldn't use FIFOs since this can greatly obscure timing and I would avoid doing any low level protol operation at the task level. The operation is very simple for an interrup callback and you would need a very high priority task to get close to its accuracy, which would be a waste of resources.

Below I have pasted extracts of the UART rx callback an timer handlers as reference.

Regards

Mark

// The tty driver calls this routine each time that it has received a character
// The inter-character space is monitored to detect an RTU frame termination
//
extern void fnRetrigger_T1_5_monitor(QUEUE_HANDLE Channel)
{
    unsigned char ucMODBUSport = fnMapMODBUSport(Channel);
    if (iModbusSerialState[ucMODBUSport] == MODBUS_CHARACTER_TERMINATING) { // if a character is received after a space of T1.5 but before T3.5 it represents a corrupted reception
        iModbusSerialState[ucMODBUSport] = MODBUS_FRAME_NOT_OK;          // inter-character gap violation (between 1.5 and 3.5 characters)
        fnFlush(SerialHandle[ucMODBUSport], FLUSH_RX);
        fnInterruptMessage(MODBUS_TASK, (unsigned char)(EVENT_INCOMPLETE_FRAME_RECEIVED - ucMODBUSport)); // schedule task to handle statistics of invalid character gaps
        return;
    }
    fnConfigureInterrupt((void *)&timer_setup_1_5[ucMODBUSport]);        // retrigger / start T1.5 timer
}

// An inter-character space of greater than 1.5 characters has been detected
// Now start the T3.5 timer to detect whether it is an end of frame or if it is an illegal gap
//
static void fnTimer_T_1_5_fired_0(ucMODBUSport)
{
    iModbusSerialState[ucMODBUSport] = MODBUS_CHARACTER_TERMINATING;
    fnConfigureInterrupt((void *)&timer_setup_3_5[ucMODBUSport]);        // start T3.5 timer
}


// An inter-character space of greater than 3.5 characters has been detected - this is the end of a valid RTU frame
//
static void fnTimerT3_5_fired(unsigned char ucMODBUSport)
{
    if (iModbusSerialState[ucMODBUSport] != MODBUS_FRAME_NOT_OK) {
        iModbusSerialState[ucMODBUSport] = MODBUS_FRAME_OK;              // 3.5 characters of idle - this is the end of a valid frame
        rxFrameLength[ucMODBUSport] = fnMsgs(SerialHandle[ucMODBUSport]);// save the length of the frame
        fnInterruptMessage(MODBUS_TASK, (unsigned char)(EVENT_VALID_FRAME_RECEIVED - ucMODBUSport)); // schedule task to handle valid RTU frame on this MODBUS port
    }
    else {
        fnFlush(SerialHandle[ucMODBUSport], FLUSH_RX);
        iModbusSerialState[ucMODBUSport] = MODBUS_IDLE;
        fnInterruptMessage(MODBUS_TASK, (unsigned char)(EVENT_BAD_FRAME_TERMINATED - ucMODBUSport)); // schedule task to handle statistics of bad frame terminations
    }
}
1,299 Views
dave408
Senior Contributor II

mjbcswitzerland​, yours is a very elegant and simple way to handle the Modbus RTU timing requirements.  It seems to work very well so far.  While my end result is not successful (i.e. command getting back to the master), in testing so far, my modbus RTU command reception has been great.  Now I think I'm just down to figuring out what's going on with the message queue.

0 Kudos
1,299 Views
dave408
Senior Contributor II

Baby steps -- I started by disabling the RX FIFO with the following function:

void DisableFifo( uint32_t instance)

{

    UART_Type *base = g_uartBase[instance];

    UART_HAL_DisableReceiver( base);

    UART_HAL_FlushRxFifo( base);

    UART_PFIFO_REG( base) &= ~UART_PFIFO_RXFE_MASK;

    UART_HAL_FlushRxFifo( base);

    UART_HAL_EnableReceiver( base);

}

I know you don't use the KSDK, but it should still be pretty clear how I am disabling, especially line 6 which is just resetting RXFE after I disable and flush the RX buffer.

Things improved -- I can *occasionally* process exactly one 17 byte command now.  Smiley Happy  But after that, I just get a kStatus_UART_RxBusy status when calling UART_DRV_ReadDataBlocking, and thus far I am unable to recover from this condition.  I'm currently looking for tips on this forum, and am going through the SDK source to determine how I am getting into this state.

0 Kudos
1,299 Views
dave408
Senior Contributor II

Thank you, Mark.  I will look over your docs and try to incorporate your approach into my code to see if it makes things behave more predictably.

0 Kudos