Author Topic: STM32 Virtual Com port receive data (Maybe more of a general C code question)  (Read 4781 times)

0 Members and 2 Guests are viewing this topic.

Offline petemateTopic starter

  • Regular Contributor
  • *
  • Posts: 126
Hi guys, I am working on a project that requires me to send data from a host(a terminal) to a device, which is based on an STM32F103. For this, I was planning to use the USB Communications Device Class, which is a virtual COM port.

First of all I should probably say that I am quite the newbie for this kind of thing. Its not that I don't know C, its that I have a really hard time navigating the ton of declarations, includes and whatever else is going on in the many different files that are included in such projects. I don't know the deeper meaning behind the "interplay" between the files. I am also sorry if this question is too implicit, as it probably does require some knowledge about the STM32 project structure, libraries, etc.

Anyway, I have no problem sending data via the function CDC_Transmit_FS(), but I am not really sure about how to receive data. There is a function, CDC_Receive_FS(), but from what I can understand, this function is not supposed to be used for receiving data. From what I understand, it is a callback function that the USB lib runs every time it receives some data. The data is then stored in a UserRxBufferFS array declared in the same usbd_cdc_if.c file. I can verify that the UserRxBufferFS array gets filled with what I write in a terminal on the host.


static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
  /* USER CODE BEGIN 6 */
  USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
  USBD_CDC_ReceivePacket(&hUsbDeviceFS);
  return (USBD_OK);
  /* USER CODE END 6 */
}


Now, what I don't understand is how I use the received data in e.g. my main loop. I can't just declare a variable "myvariable=UserRxBufferFS" in my main loop, for instance, as the compiler will then throw an error about the UserFxBufferFS being undeclared.

 What I see most people do, is to modify the CDC_Receive_FS() function to work on the received data, but that doesn't sit right with me: It seems to create unnecessary high degrees of complexity to weave files together like this. Basically, in my main.c loop, I'd like to use a switch-case statement to sort through all possible commands sent to the device via the terminal. How could I avoid having to do this inside the CDC_Receive_FS function ?


Thank you for your time :)
« Last Edit: December 21, 2020, 08:58:42 pm by petemate »
 

Offline bugnate

  • Regular Contributor
  • *
  • Posts: 58
  • Country: us
"Weaving" is exactly what you have to do. Your understanding of CDC_Receive_FS() is correct except that it will be called in 64 byte (or less) chunks. What you would typically do is copy the data out of the usb rx buffer ("Buf") into another buffer and defer further processing until after the interrupt finishes. If your commands are trivial (e.g., one byte, infrequently), you can get away with something easy like your switch statement. Otherwise you have a little work to do.

This looks like the ST CubeMX code. You may want to look at other libraries like libopencm3 and see if that more to your liking, but I haven't used them myself.
 
The following users thanked this post: petemate

Online langwadt

  • Super Contributor
  • ***
  • Posts: 4436
  • Country: dk
copy the data to your own buffer where you can "consume" it as you like,

maybe have a look at this, https://stm32f4-discovery.net/2014/08/library-24-virtual-com-port-vcp-stm32f4xx/
 
The following users thanked this post: petemate

Offline petemateTopic starter

  • Regular Contributor
  • *
  • Posts: 126
"Weaving" is exactly what you have to do. Your understanding of CDC_Receive_FS() is correct except that it will be called in 64 byte (or less) chunks. What you would typically do is copy the data out of the usb rx buffer ("Buf") into another buffer and defer further processing until after the interrupt finishes. If your commands are trivial (e.g., one byte, infrequently), you can get away with something easy like your switch statement. Otherwise you have a little work to do.

This looks like the ST CubeMX code. You may want to look at other libraries like libopencm3 and see if that more to your liking, but I haven't used them myself.

Thanks for your reply! Why with all this weaving? When i took a programming course, i was tought about how to "compartmentalize" functions, not to pass values though global variables, etc. So why does this implementation encourage it?
I ended up doing it this way: https://www.openstm32.org/tiki-view_forum_thread.php?forumId=7&comments_parentId=3169&highlight=CDC_Receive_FS%20usb
Basuically, the solution is to copy the received data into a separate buffer which is declared as a global variable in usbd_cdc_if.c and then #include "usb_dcd_if.h" in my main.c. Its just so ugly. And why do I have to specifically include that file in my main.c, when it is already included in "usb_device.h", which is also in my main.c?

You mention that this will only work if my commands are trivial. Why is that? I am planning to do some SCPI-like statements, e.g. "output:volt 10", "output:curr 5" or "output on".

Yes, its STM32CubeIDE, with code generated by CubeMX.

 

Offline bugnate

  • Regular Contributor
  • *
  • Posts: 58
  • Country: us
You mention that this will only work if my commands are trivial. Why is that? I am planning to do some SCPI-like statements, e.g. "output:volt 10", "output:curr 5" or "output on".
Because of the 64-byte packets from USB. If you send a command longer than this, it will be split among multiple callbacks. Or, if you send two messages very close in time, they may get combined into a single callback on the F103 end. Even small commands might span multiple callbacks for no apparent reason, if the sender feels like it. Or what happens if a new command comes in before the previous finishes, etc. My version of "trivial" is that all of those problems are assumed away. You will have to think about your own case.

This type of thing, especially when interacting with the (async) physical world, has a different set of problems and tactics for sure. But it doesn't have to be "ugly". You shouldn't take too many cues from CubeMX code, its more example than ideal.
« Last Edit: December 22, 2020, 11:49:50 pm by bugnate »
 
The following users thanked this post: petemate

Offline petemateTopic starter

  • Regular Contributor
  • *
  • Posts: 126
You mention that this will only work if my commands are trivial. Why is that? I am planning to do some SCPI-like statements, e.g. "output:volt 10", "output:curr 5" or "output on".
Because of the 64-byte packets from USB. If you send a command longer than this, it will be split among multiple callbacks. Or, if you send two messages very close in time, they may get combined into a single callback on the F103 end. Even small commands might span multiple callbacks for no apparent reason, if the sender feels like it. Or what happens if a new command comes in before the previous finishes, etc. My version of "trivial" is that all of those problems are assumed away. You will have to think about your own case.

This type of thing, especially when interacting with the (async) physical world, has a different set of problems and tactics for sure. But it doesn't have to be "ugly". You shouldn't take too many cues from CubeMX code, its more example than ideal.

Thanks for replying!

Why is the internal buffer set to 1000 char's by default, if the USB packet is 64 bytes long? Anyway, seems that "trivial" would mean that I should limit my commands to 64 characters(minus a \n, i guess), which should be enough, right?
 

Online langwadt

  • Super Contributor
  • ***
  • Posts: 4436
  • Country: dk
You mention that this will only work if my commands are trivial. Why is that? I am planning to do some SCPI-like statements, e.g. "output:volt 10", "output:curr 5" or "output on".
Because of the 64-byte packets from USB. If you send a command longer than this, it will be split among multiple callbacks. Or, if you send two messages very close in time, they may get combined into a single callback on the F103 end. Even small commands might span multiple callbacks for no apparent reason, if the sender feels like it. Or what happens if a new command comes in before the previous finishes, etc. My version of "trivial" is that all of those problems are assumed away. You will have to think about your own case.

This type of thing, especially when interacting with the (async) physical world, has a different set of problems and tactics for sure. But it doesn't have to be "ugly". You shouldn't take too many cues from CubeMX code, its more example than ideal.

Thanks for replying!

Why is the internal buffer set to 1000 char's by default, if the USB packet is 64 bytes long? Anyway, seems that "trivial" would mean that I should limit my commands to 64 characters(minus a \n, i guess), which should be enough, right?

64 byte is the maximum, there is no guarantee you will get your whole command in one packet.

 
The following users thanked this post: petemate

Offline bugnate

  • Regular Contributor
  • *
  • Posts: 58
  • Country: us
Why is the internal buffer set to 1000 char's by default, if the USB packet is 64 bytes long?

Yeah I don't get that either, pretty sure it is a mistake. Part of working with CubeMX is getting a feel for when you should take its code literally vs. "hints that compile" that give you the gist of things.

If you don't care about bloat, one option is to wire up CDC_Receive_FS() to _read() (but still using a second buffer etc). This would allow you to use read() and everything will look nice like typical CS 123 code again.
« Last Edit: December 23, 2020, 02:20:17 am by bugnate »
 

Offline Tagli

  • Contributor
  • Posts: 31
  • Country: tr
Why is the internal buffer set to 1000 char's by default, if the USB packet is 64 bytes long?
A USB transfer can consist of multiple USB packets. For example, the host PC can send 1000 bytes with a single transfer, with a single system call (function call). USB hardware divides this into 64 byte packets.

OTG_FS hardware on F4 series can store multiple packets in their RX buffer, as long as the allocated memory is sufficient. FS_Device hardware on F1 series is less capable and can store maximum 2 packets in double buffered mode (if I remember correctly). When hardware buffer in the uC is exhausted, uC USB hardware responds with NAK to incoming packets. This is not an error/failure. USB host continues to try sending the rest of the transfer until it hits a timeout limit (of course, timeout means failure). Ideally, before a timeout happens uC firmware moves data from hardware buffers so the endpoint can start accepting new packets again and the transfer continues normally.
Gokce Taglioglu
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf