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.