Wednesday, December 14, 2011

Bypassing the IDE

A colleague has objected, justifiably, that the Arduino can't be used for teaching C/C++ as it stands because of the way the environment works (hiding the main function, and confusing issues of scope, global variables, etc).  Judging by a conversation I had this afternoon there is probably no money in the equipment budget to buy fifty of the things for next year's programming course anyway, but it would be nice to try.  There are various tutorials on the web dealing with using the avr-gcc compiler directly, mainly reliant on using modified makefiles.  The Arduino 1.0 release seems to have broken these (some of the files are now in different directories, some have disappeared), so I tried looking at the output if you ask the Arduino environment to give detailed output on compilation and upload (in File / Preferences).


My "sketch" is called led_array_test. Here's the output.

avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /tmp/build7868979522428053699.tmp/led_array_test.cpp -o/tmp/build7868979522428053699.tmp/led_array_test.cpp.o 
avr-gcc -c -g -Os -Wall -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/wiring_pulse.c -o/tmp/build7868979522428053699.tmp/wiring_pulse.c.o 
avr-gcc -c -g -Os -Wall -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/wiring_analog.c -o/tmp/build7868979522428053699.tmp/wiring_analog.c.o 
avr-gcc -c -g -Os -Wall -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/wiring.c -o/tmp/build7868979522428053699.tmp/wiring.c.o 
avr-gcc -c -g -Os -Wall -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/wiring_shift.c -o/tmp/build7868979522428053699.tmp/wiring_shift.c.o 
avr-gcc -c -g -Os -Wall -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/wiring_digital.c -o/tmp/build7868979522428053699.tmp/wiring_digital.c.o 
avr-gcc -c -g -Os -Wall -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/WInterrupts.c -o/tmp/build7868979522428053699.tmp/WInterrupts.c.o 
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/USBCore.cpp -o/tmp/build7868979522428053699.tmp/USBCore.cpp.o 
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/main.cpp -o/tmp/build7868979522428053699.tmp/main.cpp.o 
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/Tone.cpp -o/tmp/build7868979522428053699.tmp/Tone.cpp.o 
/home/nja/arduino-1.0/hardware/arduino/cores/arduino/Tone.cpp:108: warning: only initialised variables can be placed into program memory area
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/HardwareSerial.cpp -o/tmp/build7868979522428053699.tmp/HardwareSerial.cpp.o 
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/new.cpp -o/tmp/build7868979522428053699.tmp/new.cpp.o 
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/Stream.cpp -o/tmp/build7868979522428053699.tmp/Stream.cpp.o 
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/CDC.cpp -o/tmp/build7868979522428053699.tmp/CDC.cpp.o 
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/IPAddress.cpp -o/tmp/build7868979522428053699.tmp/IPAddress.cpp.o 
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/HID.cpp -o/tmp/build7868979522428053699.tmp/HID.cpp.o 
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/WString.cpp -o/tmp/build7868979522428053699.tmp/WString.cpp.o 
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/Print.cpp -o/tmp/build7868979522428053699.tmp/Print.cpp.o 
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/WMath.cpp -o/tmp/build7868979522428053699.tmp/WMath.cpp.o 
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/wiring_pulse.c.o 
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/wiring_analog.c.o 
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/wiring.c.o 
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/wiring_shift.c.o 
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/wiring_digital.c.o 
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/WInterrupts.c.o 
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/USBCore.cpp.o 
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/main.cpp.o 
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/Tone.cpp.o 
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/HardwareSerial.cpp.o 
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/new.cpp.o 
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/Stream.cpp.o 
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/CDC.cpp.o 
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/IPAddress.cpp.o 
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/HID.cpp.o 
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/WString.cpp.o 
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/Print.cpp.o 
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/WMath.cpp.o 
avr-gcc -Os -Wl,--gc-sections -mmcu=atmega328p -o /tmp/build7868979522428053699.tmp/led_array_test.cpp.elf /tmp/build7868979522428053699.tmp/led_array_test.cpp.o /tmp/build7868979522428053699.tmp/core.a -L/tmp/build7868979522428053699.tmp -lm 
avr-objcopy -O ihex -j .eeprom --set-section-flags=.eeprom=alloc,load --no-change-warnings --change-section-lma .eeprom=0 /tmp/build7868979522428053699.tmp/led_array_test.cpp.elf /tmp/build7868979522428053699.tmp/led_array_test.cpp.eep 
avr-objcopy -O ihex -R .eeprom /tmp/build7868979522428053699.tmp/led_array_test.cpp.elf /tmp/build7868979522428053699.tmp/led_array_test.cpp.hex 
Binary sketch size: 2840 bytes (of a 32256 byte maximum)
/home/nja/arduino-1.0/hardware/tools/avrdude -C/home/nja/arduino-1.0/hardware/tools/avrdude.conf -v -v -v -v -patmega328p -carduino -P/dev/ttyACM2 -b115200 -D -Uflash:w:/tmp/build7868979522428053699.tmp/led_array_test.cpp.hex:i 

What it is doing:
  1. Compiling led_array_test.cpp (which has previously been generated from the sketch's .ino file, by including Arduino.h) to led_array_test.cpp.o.
  2. Compiling all the files in /home/nja/arduino-1.0/hardware/arduino/cores/arduino to object files (all the other lines starting avr-g++)
  3. Archiving all those files (but not led_array_test.cpp.o) into a library, core.a
  4. Linking led_array_test.cpp.o with that library.
  5. Generating ELF, EEP and HEX files (but it seems to do nothing with the EEP file?)
  6. Calling avrdude to upload the HEX file to the Arduino.
I have put together a Python script to do the same things.  It doesn't use a temporary directory, so you end up with a load of object files and other stuff scattered about, but that would be relatively simple to fix.  It also assumes that nothing fails during the building and upload.

#!/usr/bin/python

import os
import re

arduino_root = "/home/nja/arduino-1.0/hardware/arduino"
tools_root = "/home/nja/arduino-1.0/hardware/tools"
clock_speed = "16000000L"
device = "atmega328p"
usb_port = "/dev/ttyACM2"
compile_command = "avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu={1} -DF_CPU={2} -DARDUINO=100 -I{0}/cores/arduino -I{0}/variants/standard {3} -o {4}"
library_command = "avr-ar rcs core.a {0}"

library_sources = (
    "wiring_pulse.c",
    "wiring_analog.c",
    "wiring.c",
    "wiring_shift.c",
    "wiring_digital.c",
    "WInterrupts.c",
    "USBCore.cpp",
    "Tone.cpp",
    "HardwareSerial.cpp",
    "new.cpp",
    "Stream.cpp",
    "CDC.cpp",
    "IPAddress.cpp",
    "HID.cpp",
    "WString.cpp",
    "Print.cpp",
    "WMath.cpp")

object_files = []

for src in library_sources:
    obj = re.sub("\.cp?p?", ".o", src)
    object_files.append(obj)
    src = "{0}/cores/arduino/{1}".format(arduino_root, src)
    os.system(compile_command.format(arduino_root, device, clock_speed, src, obj))

os.system(compile_command.format(arduino_root, device, clock_speed, "leds.cpp", "leds.o"))

for obj in object_files:
    os.system(library_command.format(obj))

os.system("avr-gcc -Os -Wl,--gc-sections -mmcu={0} -o leds.elf leds.o core.a -L. -lm".format(device)) 
os.system("avr-objcopy -O ihex -j .eeprom --set-section-flags=.eeprom=alloc,load --no-change-warnings --change-section-lma .eeprom=0 leds.elf leds.eep")
os.system("avr-objcopy -O ihex -R .eeprom leds.elf leds.hex")
os.system("avrdude -p{1} -carduino -P{2} -b115200 -D -Uflash:w:leds.hex:i".format(tools_root, device, usb_port))

I'm making no claims for that as an efficient, beautiful or well-written piece of code, but it works to compile a file called leds.cpp, link it with the library, and upload it to the Arduino.  I sometimes need to reset the Arduino to get the program to run (there is Python code out there to do this, which I will have to find again and include).  Note that I am not including main.cpp in the Arduino library - my leds.cpp includes the main function and looks more C++-like than the Arduino sketch.  This is because it is C++.  It is a combination of the Arduino main.cpp (which is normally included in the static library and linked with the sketch by the Arduino environment), and the setup() and loop() functions from my original sketch (the two pinMode function calls were in setup(), and everything inside the while loop apart from the final line was in loop()).

#include <Arduino.h>

#define LED1 7
#define LED2 6
#define DELAY_TIME 500

int main (void)
{
    init();
#if defined(USBCON)
    USB.attach();
#endif

    pinMode(LED1, OUTPUT);
    pinMode(LED2, OUTPUT);

    while(1)
    {
        digitalWrite(LED1, HIGH);
        delay(DELAY_TIME);
        digitalWrite(LED2, HIGH);
        delay(DELAY_TIME);
        digitalWrite(LED1, LOW);
        delay(DELAY_TIME);
        digitalWrite(LED2, LOW);
        delay(DELAY_TIME);
  
        if (serialEventRun) serialEventRun();
    }
    return 0;
}

I think this is a promising first step towards getting a system together which allows the Arduino to be programmed in pure C/C++.

No comments:

Post a Comment