Porteføljeopgave 2 –  gruppe 4

– Andreas Grøntved, Martin Jakobsen, Rasmus Petersen og Rasmus Jørgensen

Tegneløb

rsz_img_1580

Case

I forbindelse med porteføljeopgave 2, har vi skulle lave en wearable som måler fysisk aktivitet og giver feedback over bluetooth til pc.

Til opgaven har vi fået stillet en række sensorer til rådighed, heriblandt en 3-i-1 sensor som hedder “LSM9DS0 Accelerometer/Gyro/Magnetometer”. Det er denne vi har valgt at bruge.

Vi har valgt at lave en wearable som tegner den rute du bevæger dig i. Muligheden for at tegne den bevægelse man laver, inspirerede os til at tænke i retningen af tegn og gæt. På den måde kan vi altså gøre det til et spil/en leg at skulle bevæge sig.

Inspiration fik vi fra det kendte brætspil Tegn og Gæt, hvor man i hold skal gætte, hvad ens holdkammerat forsøger at tegne. I hold på 2 trækker den ene et ord og skal efterfølgende tegne det ord så godt at den anden kan gætte det.
Derudover hentede vi også inspiration fra diverse sports applikationer som fx Endomondo Sports tracker. Disse applikationer kan måle flere ting, som fx hastighed, højde, afstand og tid. Derudover tegner den, hvis man har GPS, den rute man har løbet ind på et kort, så man kan se hvor man har været. Det er denne funktion vi har taget med os over i vores wearable. Herunder er et link til en artikel som beskriver hvordan man allerede er begyndt at tænke i de baner, hvor man blander fysisk aktivitet og interaktiv tegning.

“Nyt Fænomen Tegn en big mac mens du løber”

Design

Vores plan er at måle på brugerens bevægelse, specielt orientering. Orientering er hvilken vej brugeren kigger, og bestemmer hvilken vej stregen tegnes.

Det er sensoren LSM9DS0, som indeholder accelerometer, gyrometer og magnetometer, der giver os de data vi bruger for at kigge på orientering. For at man ikke skal løbe med ledninger forbundet til en computer, tilsluttes der også et bluetooth modul som sender og modtager data. Orienteringen er udregnet med magnetometeret og udregning af de retvinklede trekanter. Herunder ses hvordan vi vælger trekanterne ud fra de informationer vi får fra vores sensorer.

Med magnetometeret får vi en værdi tilbage. Denne værdi har vi kaldt u. u er hvilken retning at personen, der tegner, går/løber i. Via denne u og de kendte grader, 0, 90, 180, 270 og 360 på et kompas finder vi vinklen v. Med v og regneregler for retvinklede trekanter beregner vi os frem til de nødvendige længder af kateterne. Vi bruger kateterne til at

Tanken er at dataen, sendt fra sensoren, skal forvandle brugeren med Arduinoen om til et tegneredskab. Det vil fungere sådan så den aktive persons placering fx. bliver vist midt på et lærred på en iPad. Når der kommer bevægelse på den aktive person, vil prikken på iPad følge hans/hendes bevægelse og efterlade en tegnestreg efter sig. På den måde kan man bruge sig selv til at tegne med. Det kan man lave til en leg ved at implementere reglerne fra tegn og gæt, så dem der følger med på iPad skal gætte hvad den aktive forsøger at tegne. Det er dog ikke den eneste måde at implementere leg og spil. Til denne aflevering er planen dog at bruge en PC og ikke en iPad.

Hardware

Diagramtegning lavet i fritzing:

POPG2opsætning.png

Billede med forklaring:

Billede med forklaring

  1. Arduinoboard
  2. “LSM9DS0” Accelerometer/Gyro/Magnetometer
  3. Logic Level Converter
  4. BlueSmirf bluetooth sensor
  5. 9Volt batteri

Problemer i forhold til hardwaren:

Indledende, da vi satte vores opsætning op, virkede vores bluetooth modul ikke. Bluetooth modulet kunne sende data, men ikke modtage. Derudover har der været nogle fejlkilder i forhold til at få sensoren “LSM9DS0” til at virke.

Bluetooth sensoren kan indstilles til to forskellige indstillinger: HID og SPP. HID står for Human Interface Device og SPP står for Serial Port Profile. HID er når en enhed kan tage imod input fra mennesker og give output baseret på input(der kan være tale om fx et tastatur). SPP arbejder primært med envejskommunikation, dvs. enheder som computerskærme, der tager output fra computer.

Vi skal bruge bluetooth sensoren i SPP indstilling. Den sensor vi fik udleveret var imidlertid sat til HID og på trods vi fulgte anvisninger til at ændre indstillingen op til SPP, nægtede sensoren at arbejde sammen med os. Da vi fik udleveret en ny sensor, fik vi data med det samme.

I forbindelse med sensoren “LSM9DS0”, opstod der enkelte fejlkilder som gav nogle sjove resultater, men her var der tale om regneregler og kode som skulle tilpasses for at give rigtige resultater.

Vi havde som udgangspunkt regnet med at vi kunne bruge accelerometeret til at vurdere om man var i bevægelse eller ikke. Det var dog ikke umiddelbart muligt for os at drage en sammenhæng, udfra de data vi modtog, om hvorvidt vi var i bevægelse.

Ved koden havde vi det klassiske problem fra matematik – at vinkler i grader og radianer ikke giver det samme resultat, når man regner cos eller sin til vinklen. Før vi fandt ud af det, var de tegnede værdier ikke særlig meget værd i processing.

Arduino kode

/*****************************************************************
 * Hardware setup: This library supports communicating with the
 * LSM9DS0 over either I2C or SPI. If you're using I2C, these are
 * the only connections that need to be made:
 * LSM9DS0 --------- Arduino
 * SCL ---------- SCL (A5 on older 'Duinos')
 * SDA ---------- SDA (A4 on older 'Duinos')
 * VDD ------------- 3.3V
 * GND ------------- GND
 *****************************************************************/

#include <SPI.h> // Included for SFE_LSM9DS0 library
#include <Wire.h>
#include <SFE_LSM9DS0.h>

#define LSM9DS0_XM  0x1D // Would be 0x1E if SDO_XM is LOW
#define LSM9DS0_G   0x6B // Would be 0x6A if SDO_G is LOW
LSM9DS0 dof(MODE_I2C, LSM9DS0_G, LSM9DS0_XM);

#define PRINT_CALCULATED

#define PRINT_SPEED 50 //Delay mellem prints

void setup()
{
  Serial.begin(115200); // Start serial at 115200 bps
  // Use the begin() function to initialize the LSM9DS0 library.
  // You can either call it with no parameters (the easy way):
  uint16_t status = dof.begin();
  // Or call it with declarations for sensor scales and data rates:  
  //uint16_t status = dof.begin(dof.G_SCALE_2000DPS, 
  //                            dof.A_SCALE_6G, dof.M_SCALE_2GS);
}

int force1 = A0;
int force2 = A1;

float forceValueOne;
float forceValueTwo;

float sendData[12];

void loop()
{  
  //Gyrometer
  dof.readGyro();
  sendData[0]= dof.calcGyro(dof.gx);
  sendData[1]= dof.calcGyro(dof.gy);
  sendData[2]= dof.calcGyro(dof.gz);

  //Accelerometer
  dof.readAccel();
  sendData[3]= dof.calcAccel(dof.ax);
  sendData[4]= dof.calcAccel(dof.ay);
  sendData[5]= dof.calcAccel(dof.az);

  //Magnetometer
  dof.readMag();
  sendData[6]= dof.calcMag(dof.mx);
  sendData[7]= dof.calcMag(dof.my);
  sendData[8]= dof.calcMag(dof.mz);

  float heading = getRetning(dof.calcMag(dof.mx),dof.calcMag(dof.my));

  //Retning i grader (som et kompas)
  sendData[9] = (float) heading;

  forceValueOne = analogRead(force1); 
  forceValueTwo = analogRead(force2);

  sendData[10] = forceValueOne;
  sendData[11] = forceValueTwo;

  //Arduino læser data fra alle de tilkoblede sensorer og sender dem ved hjælp af bluetooth,
  //og data modtages i Processing. For at gøre det nemmere at håndtere data i Processing,
  //sendes alt data i en streng. Hver sensortype separeres med "split", og de individuelle data
  //fra hver sensor separeres med " ". Derved kan der i processing bruges almindelig streng splitning
  //til at læse data fra Arduino.

  //Gyrometer
  Serial.print(sendData[0]);
  Serial.print(" ");
  Serial.print(sendData[1]);
  Serial.print(" ");  
  Serial.print(sendData[2]);
  Serial.print("split");

  //Accelerometer
  Serial.print(sendData[3]);
  Serial.print(" ");
  Serial.print(sendData[4]);
  Serial.print(" ");  
  Serial.print(sendData[5]);
  Serial.print("split");

  //Magnetometer
  Serial.print(sendData[6]);
  Serial.print(" ");
  Serial.print(sendData[7]);
  Serial.print(" ");  
  Serial.print(sendData[8]);
  Serial.print("split");

  //Retning i grader
  Serial.print(sendData[9]);
  Serial.print("split");

  //Data fra kraftsensorerne
  Serial.print(sendData[10]);
  Serial.print(" ");
  Serial.println(sendData[11]);

  delay(PRINT_SPEED);
} 

float getRetning(float hx, float hy){
  float heading = 0; 
  if (hy > 0)
  {
    heading = 90 - atan(hx / hy) * 180 / PI;
  }
  else if (hy < 0)
  {
    heading = 270 - atan(hx / hy) * 180 / PI;
  }
  else // hy = 0
  {
    if (hx < 0) heading = 180;
    else heading= 0;
  }

  return heading;
}

Her er et flowdiagram for hvordan arduinokoden opfører sig

Arduino

Processing

import processing.serial.*;
Serial port;
int lf = 10;
PFont f;
int angleKonstant = -400; //Oprettes med en værdi den ikke burde kunne have, så det
                          //kan tjekkes om den er sat eller ej

//Koordinater udregnet for sensorværdier. Disse værdier kan være større eller mindre end skærmbredde og højde
FloatList rawKoordinaterX; 
FloatList rawKoordinaterY;

float[] temp = {
  0, 0
};

//Koordinatværdier for de specifikke x og y-værdier der skal tegnes linjer mellem på skærmen
IntList tegneKoordinaterX;
IntList tegneKoordinaterY;

//Det skal være muligt at tegne noget, hvor der også kan laves mellemrum mellem streger
IntList tegnDenneKoordinat;

void setup() {
  size(600, 600);
  f = createFont("Arial", 14, true);
  port = new Serial(this, "COM17", 115200);
  port.bufferUntil(lf);
  rawKoordinaterX = new FloatList();
  rawKoordinaterY = new FloatList();
  tegneKoordinaterX = new IntList();
  tegneKoordinaterY = new IntList();
  tegnDenneKoordinat = new IntList();

  rawKoordinaterX.append(0.0);
  rawKoordinaterY.append(0.0);

  tegnDenneKoordinat.append(1);
}

boolean bevaegende = false;
boolean erTegnende = true;

void draw() {
  background(0);
  textFont(f, 32);

  //For at gøre det muligt at starte med at tegne uden først at tjekke at man vender mod
  //nord, udregnes en "angleKonstant", der bruges til at korrigere de modtagne vinkler fra
  //Arduino med. I tilfælde af, at draw loopet kører før, der er modtaget data fra arduino,
  //udregnes korrigeringsvinklen først, når der er modtaget data fra Arduino
  if (angleKonstant == -400) {
    if (retning == -1) {
      return;
    } 
    else {
      angleKonstant = int(retning);
    }
  }

  //Først korrigeres den modtagne vinkel fra Arduino
  int vinkel = int(correctAngle(retning));

  //Derefter udregnes et nyt koordinat ud fra den modtagne vinkel (ved hjælp at cos og sin)
  temp = nytKoordinat(int(vinkel), 2);

  //Og de lægges til listen af rå koordinater
  rawKoordinaterX.append(temp[0]);
  rawKoordinaterY.append(temp[1]);

  //Derefter checkes om det nye koordinat har værdier under 0 - så det er nødvendigt at forskyde
  //koordinatsystemet
  correctKoordinater();

  //Herefter findes der en konstant der angiver forholdet mellem bredden på skærmen og den
  //maksimale x-værdi (og det samme for højde og y-værdi)
  udregnSkaermKoordinat();

  stroke(255);

  for (int a=0; a<(tegneKoordinaterX.size()-1); a++) {
    line(tegneKoordinaterX.get(a), tegneKoordinaterY.get(a), tegneKoordinaterX.get(a+1), tegneKoordinaterY.get(a+1));
  }

  delay(50);
}

//While loop der kører indtil delay er gået. Bruges til at sørge for, at der ikke tegnes nye koordinater
//før der faktisk er modtaget nyt data fra Arduino
void delay(int delay)
{
  int time = millis();
  while (millis () - time <= delay);
}

//Hvis rækkeviden af x-værdier er større end skærmbredden udregnes en
//konstant, der bruges til at skalere koordinaterne ned med, så de passer
//med skærmens koordinater (det samme for y)
float scaleKonstantXY() {
  float maxX = rawKoordinaterX.max();
  float maxY = rawKoordinaterY.max();

  if ((maxX<width) && (maxY<height)) {
    return 1;
  }

  if (maxX>maxY) {
    return (width/maxX);
  } 
  else {
    return (height/maxY);
  }
}

//Korrigerer vinkel fra Arduino, så det passer med at den retning man har,
//når man starter programmet, passer med at den er ligefrem
float correctAngle(float angle) {
  angle = angle-angleKonstant;
  if (angle<0) {
    angle = 360+angle;
  }
  return angle;
}

//Hvis et tilføjet koordinat går under 0 i x eller y-værdi, lægges der en konstant
//til alle koordinater svarerende til hvormeget x eller y er mindre end 0
void correctKoordinater() {
  if (rawKoordinaterX.min()<0) {
    float correctKonstantX = abs(rawKoordinaterX.min());

    for (int i=0; i<rawKoordinaterX.size(); i++) {
      rawKoordinaterX.set(i, (rawKoordinaterX.get(i)+correctKonstantX));
    }
  }
  if (rawKoordinaterY.min()<0) {
    float correctKonstantY = abs(rawKoordinaterY.min());

    for (int i=0; i<rawKoordinaterY.size(); i++) {
      rawKoordinaterY.set(i, (rawKoordinaterY.get(i)+correctKonstantY));
    }
  }
}

//Ganger en skalleringskonstant på de "rå" x og y-koordinater, så de
//passer med skærmens størrelse
void udregnSkaermKoordinat() {
  float konstantX = scaleKonstantXY();
  float konstantY = scaleKonstantXY();  
  tegneKoordinaterX.clear();
  for (int q = 0; q<rawKoordinaterX.size();q++) {
    int koordinatX = int(rawKoordinaterX.get(q)*konstantX);
    tegneKoordinaterX.append(koordinatX);
  }

  tegneKoordinaterY.clear();
  for (int t = 0; t<rawKoordinaterY.size();t++) {
    int koordinatY = int(rawKoordinaterY.get(t)*konstantY);
    tegneKoordinaterY.append(koordinatY);
  }
}

float hosKat, modKat; 
float lastX, lastY;
float correctedAngle = 0;
float[] returKoordinat = {
  0, 0
};

//Udregner nye koordinater ved hjælp af trekantsberegniner for retvinklede 3-kanter.
//Vinklen er den der er modtaget fra Arduino og hypotenusen er en arbitrar værdi,
//der skal repræsentere at man går. Derudfra kan de to kateter udregnes. Ved vinkler
//større end de 90 grader omregnes vinklen så den ville svare til, hvis den var mindre
//end 90 grader. De to kateter lægges den forgående x og y-værdi og et nyt koordinat 
//er derved udregnet

float[] nytKoordinat(int angle, float hyp) { 
  lastX = rawKoordinaterX.get(rawKoordinaterX.size()-1);
  lastY = rawKoordinaterY.get(rawKoordinaterY.size()-1);

  if (angle < 90) {
    correctedAngle = 90-angle;
    correctedAngle = radians(correctedAngle);
    hosKat = (cos(correctedAngle))*hyp;
    modKat = (sin(correctedAngle))*hyp;
    returKoordinat[0] = lastX - hosKat;
    returKoordinat[1] = lastY + modKat;
  }
  else if ((angle > 90) && (angle < 180)) {
    correctedAngle = angle-90;
    correctedAngle = radians(correctedAngle);
    hosKat = (cos(correctedAngle))*hyp;
    modKat = (sin(correctedAngle))*hyp;
    returKoordinat[0] = lastX - hosKat;
    returKoordinat[1] = lastY - modKat;
  } 
  else if ((angle > 180) && (angle < 270)) {
    correctedAngle = 270 - angle;
    correctedAngle = radians(correctedAngle);
    hosKat = (cos(correctedAngle))*hyp;
    modKat = (sin(correctedAngle))*hyp;
    returKoordinat[0] = lastX + hosKat;
    returKoordinat[1] = lastY - modKat;
  }  
  else if ((angle > 270) && (angle < 360)) {
    correctedAngle = angle - 270;
    correctedAngle = radians(correctedAngle);
    hosKat = (cos(correctedAngle))*hyp;
    modKat = (sin(correctedAngle))*hyp;
    returKoordinat[0] = lastX + hosKat;
    returKoordinat[1] = lastY + modKat;
  }

  return returKoordinat;
}

String[] sensors = {
  "0", "0", "0", "0", "0"
};

String gyroString;
String accelString;
String magnetString;
String forceString;
String[] gyroSplit;
String[] accelSplit;
String[] magnetSplit;
String[] forceSplit;

float[] gyro = {
  0, 0, 0
};
float[] accel = {
  0, 0, 0
};
float[] magnet = {
  0, 0, 0
};

float[] force = {
  0, 0
};

String retningString = "0";
float retning = -1;

String seriel = "What?";

//Læser data fra Arduino, som er sendt som en lang streng.
//Ved hjælp af processings split funktion og funktion til
//at caste strenge til floats, kan data gemmes i float arrays

void serialEvent(Serial port) {
  seriel = port.readStringUntil(10);
  sensors = split(seriel, "split");
  gyroString = sensors[0];
  accelString = sensors[1];
  magnetString = sensors[2];
  retningString = sensors[3];
  forceString = sensors[4];

  gyroSplit = split(gyroString, " ");
  accelSplit = split(accelString, " ");
  magnetSplit = split(magnetString, " ");
  forceSplit = split(forceString, " ");
  retning = float(retningString);

  gyro[0] = float(gyroSplit[0]);
  gyro[1] = float(gyroSplit[1]);
  gyro[2] = float(gyroSplit[2]);

  accel[0] = float(accelSplit[0]);
  accel[1] = float(accelSplit[1]);
  accel[2] = float(accelSplit[2]);

  magnet[0] = float(magnetSplit[0]);
  magnet[1] = float(magnetSplit[1]);
  magnet[2] = float(magnetSplit[2]);

  force[0] = float(forceSplit[0]);
  force[1] = float(forceSplit[1]);
}

Her er et flowdiagram for hvordan processing koden opfører sig

Processing

Konklusion

Vi har arbejdet med opfordring til fysisk aktivitet via leg. Prototypen skal gøre det muligt at tegne den rute man har bevæget sig. Rammen for legen er en variation af tegn og gæt, hvor det er op til brugeren helt præcis hvordan legen udfolder sig. Prototypen kan derfor bruges til en række af lege der basalt indeholder tegn og bevægelse.

På nuværende tidspunkt antager arduinoen at der er konstant bevægelse så længe den er igang. Dette er dog ikke altid tilfældet og det giver derfor en vis fejlkilde. Man kan altså bruge prototypen uden at bevæge sig for at vise det feedback, prototypen giver.
Enten skal vi finde ud af at bruge gyrometer/accelerometer til at beregne bevægelse og hastighed, eller også skal vi have fundet et alternativ for hvordan vi kan måle bevægelse. Samtidig så må magnetometeret ikke tiltes særlig meget før værdierne bliver upræcise og det gør en eventuel placering af arduinoen besværlig. Der skal tages højde for nogle værdisving som kan opstå hvis brugeren læner sig til siden eller fremover for fx at spurte.

Den feedback man får givet, er en simpel hvid streg på en sort skærm. Det kan forbedres ved at ligge nogle farver ind som gør det mere spændende at lege med. Hvis man lavede om på farverne så man istedet tegnede på et hvidt lærred og nemt kunne skifte farve, ville brugere nemmere kunne relatere det til idéen/legen om at tegne.

Her ses prototypen i brug:

Leave a Reply