PlatformIO Community

STSTM32 - USB Serial no longer fits in ram of STM32F103C8T6 Blue Pill

I have the following project that uses USB Serial.

Back when I wrote this code the default serial port was USB Serial. I used it like this:

12 months ago it built fine, but now I get an error saying the function should take an argument. It appears that USB Serial is no longer the default, so I modified my platformio.ini according to the fix proposed in Maple mini with USB serial in Arduino fails


platform =
board = bluepill_f103c8
framework = arduino
upload_protocol = dfu
upload_port = anything
build_flags = 
    -D USBD_VID=0x0483

However now the chip does not have enough ram to hold the USB serial code.

Linking .pio\build\bluepill_f103c8\firmware.elf
c:/users/koen van vliet/.platformio/packages/toolchain-gccarmnoneeabi/bin/../lib/gcc/arm-none-eabi/9.2.1/../../../../arm-none-eabi/bin/ld.exe: .pio\build\bluepill_f103c8\firmware.elf section `._user_heap_stack' will not fit in region `RAM'
c:/users/koen van vliet/.platformio/packages/toolchain-gccarmnoneeabi/bin/../lib/gcc/arm-none-eabi/9.2.1/../../../../arm-none-eabi/bin/ld.exe: region `RAM' overflowed by 1248 
collect2.exe: error: ld returned 1 exit status
*** [.pio\build\bluepill_f103c8\firmware.elf] Error 1

Then I reversed my changes of the platformio.ini file, and used the UART instead by using
Serial.begin(9600);. This fit in ram and built succesfully.

I want to use USB serial on my Blue Pill board again. How can I make it fit in ram of the STM32F103C8T6?

Make sense since the UART HAL is way slimmer

You are overflowing RAM by a mere 1248 bytes. There are 2 paths:

  • Minimize memory usage in your part
  • Minimize memory usage in USB CDC part

For your part, you’re allocating a 16kB buffer

Which is your maximum size for a read request. You read in the cartridge data byte-by-byte in this huge buffer

then calculate the checksum over the whole data and then print the whole buffer.

This is highly wasteful. You can tweak the protocol so that you can do:

  • print header with its checksum first
  • have a 1-byte buffer (or block size for more efficient reads), read from the cartridge, print it immediately on the UART. For every output chunk written, update the internal checksum (which you have implemented as a simple 16-bit summation). Since the whole thing is additive, you can just add to the previous checksum checksum += CalculateChecksum(new_data, new_data_len);
  • at the end of the packet, write your final data checksum
  • adapt the readout logic on the other side.

This enables you to not need a 16kB buffer and also your maximum read size restriction is gone and is basically infinity / cartridge length. Thus you have freed 16kB and can do USB-CDC again.

This “streaming” approach requires just the minimal amount of memory compared to your “whole block read” approach.

The other path would be to optimize buffer sizes within the USB CDC HAL. Best I could find is these queue buffers

So the receive queue will have length 64 * 3 = 192 and the TX queue 128 bytes. Not really significant and some more memory must be used elsewhere (HAL implementations, Arduino core layer etc). You can use to find all memory usage using a map file.

But I’d really suggest a more efficient implementation of the readout logic.

1 Like

Thank you for your detailed explanation. I did not realize I was still using the ram buffer.
The cartridge is accessed in 4096 byte chunks, so if I include the header it’s well below the 16k I allocated initially. Halving the buffer size fixed the problem. In a future release I will take your advice and stream the data directly.

No problem, always here to help :slight_smile:.