STM32 Virtual Com port receive data (Maybe more of a general C code question) - Page 1
Forum section: electronics / general

author: petemate
author_rank: Regular Contributor
timestamp: December 21, 2020, 08:56:27 pm
content: 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);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

author: bugnate
author_rank: Regular Contributor
timestamp: December 22, 2020, 03:17:40 am
content: "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.

author: langwadt
author_rank: Super Contributor
timestamp: December 22, 2020, 04:04:45 am
content: 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/

author: petemate
author_rank: Regular Contributor
timestamp: December 22, 2020, 10:24:03 pm
content: Quote from: bugnate on December 22, 2020, 03:17:40 amThanks 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%20usbBasuically, 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.

author: bugnate
author_rank: Regular Contributor
timestamp: December 22, 2020, 11:45:04 pm
content: Quote from: petemate on December 22, 2020, 10:24:03 pmBecause 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.

author: petemate
author_rank: Regular Contributor
timestamp: December 22, 2020, 11:53:22 pm
content: Quote from: bugnate on December 22, 2020, 11:45:04 pmThanks 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?

author: langwadt
author_rank: Super Contributor
timestamp: December 23, 2020, 12:07:56 am
content: Quote from: petemate on December 22, 2020, 11:53:22 pm64 byte is the maximum, there is no guarantee you will get your whole command in one packet.

author: bugnate
author_rank: Regular Contributor
timestamp: December 23, 2020, 02:14:24 am
content: Quote from: petemate on December 22, 2020, 11:53:22 pmYeah 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.

author: Tagli
author_rank: Contributor
timestamp: December 23, 2020, 12:51:25 pm
content: Quote from: petemate on December 22, 2020, 11:53:22 pmA 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.