Skip to main content
Visitor II
January 7, 2020
Solved

Strange behavior with USB CDC as virtual COM port on NUCLEO-L476RG

  • January 7, 2020
  • 9 replies
  • 8215 views

Hello everybody,

I am using the NUCLEO-L476RG to send simple ACII text over UART (RS232 adapter) or USB CDC as a virtual COM port. UART communication is working seamlessly but when I use the USB CDC library functions from STM there is hapenning something strange I cannot explain:

I am sending in regular intervalls ASCII-tesxt in the follwoing format: ABP;***;***;***\n

The * stands for different numbers. normally I would see this kind of text flow on a terminal like hterm:

ABP;***;***;***\n

ABP;***;***;***\n

ABP;***;***;***\n

... and so on

but with the USB CDC usage I see occasionally this:

ABP;***;***;***\n

ABP;ABP;***;***;***\n

ABP;***;***;***\n

ABP;***;***;***\n

ABP;ABP;***;***;***\n

when I halt the debugger to check if my character array is corrupted, it isn't. So my assumption is that there is something not working correctly with the USB CDC device library from STM. This is the code, that gets invoked when I transmit the ASCII-text:

sprintf(Communication.measure,"ABP;%ld;%ld;%ld\n",Patient.Systolic, Patient.Diastolic, Patient.MAP);
if(System.Mode == UART)
{
	LPUART_transmit_message(Communication.measure);
}
else if(System.Mode == USB)
{
	CDC_Transmit_FS(Communication.measure, strlen(Communication.measure));
}

as you can see, i am using the same array preparations for the USB CDC version as for the UART version, except that the UART version functions seamlessly. The big difference between both versions is that I only utilize USB CDC device library functions provided by STM, not my own.

Has anybody experienced something similar? Or has a good hint where to find the problem?

best regards

Benjamin

    This topic has been closed for replies.
    Best answer by Benjamin Brammer

    Hey Bob,

    sorry for the late reply, but I had other stuff to attend to.

    OK the problem seems to be fixed now, although I think the real problem was my Voltage Range 2. I used this one because of optimum power efficiency and lowest power consumption in run mode (24MHc HCLK and Voltage Range 2). But this seems something not working correctly with a MSI RC of 48000 MHz. I even think this is not possible according to the reference manual under 6.2.9 there is a table that list the clock frequencies dependet on the Voltage Range. And it clearly says Range 2 has a maximum of 24MHz MSI RC range. This seems to be a bug in the STMCubeMX clock configurator, since 48MHz should not be possible with Range 2.

    I still need to figure out how I will change this when I want to use the STOP0 mode. But this is something not important right now. So in the end it seemed to be the combination a too high clock with the Voltage Range 2 that caused the problem and nothing with a stray pointer or buffer overflow or so. Quite interesting, don't you think?

    Anyhow, I am really, really thankful for your help and efforts on that problem!

    Thx,

    Bejamin

    9 replies

    Super User
    January 7, 2020

    I am presuming that LPUART_transmit_message() does not return until the entire string has been sent. Is that correct?

    After you call CDC_Transmit_FS(), you need to wait until the data has been sent before you call sprintf() to write new data into the string, Though that wouldn't produce the results you show above. Checking the CDC_Transmit_FS() return value is also a good idea. Though, again, having CDC_Transmit_FS() fail because it was still transmitting the previous data shouldn't cause what you are seeing.

    How often are you sending that string?

    Visitor II
    January 7, 2020

    Hello Bob,

    yes the LPUART_transmit_message() function does not return until the entire string is sent:

    void LPUART_transmit_message(char *message)
    {
    	strcpy(LPUARTBufferTx, message);
     
    	if(hlpuart1.gState == HAL_UART_STATE_READY)
    	{
    		if(HAL_UART_Transmit_IT(&hlpuart1, (uint8_t *)LPUARTBufferTx, strlen(message)) != HAL_OK)
    		{
    			Error_Handler();
    		}
    	}
    }

    I am sending this ASCII- texts at least every 10s mostly it is every 30s so not very offten.

    Super User
    January 7, 2020

    Do you use "Communication.measure" anywhere else in the code? Are the values always 3 digits (which makes your string always 16 bytes long including the newline)? I imagine at least the systolic might be a 2 digit value assuming a healthy person.

    I haven't yet replicated this issue on my L476-DISCO, but it has only been running for about 10 minutes so far. And I'm running a modified version of the HAL USB code which supports multiple VCOM devices. My changes shouldn't affect this issue, but you never know. About how often does this issue appear?

    What HAL version are you running? And what CubeMX version?

    Visitor II
    January 7, 2020

    yes, I use it if the host would issue a command to request the ASCII-text (ABP;***;***;***\n). But this is only for debug purposes. And works fine. The values can have two digits, but the problem occurs independent of this. I tested long intervals with the same values.

    I am using STM32CubeIDE (1.1.0) with STM32CubeMX (5.4.0) and firmware package for L4 V1.14.0 .

    Super User
    January 7, 2020

    Oh... more thoughts. Is the "Communication" variable/structure (class??? is this C++ code?) local to this function? Or does it always exist? Unlike your LPUART code, the USB code doesn't copy the data from your buffer right away. So if "Communication" is a local variable and this function exits, or Communication.measure gets de-allocated or overwritten, before the USB code actually copies the data, THAT could cause corrupted data.

    Can you have whatever program you use on the PC to display that data also show non-printable characters?

    Visitor II
    January 7, 2020

    I write in C-code. and the struct is defined in my main.h :

    /**
     * @brief	Definition of the sCom struct. This struct contains the communication
     * 			protocol buffers for data to be sent and command to be decoded.
     */
    typedef struct
    {
    	char command[10];			//ToDo: Maybe the array size needs to be more!
    	char payload[10];
    	char measure[16];
     
    }sCom;

    I declare and initialize it in main.c . But I have to tell my different .c files with extern that this struct exists. So also in the file where I invoke the transmission via LPUART or USB CDC.

    What do you mean with non-printable characters? I use hterm.

    Thanks a lot for your help and efforts! =)

    Super User
    January 7, 2020

    (1) Your measure[] buffer is too small!!! You need at least 17 bytes: 16 for the string including the newline, plus one more for the trailing NULL. And NEVER use "sprintf", always use snprintf() to avoid the buffer overflow that you are currently getting. For example:

    snprintf(Communication.measure,sizeof(Communication.measure),"ABP;%ld;%ld;%ld\n",Patient.Systolic, Patient.Diastolic, Patient.MAP);

    The 2nd parameter to snprintf() specified the maximum number of bytes to write into the buffer (1st parameter) INCLUDING THE TRAILING NULL. It will truncate the output string if necessary to keep room for the trailing NULL if the actual output string is too long.

    Now, granted, your buffer will not overflow if systolic is 3 digits, diastolic is 2 digits and MAP is 3 digits. But anything larger than that will overflow your measure buffer.

    (2) Just for testing purposes, don't re-use the "measure" buffer. See if that makes any difference.

    Visitor II
    January 7, 2020

    thanks for your answer, I did not know, that there is allways a trailing NULL. I will check this out tomorrow and hopefully confirm your answer as the best one.

    Super User
    January 7, 2020

    "C" strings always have a trailing NULL. That is how, for example, strlen() figures out how long the string is.

    One more thing - since you are using "%ld" format specifiers, what happens if for some unforeseen reason one of the values is greater than 999 (say you got bad data, or something corrupted your data in RAM)? You will end up truncating the string to force it to fit into 16 bytes (17 with NULL). The STM32L476 has LOTS of RAM. Make your "measure" buffer much larger so that your message won't ever get truncated. That way the receiving end will always get a well-formatted message, though it will have to realize the data is nonsense. Note that each "%ld" can take up to 11 characters (10 digits plus sign). Don't use "%.3ld" to try and force a maximum of 3 digits, as that will truncate bad data and possibly make it look valid.

    Visitor II
    January 7, 2020

    OK, I will implement and test everything tomorrow. thanks again!

    Super User
    January 8, 2020

    (1) Are you still using "measure" anywhere else in your program?

    (2) Put HTerm into hex display mode. This will show you EVERY character. See if the lines with the replicated "ABP;" also have any non-visible bytes.

    (2.5) I find it VERY curious that the corrupted lines always have the same corruption, the repeated "ABP;" string. This likes suspiciously like maybe the incoming command from the host to request the data (could the command string be "ABP;" ???) is once in a while getting echoed back out the USB port.

    (3) Check your stack usage. Are you anywhere near using all of the 96KBytes of RAM in the main RAM section?

    (4) Are you using an RTOS?

    (5) Do you call CDC_Transmit_FS() anywhere else in your code?

    (6) Where you call CDC_Transmit_FS(), also send that buffer out your USART using LPUART_transmit_message(). Run two terminal sessions and see if the output matches.

    (7) Since your LPUART_transmit_message() copies the string into another buffer, add a buffer for the USB version.

    else if(System.Mode == USB)
    {
    	static char tmpbuf[100];
    	// NOTE the "-1" in the strncpy 3rd param, unlike snprintf(), strncpy() does NOT
    	// guarantee it will NULL terminate the output string if the source string
    	// is too long.
    	strncpy( tmpbuf, Communication.measure, sizeof(tmpbuf)-1 );
    	CDC_Transmit_FS( tmpbuf, strlen(tmpbuf) );
    }

    (8) Probably unrelated, but.... How big is LPUARTBufferTx used in LPUART_tramsnit_message()??? That has the same size and possible overrun issue that Communiocation.measure has. And use strncpy() in LPUART_transmit_message(), not plain strcpy() for same reasons as snprintf() vs. sprintf().

    Visitor II
    January 8, 2020

    Hello Bob,

    thanks again for your efforts and help. I can check everything you suggested in the next week, as I have to switch to some different task until next week. i will respond to you as soon as I have checked everything.

    Benjamin

    Super User
    January 15, 2020

    Regarding my question (5) "do you use CDC_Transmit_FS() anywhere else in your program" and your answer "No, I am not using the API function anywhere else", the image you posted of the compiler warnings says that you *DO* (or DID) call CDC_Transmit_FS() from 8 different places in your code.

    BTW, those warnings are due to ST defining all of their API calls using "uint8_t *" instead of "char *", and I am guessing you messages are all "char" arrays. As you probably already know, to keep that compiler warning enabled (sometimes it indicates a real issue), you have to cast your buffers to uint8_t *, i.e.

    CDC_Transmit_FS( (uint8_t *)Communication.measure, strlen(Communication.measure) );

    For (6), does "The message over UART is not faulty, although I use the same buffer" mean you did something like this:

    else if(System.Mode == USB)
    {
    	LPUART_transmit_message(Communication.measure);
    	CDC_Transmit_FS(Communication.measure, strlen(Communication.measure));
    }

    And finally, if tracking down all the CDC_Transmit_FS() calls that show up in the compiler warnings you posted doesn't point you to the problem, try this. Arrange it so that you code always outputs the same string, or at least the same LENGTH string. Add code inside CDC_Transmit_FS() that checks the length parameter and if the length is not the same as your fixed string length add a line where you can set a breakpoint, and something the compiler won't optimize away, for example:

    uint8_t CDC_Transmit_FS( uint8_t * Buf, uint16_t Len )
    {
     uint8_t result = USBD_OK;
     
     if ( Len != MY_EXPECTED_LENGTH ) {
     *Buf = '!'; // BREAKPOINT HERE, Change 1st byte of string, compiler shouldn't optimize this out
     }
     
     // The rest of the normal CDC_Transmit_FS() code here....
     USBD_CDC_HandleTypeDef *hcdc = ......
    }

    Visitor II
    January 15, 2020

    Hello Bob,

    on 5) ..you got me! :D . Truely this is a special debug use case, I did implement today. it is not the normal use case and will be changed again. Nevertheless the problem existed also prior to the change from today.

    6) yes, that is exactly what I did.But I put the LPUART message ater the CDC_Transmit .

    Ok I will try your last suggestion..

    Super User
    January 21, 2020

    0x8405e2 is not a valid FLASH address on the L476 parts. So something is getting corrupted. The HAL library has many structures that contain "pointers to functions" that the use to handle interrupts. USB in particular uses lots of these.

    So look at the stack and see where the program was before the jump into oblivion. If you have a stray pointer overwriting data structures that could also explain the extra characters you see on the USB CDC output.

    Visitor II
    January 21, 2020

    Hey Bob,

    thanks for your answer. i am not shure how to do this properly. Can you give me some advice how to track that error down? As I have checked the memory map the region from 0x0010 0000 to 0x0800 0000 is reserved, that is what you meant, right? I suppose only increasing stack and heap size doesn't solve this, right?

    Maybe I did a notation error, since PC in the screenshot says 0x8405e2a... ?

    Super User
    January 21, 2020

    > As I have checked the memory map the region from 0x0010 0000 to 0x0800 0000 is reserved, that is what you meant, right?

    Not quite. The **vaild** FLASH memory region goes from 0x0800 0000 to 0x080F FFFF (this is 1 MByte, of 0x0010 0000 bytes long). The contents of the PC (0x0840 5e2a) is outside this range, and is inside a range that the data sheet shows as "RESERVED".

    Your debugging IDE should have a window somewhere that shows the stack callback trace. The Eclpise-based IDE I use has this in a window called "Debug". It looks like you may be using the Keil IDE or some other IDE that I am not familiar with, so I can't tell you where to look for this info. Try single stepping your program from the start of main(). Step into the first function call (possible HAL_Init()). Then look around your IDE for something that shows functions main() and HAL_Init(). That is the window you want to look at when your code hits the fault handler. You can (or should be able to) click on each function name in the call chain and have the IDE show you where the CPU was in each function. That will hopefully show you where a function call was made through a pointer, and therefore which pointer was corrupted. The hard part will be figuring out how that pointer got corrupted.

    Visitor II
    February 11, 2020

    Hey Bob,

    I restructered now everything andd so far I havent had a hardfault anymore.

    I tried today again to debug the AIBP;AIBP;yyy;sss;ddd; probelm and stepped manually through the CDC_Transmit_FS() function. I stopped the debugger at the following line and did always check that the content of the Buf variable is correct:

    result = USBD_CDC_TransmitPacket(&hUsbDeviceFS);

    Now very interesting is that even if the Buf content is correct and also the length of the handletype ep_in[1].length (the endpoint used for non-control data transmission to the host) I get the error with the added characters. This would mean the problem is somewhere inside the USBD_CDC_TransmitPacket(&hUsbDeviceFS) function or am I wrong? As far as I have understood the functions content:

    /**
     * @brief USBD_CDC_TransmitPacket
     * Transmit packet on IN endpoint
     * @param pdev: device instance
     * @retval status
     */
    uint8_t USBD_CDC_TransmitPacket(USBD_HandleTypeDef *pdev)
    {
     USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef *) pdev->pClassData;
     
     if (pdev->pClassData != NULL)
     {
     if (hcdc->TxState == 0U)
     {
     /* Tx Transfer in progress */
     hcdc->TxState = 1U;
     
     /* Update the packet total length */
     pdev->ep_in[CDC_IN_EP & 0xFU].total_length = hcdc->TxLength;
     
     /* Transmit next packet */
     USBD_LL_Transmit(pdev, CDC_IN_EP, hcdc->TxBuffer,
     (uint16_t)hcdc->TxLength);
     
     return USBD_OK;
     }
     else
     {
     return USBD_BUSY;
     }
     }
     else
     {
     return USBD_FAIL;
     }
    }

    is it possible that for some reason the pointer operations get corrupted?

    Super User
    February 12, 2020

    > is it possible that for some reason the pointer operations get corrupted?

    Yes. Given that the fault issues before appeared (to my best guess) to be caused by memory corruption, this is likely. But we don't know for sure since you never found the source of the corruption that caused the memory faults. That same issue is probably still there, causing the duplicate data (somehow). The difference is that it no longer corrupts something that causes a memory fault.

    The reason I suggested taking your code out of the callback functions was to try and simplify the flow of your program. When everything is done *IN* the interrupt functions you have to be real careful about having one interrupt interrupting another one, and both attempting to access some variables that they both think they have exclusive access to. Moving all that code into the main polling loop ensures exclusive access.

    Now you are back to your original problem. Rule of thumb when debugging is to simplify as much as possible and see what the smallest amount of functionality you can make and still exhibit the problem.

    For example, don't enable the ADC and related DMA. Do you still get the extra characters? You don't mention if you ever looked at your DMA/ADC interrupt functions as I suggested in a previous post (based on that having a higher interrupt priority than the LPTIMER and USB).

    Go back to the most basic. Disable everything (well, just don't enable interrupts or DMA) except USB and SysTick. In your main(), put something like this:

    while( 1 ) { // Start of your main polling loop
     static const char msg[] = "ABP;yyy;zzz;uuu\n";
     uint32_t lastTime = 0;
     
     // Send message every 10 seconds, change to whatever interval you like
     if ( (HAL_GetTick()-lastTime) >= 10000 ) {
     lastTime = HAL_GetTick();
     // sizeof()-1 is shortcut instead of using strlen() since we KNOW the length at compile time
     CDC_Transmit_FS( msg, sizeof(msg)-1 );
     }
     
    #if 0
     // Rest of your normal polling loop here, currently disabled
     // for testing purposes. You will gradually re-enable this code
     // as your testing progresses.
    #endif
    }

    Do you ever get extra data with this code? If not, enable one of the interrupts you disabled, but don't do anything in the callback. For example, enable the LPTIM interrupt, but don't have it trigger sending the "ABP;....." string. Do you get the extra data? Then re-enable the ADC/DMA interrupts. Do you get extra data? Gradually work you way back to your full functionality, enabling one thing at a time, until you have all of your original program EXCEPT that you don't use LPTIM to send the string, you only use code like above in your main polling loop.

    Visitor II
    February 13, 2020

    Hey Bob,

    thanks for your endless help and suggestions. I really appreciate that!

    I did now exactly as you told me to do so. Nevertheless I still get the error with the added characters. Maybe I did configure something wrong in CubeMX for USB? :

    0690X00000DA2EvQAL.jpg

    0690X00000DA2FFQA1.jpg

    here you can see my main():

    int main(void)
    {
     /* USER CODE BEGIN 1 */
     
     /* USER CODE END 1 */
     
     
     /* MCU Configuration--------------------------------------------------------*/
     
     /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
     HAL_Init();
     
     /* USER CODE BEGIN Init */
     
     /* USER CODE END Init */
     
     /* Configure the system clock */
     SystemClock_Config();
     
     /* USER CODE BEGIN SysInit */
     
     
     /* USER CODE END SysInit */
     
     /* Initialize all configured peripherals */
     MX_GPIO_Init();
     /*MX_DMA_Init();
     MX_ADC1_Init();
     MX_OPAMP1_Init();
     MX_OPAMP2_Init();*/
     MX_USB_DEVICE_Init();
     /*MX_LPTIM1_Init();
     MX_COMP1_Init();
     MX_LPTIM2_Init();
     MX_LPUART1_UART_Init();
     MX_COMP2_Init();
     MX_DAC1_Init();*/
     /* USER CODE BEGIN 2 */
     
     /* set initial sSystem variables (State and Mode) */
     System.Mode = UART;
     System.State = LowPowerState;
     
     /* activate TRS3221E for RS232 <-> LPUART communication with auto power off feature */
     TRS3221E_FORCEOFF_HIGH;
     TRS3221E_FORCEON_LOW;
     TRS3221E_EN_LOW;
     
     /* debug features */
     DBGMCU->APB1FZR1 |= DBGMCU_APB1FZR1_DBG_LPTIM1_STOP | DBGMCU_APB1FZR2_DBG_LPTIM2_STOP;		// timer will halt during debug breakpoint
     
    #if 0
     /* calibrate ADC and OPAMP again */
     HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
     HAL_OPAMPEx_SelfCalibrateAll(&hopamp1, &hopamp2);
     
     /* activate OPAMPs */
     OPAMP_start();
     
     /* activate DAC and comparator for battery voltage monitoring */
     DAC_set_voltage(&hdac1, DAC_CHANNEL_1, 2.2);
     HAL_COMP_Start(&hcomp2);
     
     /* set DACOUT2 for compare value of valid IABP signal (1.2V or 120mmHg) */
     DAC_set_voltage(&hdac1, DAC_CHANNEL_2, 1.0);
     
     
     HAL_Delay(50);
     
    #endif
     /* USER CODE END 2 */
     
     
     
     /* Infinite loop */
     /* USER CODE BEGIN WHILE */
     while (1)
     {
    	 /* prepare LPUART for reception */
    	/* if(System.Mode == UART)
    	 {
    		 LPUART_receive_messages();
    	 }
    */
    	 /* check if USB is plugged in or not */
    	 if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_9) && System.Mode == UART)
    		{
    			System.Mode = USB;
    			/* deactivate TRS3221E */
    			TRS3221E_EN_HIGH;
    			TRS3221E_FORCEOFF_LOW;
     
    		}
    		else if(!HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_9) && System.Mode == USB)
    		{
    			System.Mode = UART;
    			/* activate TRS3221E for RS232 <-> LPUART communication with auto power off feature */
    			 TRS3221E_FORCEOFF_HIGH;
    			 TRS3221E_EN_LOW;
    		}
     
    	 static const char msg[] = "ABP;yyy;zzz;uuu\n";
    	 static uint32_t lastTime = 0;
     
    	 // Send message every 10 seconds, change to whatever interval you like
    	 if ( (HAL_GetTick()-lastTime) >= 3000 ) {
    	 lastTime = HAL_GetTick();
    	 // sizeof()-1 is shortcut instead of using strlen() since we KNOW the length at compile time
    	 if(System.Mode == USB)
    	 {
    	 	CDC_Transmit_FS( msg, sizeof(msg)-1 );
    	 }
     
    	 }
     
    #if 0
     
    	 /* check for a valid ADC signal with the comparator only if no measurement is ongoing */
    	 if(!System.isADCconversionStarted)
    	 {
    		 if(hcomp1.State != HAL_COMP_STATE_BUSY)
    		 {
    			 /* activate COMP1 for valid ADC IN signal detection */
    			 HAL_COMP_Start(&hcomp1);
    		 }
    	 }
    	 /* deactivate comparator 1 when measurement is ongoing to prevent unnecessary comparator triggering */
    	 else if(System.State == RunState && System.isADCconversionStarted)
    	 {
    		 if(hcomp1.State == HAL_COMP_STATE_BUSY)
    		 {
    			 /* deactivate valid ADC IN signal checking */
    			 HAL_COMP_Stop(&hcomp1);
    		 }
     
    	 }
     
    	 /* start ADC conversion if a valid ADC signal is present */
    	 if(System.validADCSignal && !System.isADCconversionStarted)
    	 {
    		 ADC_conversion_start(&System);
    	 }
    	 else if(!System.validADCSignal && System.isADCconversionStarted)
    	 {
    		 ADC_conversion_stop(&System, &sADCVoltage);
    	 }
     
    	 /* if ADC buffer is full this function handles MAX/MIN calculation and data validation */
    	 if(Flags.ADCBufferIsFull)
    	 {
    		 ADCbuffer_is_full();
    	 }
    	 /* calculate AIBP signal and send data over UART/USB if appropriate time has elapsed */
    	 if(Flags.lengthIsFinished)
    	 {
    		 if((System.interval - System.length) > 0)
    		 {
    			 length_is_finished();
    			 Flags.lengthIsFinished = false;
    		 }
    		 else
    		 {
    			 length_is_finished();
    			 Flags.lengthIsFinished = false;
    			System.State = RunState;
    			LD_GREEN_ON;
    		 }
     
    	 }
     
    	 /* start/stop ADC in dependency of System.State */
    	 if(System.State == RunState)
    	 {
    		 if(HAL_DMA_GetState(&hdma_adc1) == HAL_DMA_STATE_READY)
    		 {
    			 if(HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&ADCBuffer, 2) != HAL_OK)
    			 {
    				 Error_Handler();
    			 }
    		 }
    	 }
    	 else if(System.State == LowPowerState)
    	 {
    		 if(HAL_DMA_GetState(&hdma_adc1) == HAL_DMA_STATE_BUSY)
    		 {
    			 if(HAL_ADC_Stop_DMA(&hadc1) != HAL_OK)
    			 {
    				 Error_Handler();
    			 }
    		 }
    	 }
     
    #endif
     
     /* USER CODE END WHILE */
     
     /* USER CODE BEGIN 3 */
     }
     /* USER CODE END 3 */
    }

    Everything is now off except for GPIO init USB init. What sorcery is this?!?

    I also did log my hterm output, here you can see that it is occasional happening:

    11:13:40.375:

    ABP;yyy;zzz;uuu

    11:13:43.369:

    ABP;yyy;zzz;uuu

    11:13:46.363:

    ABP;yyy;zzz;uuu

    11:13:49.363:

    ABP;ABP;yyy;zzz;uuu

    11:13:52.364:

    ABP;yyy;zzz;uuu

    11:13:55.363:

    ABP;yyy;zzz;uuu

    11:13:58.364:

    ABP;yyy;zzz;uuu

    11:14:01.355:

    ABP;yyy;zzz;uuu

    11:14:04.365:

    ABP;yyy;zzz;uuu

    11:14:07.357:

    ABP;yyy;zzz;uuu

    11:14:10.360:

    ABP;yyy;zzz;uuu

    11:14:13.358:

    ABP;yyy;zzz;uuu

    11:14:16.359:

    ABP;yyy;zzz;uuu

    11:14:19.346:

    ABP;yyy;zzz;uuu

    11:14:22.358:

    ABP;yyy;zzz;uuu

    11:14:25.356:

    ABP;yyy;zzz;uuu

    11:14:28.354:

    ABP;yyy;zzz;uuu

    11:14:31.349:

    ABP;yyy;zzz;uuu

    11:14:34.354:

    ABP;yyy;zzz;uuu

    11:14:37.343:

    ABP;yyy;zzz;uuu

    11:14:40.344:

    ABP;yyy;zzz;uuu

    11:14:43.347:

    ABP;yyy;zzz;uuu

    11:14:46.341:

    ABP;ABP;yyy;zzz;uuu

    11:14:49.336:

    ABP;yyy;zzz;uuu

    11:14:52.333:

    ABP;yyy;zzz;uuu

    11:14:55.345:

    ABP;yyy;zzz;uuu

    11:14:58.344:

    ABP;yyy;zzz;uuu

    11:15:01.343:

    ABP;ABP;yyy;zzz;uuu

    11:15:04.333:

    ABP;yyy;zzz;uuu

    11:15:07.337:

    ABP;yyy;zzz;uuu

    11:15:10.340:

    ABP;yyy;zzz;uuu

    11:15:13.332:

    ABP;yyy;zzz;uuu

    11:15:16.339:

    ABP;ABP;yyy;zzz;uuu

    11:15:19.332:

    ABP;yyy;zzz;uuu

    11:15:22.329:

    ABP;yyy;zzz;uuu

    11:15:25.323:

    ABP;yyy;zzz;uuu

    11:15:28.322:

    ABP;yyy;zzz;uuu

    11:15:31.331:

    ABP;yyy;zzz;uuu

    11:15:34.319:

    ABP;yyy;zzz;uuu

    11:15:37.318:

    ABP;yyy;zzz;uuu

    11:15:40.318:

    ABP;yyy;zzz;uuu

    11:15:43.324:

    ABP;yyy;zzz;uuu

    11:15:46.321:

    ABP;yyy;zzz;uuu

    11:15:49.319:

    ABP;yyy;zzz;uuu

    11:15:52.308:

    ABP;yyy;zzz;uuu

    11:15:55.306:

    ABP;yyy;zzz;uuu

    11:15:58.311:

    ABP;yyy;zzz;uuu

    11:16:01.313:

    ABP;yyy;zzz;uuu

    11:16:04.310:

    ABP;ABP;yyy;zzz;uuu

    11:16:07.310:

    ABP;yyy;zzz;uuu

    11:16:10.309:

    ABP;yyy;zzz;uuu

    11:16:13.296:

    ABP;yyy;zzz;uuu

    11:16:16.302:

    ABP;yyy;zzz;uuu

    11:16:19.308:

    ABP;yyy;zzz;uuu

    11:16:22.296:

    ABP;yyy;zzz;uuu

    11:16:25.303:

    ABP;yyy;zzz;uuu

    11:16:28.293:

    ABP;yyy;zzz;uuu

    11:16:31.293:

    ABP;yyy;zzz;uuu

    11:16:34.292:

    ABP;yyy;zzz;uuu

    11:16:37.286:

    ABP;yyy;zzz;uuu

    11:16:40.296:

    ABP;yyy;zzz;uuu

    11:16:43.294:

    ABP;yyy;zzz;uuu

    11:16:46.286:

    ABP;yyy;zzz;uuu

    11:16:49.293:

    ABP;yyy;zzz;uuu

    11:16:52.290:

    ABP;yyy;zzz;uuu

    11:16:55.280:

    ABP;yyy;zzz;uuu

    11:16:58.278:

    ABP;yyy;zzz;uuu

    11:17:01.288:

    ABP;yyy;zzz;uuu

    11:17:04.287:

    ABP;yyy;zzz;uuu

    11:17:07.276:

    ABP;yyy;zzz;uuu

    11:17:10.282:

    ABP;yyy;zzz;uuu

    11:17:13.275:

    ABP;yyy;zzz;uuu

    11:17:16.279:

    ABP;yyy;zzz;uuu

    11:17:19.276:

    ABP;ABP;yyy;zzz;uuu

    11:17:22.271:

    ABP;yyy;zzz;uuu

    11:17:25.265:

    ABP;yyy;zzz;uuu

    11:17:28.261:

    ABP;yyy;zzz;uuu

    11:17:31.273:

    ABP;ABP;yyy;zzz;uuu

    11:17:34.262:

    ABP;yyy;zzz;uuu

    11:17:37.259:

    ABP;yyy;zzz;uuu

    11:17:40.258: