Can I create a PlatformIO AVR project from existing code, including the LUFA library?

I want to contribute to an existing project, a USB to GPIB adapter at github xyphro/UsbGpib, by making some software modifications.

This project is based on a ATmega32U4 micro-controller.

The software is C and includes the LUFA USB library at github abcminiuser/lufa

I started by investigating using MicroChip Studio to do this development, but I am hitting issues with including the LUFA library. It is getting to difficult.

So now I am looking at using PlatformIO to develop with AVR, and later another project ESP-32. I have searched for LUFA & PlatformIO but no hits.

Can anyone point me to instructions that would help bring in existing code and the associated LUFA library. It is the LUFA part of the question I can’t find an answer to.

What you really want to do in this case is to compile the project (UsbGpib in this case) with the intended build system.

E.g., get a Ubuntu 24 VM, sudo apt install gcc-avr avr-libc make, clone the UsbGpip repo, and git clone https://github.com/abcminiuser/lufa inside that repo to get LUFA at the expected place. Then build the e.g. bootloader verbosely with cd SW/MassStorage/ && make V=1. All compile commands should appear, e.g.,

max@max-VirtualBox:~/temp/UsbGpib/SW/MassStorage$ make V=1
 [INFO]    : Begin compilation of project "BootloaderMassStorage"...

avr-gcc (GCC) 7.3.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 [GCC]     : Compiling C file "BootloaderMassStorage.c"
avr-gcc -c -pipe -gdwarf-2 -g2 -mmcu=atmega32u4 -fshort-enums -fno-inline-small-functions -Wall -fno-strict-aliasing -funsigned-char -funsigned-bitfields -ffunction-sections -I. -DARCH=ARCH_AVR8 -DDMBS_ARCH_AVR8 -mrelax -fno-jump-tables -flto -fuse-linker-plugin -x c -Os -std=gnu99 -Wstrict-prototypes -DF_CPU=16000000UL -DUSE_LUFA_CONFIG_HEADER -IConfig/ -DBOOT_START_ADDR=0x7000 -DAUX_BOOT_SECTION_SIZE='((8 - 4) * 1024)' -I. -I../../LUFA/.. -DARCH=ARCH_AVR8 -DBOARD=BOARD_ -DF_USB=16000000UL   -MMD -MP -MF obj/BootloaderMassStorage.d BootloaderMassStorage.c -o obj/BootloaderMassStorage.o
 [GCC]     : Compiling C file "Descriptors.c"
avr-gcc -c -pipe -gdwarf-2 -g2 -mmcu=atmega32u4 -fshort-enums -fno-inline-small-functions -Wall -fno-strict-aliasing -funsigned-char -funsigned-bitfields -ffunction-sections -I. -DARCH=ARCH_AVR8 -DDMBS_ARCH_AVR8 -mrelax -fno-jump-tables -flto -fuse-linker-plugin -x c -Os -std=gnu99 -Wstrict-prototypes -DF_CPU=16000000UL -DUSE_LUFA_CONFIG_HEADER -IConfig/ -DBOOT_START_ADDR=0x7000 -DAUX_BOOT_SECTION_SIZE='((8 - 4) * 1024)' -I. -I../../LUFA/.. -DARCH=ARCH_AVR8 -DBOARD=BOARD_ -DF_USB=16000000UL   -MMD -MP -MF obj/Descriptors.d Descriptors.c -o obj/Descriptors.o
[..]
avr-size --mcu=atmega32u4 --format=avr BootloaderMassStorage.elf
AVR Memory Usage
----------------
Device: atmega32u4

Program:    3010 bytes (9.2% Full)
(.text + .data + .bootloader)

Data:        395 bytes (15.4% Full)
(.data + .bss + .noinit)

You take very good note of all compiler settings used there, during the compile and linking stage. Fundamentally, you have to match them with the PlatformIO build system using build_flags, build_unflags (docs), etc. PlatformIO also shows you the verbose compiler commands with Advanced → Verbose Build (docs). This will lead you to add e.g. settings such as

build_flags = 
   -DF_USB=16000000UL
   -DBOARD=BOARD_
   -DUSE_LUFA_CONFIG_HEADER
   -IConfig/
   ; and lots more ...

When it comes to LUFA, you need to tell PlatformIO how to build the library in terms of to-be-compiled files, include directories, compiler settings etc., best using a library.json file. I’ve created a simple one, the most important parts of which are

Again, the “srcFilter” selects the to be compiled files – these can be read from the build log obtained earlier. libArchive: false needs to be set too because the library implements ISRs, which are not correctly linked when this is true.

All in all, this then gives a compilable bootloader and firmware. I’ve prepared them at

The bootloader (MassStorage) in PlatformIO compiles exactly to the same size as with the makefile buildsystem on my Ubuntu machine.

Checking size .pio\build\sparkfun_promicro16\firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:   [==        ]  15.4% (used 395 bytes from 2560 bytes)
Flash: [=         ]   9.2% (used 3010 bytes from 32768 bytes)
Building .pio\build\sparkfun_promicro16\firmware.hex
==========================[SUCCESS] Took 4.04 seconds ==========================

The firmware (TestAndMeasurement) is slightly smaller because PlatformIO gives the object files in a different order to the linker, which then places functions at different address – this enables sometimes the usage of the smaller relative-jump (rjmp) instruction, sometimes the bigger absolute jump (jmp) instruction. PlatformIO’s linking order seems more advantegous here. This should have no effect on the workings of the firmrware, though. The firmware could in fact be even smaller because the author seems to have forgotten to turn on -flto and -fdata-sections to further reduce the size and removed unused functions / data.

In any case though, I would always still use the original build system to build and upload the firmware, too. There can still be discrepancies and slight overlooks between the two build system. It’s fine if you just need PlatformIO here so that the project is nicely editable in VSCode or whatever, but I would not force a different build system onto the project or author.

1 Like

Hi
Thank you very much for the obvious time and effort you have expended to answer my question. Everything you say makes sense. I already run both Linux and Windows so no problem running gcc on Ubuntu. I will try it.

The only minor point I would differ on is the aim to minimise the size of the firmware. The primary aim of the adapter is to maximise communication speed. The code author has the compiler set to minimise firmware size. I was planning to maximise firmware size to just fit within the available memory.

My limited understanding of compilers is that increasing the size of firmware replaces calls with code (over simplification). The result being the repetition of code, but avoiding the delay of a calls. That should result in faster execution but dependent on the source code. My plan was to compile small and large, then measure the difference (if any) in performance.

Just a minor point. The command make V=1 works but the V=1 option has no effect on my Ubuntu version. It is not listed in the manual.

Usually V=1 or VERBOSE=1 turns on verbose output in most Makefiles, but if it’s not changing the output (even after a make clean) but you can still see the compile commands, that’s fine.