Using the stock CDC drivers

mainRawCDC.c contains a minimal amount of code to configure the MCU hardware and USB device stack. It will allow the MCU to enumerate over USB as a virtual COM port when a micro-USB cable is plugged into CN1 (and goes to a USB host such as a PC) and power is applied through CN13. It will attempt to send two messages over USB: test and message:

  1. The USB stack is initialized by using the MX_USB_Device_Init() function after the hardware is fully initialized:
int main(void)
{
HWInit();=
MX_USB_DEVICE_Init();
  1. There is a single task that outputs two strings over USB, with a forced 100 tick delay after the second transmission using a naive call to usbd_cdc_if.cCDC_Transmit_FS:
void usbPrintOutTask( void* NotUsed)
{
while(1)
{
SEGGER_SYSVIEW_PrintfHost("print test over USB");
CDC_Transmit_FS((uint8_t*)"test ", 5);
SEGGER_SYSVIEW_PrintfHost("print message over USB");
CDC_Transmit_FS((uint8_t*)"message ", 8);
vTaskDelay(100);
}
}
  1. After compiling and loading this application to our target board, we can observe the output of the USB port by opening a terminal emulator (Tera Term in this case). You'll likely see something similar to the following screenshot:

Since we were outputting a single line containing test and then a single line containing message, we would hope that the virtual serial port would contain that same sequence, but there are multiple test lines that aren't always followed by a message line.

Watching this same application run from SystemView shows that the code is executing in the order that we would expect:

Upon closer inspection of CDC_Transmit_FS, we can see that there is a return value that should have been inspected. CDC_Transmit_FS first checks to ensure that there isn't already a transfer being performed before overwriting the transmit buffer with new data. Here are the contents of CDC_Transmit_FS(automatically generated by STM Cube):

  uint8_t result = USBD_OK;
/* USER CODE BEGIN 7 */
USBD_CDC_HandleTypeDef *hcdc =
(USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData

if (hcdc->TxState != 0){
return USBD_BUSY;
}
USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len);
result = USBD_CDC_TransmitPacket(&hUsbDeviceFS);
/* USER CODE END 7 */
return result;

Data will only be transmitted if there isn't already a transfer in progress (indicated by hcdc->TxState). So, to ensure that all of the messages are transmitted, we have a number of options here.

  1.  We could simply wrap each and every call to CDC_Transmit_FS in a conditional statement to check whether the transfer was successful:
int count = 10;
while(count > 0){
count--;
if(CDC_Transmit_FS((uint8_t*)"test ", 5) == USBD_OK)
break;
else
vTaskDelay(2);
}

There are several downsides to this approach:

    • It is slow when attempting to transmit multiple messages back to back (because of the delay between each attempt).
    • If the delay is removed, it will be extremely wasteful of CPU, since the code will essentially poll on transmission completion.
    • It is undesirably complex. By forcing the calling code to evaluate whether a low-level USB transaction was valid, we're adding a loop and nested conditional statements to something that could potentially be very simple. This will increase the likelihood that it is coded incorrectly and reduce readability.
  1. We could write a new wrapper based on usbd_cdc_if.c that uses FreeRTOS stream buffers to efficiently move data to the USB stack. This approach has a few caveats:
    • To keep the calling code simple, we'll be tolerant of dropped data (if space in the stream buffer is unavailable).
    • To support calls from multiple tasks, we'll need to protect access to the stream buffer with a mutex.
    • The stream buffer will effectively create a duplicate buffer, thereby consuming additional RAM.
  2. We could use a FreeRTOS queue instead of a stream buffer. As seen in Chapter 10, Drivers and ISRs, we would receive a performance hit when using a queue (relative to a stream buffer) since it would be moving only a single byte at a time. However, a queue wouldn't require being wrapped in a mutex when used across tasks.
The best solution depends on many factors (there's a list of considerations at the end of Chapter 10Drivers and ISRs). For this example, we'll be using a stream buffer implementation. There is plenty of room for the extra space required by the buffer. The code here is only intended to support occasional short messages, rather than a fully reliable data channel. This limitation is mainly being placed to minimize complexity to make the examples easier to read.

Let's now have a look at how options 2 and 3 look, relative to the STM HAL drivers already present:

For this driver, we'll be modifying the stubbed out HAL-generated code supplied by ST (usbd_cdc_if.c) as a starting point. Its functionality will be replaced by our newly created VirtualCommDriver.c. This will be detailed in the next section.

We'll also make a very small modification to the CDC middleware supplied by STM (usbd_cdc.c/h) to enable a non-polled method for determining when transfers are finished. The USBD_CDC_HandleTypeDef struct in usbd_cdc.h already has a variable named TxState that can be polled to determine when a transmission has completed. But, to increase efficiency, we'd like to avoid polling. To make this possible, we'll add another member to the struct – a function pointer that will be called when a transfer is complete: usbd_cdc.h (additions in bold):

typedef struct
{
uint32_t data[CDC_DATA_HS_MAX_PACKET_SIZE / 4U]; /* Force 32bits
alignment */
uint8_t CmdOpCode;
uint8_t CmdLength;
uint8_t *RxBuffer;
uint8_t *TxBuffer;
uint32_t RxLength;
uint32_t TxLength;
//adding a function pointer for an optional call back function
//when transmission is complete
void (*TxCallBack)( void );
__IO uint32_t TxState;
__IO uint32_t RxState;
}
USBD_CDC_HandleTypeDef;

We'll then add the following code to usbd_cdc.c. (additions in bold):

    }
else
{
hcdc->TxState = 0U;
if(hcdc->TxCallBack != NULL)
{
hcdc->TxCallBack();
}
}
return USBD_OK;
}

This addition executes the function pointed to by TxCallBack if it has been provided (indicated by a non-NULL value). This happens when TxState in the CDC struct is set to 0. TxCallBack was also initialized to NULL in USBD_CDC_Init()

Modifying drivers supplied by STM will make it harder to migrate between different versions of HAL. These considerations must be weighed against any advantages they provide.
NOTE: More recent versions of HAL and STMCubeIDE include support for TxCallBack, so this modification won't be necessary if you're starting from scratch with the latest released code from ST.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.48.135