cancel
Showing results for 
Search instead for 
Did you mean: 

STM32 F0 HAL 1.6.0 USB CDC usage (receiving data)

robodude1010
Associate II
Posted on August 01, 2016 at 06:35

I've got an f070 chip on a board interfaced with a USB port, I used the current 1.6.0 HAL libraries for the f0 series in MXCube to generate the base code needed and through a little of my own have demonstrated that the function CDC_Transmit_FS() (of usb_cdc_if.c) works by sending formatted text and variables back over the USBport through the VCP interface and into a terminal application on my computer.  I can't for the life of me figure out how to read from the receive buffer or otherwise receive data sent from the computer using the HAL firmware - CDC_Receive_FS() of the same class is static and modifying that and invoking it makes the chip hang completely on trying to read (including with no data written).

There are several tutorials to how to use the HAL CDC interface, but none for the current HAL driver version and for the f0 series chips, and there seems to be some dramatic differences in their implementation on other firmware versions and for other stm32 platforms, so I'm wondering what I'm supposed to be calling in my program to receive data (in any format) on the microcontroller from the attached computer.

Ideas?  Is there an alternative version of the firmware with a working receive/read function?  Is there a version designed for a different series of chips that could be easily ported or integrated into the ones I'm using?  I may be missing something obvious, but it's been difficult to tell after digging through the firmware source somewhat and I haven't been able to locate an example that will compile or that seems to be using the same functions I see available in the headers for the CDC classes.
4 REPLIES 4
denis2
Associate II
Posted on August 03, 2016 at 21:44

Recently faced exactly the same problem and after a sleepless night finally found a solution 🙂

The key point here is that receiving is asynchronous. So you have to enable receiving and then HAL will call CDC_Receive_FS by its own when a data is received.

Step 1: call USBD_CDC_ReceivePacket from CDC_Init_FS. This function will not actually receive data itself but will arm HW.

Step 2: when a data is received then HAL will call CDC_Receive_FS function. You can catch data inside of this function and pass it to your code somehow (e.g. generate SW interrupt, release mutex or whatever)

robodude1010
Associate II
Posted on August 04, 2016 at 05:03

Thanks for the tip!  It did seem like externally calling the function was causing the lockups (specifically, setting the buffer), and it seems like making it works requires exactly as you describe - even the extra ReceivePacket call in the initialization function to 'prime' the mechanism.  I have verified that I can receive data by calling CDC_Transmit_FS within the receive call as a loopback, and I can verify that UserRxBufferFS works as it was described, though the mechanisms that make it work lie entirely in code that's not dumped into the inc and src folders when created with CubeMX.

The problem then comes to the last part, as an identical loopback call outside of the receive function returns no data (simply passing the array outside of the class does not copy the data), and I don't have much multithreading experience with C.  So what is the simplest method of being able to read an array of characters (basically just a copy of UserRxBufferFS) in an external function to parse it for commands and data? 

Could it be as easy as creating an extern or volatile variable?  Is using an interrupt the right way to do it?  Can you use pthread.h for dealing with this, or is there something more specific to the hardware?  I don't really know how architecturally separated the USB hardware is from the rest of the core, so I'm not sure if it's treated quite the same as dealing with other threads.

Anyways, if there's a good example or some other demonstration/tutorial that should work on this hardware, I'd appreciate it!

denis2
Associate II
Posted on August 05, 2016 at 12:36

Well, I can describe how I did it for my project. Hope this helps..

I'm using FreeRTOS in my project and I need to pass data received over USB to a task. At the same time, the receiving routine itself is executed as a part of interrupt handling (if I'm not mistaken). So, we basically would need to do two things:

- Copy received data from the RX buffer to some area in the memory

- Notify a task that a new data is available in the memory.

Here are steps to achieve that:

1) Define and allocate a ring buffer, that will be used to store received data. Note that we cannot just keep received data in RX buffer, as it will be likely overwritten immediately after exiting from CDC_ReceiveFS. Thus it is safer to copy data from this buffer to some other memory.

2) Put received data in the ring buffer when they comes into CDC_ReceiveFS:

extern SemaphoreHandle_t usbRxSemaphore;

static int8_t CDC_Receive_FS (USB_CDC_DEVICE_T deviceID, uint8_t* Buf, uint32_t *Len)

{

 static BaseType_t xHigherPriorityTaskWoken = pdFALSE;

 // Copy received data to ring buffer

 RB_push(RING_BUFFER_UART1, Buf, Len);

 xSemaphoreGiveFromISR( usbRxSemaphore, &xHigherPriorityTaskWoken );

  USBD_CDC_SetRxBuffer(deviceID, &hUsbDeviceFS, &Buf[0]);

  USBD_CDC_ReceivePacket(deviceID,&hUsbDeviceFS);

  return (USBD_OK);

}

3) Handle received data in a task, once they are received

void StartUartOneTask(void const * argument)

{

  uint8_t *data;

  for(;;)

  {

   if( xSemaphoreTake( usbRxSemaphore, ( TickType_t ) portTICK_PERIOD_MS * 10000 ) == pdTRUE )

   {

    RB_pop(RING_BUFFER_UART1, data);

    // TODO: handle received data

   }

   else

   {

    CDC_Transmit_FS(USB_CDC_ONE ,''waiting for data\r\n'', 18);

   }

  }

}

If you are not using FreeRTOS, then I think you can just use a volatile variable as a semaphore.

robodude1010
Associate II
Posted on August 06, 2016 at 05:28

Thanks again for the help! After a bunch of monkeying around, I finally got something to work. I tried using pthread.h as a basis to control it with a mutex, but the standard mutex type didn't seem to exist in this pthread library, so I gave up on that. I then tried manually setting a volatile variable as a blocker to prevent simultaneous access of the stored data, but to no avail. Since my program should be well under the total memory available and because it seemed to offer some other helpful elements, I remade the project with the FreeRTOS libraries as well. After some more experimentation, I finally got something working. It is a limited example and I'm certain there are more efficient and better implementations for high performance and data throughput, but since it took me so long to figure out, I figure I'd put the code I used as a basis to give anyone else with a similar problem a starting point.

I'm using a STM32F070CBT6, HAL 1.6.0 libraries, and FreeRTOS included as generated by the CubeMX application. So when generating the code, make sure the FreeRTOS option is checked, the USB_DEVICE is the CDC class middleware, the USB device is enabled, and the system timer under SYS is set to an appropriate hardware timer (I used TIM6 because it was free and basic). I'm using an 8MHz HSE oscillator as set on the clock configuration tab. After that code is generated add these bits: to usbd_cdc_if.h

#include ''FreeRTOS.h''
#include ''semphr.h''
SemaphoreHandle_t usbRxSemaphore ;
int8_t CDC_Read(uint8_t* readData, uint8_t length) ;

to the appropriate sections then add this to usbd_cdc_if.c

volatile uint8_t bufferedInput[APP_RX_DATA_SIZE] ;
static int8_t CDC_Init_FS(void)
{ 
/* USER CODE BEGIN 3 */ 
/* Set Application Buffers */
USBD_CDC_SetTxBuffer(&hUsbDeviceFS, UserTxBufferFS, 0);
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, UserRxBufferFS);
USBD_CDC_ReceivePacket(&hUsbDeviceFS); //This is new to the function, the rest is standard HAL
return (USBD_OK);
/* USER CODE END 3 */ 
}
static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len)
{
static BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// Copy received data to buffer
for(uint8_t i = 0; i < APP_RX_DATA_SIZE; i++)
bufferedInput[i] = UserRxBufferFS[i] ;
xSemaphoreGiveFromISR( usbRxSemaphore, &xHigherPriorityTaskWoken );
/* USER CODE BEGIN 6 ********** original HAL code ******************** */
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
return (USBD_OK);
/* USER CODE END 6 ******************************/ 
}
int8_t CDC_Read(uint8_t* readData, uint8_t length){
if(xSemaphoreTake(usbRxSemaphore, 2) == pdTRUE){
for(uint8_t i = 0; i < APP_RX_DATA_SIZE; i++){
readData[i] = bufferedInput[i] ;
bufferedInput[i] = NULL ;
}
xSemaphoreGive(usbRxSemaphore) ;
return 1 ;
}
return 0 ;
}

and then in main.c, in place of the normal StartDefaultTask:

uint8_t usbRxData[8] ; //The number should be the same as APP_RX_DATA_SIZE
/* StartDefaultTask function */
void StartDefaultTask(void const * argument)
{
/* init code for USB_DEVICE */
MX_USB_DEVICE_Init();
usbRxSemaphore = xSemaphoreCreateMutex() ;
/* USER CODE BEGIN 5 */
/* Infinite loop */
for(;;){
HAL_Delay(1000) ;
if(CDC_Read(usbRxData, 8)) //The number should be the same as APP_RX_DATA_SIZE
CDC_Transmit_FS(usbRxData, 8) ;
}
/* USER CODE END 5 */ 
}

And that will give you a VERY basic loopback over the USB VCD. Using a ring buffer instead of a fixed buffer is a much more versatile solution, but there doesn't seem to be one built in as a standard library, so I didn't include it in this. For limitations, this code will still pass back all-null data strings, it will ignore any data beyond the first APP_RX_DATA_SIZE bytes in the transmission, and for whatever reason.... it will lock up if you send 25 bytes in a single packet. 24 bytes is fine (but with APP_RX_DATA_SIZE set to 8 as I have it, you only see the first 😎 but the 25th byte sent hard locks the chip. Sending multiple 24 byte packets, even before the buffer is read out and returned, does not seem to be a problem. I suspect this is a buffer size limit somewhere in the HAL code that I didn't change, but for my application, I don't need to. Hopefully that's a good starting point for someone! It's been a frustrating few days even trying to figure things out with the help!