Interfacing an Arduino Uno (uploading etc.) with the Arduino IDE (using the Fedora package) works fine under Fedora 21.

But I rather want to use vim + make + vim-quickfix-mode etc.

How can I do that?

Preferably via the tools available from the Fedora repositories.

I assume that the IDE calls externals command line utilities for the uploading etc.

The equivalent to the IDE's serial monitor is probably connecting a terminal emulator (e.g. screen) to /dev/ttyACM0, right?

Perhaps there is a good example project one can look at the makefile?

  • Bumping as all of these answers are quite old As of Jun 05th 2021, the arduino-cli tool is extremely good. You can make a very simple 2 line Makefile in no time.
    – Tim
    Commented Jun 6, 2021 at 2:11

6 Answers 6


I use the command-line interface to the arduino command.

I run it like this:

arduino --upload sketch/sketch.ino --port /dev/ttyUSB*

There is a page which describes other command-line tools, like inotool. That page also has an example Makefile. These alternatives seem enticing, but apparently, as of this writing, none of them work. I'm assuming this is due to some recent changes in Arduino IDE distribution files which they depend on.

Running arduino as above is a bit slow, because it has to load Java I guess, but at least it works. There is also an arduino-builder command that comes with the Arduino IDE distribution. As of this writing, it was not sufficiently well-documented for me to be able to figure out how to use it. For instance there are no example command lines in the README or in any of the tutorials I came across, and I couldn't figure out how to use it to upload code to the board. However, presumably it is able to give us a faster compile than arduino. The README also mentions being able to reuse object files from a previous compile, so there is some make-like functionality.

To view serial output I use something like

stty -F /dev/ttyUSB* 1000000 raw -clocal -echo
cat /dev/ttyUSB*

The number 1000000 should match the number you pass to Serial.begin() in your board code. You can also use screen if you have a board program which is appropriately interactive, or you can use whatever utility to write directly to the device. By the way, my Due shows up as /dev/ttyACM0 (rather than /dev/ttyUSB0 for the Uno).

  • 1
    Make sure you have a single *.ino file in the directory, as it does not seem to necessarily upload the one mentioned by the --upload parameter. Commented Apr 19, 2019 at 12:39
  • No X11 DISPLAY variable was set, but this program performed an operation which requires it. For me
    – Jackie
    Commented Apr 11, 2020 at 2:51

I would suggest Googling for Makefile projects. I did one a while back for the Blink program, by basically seeing what got generated by the IDE and replicating that in a more general way.

# Simple Arduino Makefile
# Author: Nick Gammon
# Date: 18th March 2015

# where you installed the Arduino app
ARDUINO_DIR = C:/Documents and Settings/Nick/Desktop/arduino-1.0.6/

# various programs
CC = "$(ARDUINO_DIR)hardware/tools/avr/bin/avr-gcc"
CPP = "$(ARDUINO_DIR)hardware/tools/avr/bin/avr-g++"
AR = "$(ARDUINO_DIR)hardware/tools/avr/bin/avr-ar"
OBJ_COPY = "$(ARDUINO_DIR)hardware/tools/avr/bin/avr-objcopy"

MAIN_SKETCH = Blink.cpp

# compile flags for g++ and gcc

# may need to change these
F_CPU = 16000000
MCU = atmega328p

# compile flags
GENERAL_FLAGS = -c -g -Os -Wall -ffunction-sections -fdata-sections -mmcu=$(MCU) -DF_CPU=$(F_CPU)L -MMD -DUSB_VID=null -DUSB_PID=null -DARDUINO=106
CPP_FLAGS = $(GENERAL_FLAGS) -fno-exceptions

# location of include files
INCLUDE_FILES = "-I$(ARDUINO_DIR)hardware/arduino/cores/arduino" "-I$(ARDUINO_DIR)hardware/arduino/variants/standard"

# library sources
LIBRARY_DIR = "$(ARDUINO_DIR)hardware/arduino/cores/arduino/"


    $(CC) $(CC_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)avr-libc/malloc.c -o malloc.c.o
    $(CC) $(CC_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)avr-libc/realloc.c -o realloc.c.o
    $(CC) $(CC_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)WInterrupts.c -o WInterrupts.c.o
    $(CC) $(CC_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)wiring.c -o wiring.c.o
    $(CC) $(CC_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)wiring_analog.c -o wiring_analog.c.o
    $(CC) $(CC_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)wiring_digital.c -o wiring_digital.c.o
    $(CC) $(CC_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)wiring_pulse.c -o wiring_pulse.c.o
    $(CC) $(CC_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)wiring_shift.c -o wiring_shift.c.o
    $(CPP) $(CPP_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)HardwareSerial.cpp -o HardwareSerial.cpp.o
    $(CPP) $(CPP_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)IPAddress.cpp -o IPAddress.cpp.o
    $(CPP) $(CPP_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)main.cpp -o main.cpp.o
    $(CPP) $(CPP_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)new.cpp -o new.cpp.o
    $(CPP) $(CPP_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)Print.cpp -o Print.cpp.o
    $(CPP) $(CPP_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)Stream.cpp -o Stream.cpp.o
    $(CPP) $(CPP_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)Tone.cpp -o Tone.cpp.o
    $(CPP) $(CPP_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)USBCore.cpp -o USBCore.cpp.o
    $(CPP) $(CPP_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)WMath.cpp -o WMath.cpp.o
    $(CPP) $(CPP_FLAGS) $(INCLUDE_FILES) $(LIBRARY_DIR)WString.cpp -o WString.cpp.o
    rm core.a
    $(AR) rcs core.a malloc.c.o
    $(AR) rcs core.a realloc.c.o
    $(AR) rcs core.a WInterrupts.c.o
    $(AR) rcs core.a wiring.c.o
    $(AR) rcs core.a wiring_analog.c.o
    $(AR) rcs core.a wiring_digital.c.o
    $(AR) rcs core.a wiring_pulse.c.o
    $(AR) rcs core.a wiring_shift.c.o
    $(AR) rcs core.a CDC.cpp.o
    $(AR) rcs core.a HardwareSerial.cpp.o
    $(AR) rcs core.a HID.cpp.o
    $(AR) rcs core.a IPAddress.cpp.o
    $(AR) rcs core.a main.cpp.o
    $(AR) rcs core.a new.cpp.o
    $(AR) rcs core.a Print.cpp.o
    $(AR) rcs core.a Stream.cpp.o
    $(AR) rcs core.a Tone.cpp.o
    $(AR) rcs core.a USBCore.cpp.o
    $(AR) rcs core.a WMath.cpp.o
    $(AR) rcs core.a WString.cpp.o
    $(CC) -Os -Wl,--gc-sections -mmcu=$(MCU) -o $(MAIN_SKETCH).elf $(MAIN_SKETCH).o core.a -lm
    $(OBJ_COPY) -O ihex -j .eeprom --set-section-flags=.eeprom=alloc,load --no-change-warnings --change-section-lma .eeprom=0 $(MAIN_SKETCH).elf $(MAIN_SKETCH).eep
    $(OBJ_COPY) -O ihex -R .eeprom $(MAIN_SKETCH).elf $(MAIN_SKETCH).hex

You would need to change ARDUINO_DIR at least, to reflect where you installed the IDE. If you use other things like the Wire library you would need to expand it somewhat to compile additional libraries. Again, you can use what the IDE generates itself to guide your changes.

The lines with the leading spaces above would need the tab character instead of spaces, as is normal for a Makefile.

  • 2
    An upload section in the make file would also be useful, to upload the compiled sketch to the arduino (using avr-dude).
    – Gerben
    Commented Sep 8, 2015 at 9:04
  • That would definitely be useful. However as, so far, the IDE has met my needs for compiling and uploading, I am not strongly motivated to work out how to do it. :)
    – Nick Gammon
    Commented Sep 8, 2015 at 9:46
  • Take a look at Metamorphic answer for an easier solution Commented Sep 30, 2017 at 21:48
  • Someone suggested editing out the statement "I would suggest Googling for Makefile projects." on the grounds that people probably reached this page from Google anyway. However I think it is still a helpful suggestion, especially in direct response to the posted question. My answer gave one suggested way of achieving the result, a Google search may supply others.
    – Nick Gammon
    Commented Apr 1, 2021 at 5:05

What you need is a Makefile. There are a few Makefile projects around for Arduino. Googling for "Arduino Makefile" returns many results including what looks like a good one on Github: https://github.com/sudar/Arduino-Makefile

Compiling from the command line isn't trivial due to the way the Arduino IDE handles libraries.

The equivalent to the IDE's serial monitor is probably connecting a terminal emulator (e.g. screen) to /dev/ttyACM0, right?

For the serial monitor I would recommend minicom. It is a fully functional terminal emulator (vt102) on the command line.

minicom -D /dev/ttyACM0 -b 115200

... for example.


Official CLI tool

The arduino team is developing a cli client https://github.com/arduino/arduino-cli

Announcement: https://blog.arduino.cc/2018/08/24/announcing-the-arduino-command-line-interface-cli/

You can do almost everything with this, from downloading boards and libraries, to compile and upload scripts. What's missing is the monitoring part (you can use Metamorphic's method using stty cat, It works!)

The commands are very similar to Metamorphic's answer since this tool is branching out of that one

Instructions in the Github repo and the man page:

    $ arduino-cli Arduino Command Line Interface (arduino-cli).

    Usage:   arduino-cli [command]

    Examples: arduino <command> [flags...]

    Available Commands:
      board         Arduino board commands.
      compile       Compiles Arduino sketches.
      config        Arduino Configuration Commands.
      core          Arduino Core operations.
      help          Help about any command
      lib           Arduino commands about libraries.
      sketch        Arduino CLI Sketch Commands.
      upload        Upload Arduino sketches.
      version       Shows version number of Arduino CLI.

If you want a fully compatible solution for your arduino project (yes, you can share your project with other people that use just plain Arduino IDE) you need to check amake a tool to simplify the cli of the arduino, I use it with Geany but others are using it with vi, Atom, etc.

It's inspired and the now dead Ino and Arturo projects; please take 5 minutes to test it and please give feedback.

Example use:

cd ~/Arduino/Blink/
[move to your arduino project folder]

amake -v uno Blink.ino
[to compile/verify your code]

amake -u uno Blink.ino /dev/ttyUSB0
[to upload your code to an arduino connected via USB]

It has some smart glue in there, it can remember the board and file, and even autodetect the usb of the board; so after a successfull "amake -v" command you can do this on the command line and it will work.

amake -v
[to compile/verify your code]

amake -u
[to upload your code to an arduino connected via USB]

If you use some IDE macros you can craft the compile and upload commands easily, for example using Geany IDE it will became:

  • Compile/Verify: cd %d; amake -v uno %f
  • Upload: cd %d; amake -u uno %f

You can get more help running just "amake" or "amake -h" once installed.

Also, it can support EVERY board/lib/programmer you have installed/configured in your Arduino IDE, yes, modern board like the Adafuit Trinket M0 / Arduino M0 etc...

Just fire your Arduino IDE, go to the board manager, install support and that's all, jut follow some simple instructions and your are set.

The board you have is not supported? not a problem, detect the fqbn (read the README.md file) and pass it along as the board name.

I'm looking for testers to grow up the number or board aliases and auto detection of the proper USB signatures.

Remember this is a private grown tool, now shared with the public, you know, just a programmer scratching it's itch...



A great way to compile for and flash an Arduino device from the command line and integrate with Vim is to use PlatformIO Core (CLI).

Since it's written in Python it's easy to install even when it's not packaged for your Linux distribution. PlatformIO supports many microcontroller devices, not just Arduino ones. Thus, you don't have to switch your development environment when you target another platform. PlatformIO takes care of downloading the right toolchain (compiler etc.) for your target.


Getting started with PlatformIO and an Arduino Pro Mini 3.3v clone on Fedora 35:

mkdir -p ~/local
cd ~/local
python -m venv platformio
source platformio/bin/activate
pip install platformio

This installs PlatformIO (CLI) in a virtual environment, i.e. its main command is then available from ~/local/platformio/bin/pio and pio is found in your PATH when the virtual environment is activated.

Next, we need to find the right board ID for setting up a new project:

pio boards arduino | less

That means search for Pro.*Mini in the listing. There are several versions of the Pro Mini, in our example the pro8MHzatmega328 (3.3 V, 8 MHz, ATmega328, 30 kb flash, 2 kB RAM) is the right one as can be validated by looking at the board markings as well as its voltage regulator, and CPU markings.

To set up a new project

mkdir ~/project/some_new_project
cd ~/project/some_new_project
pio project init --board pro8MHzatmega328 --ide vim

Which prints:

The current working directory /home/juser/project/arduino/pro-mini will be used for the project.

The next files/directories have been created in /home/juser/arduino/pro-mini
include - Put project header files here
lib - Put here project specific (private) libraries
src - Put project source files here
platformio.ini - Project Configuration File
Platform Manager: Installing atmelavr
Downloading  [####################################]  100%
Unpacking  [####################################]  100%
Platform Manager: atmelavr @ 3.4.0 has been installed!
Tool Manager: Installing platformio/toolchain-atmelavr @ ~1.70300.0
Downloading  [####################################]  100%          
Unpacking  [####################################]  100%          
Tool Manager: toolchain-atmelavr @ 1.70300.191015 has been installed!
The platform 'atmelavr' has been successfully installed!
The rest of the packages will be installed later depending on your build environment.

Project has been successfully initialized including configuration files for `vim` IDE.

For a first test one can copy a simple blink-led example using the usual setup()/loop() functions. It's possible to re-use an existing 'sketch' from the Arduino IDE, one just has to add #include <Arduino.h> at the top and place it under src/some-name.cc or src/some-name.cpp.

To compile everything:

pio run

Which prints:

Processing pro8MHzatmega328 (platform: atmelavr; board: pro8MHzatmega328; framework: arduino)
Verbose mode can be enabled via `-v, --verbose` option
CONFIGURATION: https://docs.platformio.org/page/boards/atmelavr/pro8MHzatmega328.html
PLATFORM: Atmel AVR (3.4.0) > Arduino Pro or Pro Mini ATmega328 (3.3V, 8 MHz)
DEBUG: Current (avr-stub) On-board (avr-stub, simavr)
 - framework-arduino-avr 5.1.0 
 - toolchain-atmelavr 1.70300.191015 (7.3.0)
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
LDF Modes: Finder ~ chain, Compatibility ~ soft
Found 5 compatible libraries
Scanning dependencies...
No dependencies
Building in release mode
Compiling .pio/build/pro8MHzatmega328/src/sos-switch.cc.o
Linking .pio/build/pro8MHzatmega328/firmware.elf
Checking size .pio/build/pro8MHzatmega328/firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:   [          ]   0.4% (used 9 bytes from 2048 bytes)
Flash: [          ]   3.8% (used 1170 bytes from 30720 bytes)
========================= [SUCCESS] Took 0.51 seconds =========================

So for this example I created src/sos-switch.cc which is compiled to .pio/build/.../sos-switch.cc.o.

To upload (flash) the device:

pio run --target upload

As always, a common pitfall are the permissions of the USB device. For example, on Fedora, my USB2TTL serial device registers as /dev/ttyUSB0 with read-write permissions for just root:dialout. Thus, you have to add your user to the dialout group or adjust the permissions by other means.

Vim Quickfix Mode

For integrating with Vim's quickfix mode a simple makefile is sufficient, e.g.:

.PHONY: all
all: build

.PHONY: build
    pio run

.PHONY: upload
    pio run --target upload

Thus, everything is built when :make is invoked in Vim, and there one can navigate the quickfix mode as usual.

Serial Monitoring

An easy way to monitor the serial interface is to use picocom, e.g.:

picocom --baud 9600 --echo --imap lfcrlf --noreset /dev/ttyUSB0


  • --imap lfcrlf maps newline to carriage return + newline such that we can just write Serial.print("multi\nlines\n') instead of Serial.print("multi\r\nlines\r\n") in our program
  • --noreset tells picocom to not clear DTR - without it, when DTR is connected, the Anrdoid device is reset

Of course, one has to terminate picocom when uploading.

Alternatively, one can use the PlatformIO monitor command:

pio device monitor

In contrast to picocom, it doesn't require explicit mapping or DTR handling. However, as with picocom, it must be terminated when uploading/flashing. That means PlatformIO doesn't automatically suspends monitoring when pio run --target upload is invoked.

Closing Remarks

  • PlatformIO is currently packaged for Fedora and thus might be directly available in Fedora 36 or so
  • the classic Arduino IDE was packaged for Fedora, but isn't available anymore in Fedora 35 because of the big Java package exodus
  • there is also a PlatoformIO plugin for VSCode which seems to be quite popular
  • 1
    Nice answer. Re “--imap lfcrlf”: This is not needed if you use Serial.println(), which is the “Arduino way” of printing a new line. Commented Jan 1, 2022 at 21:05
  • @EdgarBonet yes, Serial.println() writes \r\n, thus we don't need the mapping if it's used exclusively. However, both the classic Arduino IDE and the PlatformIO monitor implicitly map \n to \r\n such that you get away with single \n. If you transmit \r\n you might end up with \r\r\n, which doesn't hurt. Even if your program uses Serial.println() you still might use \n in multi-line strings. Updated my answer to illustrate this point. However, for an end-user facing console, it makes sense to stick to \r\n and detect oversights by not having such a mapping. Commented Jan 2, 2022 at 11:38

Not the answer you're looking for? Browse other questions tagged or ask your own question.