cancel
Showing results for 
Search instead for 
Did you mean: 

VCP with STM32

hisham23
Associate II
Posted on June 01, 2012 at 15:43

Hello,

I am working on virtual serial port to communicate between computer and device through usb.

So i downloaded ''STM32F105/7,STM32F2 and STM32F4 USB on-the-go Host and device library.''  from http://www.st.com/stonline/stappl/resourceSelector/app?page=resourceSelector&doctype=FIRMWARE&ClassID=1734

And i selected vcp from project lists, and start reading the code.

I found that the main application only contains initialization of the hardware and the interrupts.

So the code that is sending data is working in the interrupt, but i cannot find the portion that is responsible to send data... Please if any one can explain me the flow of the program.

Note that i read the manuals from ST concerning this project but i didn't get the point, because they wrote 2 papers only about this application.

Regards,

Hisham
12 REPLIES 12
harinath
Associate III
Posted on June 01, 2012 at 16:35

I work with STM32F103RE. IAR EW+FreeRTOS environment. The device communicates with PC using Virtual Com Port.

Since USB data pins, CAN are multiplexed, for communication we use CAN interrupt event.

void
USB_Interrupts_Config(
void
)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 14;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}

Below is the code for the interrupt handler. FreeRTOS related code is inside written inside handler.

//This function handles USB Low Priority or CAN RX0 interrupts requests.
void
USB_LP_CAN1_RX0_IRQHandler(
void
)
{
#ifdef _VCOM
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
USB_Istr(); 
// Responsible for reading data (usb_istr.c)
if
(count_out) {
xQueueSendFromISR(s_usbQueue, &count_out, &xHigherPriorityTaskWoken);
count_out=0;
}
portEND_SWITCHING_ISR(xHigherPriorityTaskWoken);
#endif
}

From the above function, briefly you can understand how to get read the data.

Below is the function to write the data to Virtual Com buffer in order to send to PC.

void
Virtual_Com_Write_Buffer(u8* data_buffer, u8 Nb_bytes)
{
/* The USB cable is plugged into the USB-Mini connector */
u8 b_size = Nb_bytes;
u8 b_index = 0;
u8 actual_input_size;
while
(b_index < Nb_bytes)
{
if
(b_size < VIRTUAL_COM_PORT_DATA_SIZE)
{
actual_input_size = b_size;
} 
else
{
actual_input_size = VIRTUAL_COM_PORT_DATA_SIZE;
}
/* wait until the data transmission is finished */
while
(GetEPTxStatus(ENDP1) == EP_TX_VALID); 
//0x30
/* Write data to the virtual UART through USB port */
UserToPMABufferCopy(&(data_buffer[b_index]), ENDP1_TXADDR,actual_input_size);
SetEPTxCount(ENDP1,actual_input_size); 
SetEPTxValid(ENDP1);
b_size = b_size - actual_input_size;
b_index= b_index + actual_input_size; 
} 
}

Hope you got an idea how the communication goes between Device and PC.
tsuneo
Senior
Posted on June 01, 2012 at 20:54

Hi Harinath,

Is your Virtual_Com_Write_Buffer() a simplified one?

This routine doesn't append ZLP (Zero-Length Packet), when the last transaction goes in full-size packet (usually 64 bytes, equals to wMaxPacketSize of the endpoint).

For example, when just 64 bytes (or 128, 192, ...) is sent over your Virtual_Com_Write_Buffer(), the transactions should complete on the device, but it doesn't appear on the PC application. It's because PC host controller still waiting for next transaction. To terminate the transfer, send ZLP after the last full-size packet.

Tsuneo

harinath
Associate III
Posted on June 02, 2012 at 03:56

Find the firmware source here:

http://www.st.com/internet/evalboard/product/250367.jsp

It has complete firmware for the iNEMO v2 board based on STM32.

hfs99
Associate II
Posted on June 02, 2012 at 21:14

Hello Issa,

I am struggling with the exact same question. Too bad the forum search function does not work better - this question must have been answered many times already. (Good of Harinath to post an example but I fear it will only cause confusion - the library used is different?). Anyway, here are some notes:

  • Since the STM32 is in Device mode, it cannot simply send data; it must wait to be polled by the host.
  • usbd_cdc_core.c declares APP_Rx_Buffer[APP_RX_DATA_SIZE] as well as APP_Rx_ptr_in and APP_Rx_ptr_out. Basically a circular buffer. If ptr_in == ptr_out, there is no data to be sent.
  • VCP_DataTx() in usbd_cdc_vcp.c increments ptr_in by the number of characters added to APP_Rx_Buffer. At this time, ptr_in does not equal ptr_out.
  • usbd_cdc_SOF() in usbd_cdc_core.c is presumably called every frame. This in turn calls Handle_USBAsynchXfer(), also in usbd_cdc_core.c. If ptr_out != ptr_in, Handle_USBAsynchXfer() sets USB_Tx_State = 1, and calls DCD_EP_Tx() in usb_dcd.c to start the transfer.

So, to summarize, if we're modifying the example code, one key to a replacement for VCP_Data_Tx() is to make sure the APP_Rx_ptr_in is modified.

* I didn't get anything working yet!

* I don't yet understand why we're using something called APP_Rx_Buffer to transmit; I don't see an APP_Tx_Buffer.

-------

Chinzei: apologies for my ignorance and laziness - The ZLP you mention is required only if my last packet in a ''paragraph'' (sorry for the bad terminology) is completely full? Or one is needed at the end of each paragraph even if the last was not full?

Is that requirement specific to VCOM, or general to ''IN'' packets?

Thanks,

-Hugh

tsuneo
Senior
Posted on June 02, 2012 at 21:45

Harinath,

You are referring to this source of virtual_com

iNEMO GUI and firmware

http://www.st.com/internet/com/SOFTWARE_RESOURCES/SW_COMPONENT/SW_FUNCTION/inemo_gui_2_0_0.zip

C:\Program Files\STMicroelectronics\iNEMO Suite\Firmware\iNEMO_Project\iNEMO_Project\virtual_com\src\virtual_com.c

At quick glance of this source files, this application likely sends less than 64 bytes to host. And then, the author wasn't aware of above flaw.

As of STM32_USB-Host-Device_Lib_V2.1.0,

On the UART side, at UART RX interrupt (EVAL_COM_IRQHandler()), VCP_DataTx() is called. In this routine, received data is stacked on APP_Rx_Buffer[]

On the USB side, (STM32_USB-Host-Device_Lib_V2.1.0\Libraries\STM32_USB_Device_Library\Class\cdc\src\usbd_cdc_core.c)

at SOF interrupt (usbd_cdc_SOF()), Handle_USBAsynchXfer() is called at every CDC_IN_FRAME_INTERVAL. In this routine, the first packet is passed from APP_Rx_Buffer[] to the bulk IN endpoint, calling DCD_EP_Tx(). At the completion of the IN transaction, endpoint interrupt fires. The stack calls usbd_cdc_DataIn() for next packet. This routine puts the next packet calling DCD_EP_Tx(), if any. In this way, usbd_cdc_DataIn() is called until all data on APP_Rx_Buffer[] is sent out.

This implementation doesn't count in ZLP for the last full-size packet, too.

CDC_IN_FRAME_INTERVAL seems to be for a latency timer, but wrongly implemented.

A latency timer is coded in this way.

1) The USB side waits until 64 bytes or more data stacks on a buffer. This condition is polled at both of SOF and endpoint interrupt. When enough data stacks, a full-size packet is sent over the bulk IN endpoint.

2) When specified interval (latency timer) is expired after the last UART RX, all of data on the buffer is sent out to host, even if it is less than full-size.

OR

2') When specified interval is expired after the last bulk IN transaction, ditto.

Anyway, the concept of transfer split to transactions, and transfer termination, are the first hurdle to learn USB. I've seen many ones have fallen in this pitfall.

I explained on this issue in this post. Hope this help you.

http://www.cygnal.org/ubb/Forum9/HTML/001627.html

Tsuneo

tsuneo
Senior
Posted on June 02, 2012 at 22:17

Hi Hugh,

> Is that requirement specific to VCOM, or general to ''IN'' packets?

It's general to IN transfers.

As of PC CDC driver (Windows, Linux and Mac), the driver requests 4K Bytes bulk IN transfer to PC host controller (HC). HC splits this request into IN transactions to the CDC device. Until the device terminates this transfer by a short transaction (less than 64 bytes, including ZLP), HC doesn't complete the transfer. Therefore, PC CDC driver couldn't receive any data from HC, if the CDC device wouldn't send short transaction (packet).

Tsuneo

hisham23
Associate II
Posted on June 04, 2012 at 12:37

Hello, Thanks you all for your support.

I want to share my conclusion about the code. First USB_OTG_BSP_EnableInterrupt function enables these interrupts OTG_HS_EP1_OUT_IQRHandler, and OTG_HS_EP1_IN_IRQHandler from usb_bsp.c source code. Where OTG_HS_EP1_IN_IRQHandler handles data recieved over USB, and OTG_HS_EP1_IN_IRQHandler handles data sent over USB as follows.


#ifdef USB_OTG_HS_DEDICATED_EP1_ENABLED 

/**

* @brief This function handles EP1_IN Handler.

* @param None

* @retval None

*/

void OTG_HS_EP1_IN_IRQHandler(void)

{

USBD_OTG_EP1IN_ISR_Handler (&USB_OTG_dev);

}


/**

* @brief This function handles EP1_OUT Handler.

* @param None

* @retval None

*/

void OTG_HS_EP1_OUT_IRQHandler(void)

{

USBD_OTG_EP1OUT_ISR_Handler (&USB_OTG_dev);

}

#endif

I will start with the flow of OTG_HS_EP1_OUT_IQRHandler. 1- Calls

USBD_OTG_EP1OUT_ISR_Handler

that is defined in file: usb_dcd_int.c 2-

USBD_OTG_EP1OUT_ISR_Handler

calls DataOutStage that is defined in file: usbd_core.c. 3- DataOutStage calls DataRx that is defined in file: usbd_cdc_vcp.c. 4- DataRx function is used to proccess data recieved, in this case it sends data to serial port. So if we want to modify the way we want to process data received we just have to modify DataRx function. Now i will explain the flow of OTG_HS_EP1_IN_IQRHandler. 1- Calls

USBD_OTG_EP1IN_ISR_Handler

which is defined in usb_dcd_int.c. 2-

USBD_OTG_EP1IN_ISR_Handler

Calls DataInStage function which is defined usbd_core.c. 3- DataInStage Calls DataIn function which is defined in usbd_cd_core.c. 4- DataIn Calls DCD_EP_TX function which is defined in usb_dcd.c. And the last function is responsible to send data through usb. In this example which is VCP, what happens is the following: When data is received over serial port an interrupt is generated and the function DataTx is fired, which is defined in usbd_cdc_vcp.c. This function takes the data received to serial port, and put them is transmit buffer, which is then send over usb when OTG_HS_EP1_IQRHandler interrupt is fired. Please if am wrong with something inform me. Regards, Hisham
tsuneo
Senior
Posted on June 04, 2012 at 22:05

> and put them is transmit buffer, which is then send over usb when OTG_HS_EP1_IQRHandler interrupt is fired.

Please note, outside of endpoint ISR (USBD_OTG_EP1OUT_ISR_Handler()), the first packet is put to the USB IN endpoint by DCD_EP_Tx(). In this example, its Handle_USBAsynchXfer(), called by SOF interrupt. The endpoint ISR is called just after an IN transaction completes successfully. And then, the endpoint ISR puts the second and latter packets to the IN endpoint.

The IN endpoint interrupt doesn't occur, unless the IN endpoint is filled outside of its ISR. Many ones have often asked here, why doesn't IN transfer start. Naturally it's because they modify just endpoint ISR.

Tsuneo

hisham23
Associate II
Posted on June 06, 2012 at 09:21

Sorry tsuneo but i didn't get your point, can you share your ideas on the code please.

And yes you are right, i don't know when IN endpoint interrupt occur, so in case i want to write data from usb to pc i should fire this interrupt right?