Sunday, October 26, 2014

Let's do it by Java

Today we wanted to compile native C source code for atmega2560 and run it on arduino. But we didn't want to use Arduino IDE. We wanted to make the compilation process complete automatic by using Java. To make the long story short:

First we had to find out how to compile source code for Arduino Mega2560 from command line. Second use Java to run commands.

Our sample program will be simple blinking application. The LED located at Arduino Mega2560 board (pin PB7 represented as pin 13 on Arduino) will blink at 1Hz frequency.

 #define F_CPU 16000000UL  
 #include <avr/io.h>  
 #include <util/delay.h>  
 #define SET_BIT(Port, Bit) Port |= (1 << Bit)  
 #define CLEAR_BIT(Port, Bit) Port &= ~(1 << Bit)  
 int main() {  
      SET_BIT(DDRB, 7);  
      while(1) {  
           SET_BIT(PORTB, 7);  
           _delay_ms(500);  
           CLEAR_BIT(PORTB, 7);  
           _delay_ms(500);  
      }  
      return 0;       
 }  

Save this source code into the file called source.c. Now compile it by invoking this command:

 avr-gcc -mmcu=atmega2560 -Os -Wall -osource.elf source.c  

This will compile the source.c file and store the result of compilation in file called source.elf. The next step is to extract portions of the binary (source.elf) and save the information into .hex files. The GNU utility that does this is called avr-objcopy.

 avr-objcopy -j.text -j.data -Oihex source.elf source.hex  

And now just upload it by using avrdude to Arduino:

 avrdude -CC:\tools\avr\etc\avrdude.conf -cwiring -patmega2560 -PCOM6 -b115200 -F -Uflash:w:source.hex -D  

Note: the -D option means disable chip erasing before programming. But the bootloader erases the flash by default and the erase is probably not implemented by the bootloader because the avrdude fail to upload the hex file without the -D option. It will print the following error.

 avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed  
      To disable this feature, specify the -D option.  
 avrdude: erasing chip  
 avrdude: stk500v2_command(): command failed  

If the upload process was successful the LED should start to blink.

Now we are ready to implement the compilation and upload process in Java. The preferred way of invoking commands in Java is to use ProcessBuilder. One thing to mention is, always read the process output. Otherwise the output buffer may become full and the process will stop until the buffer becomes empty.

 package com.mechatronichacks.arduino;  
 import java.io.File;  
 import java.io.IOException;  
 import java.util.Scanner;  
 public class ArduinoUpload {  
      public ArduinoUpload() {  
           try {  
                int result = this.execute(new String[]{"C:\\tools\\avr\\bin\\avr-gcc", "-mmcu=atmega2560", "-Os", "-Wall", "-osource.elf", "source.c"});  
                if(result == 0) {  
                     System.out.println("Compilation successful");  
                }  
                if(result == 0)     {  
                     result = this.execute(new String[]{"C:\\tools\\avr\\bin\\avr-objcopy", "-j.text", "-j.data", "-Oihex", "source.elf", "source.hex"});  
                }  
                if(result == 0) {  
                     System.out.println("Linking successful");  
                }  
                if(result == 0) {  
                     result = this.execute(new String[]{"C:\\tools\\avr\\bin\\avrdude", "-CC:\\tools\\avr\\etc\\avrdude.conf", "-cwiring", "-patmega2560", "-PCOM6", "-b115200", "-F", "-Uflash:w:source.hex", "-D"});       
                }  
                if(result == 0) {  
                     System.out.println("Upload successful");  
                }  
           } catch (IOException e) {  
                e.printStackTrace();  
           } catch (InterruptedException e) {  
                e.printStackTrace();  
           }  
      }  
      private int execute(String[] command) throws IOException, InterruptedException {  
           ProcessBuilder builder = new ProcessBuilder(command);  
           //now set the working directory for process execution  
           builder.directory( new File("C:\\MechatronicHacks\\").getAbsoluteFile() );   
           builder.redirectErrorStream(true);  
            Process process = builder.start();  
           Scanner s = new Scanner(process.getInputStream());  
           StringBuilder text = new StringBuilder();  
           while (s.hasNextLine()) {  
                text.append(s.nextLine());  
                text.append("\n");  
           }  
           s.close();  
           System.out.println(text);  
           return process.waitFor();  
      }  
      public static void main(String[] args) {  
           new ArduinoUpload();  
      }  
 }