Arduino kombineret med Processing
Som beskrevet tidligere, så er der på Arduinoboardet et USB stik. Dette bruges når man vil uploade et nyt program til sit Arduino board, eller hvis man benytter Serial Monitor funktionen i Arduino IDE (se bl.a. Test af Programmer). Med andre ord - der kan sendes og modtages data via USB stikket!
Man kan udnytte muligheden for datakommunikation via USB stikket på Arduinoboardet, ved at have et program kørende på sin computer, der kan udveksle data med Arduinoboardet; Det kan være Arduinoen der sender måledata fra en temperatursensor, via USB stikket, til PC’en, hvor det gemmes og vises. Det kan også være, at man fra PC’en skal kunne lave en opsætning i den måde ens program på Arduinoen kører på.
Til at lave sit eget program der kan køres på en PC (Windows/Linux/Mac), kan anvendes open source programmet Processing[1]. Det er opbygget lidt i stil med Arduino IDE programmet, og programmeringssproget bygger på Java i en let tilgængelig indpakning.
Kodeeksempler
Til dette kapitel er der nogle kodeeksempler man kan anvende. De er samlet i en ZIP-fil.
I gang med Processing
For at komme i gang med at programmere i Processing, skal programmet hentes. Dette gøres fra http://processing.org, hvor man kan hente en udgave til enten Windows/Mac eller Linux.
Når programmet startes, ses programmeringsvinduet som vist på figur 1.
Figur 1 Programmeringsvinduet i Processing.
Et standard program består - ligesom et Arduinoprogram - af to dele;
En void setup()
del og en void draw()
del. Setup-delen køres kun når programmet startes, mens draw-delen loop’er hele tiden.
Hello World i Processing
For lige at få startet op på et Processing program, kan man lave følgende “Hello World” programkode.
void setup()
{
size(800, 600); // window size
background(255, 204, 0); // R G B value
}
void draw()
{
fill(255); // white text
textSize(32); // text size
textAlign(CENTER); // center align text
text("Hello World", width/2, height/2);
}
Under setup()
defineres vinduets størrelse, samt baggrundsfarven for vinduet. Under draw()
sættes tekstfarven til hvid, tekststørrelsen til 32 punkt, og centreret tekst i forhold til tekstplaceringen. Teksten “Hello World” placeres i centeret af vinduet ved at bruge width/2
og height/2
, der begge har værdien af henholdsvis den bredde og højde der er defineret under size()
.
For yderligere information omkring hvordan man kommer i gang med at programmere i Processing, henvises til relevant information på internettet.
Simpel dataudveksling
Man kan have et simpelt system, hvor et Arduinoboard måler temperaturen med en temperatursensor, og oversætter måleværdien til en tekst ( f.eks. teksten “23 °C”), og sender den til computeren, hvor et Processing program “råt” udskriver teksten. Her forventer computerprogrammet kun at der kommer en tekststreng med den tekst den skal vise.
I det følgende gives et eksempel, hvor netop en sådan simpel dataudveksling finder sted, mellem et Arduinoboard og en computer. På computeren kører et Processing program, der modtager og viser data.
Programkoden til Arduinoboardet konverterer et heltal (integer) om til en tekst (String), og sender denne via den serielle port, over USB stikket, til computeren, hvor den bliver modtaget af Processing programmet. Hver datatransmission afsluttes med et “linjeskift”-tegn.
Nedenfor ses programkoden til Arduinoboardet.
/* Simple
*
* Demonstrates transmission of data without a protocol.
*
* Data is text based, and read as chars.
*
*
* HNL, March 2018
*/
char command;
char data[6];
int datavalue;
int digits=0;
void setup()
{
Serial.begin(9600); // Communication speed
datavalue = 345; // Use datavalue 345
}
void loop()
{
// update command and datavalue variables
itoa(datavalue,data,10); // convert int to char(s)
digits = numberOfDigits(datavalue); // get number of digits in datavalue
// Transmit data via the serial port
Serial.println(data);
delay(2000); // wait 2 seconds
}
int numberOfDigits(int input)
{
int returnvalue;
if ((input > -10) && (input < 10)) // 1 digit
{
returnvalue = 1;
}
else if (((input > -100) && (input < -9)) || ((input > 9) && (input < 100))) // 2 digits
{
returnvalue = 2;
}
else if (((input > -1000) && (input < -99)) || ((input > 99) && (input < 1000))) // 3 digits
{
returnvalue = 3;
}
else if (((input > -10000) && (input < -999)) || ((input > 999) && (input < 10000))) // 4 digits
{
returnvalue = 4;
}
else if (((input > -32769) && (input < -9999)) || ((input > 9999) && (input < 32768))) // 5 digits
{
returnvalue = 5;
}
if (input<0)
{
returnvalue++; // if negative add 1 digit for sign
}
return returnvalue;
}
Nedenfor ses Processing programkoden, der tager udgangspunkt i eksemplet fra Learning Processing[2].
// Learning Processing
// Daniel Shiffman
// http://www.learningprocessing.com
// http://learningprocessing.com/examples/chp19/example-19-08-serial-input-one-number
// Example 19-8: Reading from serial port
// Mods by HNL 30/9 2014
import processing.serial.*;
int val = 0; // To store received data converted to integer
color tColor;
String data = "";
Serial port; // The serial port object
void setup() {
size(200,200);
// In case you want to see the list of available ports
// println(Serial.list());
// Using the first available port (might be different on your computer)
port = new Serial(this, Serial.list()[0], 9600);
data="---";
}
void draw()
{
background(255);
tColor = color(0, 128, 255);
fill(tColor);
textSize(32); // text size
textAlign(CENTER); // center align text
if (data != null) // write received data to window
{
text(data, width/2, height/2);
}
}
// Called whenever there is something available to read
void serialEvent(Serial port) {
// Data from the Serial port is read in serialEvent() using the readStringUntil() function and assigned to the global variable: val
data = port.readStringUntil('\n');
if (data != null)
{
val = int(data);
// For debugging
println( "Raw Input:" + data + " converted: " + val);
}
}
Protokolbaseret dataudveksling
Hvis man skal have et avanceret system, hvor man kan sende forskellige typer måledata og kommandoer frem og tilbage, får man brug for at kunne skelne de forskellige værdier fra hinanden; så ved man hvornår det er en temperatur-værdi man får, og hvornår det er en information hvad lufttrykket er. Løsningen på dette er at lave en protokol.
En protokol er et lille simpelt kommunikationssprog, som man selv opfinder. Her kan man bestemme, hvordan man vil identificere en temperaturværdi, henholdsvis en lufttryksværdi, eller hvad det nu er ens system skal kunne sende information om
Man sender data i pakker, hvor man kan have et antal dataværdier indsat. Datapakken kan for eksempel se ud som vist på figur 2, hvor man sender et start-tegn, en kommando, data og et slut-tegn.
Figur 2 Eksempel på datapakke.
Protokollen kan så se ud som vist i tabellen nedenfor.
Kommando | Kommando symbol |
Temperatur | T |
Lufttryk | L |
Lysniveau | S |
Man kan for eksempel vælge “£” som start-tegn, og “#” som slut-tegn, og så vil en datapakke med en temperaturmåling på 20 °C se ud som vist på figur 3.
Figur 3 Eksempel på datapakke hvor temperaturværdien 20 sendes.
Det er dermed tekststrengen “£T20#” der sendes fra Arduinoboardet over til computerprogrammet. Det er i alt 5 tegn, og dermed 5 bytes der sendes.
I programmet på computeren, læses data fra £-tegnet kommer, og indtil #-tegnet modtages. Herefter “fiskes” kommando-tegnet ud, og data bliver herefter behandlet ud fra hvad kommando-tegnet betyder i protokollen.
Programkoden til Arduinoboardet ses her, hvor programmet som udgangspunkt er sat op til at sende kommandoen T, samt dataværdien 345. Både kommando og dataværdi kan let ændres i programmet, således målinger fra forskellige sensorer let kan sendes i stedet for:
/* SimpleProtocol
*
* Demonstrates data package transmission with a simple protocol
*
* Packet layout:
* ________________________
* | £ | Command | Data | # |
* ------------------------
*
* Data is text based, and read as chars
*
*
* HNL, March 2018
*/
const char startFrame = '£';
const char endFrame = '#';
char command;
char data[6];
int datavalue;
int digits=0;
void setup()
{
Serial.begin(9600); // Communication speed
command = 'T'; // Use command T
datavalue = 345; // Use datavalue 345
}
void loop()
{
// update command and datavalue variables
itoa(datavalue,data,10); // convert int to char(s)
digits = numberOfDigits(datavalue); // get number of digits in datavalue
// Transmit data package via the serial port
Serial.print(startFrame); // Start byte
Serial.print(command); // Command byte
for (int i=0; i<digits; i++)
{
Serial.print(data[i]); // each data byte
}
Serial.print(endFrame); // End byte
delay(2000); // wait 2 seconds
}
int numberOfDigits(int input)
{
int returnvalue;
if ((input > -10) && (input < 10)) // 1 digit
{
returnvalue = 1;
}
else if (((input > -100) && (input < -9)) || ((input > 9) && (input < 100))) // 2 digits
{
returnvalue = 2;
}
else if (((input > -1000) && (input < -99)) || ((input > 99) && (input < 1000))) // 3 digits
{
returnvalue = 3;
}
else if (((input > -10000) && (input < -999)) || ((input > 999) && (input < 10000))) // 4 digits
{
returnvalue = 4;
}
else if (((input > -32769) && (input < -9999)) || ((input > 9999) && (input < 32768))) // 5 digits
{
returnvalue = 5;
}
if (input<0)
{
returnvalue++; // if negative add 1 digit for sign
}
return returnvalue;
}
I programeksemplet sendes kommandoen T sammen med dataværdien 345. Man kan selv ændre/tilføje i koden, og lade sine egne sensormålinger blive sendt.
På computeren anvendes følgende Processing program:
/*
Protocol based communication example
Communicates with Arduino via COM port.
Protocol:
--------------------
| @ | Cmd | Data | £ |
--------------------
Cmd = command byte
Data = data bytes [0-11]
HNL April 2016, March 2018
*/
import processing.serial.*;
final int STATE_NOT_CONNECTED = 0;
final int STATE_PORT_CONNECTED = 1;
final int STATE_CONNECTED = 2;
final color BACKGROUND = color(25, 156, 132);
final color TEXTCOLOR =color(0);
final int TEXTSIZE_MAIN = 40;
final int TEXTSIZE_SMALL = 20;
final int SPACE_BETWEEN_LINES = 30;
final int START_VALUE_X_LIST = 50;
final int START_VALUE_Y_LIST = 200;
final int LINE_HEIGHT_LIST = TEXTSIZE_SMALL;
final int MAX_PX_IN_LINE_LIST = 300;
final int BAUDRATE = 9600;
String data = "";
String receivedData = "";
String command ="";
String value = "";
final char startchar = '£';
final char endchar = '#';
int val;
int state;
String[] foundPorts = new String[10];
Boolean portSelected;
int portNumberSelected;
Serial myPort; // The serial port object
PFont font;
void setup()
{
size(800,600);
background(255);
state = STATE_NOT_CONNECTED;
portSelected = false;
portNumberSelected=0;
// List all the available serial ports:
printArray(myPort.list());
foundPorts = myPort.list();
print("Found Ports: ");
println(foundPorts.length);
}
void draw()
{
background(BACKGROUND);
fill(TEXTCOLOR);
textAlign(CENTER);
textSize(TEXTSIZE_MAIN); // text size
text("Arduino Communication Example", width/2, 50);
if (state == STATE_NOT_CONNECTED)
{
textSize(TEXTSIZE_SMALL);
textAlign(LEFT);
text("USB connection to Arduino board is not etablished.", 20, 120);
text("Please insert USB cable to PC, and select the device on the list below.", 20, 145);
if (foundPorts.length > 0)
{
// Write out all found ports
for (int i=0; i< myPort.list().length ; i++)
{
textAlign(LEFT);
text(foundPorts[i], START_VALUE_X_LIST, START_VALUE_Y_LIST+(i*SPACE_BETWEEN_LINES));
}
}
}
else if (state == STATE_PORT_CONNECTED)
{
myPort = new Serial(this, foundPorts[portNumberSelected], BAUDRATE);
myPort.bufferUntil(endchar);
state = STATE_CONNECTED;
}
else if (state == STATE_CONNECTED)
{
textAlign(RIGHT);
textSize(TEXTSIZE_SMALL);
text(foundPorts[portNumberSelected] + " selected", width-20, 120);
if (command.equals("T")==true)
{
textAlign(LEFT);
text("Temperature: ", 50, 400);
text(value, 200, 400);
text("C ", 235, 400);
}
else if (command.equals("L")==true)
{
textAlign(LEFT);
text("Humidity: ", 50, 400);
text(value, 200, 400);
text("% ", 235, 400);
}
else if (command.equals("S")==true)
{
textAlign(LEFT);
text("Light: ", 50, 400);
text(value, 200, 400);
text("Lux ", 235, 400);
}
}
}
// Called whenever there is something available to read
void serialEvent(Serial port)
{
println("Serial event");
// Data from the Serial port is read in serialEvent() using the read() function and assigned to the global variable: val
data = port.readStringUntil(endchar);
println("Port opened");
if (data.charAt(0) == startchar)
{
data = data.substring(1, data.length());
receivedData = data.substring(0,data.length() - 1);
command = receivedData.substring(0,1);
value = receivedData.substring(1,receivedData.length());
if (checkForNumberInString(value) == false)
{
value = "--";
}
}
}
void mouseReleased()
{
if ( (state == STATE_NOT_CONNECTED) && (foundPorts.length > 0) )
{
for (int j = 0; j < foundPorts.length ; j++)
{
if ( (mouseX > START_VALUE_X_LIST) && (mouseX < START_VALUE_X_LIST + MAX_PX_IN_LINE_LIST) &&
(mouseY > START_VALUE_Y_LIST - LINE_HEIGHT_LIST + (j*SPACE_BETWEEN_LINES)) && (mouseY < START_VALUE_Y_LIST + (j*SPACE_BETWEEN_LINES)) )
{
portSelected = true;
portNumberSelected = j;
state = STATE_PORT_CONNECTED;
}
}
}
}
Boolean checkForNumberInString(String input)
{
int i;
int charvalue;
Boolean numberfound = true;
for (i=0; i < input.length(); i++)
{
charvalue = int(input.charAt(i));
if (((charvalue >= 48) && (charvalue <= 57)) || (charvalue == 45)) // if char is a number or sign (ASCII)
{
}
else
{
numberfound = false;
}
}
return numberfound;
}
Programmet er lavet, så den selv finder og lister alle COM porte på computeren op, hvorefter man klikker på den COM port som Arduinoboardet har fået tildelt. Herefter detekteres hvilken kommando der modtages, og værdien udskrives.
Bemærk, at værdien konverteres fra tekst til heltal, og kan dermed bruges videre i forhold til matematiske operationer på talværdien.