This articles was published on 2013-03-16
Today my Arduino Due from Taobao arrived:
This is the first Arduino with an ARM Microcontroller instead of an AVR. Considering this and the fact that it has 512Kb of flash and 96Kb of SRAM I gave it a try and compiled mruby for it…
It turned out to be straight forward but due to the reason that I prefer a command-line compilation over the Arduino IDE I took a small U-Turn and extracted the compilation from the IDE. The following small Ruby script is doing the compilation for the ARM Cortex binary of the Arduino Due:
#!/usr/bin/env ruby require 'serialport' # Activate/deactivate erasing and flashing of Board FLASH = true USB_PORT = "ttyACM0" # Building folder BUILD_DIR = "/home/daniel/Documents/mruby_arduino_due/build" # Arduino Application Folder ARDUINO_DIR = "/opt/arduino-1.5.2" # Standard Paths for build process SAM_DIR = "#{ARDUINO_DIR}/hardware/arduino/sam" BIN_DIR = "#{ARDUINO_DIR}/hardware/tools/g++_arm_none_eabi/bin" TARGET_DIR = "#{SAM_DIR}/variants/arduino_due_x" ARDUINO_SRC = "#{SAM_DIR}/cores/arduino" # C Flags CFLAGS_1 = "-c -g -Os -w -ffunction-sections -fdata-sections -nostdlib --param max-inline-insns-single=500" CFLAGS_2 = "-fno-rtti -fno-exceptions" # Used for C++ files CFLAGS_3 = "-Dprintf=iprintf -mcpu=cortex-m3 -DF_CPU=84000000L -DARDUINO=152 -D__SAM3X8E__ -mthumb -DUSB_PID=0x003e -DUSB_VID=0x2341 -DUSBCON" INCLUDES = "-I#{SAM_DIR}/system/libsam -I#{SAM_DIR}/system/CMSIS/CMSIS/Include/ -I#{SAM_DIR}/system/CMSIS/Device/ATMEL/ -I#{SAM_DIR}/cores/arduino -I#{TARGET_DIR}" def execute cmd result = `#{cmd}` puts result unless result == '' end execute "rm -Rf #{BUILD_DIR}" execute "mkdir #{BUILD_DIR}" execute "cp -Rf mruby.cpp #{BUILD_DIR}/mruby.cpp" execute "cp -Rf libmruby.a #{BUILD_DIR}/libmruby.a" USER_FILES = %w(mruby.cpp) C_FILES = %w(WInterrupts.c syscalls_sam3.c cortex_handlers.c wiring.c wiring_digital.c itoa.c wiring_shift.c wiring_analog.c hooks.c iar_calls_sam3.c) CPP_FILES = %w(main.cpp WString.cpp RingBuffer.cpp UARTClass.cpp cxxabi-compat.cpp USARTClass.cpp USB/CDC.cpp USB/HID.cpp USB/USBCore.cpp Reset.cpp Stream.cpp Print.cpp WMath.cpp IPAddress.cpp wiring_pulse.cpp) def add_to_lib file execute "#{BIN_DIR}/arm-none-eabi-ar rcs #{BUILD_DIR}/core.a #{BUILD_DIR}/#{file}" end puts "CC (User Files)" # Userspecific Code USER_FILES.each do |src_file| obj_file = "#{src_file}.o" execute "#{BIN_DIR}/arm-none-eabi-g++ #{CFLAGS_1} #{CFLAGS_2} #{CFLAGS_3} #{INCLUDES} #{BUILD_DIR}/#{src_file} -o #{BUILD_DIR}/#{obj_file}" add_to_lib obj_file end puts "CC (C Files)" # Arduino Standard C Code C_FILES.each do |src_file| obj_file = "#{src_file}.o" execute "#{BIN_DIR}/arm-none-eabi-gcc #{CFLAGS_1} #{CFLAGS_3} #{INCLUDES} #{ARDUINO_SRC}/#{src_file} -o #{BUILD_DIR}/#{obj_file}" add_to_lib obj_file end puts "CC (CPP Files)" # Arduino Standard C++ Code CPP_FILES.each do |src_file| obj_file = "#{src_file}.o" obj_file.sub! 'USB/', '' execute "#{BIN_DIR}/arm-none-eabi-g++ #{CFLAGS_1} #{CFLAGS_2} #{CFLAGS_3} #{INCLUDES} #{ARDUINO_SRC}/#{src_file} -o #{BUILD_DIR}/#{obj_file}" add_to_lib obj_file end puts "CC (variant)" execute "#{BIN_DIR}/arm-none-eabi-g++ #{CFLAGS_1} #{CFLAGS_2} #{CFLAGS_3} #{INCLUDES} #{TARGET_DIR}/variant.cpp -o #{BUILD_DIR}/variant.cpp.o" add_to_lib 'variant.cpp.o' puts "LD" # Link User specific things and Arduino Specific things together execute "#{BIN_DIR}/arm-none-eabi-g++ -Os -Wl,--gc-sections -mcpu=cortex-m3 -T#{TARGET_DIR}/linker_scripts/gcc/flash.ld -Wl,-Map,#{BUILD_DIR}/mruby.cpp.map -o #{BUILD_DIR}/mruby.cpp.elf -L#{BUILD_DIR} -lm -lgcc -mthumb -Wl,--cref -Wl,--check-sections -Wl,--gc-sections -Wl,--entry=Reset_Handler -Wl,--unresolved-symbols=report-all -Wl,--warn-common -Wl,--warn-section-align -Wl,--warn-unresolved-symbols -Wl,--start-group #{BUILD_DIR}/libmruby.a #{BUILD_DIR}/syscalls_sam3.c.o #{BUILD_DIR}/mruby.cpp.o #{TARGET_DIR}/libsam_sam3x8e_gcc_rel.a #{BUILD_DIR}/core.a -Wl,--end-group" # Build Binary for target puts "PACK" execute "#{BIN_DIR}/arm-none-eabi-objcopy -O binary #{BUILD_DIR}/mruby.cpp.elf #{BUILD_DIR}/mruby.cpp.bin" if FLASH SerialPort.open("/dev/#{USB_PORT}", 1200) {|sp| puts "Reset Board" } # Upload to Board puts "Upload to Flash" execute "#{ARDUINO_DIR}/hardware/tools/bossac --port=#{USB_PORT} -U false -e -w -v -b #{BUILD_DIR}/mruby.cpp.bin -R" end SerialPort.open("/dev/#{USB_PORT}", 9600) do |sp| loop do print sp.read sleep 1 end end
This script expects that:
The mruby.cpp file could look like this:
#include "stdbool.h" #include "Arduino.h" #include "../mruby_src/include/mruby.h" #include "../mruby_src/include/mruby/irep.h" #include "../mruby_src/include/mruby/string.h" #include "../mruby_src/include/mruby/value.h" int led = 13; mrb_value myputs (mrb_state * mrb, mrb_value self) { mrb_value val; mrb_get_args (mrb, "S", & val); Serial.println (RSTRING_PTR (val)); return mrb_nil_value (); } //extern const char bytecode []; const char bytecode[] = { 0x52,0x49,0x54,0x45,0x30,0x30,0x30,0x39,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x39, 0x30,0x30,0x30,0x30,0x4d,0x41,0x54,0x5a,0x20,0x20,0x20,0x20,0x30,0x30,0x30,0x39, 0x30,0x30,0x30,0x30,0x00,0x00,0x00,0xa8,0x00,0x01,0x00,0x00,0x20,0x20,0x20,0x20, 0x20,0x20,0x20,0x20,0xc3,0xd6,0x00,0x00,0x00,0x6a,0x53,0x43,0x00,0x02,0x00,0x06, 0x00,0x02,0x4f,0x6a,0x00,0x00,0x00,0x0a,0x01,0x00,0x00,0x11,0x01,0x00,0x40,0x20, 0x00,0x80,0x80,0x01,0x01,0x00,0x40,0x01,0x01,0x80,0x00,0x3d,0x02,0x40,0x00,0x03, 0x02,0x00,0xc0,0xad,0x01,0x81,0x00,0x3e,0x01,0x00,0x80,0xa0,0x00,0x00,0x00,0x4a, 0x23,0x91,0x00,0x00,0x00,0x02,0x11,0x00,0x08,0x31,0x20,0x2b,0x20,0x31,0x20,0x3d, 0x20,0x11,0x00,0x00,0x92,0xc3,0x00,0x00,0x00,0x04,0x00,0x06,0x4f,0x62,0x6a,0x65, 0x63,0x74,0x00,0x03,0x6e,0x65,0x77,0x00,0x06,0x6d,0x79,0x70,0x75,0x74,0x73,0x00, 0x01,0x2b,0x22,0xc0,0x00,0x00,0x00,0x00, }; // the setup routine runs once when you press reset: void setup() { delay(10000); // initialize the digital pin as an output. pinMode(led, OUTPUT); Serial.begin(9600); Serial.println("Setup"); mrb_state *mrb = mrb_open(); mrb_define_method (mrb, mrb-> object_class, "myputs", myputs, ARGS_REQ (1)); mrb_load_irep (mrb, bytecode); if (mrb-> exc) { Serial.println ("exeption occured!"); } mrb_close(mrb); } // the loop routine runs over and over again forever: void loop() { Serial.println("Hello World"); digitalWrite(led, HIGH); // turn the LED on (HIGH is the voltage level) delay(1000); // wait for a second digitalWrite(led, LOW); // turn the LED off by making the voltage LOW delay(1000); // wait for a second }
I build this file based on a standard Arduino Sketch. It will boot up the unit, wait for 10 seconds, then create a mruby instance and execute the byte code (calculating 1 + 1
). Afterwards it will close the instance and start to turn the LED on pin 13 on and off every second.
The bytecode can be generated by:
mrbc -Bbytecode test.rb
This will create the file test.c with the bytecode array. You can also include this file directly in the build process, so that you don’t have to copy/paste it into the source code all the time. The mruby code for the bytecode example above was:
o = Object.new o.myputs "1 + 1 = #{1 + 1}"
Before you can execute the build script you have to cross compile the libmruby.a:
MRuby::CrossBuild.new("Arduino Due") do |conf| toolchain :gcc # GNU Linux ARDUINO_PATH = '/opt/arduino-1.5.2' BIN_PATH = "#{ARDUINO_PATH}/hardware/tools/g++_arm_none_eabi/bin" SAM_PATH = "#{ARDUINO_PATH}/hardware/arduino/sam" TARGET_PATH = "#{SAM_PATH}/variants/arduino_due_x" conf.cc do |cc| cc.command = "#{BIN_PATH}/arm-none-eabi-gcc" cc.include_paths = ["#{SAM_PATH}/system/libsam -I#{SAM_PATH}/system/CMSIS/CMSIS/Include/", "#{SAM_PATH}/system/CMSIS/Device/ATMEL/", "#{SAM_PATH}/cores/arduino -I#{TARGET_PATH}", "#{MRUBY_ROOT}/include"] cc.flags << '-g -Os -w -ffunction-sections -fdata-sections -nostdlib --param max-inline-insns-single=500 ' + '-Dprintf=iprintf -mcpu=cortex-m3 -DF_CPU=84000000L -DARDUINO=152 -D__SAM3X8E__ -mthumb -DUSB_PID=0x003e -DUSB_VID=0x2341 -DUSBCON' cc.compile_options = "%{flags} -o %{outfile} -c %{infile}" end conf.archiver do |archiver| archiver.command = "#{BIN_PATH}/arm-none-eabi-ar" archiver.archive_options = 'rcs %{outfile} %{objs}' end # No binaries necessary conf.bins = [] end
The output of the build will look like this:
daniel@ubuntu:~/Documents/mruby_arduino_due$ ./make.rb CC (User Files) CC (C Files) CC (CPP Files) CC (variant) LD PACK Reset Board Upload to Flash Erase flash Write 162048 bytes to flash [==============================] 100% (633/633 pages) Verify 162048 bytes of flash [==============================] 100% (633/633 pages) Verify successful Set boot flash true CPU reset. Setup 1 + 1 = 2 Hello World Hello World Hello World Hello World ...
If you are interested in the chipKit Max32 have a look at the great blog of kyab. He has done some impressive work about building the mruby-arduino GEM and shrinking mruby even more.