Arduino aansturen met Java – Een workshop

In dit artikel gaan we aan de hand van Java laten zien hoe je met het Arduino-platform kunt communiceren. De Arduino is een open source microcontroller prototype platform die is gebaseerd op flexibele, eenvoudig te gebruiken hard- en software. De Arduino is vooral gericht op eenieder die geïnteresseerd is in het ontwikkelen van interactieve objecten of omgevingen.

Op de arduino website kun je heel veel informatie vinden en is zowel de hardware als de software uitgebreid gedocumenteerd waardoor je snel aan de slag kunt en er is een levendig community forum, waar je terecht kunt voor al je vragen en ondersteuning. We geven u eerst een introductie tot het Ardiuno-platform, en daarna zullen we een Java-applicatie bouwen, waarmee we Jenkins-feeds kunnen inlezen en op basis van de build-informatie een stoplicht en een algemeen project-status – de bekende zonnetjes en wolkjes van Jenkins tegenhanger – aanstuurt. De Java-applicatie is op Swing gebaseerd  en zal door middel van een USB verbinding serieel met een Arduino communiceren. De broncode is beschikbaar op bitbucket en te vinden op https://bitbucket.org/bendh/jenkinstoarduino/ .

De functionaliteit

Het stoplicht is gemapped naar de volgende statussen:

Groen: Laatste build was succesvol

Geel: Alle andere statussen (Not build, Unstable en Aborted)

Rood: Laatste build gefaald

Naast het driekleurig stoplicht hebben we ook een RGB-led. Hiermee wordt de score van het project aangeduid:

Groen: 100%

Geel: 75%-99%

Paars: 50%

Rood: 0-25%


Om verbinding te kunnen maken met Arduino en de andere besproken onderdelen in dit artikel, heb je de volgende materialen nodig:
 
Hardware
- Een Arduino
- Een usb kabel
- Een groene, een gele en een rode led
- drie voorschakel weerstanden van 220 ohm
- Een voorschakel weerstand van 270 ohm
- Een RGB led
- Een breadboard
- veroboard (optioneel)
- Behuizing (optioneel)
 
Alle deze materialen kun je bij Conrad.nl bestellen voor ongeveer 45 euro.

Software - Je favoriete Java IDE - De Arduino IDE (te downloaden op http://arduino.cc/en/Main/Software)

De hardware – Arduino

De hardware van een Arduino is op basis van de Atmel AVR 8 bits Atmega-processor opgebouwd. Naast deze microchip is er een open source generieke printplaat ontwikkeld waardoor je erg eenvoudig hardware kan aan de printplaat kunt toevoegen. Er zijn talloze varianten van de Arduino, van een generieke prototype board tot mini boards die speciaal ontworpen zijn om bijvoorbeeld op textiel te monteren.

De software – Arduino IDE

De software bestaat uit een bootloader en een programmeertaal die gebaseerd is op Wire en Processing. De syntax van de programmeertaal lijkt op c++. Om de Arduino te programmeren is er een IDE ontwikkeld die je op de Arduino site kan downloaden. Daarnaast kun je ook eclipse CDT gebruiken met de avr plugin, op mijn blog heb ik een artikel die de eclipse setup beschrijft (http://benooms.nl/?p=14). In dit artikel gebruiken we de Arduino IDE voor het gemak.

De microchip pinnen zijn op alle Arduino printplaten op dezelfde manier bedraad en hebben eigen Arduino synoniemen die via de programmeertaal benaderbaar zijn.

Als je wilt gaan starten met Arduino, let dan op welke type je koopt. Er zijn namelijk verschillende versies te koop. Het is – wanneer je voor de eerste keer begint met elektronica – erg eenvoudig om een bedradingsfoutje te maken en de chip daardoor onbruikbaar te maken. Tenzij je al electronica ervaring heb en een specifieke board wil hebben adviseer ik de Arduino Uno en dan wel de versie met de dip28 AVR chip. Deze versie heeft het voordeel dat je de microchip kan vervangen als deze defect is of wanneer je de geprogrammeerde chip standalone wilt gebruiken. Je kunt dan het board hergebruiken en een nieuwe chip plaatsen. In de afbeelding hieronder hebben we de microprocessor in het rood gearceerd.


Afbeelding 1: ArduinoUno R3

De Arduino IDE installeren en gebruiken

De website van Arduino biedt eenvoudige en volledig gedocumenteerde informatie over de installatie. De handleiding vind je op http://arduino.cc/en/Guide/HomePage

De installatiestappen zijn als volgt:

  • Download en installeer de Arduino IDE
  • sluit je Arduino op je pc aan via een USB-kabel
  • Installeer de USB-drivers die je terugvindt in de installatiefolder van de Arduino IDE (alleen voor Windows)
  • Selecteer via het menu in Tools > Board jouw Arduino
  • Selecteer in Tools > Serial de com poort waar de Arduino op aangesloten zit (meestal is dit COM3 onder Windows)

Sketch

Een Arduino-programma wordt een sketch genoemd. De structuur van een sketch is eenvoudig en bestaat uit twee methoden, setup() en loop(). De setup() methode wordt eenmaal uitgevoerd wanneer de microprocessor start of wanneer deze gereset wordt. In dit codeblok zet je initialisatiecode zoals bijvoorbeeld de poortconfiguraties, seriële poort configuratie, enzovoort. De loop() methode is de methode waar het echte werk verricht wordt, de code in dit blok wordt eindeloos herhaald. Feitelijk is deze methode te vergelijke met een while(true) codeblok.

Na de setup is het raadzaam om een bestaande werkende sketch te laden om te testen of de installatie, de seriële connectie en de Arduino zelf naar behoren werken. In de Arduino IDE zijn onder File > Examples > sketches te vinden die demo's bevatten van verschillende features. Bijna alle Arduino's hebben op poort 13 standaard een led aangesloten. Deze gaan we laten knipperen als zijnde het equivalent van de ‘hello world’ van de microcontrollers. Open hiervoor File > Examples > 1.Basics > Blink. Nadat de sketch is geladen, kun je deze uploaden naar je Arduino door op de upload knop te klikken. Als alles goed gaat zie je na een paar seconden een bericht ‘Upload succesfull’ en zie je op je Arduino een led knipperen. Gefeliciteerd, je hebt nu de ‘hello world’ onder de microcontrollers uitgevoerd!.

De code

We lopen even door de code van de Blink sketch. Voordat we een microcontroller kunnen gebruiken, moeten we aangeven welke functies de poorten moeten vervullen. Elke poort kan zowel voor input of output gebruikt worden. Voor de Blink sketch wordt in de setup poort 13 als output geïnitialiseerd waarna doormiddel van digitalWrite en een delay de poort hoog en laag gemaakt worden. Hoog is 5 Volt en laag 0 Volt, de gegeven delay is in milliseconden.


// Pin 13 has an LED connected on most Arduino boards.
// give it a name:
int led = 13;
 
// the setup routine runs once when you press reset:
void setup() {                
 // initialize the digital pin as an output.
 pinMode(led, OUTPUT);     
}

// the loop routine runs over and over again forever: void loop() { 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 }

Jenkins status monitor

Nu we u een kleine introductie hebben gegeven van de Blink sketch, bespreken we hier de sketch en de hardware die we voor dit project gaan gebruiken. De hardware setup is eenvoudig en bestaat uit de voeding van de Arduino naar de breadboard en het verbinden van de led's via de voorschakelweerstanden naar de arduino poorten. We gebruiken hier de USB-voeding voor ons project. Dit kan op een veilige manier, omdat de hele schakeling ongeveer 100 mA gebruikt en we daardoor ver onder de 500 mA grens zitten. De voorschakelweerstanden zijn nodig omdat de led’s een spanning van maximaal 2 Volt verdragen en de arduino poorten 5 Volt leveren (zie afbeelding 2).


Afbeelding 2: Schematische weergave

De Sketch start met het initialiseren van een aantal variabelen. De laatste drie integer-waarden zijn de poorten die we gaan gebruiken voor het stoplicht en de projectstatus led. Voor de projectstatus led hebben we drie poorten nodig omdat we deze analoog gaan gebruiken om de kleuren te mixen die we willen hebben voor een bepaalde status.


String command = "";
boolean commandIsReceived = false;
// Prefix expected for all commands
String commandMask = "jta";
// Commands whe expect to receive
String REDCOMMAND = "R";
String YELLOWCOMMAND = "Y";
String GREENCOMMAND = "G";
String CHECKCOMMAND = "CHECK";
String STATUSCOMMAND = "STAT";// with the value appended like STAT25
// Needed ports for the trafficlight
int REDLIGHT = 5;
int YELLOWLIGHT = 6;
int GREENLIGHT = 7;
// Needed ports for the RGB led
int STATUSLIGHTR = 2;
int STATUSLIGHTG = 3;
int STATUSLIGHTB = 4;
// For debug purposes
String stat = " status light: ";

In de setup zetten we de seriële verbinding op met een snelheid van 9600 baud op, reserveren we fysiek geheugen voor de command String en zetten we alle poorten die we gebruiken willen als output.


void setup() {
 Serial.begin(9600);
command.reserve(200);
pinMode(REDLIGHT,OUTPUT);
pinMode(YELLOWLIGHT,OUTPUT);
pinMode(GREENLIGHT,OUTPUT);
pinMode(STATUSLIGHTR,OUTPUT);
pinMode(STATUSLIGHTG,OUTPUT);
pinMode(STATUSLIGHTB,OUTPUT);
}

Hierna zetten we de main loop op. Kort gezegd, als je een commando hebt, voer dan de methode checkCommand uit en print de output van checkCommand naar de seriële poort[LJ1] . Reset daarna command en de boolean commanIsReceived. De boolean commanIsReceived wordt door de seriele eventhandler gezet (de serialEvent() die later uitgelegd wordt)


void loop() {
 if(commandIsReceived) {
Serial.println(checkCommand());
command = "";
commandIsReceived = false;
}
}

CheckCommand controleert de ontvangen input. Als er fouten zijn worden deze geprint naar de seriële poort en een boolean false (cijfer 0 in arduino) teruggegeven. Wanneer de validatie geslaagd is, wordt het commando uitgevoerd.


boolean checkCommand(){
 String commandPart = command.substring(3);
if(command.startsWith(commandMask)){
if (commandPart == REDCOMMAND){
setLight(REDLIGHT);
return true;
} else if (commandPart == YELLOWCOMMAND){
setLight(YELLOWLIGHT);
return true;
} else if (commandPart == GREENCOMMAND){
setLight(GREENLIGHT);
return true;
} else if (commandPart == CHECKCOMMAND) {
checkStatus();
return true;
} else if (commandPart.startsWith(STATUSCOMMAND)){
String percentString = commandPart.substring(4);
char charBuffer[percentString.length()+1];
percentString.toCharArray(charBuffer,percentString.length()+1);
int value = atoi(charBuffer);
setStatusLight(value);
return true;
}
Serial.println("command not reconized: "+commandPart);
command = "";
return false;
}
Serial.println("prefix not reconized in :"+command);
command = "";
return false;
}

De commando's worden in twee stappen uitgevoerd. In de eerste stap wordt het stoplicht gezet op basis van een meegegeven poort die aan moet worden gezet.


void setLight(int light){
      digitalWrite(REDLIGHT,LOW);
digitalWrite(YELLOWLIGHT,LOW);
digitalWrite(GREENLIGHT,LOW);
switch(light){
case 5:
digitalWrite(REDLIGHT,HIGH);
break;
case 6:
digitalWrite(YELLOWLIGHT,HIGH);
break;
case 7:
digitalWrite(GREENLIGHT,HIGH);
break;
}
}

De poort die moet worden aangezet, moet worden ingesteld op ‘HIGH’ gezet en de overige stoplichtpoorten op ‘LOW’. Deze code kan 6 regels kleiner door direct de waarde 1 toe te wijzen aan de roodlicht poort, We moeten dan wel de technische ‘AVR’ poortnaam gebruiken. De mapping van de Arduino poortnamen die naar de AVR poortnamen lopen (zie afbeelding 3).


Afbeelding 3: Maopping van de Arduino poortnamen

Voor de leesbaarheid heb ik hier niet voor gekozen, maar stel dat we het scenario van het stoplicht op rood zetten, zouden herschrijven, dan zou het er als volgt uitzien. Eerst de normale code:


case 2:
     digitalWrite(REDLIGHT,HIGH);
     digitalWrite(YELLOWLIGHT,LOW);
     digitalWrite(GREENLIGHT,LOW);
     break;

Die code zou dan herschreven kunnen worden in:


case 2:
     PORTD  = b00010000;
     break;

PORTD vind je in de mapping terug onder PD[LJ2] . In de volgende methode wordt de status led ingesteld door middel van PWM-waarden voor de drie RGB-poorten. PWM staat voor pulse width modulation. Door een waarde tussen de 0 en 255 te geven wordt de betreffende led verlicht. 0 betekent uit en 255 fel brandend. Doordat de drie leds in een behuizing zitten, kun je een gemixte kleur krijgen door verschillende waarden voor de leds op te geven.


void setStatusLight(int percent){
// For debug purposes
//String stat = "statusVal:";
//stat +=percent;

if (percent > 0 && percent <= 25 ){
//Red
analogWrite(STATUSLIGHTR,255);
analogWrite(STATUSLIGHTG,0);
analogWrite(STATUSLIGHTB,0);
// For debug purposes
//stat = "status light:red";
//Serial.println(stat);
} else if (percent > 0 && percent <= 50){
// Purple
analogWrite(STATUSLIGHTR,125);
analogWrite(STATUSLIGHTG,0);
analogWrite(STATUSLIGHTB,255);
// For debug purposes
//stat ="status light:purple";
//Serial.println(stat);
}else if (percent > 0 && percent <= 75){
// Yellow
analogWrite(STATUSLIGHTR,255);
analogWrite(STATUSLIGHTG,255);
analogWrite(STATUSLIGHTB,0);
// For debug purposes
// stat ="status light:yellow";
//Serial.println(stat);
} else if(percent > 75 ) {
// Green
analogWrite(STATUSLIGHTR,0);
analogWrite(STATUSLIGHTG,255);
analogWrite(STATUSLIGHTB,0);
// For debug purposes
// stat ="status light:green";
//Serial.println(stat);
}
}

In de checkStatus()-methode worden de waarden gelezen die op basis van de commando's ingesteld zijn voor de poorten, Hierdoor kun je zonder leds je programma controleren nadat je commando's verzonden hebt door de commando CHECK te sturen naar de Arduino. Als je de CHECK commando stuurt zal de Arduino de toestand van het stoplicht naar de seriele poort wegschrijven. Alles wat we wegschrijven naar Serial.println wordt door de Arduino naar de seriele poort gestuurd en kunnen we op de pc afvangen. In eerste instantie lijkt het op het oog wat vreemd om zoveel print en println te zien, maar de reden hiervoor is dat Arduino slecht omgaat met het concateneren van Strings. Een regel zoals println("een waarde"+digitalRead(REDLIGHT)) geeft onverwachte output omdat de String "een waarde" nog niet geïnitialiseerd is voordat de int waarde van digitalRead(REDLIGHT) toegevoegd wordt.


void checkStatus(){
     Serial.println("debug status information");
String debugInfo = "Red:";
debugInfo += digitalRead(REDLIGHT);
debugInfo += " Orange:";
debugInfo += digitalRead(YELLOWLIGHT);
debugInfo += " green:";
debugInfo += digitalRead(GREENLIGHT);
debugInfo += stat;
Serial.println(debugInfo);
}

De laatste methode serialEvent() is een event handler die afgaat wanneer data ontvangen wordt via de seriele poort van de Arduino en zet de ontvangen data in de string command waarna de loop() methode de command String gebruikt om de leds aan te sturen.


/*
 SerialEvent occurs whenever a new data comes in the
hardware serial RX.  This routine is run between each
time loop() runs, so using delay inside loop can delay
response.  Multiple bytes of data may be available.
*/
void serialEvent() {
 while (Serial.available()) {
   // get the new byte:
   char inChar = (char)Serial.read();
   // add it to the inputString:
   if (inChar == '\n') {
   // if the incoming character is a newline, set a flag
   // so the main loop can do something about it:
     commandIsReceived = true;
     return;
   }
   command += inChar;
 }
}

Het Java-gedeelte

Het Java deel van het project bestaat uit een op Swing gebaseerde system tray applicatie die op basis van de geconfigureerde url en Arduino-instellingen de Jenkins-server polt, en op basis van het Jenkins-project gegevens de Arduino aanstuurt.

Voor de seriële communicatie gebruiken we de RXTX library. RXTX is een implementatie van de Sun CommAPI die het mogelijk maakt om met Java met een seriële poort te communiceren. De library is voor verschillende platformen te gebruiken, waaronder in ieder geval Windows, Linux en Mac OS X. Om de RXTX library te kunnen gebruiken moet in het classpath ook de rxtx.dll voor Windows aanwezig zijn, Zonder deze dll kan geen seriële communicatie plaatsvinden. De dll bevat de C code die de RXTX library via JNI calls aanroept. In onze applicatie vind je het gebruik[LJ3]  van RXTX in de class[LJ4]  SerialBridgeConnector[LJ5] . In deze class wordt een aantal methoden geboden die de beschikbare poorten weergeeft, de verbindingsstatus geven en data serieel kan lezen en schrijven. De beschikbare poorten gebruiken we in het Settings scherm voor de “Arduino COM port” Combobox. Deze lijst met poorten wordt vervolgens gevuld met actieve seriële poorten. In onze applicatie [LJ6] wordt alle data – die door de Arduino wordt verstuurd – in een label getoond onderin de “setting” tab[LJ7] . Met de ‘Test serial’ knop kun je dit vervolgens uittesten. Als je bijvoorbeeld jtaCHECK in het tekstvak invoert en daarna op ‘Test serial’ drukt, dan wordt het commando “jtaCHECK” naar de Arduino gestuurd. De Arduino ontvangt de data en voert de methode checkStatus() uit. De Arduino controleert de led-poorten en stuurt vervolgens de ingelezen poorten via Serial.println weer naar de pc. In onze applicatie wordt er een event afgevuurd doordat er data op de seriële poort aanwezig is, en wordt de ontvangen data getoond in een label. Dit is schematisch weergegeven in de volgende twee diagrammen (afbeelding 4 en 5).


Afbeelding 4


Afbeelding 5

 

De JenkinsHandler class is de hulpklasse die verantwoordelijk is voor de communicatie richting het Jenkins project en gegevens uit de response vertaald naar een projectstatus die we is onze applicatie kunnen gebruiken. Doormiddel van SAX en XPath expressies wordt de data geëxtraheerd en verder verwerkt om de applicatieview en Arduino aan te sturen. In de MainFrame class is een inner class genaamd StatusUpdateTask. Deze class is een SwingWorker-implementatie die in een apparte Thread je Jenkins server polt, de Swing view op basis van de Jenkins gegevens ververst en de commando’s naar de Arduino stuurt. De StatusUpdateStask wordt afgevuurd door een Swing Timer die door de MainFrame gecontroleerd wordt.

Tot slot
Ik hoop met dit simpele project een indruk gegeven te hebben hoe makkelijk seriële communicatie tussen een Java-applicatie en een Arduino te implementeren is. De verdere hardware-implementatie hangt volledig af van wat voor stoplicht je gaat gebruiken. Alle broncode voor dit project is te vinden op  https://bitbucket.org/bendh/jenkinstoarduino/. Het project is een Maven-project en bevat ook de Arduino sketch en hardware schema’s. De broncode leent zich er goed voor om als bootstrap te gebruiken voor een ander Java to Arduino project.