cancel
Showing results for 
Search instead for 
Did you mean: 

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

Benjamin Brammer
Senior II

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

50 REPLIES 50

Hey Bob,

I think I did correct some errors in my routines. For example I used to write the following:

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

but I corrected it to use the exact array length:

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

also I had to implement a "button pressed routine which tells me if a button has been pressed:

void HAL_GPIO_EXTI_Callback (uint16_t GPIO_Pin)
{
	if(GPIO_Pin == PUSH_BUTTON_Pin && !Button.isButtonPressedShort)
	{
		Button.isButtonPressedShort = true;
 
		/* time stamp for evaluation purpose */
		if(System.Mode == UART)
		{
			LPUART_transmit_message("Button pressed!\n");
		}
		else if(System.Mode == USB)
		{
			uint8_t tempbuf[] = "Button pressed!\n";
			CDC_Transmit_FS(tempbuf, strlen(tempbuf));
		}
..
..

Very interesting is that when I push the button randomly, i sometimes get the following output on the terminal program:

Button pressed!\n

Button pressed!\n

ButtButton pressed!\n

Button pressed!\n

....

Yes , it is exactly the same 4 characters which are also added with

ABP;yyy;zzz;uuu\n

ABP;yyy;zzz;uuu\n

ABP;yyy;zzz;uuu\n

ABP;ABP;yyy;zzz;uuu\n

ABP;yyy;zzz;uuu\n

...

Sadly I still don't get what the problem is, allthough the hardfault has changed it's reason. It is now a precise data access violation and the address has also changed for the PC when the error occurs. This time in the following function:

/* Direction counter changed from Down to Up interrupt */
  if (__HAL_LPTIM_GET_FLAG(hlptim, LPTIM_FLAG_UP) != RESET)
  {
    if (__HAL_LPTIM_GET_IT_SOURCE(hlptim, LPTIM_IT_UP) != RESET)
    {
      /* Clear Direction counter changed from Down to Up flag */
      __HAL_LPTIM_CLEAR_FLAG(hlptim, LPTIM_FLAG_UP);
 
...

I did actually change the NVIC priorities o the following:

0690X00000D9DBCQA3.jpg

I also tried settign the optimization level to "debug", but the 4 character adding problem still remains..

> In your last paragraph you pointed out the difficulties when using strncpy() and strlen(). If I would copy

> a string which size is smaller than my buffer array into the buffer via strncopy and would set the length

> of my buffer as the n value, then my buffer would be padded with NULL and this wouldn't be a problem,

> or am I wrong?

Calling strncpy() with a source string whose length is smaller than the destination will (should) zero-fill the destination buffer. The only issue is when the source string is >= the size of the destination buffer.

> sizeof(Communication.measure)/sizeof(Communication.measure[0])

This change is OK, but has no effect in this case and is exactly the same as "sizeof(Communication.measure)". Because this is a char array, sizeof [0] == 1. This *IS* a useful construct to determine the number of elements in an array whose elements are larger than 1 byte AND for which you don't know the size at compile time. For example:

int array[] = { 1, 2, 3,/* lots of data here */ };
int i;
for ( i = 0; i < sizeof(array)/sizeof(array[0]); i++ ) {
    //Do something with array[i]
}

Your interrupt priorities are inverted. If LPTIM1 and/or LPTIM2 are going to be calling functions to send data via LPUART and/or USB, the LPUART and USB need to have higher interrupt priorities (smaller values). I don't *THINK* this is really an issue (yet) with you code since I presume the timer interrupts are "infrequent" (meaning you will not get another timer interrupt until after the uart/usb data has been sent). So don't change the priorities. At least not yet. Just keep this in mind for other cases like this. If you don't carefully plan your function calls this can lead to one interrupt blocking another interrupt (much like a priority inversion when using an RTOS).

		else if(System.Mode == USB)
		{
			uint8_t tempbuf[] = "Button pressed!\n";
			CDC_Transmit_FS(tempbuf, strlen(tempbuf));
		}

Not good programming practice. You declare tempbuf[] as local to the "else if" code block, so it is allocated on the stack. Being local means it goes out of scope (i.e. de-allocated and that RAM open for re-use somewhere else) immediately after the close curly brace. That means that any interrupt, or subsequent function call will possibly overwrite that RAM area before the USB block is done with it.

As a general rule, never use a local buffer to pass data to a function unless you can guarantee that the function will be completely done with the data in that buffer BEFORE the function returns. For a simplistic example, strlen() uses the buffer only during the strlen() call. However, HAL_USART_Transmit_IT() or HAL_USART_Transmit_DMA() are NOT like that. They store pointers to the buffer, but require the buffer contents to be valid until the data has actually been sent out the UART. I haven't fully traced the flow of CDC_Transmit_FS() to see exactly WHEN the data is copied from your buffer into the USB Tx FIFO. And unless you can prove otherwise, I would NOT expect that to happen before the CDC_Transmit_FS() call returns.

You didn't post info on exactly which assembly instruction caused this new "precise data access" fault, and which memory address was being addressed when the fault occurred, I want to scream "you didn't follow the lead the CPU gave you". What exact assembly instruction caused the fault? What exact memory address was being accessed? If you found that out, you didn't post it. So we we are back at guessing. Don't do half-a$$ed debugging. **IF** the fault indeed happened from one of the lines you posted, it could be the hlptim pointer itself got corrupted. Or it could be the contents of the structure that hlptim points to got corrupted (most likely the "Instance" member). **IF** it was the hlptim variable that got corrupted, the only normal (non-fault) interrupts with higher priority than the LPTIM interrupts are the DMA1 Channel 1 interrupt (could be ADC, TIM2, TIM17 or TIM4) or system tick timer. So check both of these interrupt calls. I would start with the DMA interrupt, perhaps it is your ADC???? But since you didn't post all the fault info, we don't know what was corrupted. That was a lost opportunity. Yeah I'm being a bit harsh here, but for cryout out loud, don't ask for help from others if you won't follow though in your own debugging. You have to be methodical. You have to follow every lead as far as you can and see what it tells you. Somethings it is nothing useful. Sometimes is points to a dead end. But sometimes it gives you a useful clue.

You are calling lots of functions from inside interrupt functions - remember, every HAL function with "callback" in its name is called from an interrupt. Try simplifying the flow of you code. Change all of your callbacks so that the callback only sets a flag (declared as "volatile"). Then your main polling loop checks for each of these flags and then does whatever needs doing (sending a string, reading data, whatever). Keep your interrupt code as small and short as possible (unlike the HAL design philosophy which throws EVERYTHING into the interrupts). For example:

volatile bool SendDataFlag = false; // or "int" if you don't like "bool"
 
LPTIM_xxxx_Callback( ... )
{
   SendDataFlag = true;
}
 
main(...)
{
   while( true ) {
      if ( SendDataFlag ) {
         SendDataFlag = false;
         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));
         }
      }
   }
}

Hello Bob,

thanks for your answer. No, you are right, I shouldn't do half - a$$ed debugging. I am sorry, I did not take the time and collect every information necessary.

>Your interrupt priorities are inverted. If LPTIM1 and/or LPTIM2 are going to be calling functions to send data via LPUART and/or USB, the LPUART and USB need to have higher interrupt priorities (smaller values).

Actually it is exactly as you thought. I do send UART or USB messages inside the HAL_LPTIM_AutoReloadMatchCallback(LPTIM_HandleTypeDef *hlptim) function (LPTIM2). But you are correct the callback function gets triggered every second and a message is send earliest after 3s or later (this is programmable) and beside this my callback function gets triggered after the appropriate IRQ-handler.

>As a general rule, never use a local buffer to pass data to a function unless you can guarantee that the function will be completely done with the data in that buffer BEFORE the function returns.

So I will use a global array to have that memory allocated from startup until the end of time. Off course if I could guarantee that the content of that buffer array is used prior to its de-allocation, I could do different.

>You are calling lots of functions from inside interrupt functions - remember, every HAL function with "callback" in its name is called from an interrupt. Try simplifying the flow of you code. Change all of your callbacks so that the callback only sets a flag (declared as "volatile"). Then your main polling loop checks for each of these flags and then does whatever needs doing (sending a string, reading data, whatever). Keep your interrupt code as small and short as possible (unlike the HAL design philosophy which throws EVERYTHING into the interrupts).

But the HAL-callback function trigger after the IRQ handler. I thought this is exactly the way STM wanted it to be. So the callback functions are not inside an IRQ-handler and can be used to do stuff triggered by the interrupt but outside that IRQ-handler.

edit: Sorry, you were right. I now manually stepped through the callback functions and it is indeed as you said: When the IRQ-handler gets executed the HAL library checks for each interrupt flag and executes the appropriate callback function. At the end of each callback function the IRQ-handler returns to normal thread mode.

Ok so I will change my code according to your suggestions with the global flags.

Thanks again for your help and patience!

When I have another hardfault I will extract all information.

Benjamin Brammer
Senior II

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?

> 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.

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:

Your USB cfg shows self powered, so can we presume you are not powering this from the STLink USB connector on the NUCLEO board?

My config has VBUS sensing, link power management and signal start of frame all disabled. I would not expect that to make a difference, but I don't know. The USB Device CDC config is identical to mine (and is the default).

Bad juju for sure! Also known as "Engineering H@#$" (the forums software won't let me use the real word here :), where everything it right and nothing works. When that is the case, go back and check your assumptions.

Have you tried any terminal program other than hterm? On the very off chance that this is an issue on the PC side. Something like TeraTerm or PuTTY?

I just re-read one of your earlier posts that said that sometimes the USB CDC device doesn't enumerate (Windows doesn't recognize it), and this causes (or caused) a fault on the STM32L476. I had forgotten about that. Is that still the case, that sometimes the USB device doesn't show up in Windows?

Change the code in your main polling loop to the following:

static const char msg1[] = "ABP;yyy;zzz;uuu\n";
static const char msg2[] = "1234567890VWXYZ\n";  // same length as msg1
uint32_t lastTime = 0; // Oops, didn't need to be "static" in my first example
int count = 0;
 
if ( (HAL_GetTick()-lastTime) >= 3000 ) {
   lastTime = HAL_GetTick();
   count++
   if ( (count&0x01) == 0 )
   {
      CDC_Transmit_FS( msg1, sizeof(msg1)-1 );
   }
   else {
      CDC_Transmit_FS( msg2, sizeof(msg2)-1 );
   }
}

This code will output alternating 16 byte (including newline) messages. If you still get the extra data in your terminal program it will be interesting to see what that extra data is.

If you still get the extra data, try starting with a blank slate. Start a new project in a different directory, with a new CubeMX config. Configure only your clocks and oscillator pins (if needed) and the USB CDC interface. No GPIO pins (other than oscillator and USB), no ADC, no nothing. In your main polling loop, put only the code above.

Do you see the extra data from this bare-bones program?

If you do not get the extra data in your bare-bones project, start adding the functionality from your original project, one peripheral or function at a time. Then test. If no extra data, add another feature. Do this until either you start getting the extra data, or until you have all of the functionality of your original program added to the "bare bones" project.

WARNING: Do **NOT** rely on CubeIDE and CubeMX to re-generate code and keep all of your custom code. Use version control if you aren't all ready, even if this is simply making a copy of the project directory tree. Always, always, always save a copy of your project before letting CuberMX/CubeIDE re-generate code. Then compare the old and new directory trees and make sure that your custom code didn't change.

>Your USB cfg shows self powered, so can we presume you are not powering this from the STLink USB connector on the NUCLEO board?

Yes. I am either powering over a battery or the VBUS when my board is attached as USB CDC VCP. Power source switching is done automatically with LM66100EVM from TI.

>Have you tried any terminal program other than hterm? On the very off chance that this is an issue on the PC side. Something like TeraTerm or PuTTY?

Yes, I have tried PuTTY.

>I just re-read one of your earlier posts that said that sometimes the USB CDC device doesn't enumerate (Windows doesn't recognize it), and this causes (or caused) a fault on the STM32L476. I had forgotten about that. Is that still the case, that sometimes the USB device doesn't show up in Windows?

Yes, sometimes this is happening.

>WARNING: Do **NOT** rely on CubeIDE and CubeMX to re-generate code and keep all of your custom code. Use version control if you aren't all ready, even if this is simply making a copy of the project directory tree. Always, always, always save a copy of your project before letting CuberMX/CubeIDE re-generate code. Then compare the old and new directory trees and make sure that your custom code didn't change.

I use Git as a local repository, so yes I use version control.

I think I have found the problem! As you suggested, I started a clean new project and just added the USB CDC VCP functionality. At first I sometimes did get the added character problem but after I have disabled the link power management, everything was fine. I then tried the same with my original project but the problem was still present. The only difference now was the clock configuration. Although I used the same HCLK. the source for the USB clock is different. In my original project I use the MSI RC at 48 MHz because I feed the PLL with the HSI RC as I want to utilize the STOP0 mode for power consumption when the main functionality works seemlessly. Thats the reason why I use LPTIM1&2 and LPUART. When a USB connection is established my project doesn't need to go into STOP0 mode. Since HSI16 is the clock that can be requested I used this as the PLL source to make programming easier, but I have read now again that I also can utilize MSI as a request from WFI. Anyhow this is the major difference, and when I changed the new test-project to use the same clock configuration as my main project, I get the same added character mistakes. I will read now again which clock configuration makes most sense in my application and try it with this one again.

[edit] Good catch.[/edit]

I haven't tried that clock combination. Using MSI=48MHz for USB *should* work according to the reference manual as long as you enable the 32KHz LSE oscillator and allow the MSI to auto-calibrate (from the 32KHz). But like I said, I haven't tried that. My system is currently running USB from HSI16 RC through the PLL while on the NUCLEO board, and I was not able to replicate your issue. My actual product will use an external HSE crystal.

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