Developing Arduino Sketches with Neovim
The code in the previous posts for the Atmega board were authored in the Arduino IDE. Being an avid neovim/Vim user, I quickly felt slowed down trying to move around and make edits. I decided to look into what it would take to develop with neovim.
Doing a search online for “vim arduino”, one of the first results is vim-arduino. From the README, this plugin allows for compiling and uploading of sketches. I also want language server support, syntax highlighting and go to definition. My neovim setup makes use of nvim-lspconfig. Doing a quick search through its help docs shows support for the Arduino language server.
The vim-arduino plugin can use the Arduino IDE or the Arduino CLI. However, the language server requires the Arduino CLI, as well as clangd.
Arduino CLI
The first step was to get the Arduino CLI installed and working. The Arduino CLI provides installation instructions for the supported platforms. I’m on a Mac with homebrew installed, so I did:
brew install arduino-cli
I then utilized the instructions on the getting started page from the Arduino CLI. I didn’t quite follow them in order or to the letter.
Creating a Config File
The getting started docs say that the Arduino CLI doesn’t need a configuration file and that it’s a convenience. However, the Arduino language server does require a config file. Be sure and save the path to the config file that the command returns.
arduino-cli config init
The above command gave the following output:
Config file written to: /Users/nick/Library/Arduino15/arduino-cli.yaml
Installing Board Dependencies
Prior to compiling and uploading, the board needs to be connected and its dependencies installed.
First the index should be updated. I didn’t find this step necessary, but did it just in case.
arduino-cli core update-index
After connecting the board to my computer I listed the board:
arduino-cli board list
This provided the following output:
Port Protocol Type Board Name FQBN Core
/dev/cu.Bluetooth-Incoming-Port serial Serial Port Unknown
/dev/cu.debug-console serial Serial Port Unknown
/dev/cu.usbmodemHIDGD1 serial Serial Port (USB) Arduino Leonardo arduino:avr:leonardo arduino:avr
The last line happened to be the one of interest. The main columns to save are the Port, FQBN, and Core. FQBN stands for “Fully Qualified Board Name”.
Then I installed the core for the board based on the Core column of the above
board list.
arduino-cli core install arduino:avr
Compile and Upload a Sketch
To ensure the CLI can correctly communicate with the board a simple sketch was created. This sketch was then be compiled and uploaded to the Atmega board.
In a directory that was okay to create subdirectories in, I created a new sketch and used a slightly modified version of the example sketch from the getting started docs.
arduino-cli sketch new test
cd test
echo 'void setup() {
pinMode(LED_BUILTIN_RX, OUTPUT);
}
void loop() {
digitalWrite(LED_BUILTIN_RX, HIGH);
delay(1000);
digitalWrite(LED_BUILTIN_RX, LOW);
delay(1000);
}' > test.ino
A thing to note about Arduino sketches and the CLI, the directory name of the
sketch must match the name of the primary .ino file. This is captured somewhat
in the
sketch build process
documentation.
All .ino […] files in the sketch folder (shown in the Arduino IDE as tabs with no extension) are concatenated together, starting with the file that matches the folder name followed by the others in alphabetical order.
Compiling and uploading are two separate commands. I can’t stress this enough. I kept only compiling, wondering why I wasn’t seeing changes. The Port and FQBN are required from the board listing above for these commands.
arduino-cli compile -p /dev/cu.usbmodemHIDGD1 --fqbn arduino:avr:leonardo
arduino-cli upload -p /dev/cu.usbmodemHIDGD1 --fqbn arduino:avr:leonardo
Watch out for the port changing on first upload. I got the following output
after the first upload. This required me to change the port for subsequent
uploads to -p /dev/cu.usbmodem2101.
New upload port: /dev/cu.usbmodem2101 (serial)
The port isn’t actually needed for the compile command, but I put it there so I can quickly toggle and update the command for compile versus upload.
The board should have a blinking led now.
Arduino Language Server
In order to use the Arduino language server it and its dependencies need to be installed. The Arduino CLI was already installed above. The language server is a go module that can be installed:
go install github.com/arduino/arduino-language-server@latest
I happened to have go set up where the installed modules are in my path.
clangd is required. I had LLVM installed already via brew which provided
clangd. If LLVM/clangd is not installed yet then it can be done with brew.
brew install llvm
Enabling in neovim
Getting the Arduino language server to work with neovim took a bit of work.
Initially I only enabled it in my init.lua file:
vim.lsp.enable('arduino_language_server')
However when I tried to open the above test.ino file up in neovim I got an error message:
Client arduino_language_server quit with exit code 1 and and signal 0. Check log for errors: /Users/nick/.local/state/nvim/lsp.log
Instead of navigating to the ~/.local dir I opened the log up in neovim with
the :LspLog command. At the bottom of the log there was:
[ERROR][2025-12-28 21:02:25] …p/_transport.lua:36 “rpc” “arduino-language-server” “stderr” “21:02:25.970684 Path to ArduinoCLI config file must be set.\n”
I was a little surprised. There clearly was a default location for the CLI config file, yet the language server required it to be explicitly called out. There is an example invocation in the Arduino language server README which says:
To start the language server the IDE may provide the path to Arduino CLI and clangd
./arduino-language-server \
-clangd /usr/local/bin/clangd \
-cli /usr/local/bin/arduino-cli \
-cli-config $HOME/.arduino15/arduino-cli.yaml \
-fqbn arduino:mbed:nanorp2040connect
The may part indicates the paths are optional, but left the CLI config file in a bit of an unknown state.
I created after/lsp/arduino_language_server.lua in my neovim directory with the
following contents:
return {
cmd = {
"arduino-language-server",
"-cli-config",
vim.fn.expand("~/Library/Arduino15/arduino-cli.yaml"),
"-fqbn",
"arduino:avr:leonardo"
}
}
This provided the board FQBN and CLI config file from earlier, but left the CLI
and clangd to be found in the system path. Restarting neovim and opening the
test.ino file gave me syntax highlighting and I was able to go to the
definition of OUTPUT and other defines.
I would like to find a way to populate the FQBN based on the sketch directory or similar, but figured hardcoding is acceptable for now since I’ll mostly be working with the Atmega board, which happens to work as a leonardo board.
vim-arduino
My neovim setup uses lazy.nvim.
I added lua/plugins/arduino.lua to my neovim files with the following content.
return { "stevearc/vim-arduino", name = "arduino" }
This installed the vim-arduino plugin. I could now see the help docs,
:help ArduinoVerify, but I was not able to run the commands. I would get:
E492: Not an editor command: ArduinoVerify
Doing some digging it appears to be an issue with lazy.nvim and a pure
vimscript plugin. I found,
https://github.com/stevearc/vim-arduino/issues/59
which mentions explicitly listing all the commands to get them to load.
return {
"stevearc/vim-arduino",
name = "arduino",
cmd = {
"ArduinoAttach",
"ArduinoChooseBoard",
"ArduinoChooseProgrammer",
"ArduinoChoosePort",
"ArduinoVerify",
"ArduinoUpload",
"ArduinoSerial",
"ArduinoUploadAndSerial",
"ArduinoInfo",
},
}
After this I was able to execute the commands.
Sketch Project File
The Arduino CLI documentation mentions a sketch project file. This file allows project specific settings and build profiles.
My main use case is to minimize command line arguments and avoiding choosing a
board and port when using vim-arduino. In the test sketch directory I created
a sketch.yml file.
echo 'default_fqbn: arduino:avr:leonardo
default_port: /dev/cu.usbmodem2101' > sketch.yml
After this I was able to reduce my command line invocation to:
arduino-cli compile
arduino-cli upload
I was also able to use the the :ArduinoVerify and :ArduinoUpload commands
from vim-arduino without needing to configure the board or the port.