In the past, I used to upload projects to ATmega328P microcontrollers using a FTDI module and the Arduino IDE without any issues. After switching from the Arduino IDE to PlatformIO, I wanted to make sure that uploading from there works as well.
This turned out to be less straightforward than expected, mainly due to the non-standard ATmega328P bootloader that I decided to use.
Connecting the FTDI module to the ATmega328P
Wiring is identical to the setup in my previous article where I described uploading from the Arduino IDE:

MiniCore and Urboot bootloader
I decided to use the Urboot bootloader from the MiniCore project for my ATmega328P microcontrollers long ago, before I even considered trying out PlatformIO.
PlatformIO's built-in board definitions don't automatically configure for Urboot/urclock - they assume standard Arduino bootloaders, which has to be overridden using a custom platformio.ini setup where the upload is configured to use a custom avrdude command:
[env:ATmega328P_MiniCore]
platform = atmelavr
board = ATmega328P
framework = arduino
board_build.f_cpu = 8000000L
upload_protocol = custom
upload_port = COM7
upload_speed = 57600
upload_flags =
-p
atmega328p
-c
urclock
-P
$UPLOAD_PORT
-b
$UPLOAD_SPEED
-D
-V
-xnometadata
upload_command = avrdude $UPLOAD_FLAGS -U flash:w:$SOURCE:i
The 8 MHz frequency and 57600 baud upload speed settings are based on the bootloader config that I used when burning the MiniCore/Urboot bootloader (using the Arduino IDE at that time):

And that's it! All this took me almost half a day to resolve, but finally I was able to start uploading projects to the microcontrollers without the need for the Arduino IDE.
The next task to explore is using PlatformIO not just for development and upload, but also for burning a bootloader to the ATmega328P.