Programming Arduino without the Arduino IDE

There aren’t really many reasons for wanting to break out of the Arduino IDE and compile your own code directly. The IDE provides you with a lot of useful features (such as easy serial monitoring and automatic generation of appropriate build sequences) that take considerable effort to achieve yourself. That said, for learning how the whole thing actually works, there’s no replacement for doing it yourself.

There is one way of breaking out of the IDE and also the bundled libraries entirely (described here and here) which uses only the AVR standard library headers and a handful of preprocessor macros for dealing with pin IO. I won’t talk about that here because it’s insanely hardware dependent and really a way of bypassing the entire Arduino platform – not just the IDE – but it’s worth knowing about.

The programming language used to write “sketches” within the Arduino IDE is a variant of the language implemented as part of the Wiring platform. Both languages are based on C/C++ and are, themselves, implemented directly in C/C++. Compiled sketches are just the result of adding the Arduino libraries to the user’s code and compiling the combination with a standard C++ compiler. This post is about how the same result can be reached without ever opening the IDE.

The most instructive first step to understanding almost any program is to find its main function. Arduino hides the function from users with its setup() and loop() abstraction, the logic behind which should become clear after looking at main().

main.cpp (hardware/arduino/cores/arduino/main.cpp):

#include <Arduino.h>

int main(void)
{
    init();

#if defined(USBCON)
    USBDevice.attach();
#endif

    setup();

    for (;;) {
        loop();
        if (serialEventRun) serialEventRun();
    }
    return 0;
}

The setup() function is called once after internal initialisation processes and before the main code. The loop() function is called in a very tight infinite for loop. The Arduino hardware is extremely basic so even the seemingly asynchronous serial handler functionality is integrated into the same loop (that’s the serialEventRun bit). The hardware is only capable of single-tasked cycling like this so one of the key roles of the setup()/loop() abstraction is to prevent the bored and the interested from removing the boilerplate code and potentially breaking their own programs.

In creating your own programs, you’d want to leave most of this code intact. The arduino.h header definitely needs to be included and you’d probably want to leave init() and the USB setup in there too. The calls to setup() and loop() should be removed, unless you’re actually planning to implement those functions, and replaced with your own code. The async serial handler line can be taken out if you’re not using that functionality.

The code that actually goes into the gaps is basically the same standard Arduino code you’d write in the IDE. In fact, it’s pretty much identical — the Arduino language is kinda cool in that it doesn’t really hide the complexity of C/C++ from its users. None of the Arduino enhancements are important differences because the library functions themselves are written in C++ so can be used from within user C++ code transparently. The only thing to watch out for is some of the types: the Arduino types boolean and byte are synonyms (using typedef) for uint8_t and word is a typedef for unsigned int. There’s a couple of preprocessor constants as well. The details are easy enough to find with a quick flick through the arduino.h file.

Once written, there’s the small matter of building the code and transferring it onto the Arduino hardware. This is something the IDE makes very simple when it isn’t really. I’ll make another post about the build process later but, for now, here’s a sample GNU makefile — the one we’re using for part of our project:

CORES=./lib/hardware/arduino/cores/arduino
STDVARIANT=./lib/hardware/arduino/variants/standard
AVRD_CONF=./avrdude.conf

MMU=atmega328p
CPUCLOCK=16000000L
ARDUINOVER=103
SERIALPORT=/dev/ttyACM0
BAUDRATE=115200

CODENAME=mycode

GC_HWOPTS=-mmcu=$(MMU) -DF_CPU=$(CPUCLOCK) -DARDUINO=$(ARDUINOVER)
GC_STDINC=-I$(CORES) -I$(STDVARIANT)
GPP_OPTS=-c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections $(GC_HWOPTS) $(GC_STDINC)
GCC_OPTS=-c -g -Os -Wall -ffunction-sections -fdata-sections $(GC_HWOPTS) $(GC_STDINC)
GCC_LINKOPTS=-Os -Wl,--gc-sections -L. -lm -mmcu=$(MMU)
OBJCPY_EEPOPTS=-O ihex -j .eeprom --set-section-flags=.eeprom=alloc,load --no-change-warnings --change-section-lma .eeprom=0
OBJCPY_HEXOPTS=-O ihex -R .eeprom
AVRD_OPTS=-C$(AVRD_CONF) -p$(MMU) -carduino -P$(SERIALPORT) -b$(BAUDRATE) -D -Uflash:w:./$(CODENAME).hex:i

build:
    avr-g++ $(GPP_OPTS) -o$(CODENAME).o $(CODENAME).cpp
    avr-gcc $(GCC_OPTS) -owiring_analog.o $(CORES)/wiring_analog.c
    avr-gcc $(GCC_OPTS) -owiring_digital.o $(CORES)/wiring_digital.c
    avr-gcc $(GCC_OPTS) -owiring_pulse.o $(CORES)/wiring_pulse.c
    avr-gcc $(GCC_OPTS) -owiring_shift.o $(CORES)/wiring_shift.c
    avr-gcc $(GCC_OPTS) -owiring.o $(CORES)/wiring.c
    avr-gcc $(GCC_OPTS) -oWInterrupts.o $(CORES)/WInterrupts.c
    avr-g++ $(GPP_OPTS) -oCDC.o $(CORES)/CDC.cpp
    avr-g++ $(GPP_OPTS) -oHardwareSerial.o $(CORES)/HardwareSerial.cpp
    avr-g++ $(GPP_OPTS) -oHID.o $(CORES)/HID.cpp
    avr-g++ $(GPP_OPTS) -oIPAddress.o $(CORES)/IPAddress.cpp
    avr-g++ $(GPP_OPTS) -omain.o $(CORES)/main.cpp
    avr-g++ $(GPP_OPTS) -onew.o $(CORES)/new.cpp
    avr-g++ $(GPP_OPTS) -oPrint.o $(CORES)/Print.cpp
    avr-g++ $(GPP_OPTS) -oStream.o $(CORES)/Stream.cpp
    avr-g++ $(GPP_OPTS) -oTone.o $(CORES)/Tone.cpp
    avr-g++ $(GPP_OPTS) -oUSBCore.o $(CORES)/USBCore.cpp
    avr-g++ $(GPP_OPTS) -oWString.o $(CORES)/WString.cpp
    avr-g++ $(GPP_OPTS) -oWMath.o $(CORES)/WMath.cpp
    avr-ar rcs ./core.a ./wiring_analog.o
    avr-ar rcs ./core.a ./wiring_digital.o
    avr-ar rcs ./core.a ./wiring_pulse.o
    avr-ar rcs ./core.a ./wiring_shift.o
    avr-ar rcs ./core.a ./wiring.o
    avr-ar rcs ./core.a ./WInterrupts.o
    avr-ar rcs ./core.a ./CDC.o
    avr-ar rcs ./core.a ./HardwareSerial.o
    avr-ar rcs ./core.a ./HID.o
    avr-ar rcs ./core.a ./IPAddress.o
    avr-ar rcs ./core.a ./main.o
    avr-ar rcs ./core.a ./new.o
    avr-ar rcs ./core.a ./Print.o
    avr-ar rcs ./core.a ./Stream.o
    avr-ar rcs ./core.a ./Tone.o
    avr-ar rcs ./core.a ./USBCore.o
    avr-ar rcs ./core.a ./WString.o
    avr-ar rcs ./core.a ./WMath.o
    avr-gcc $(GCC_LINKOPTS) -o$(CODENAME).elf ./core.a $(CODENAME).o
    avr-objcopy $(OBJCPY_EEPOPTS) $(CODENAME).elf $(CODENAME).eep
    avr-objcopy $(OBJCPY_HEXOPTS) $(CODENAME).elf $(CODENAME).hex
    avrdude $(AVRD_OPTS)

clean:
    -rm -f *.o *.hex *.eep core.a
Advertisements

One thought on “Programming Arduino without the Arduino IDE

  1. Great post! Do have a simple example of what you added/removed to main()? I’m uncertain why you want to replace this function. Do you not need its functionality or are you adding functionality to it? Why can’t you implement the setup() and loop() functions instead?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s