Recently, I was working on an Arduino project and needed to set up my development environment. Instead of using the Arduino IDE, I was more in favor of using my current most used editor Zed with clangd as compiler and PlatformIO for the integration with the hardware.
I want to reuse existing code and self-written libraries so that I am able to set up new projects quickly. Although my code was compiling and working on the Arduino, I was getting errors/warnings like the following in Zed and I wanted to have a clean project and make the most of my setup when writing new code.
Use of undeclared identifier 'LED_BUILTIN'
Use of undeclared identifier 'Serial'
'Led.h' file not found
The catch is: PlatformIO and clangd need to use the same compilation environment. Here’s how I resolved it:
1. Generate PlatformIO’s compilation database
PlatformIO can generate compile_commands.json:
pio run -t compiledb
This produced a command for src/main.cpp similar to:
avr-g++ ... \
-DARDUINO_AVR_UNO \
-DF_CPU=16000000L \
-DARDUINO_ARCH_AVR \
-DARDUINO=10808 \
-Iinclude \
-Isrc \
-Ilib/Led \
-I.../framework-arduino-avr/cores/arduino \
-I.../framework-arduino-avr/variants/standard \
src/main.cpp
This is preferable to manually maintaining Arduino include paths in .clangd.
2. Include Arduino explicitly
The source should explicitly include the Arduino framework:
#include <Arduino.h>
3. Don’t mix Arduino IDE paths with PlatformIO paths
Initially, .clangd contained paths such as:
~/Library/Arduino15/packages/arduino/...
Those belong to the Arduino IDE installation. For a PlatformIO project, the relevant framework is instead under:
~/.platformio/packages/framework-arduino-avr/
PlatformIO’s compilation database should be the source of truth rather than manually copying paths from the Arduino IDE installation.
4. The important diagnostic
Running:
clangd --check=src/main.cpp
revealed the real problem.
Clangd was using Apple’s clang:
/Library/Developer/CommandLineTools/usr/bin/clangd
and was interpreting the PlatformIO compilation command as a macOS ARM compilation
target 'arm64-apple-darwin'
while the source was actually intended for:
ATmega328P / AVR
The crucial diagnostic was:
unsupported option '-mmcu=' for target 'arm64-apple-darwin'
and subsequently:
'avr/pgmspace.h' file not found
This explained why Arduino symbols such as Serial were not being resolved.
5. Tell clangd to use PlatformIO’s AVR compiler
The final .clangd configuration was:
# yaml-language-server: $schema=https://json.schemastore.org/clangd.json
CompileFlags:
CompilationDatabase: .
Compiler: /Users/<YOUR_USER>/.platformio/packages/toolchain-atmelavr/bin/avr-g++
Diagnostics:
MissingIncludes: None
UnusedIncludes: Strict
The critical line is:
Compiler: /Users/<YOUR_USER>/.platformio/packages/toolchain-atmelavr/bin/avr-g++
This tells clangd to use the same AVR compiler toolchain that PlatformIO uses.
As a result, clangd understands:
- the AVR target
-mmcu=atmega328p- the AVR system headers
- Arduino AVR headers
- board-specific definitions such as
ARDUINO_AVR_UNO
The native AVR compiler test confirmed that the actual PlatformIO toolchain was working:
/Users/<YOUR_USERNAME>/.platformio/packages/toolchain-atmelavr/bin/avr-g++ \
... \
-I.../framework-arduino-avr/cores/arduino \
-I.../framework-arduino-avr/variants/standard \
-E src/main.cpp
The preprocessor successfully found text HardwareSerial.h.
Therefore the Arduino installation itself was not broken. The problem was clangd’s target/toolchain configuration.
7. Project libraries
After fixing the AVR toolchain, clangd also correctly handled the PlatformIO project include paths from compile_commands.json, including -Ilib/Led, so #include <Led.h> worked as well.
This illustrates why it is useful to let PlatformIO generate the compilation database rather than manually adding every project and framework include directory to .clangd.
Michael Hülsen