LED-kub likt vattennivå medelst accelerometer?

C, C++, Pascal, Assembly, Raspberry, Java, Matlab, Python, BASIC, SQL, PHP, etc.
Användarvisningsbild
Klas-Kenny
Inlägg: 11346
Blev medlem: 17 maj 2010, 19:06:14
Ort: Växjö/Alvesta

LED-kub likt vattennivå medelst accelerometer?

Inlägg av Klas-Kenny »

Läser just nu en kurs innehållande en del µC-programmering, och i dagens laboration var en uppgift att koppla in en 3x3x3 LED-kub och en accelerometer till µC'n.

Detta skulle sedan programmeras så att man kunde tänka sig att LED-kuben var en halvfylld vätskebehållare. Plant så är de två nedersta lagerna helt tända, och lutar man den så ska lysdioder då tändas/släckas likt vattennivån i behållaren ändras.

För att få klart uppgiften löste vi det med rent statisk programmering, en hel massa if-satser för att beskriva när varje lysdiod ska vara tänd och inte. Labbhandledaren tyckte inte det var något fel med den lösningen (Det var han som föreslog det när vi satt och klurade på något smartare..) och hade själv varken sett eller kommit på någon bättre lösning på uppgiften, men höll med om att det vore kul att lyckas göra något vettigare. Inte minst vore det ju vettigt om koden var skalbar, skulle man skala upp nuvarande till en betydligt större kub vore ju sådan statisk programmering vara fullständigt hopplös.

Så, jag som inte tycker om sådana fullösningar har fortsatt fundera på uppgiften ikväll, men har inte lyckats knäcka nöten. Så nu vänder jag mig till er.. :)

Jag tänker mig tillbaka till vektorgeometrin, och tänker mig då att se LED-kuben som en 3D-geometri (vilket det ju är). Accelerometern som då ger ut vinklar i förhållande till X- och Y-axel (Använder en tvåaxlig accelerometer, men kan tänka mig att uppskalning till treaxlig vore förhållandevis enkelt med en bra lösning), skulle kunna ses som två vektorer i rummet. Dessa vektorer kan alltså spänna upp ett plan i rummet. Då kvarstår ju i princip enbart att hitta alla koordinater som ligger under planet, för att se vilka som ska lysa.

Så långt så bra, tror jag. Men hur tusan ska man då lyckas att beskriva denna matematik i faktisk matematik istället för ord, och hur ska man lyckas implementera det i programkod? :humm:
Föreställer mig att det hela är förhållandevis enkelt, men är man inte van vid grafikprogrammering och vektorgeometrin befinner sig långt bak i huvudet så är det värre. Sen kanske jag krånglar till det mer än nödvändigt, vad vet jag. :)

Alla tips och idéer är varmt välkomna!
kodar-holger
EF Sponsor
Inlägg: 921
Blev medlem: 26 maj 2014, 12:54:35
Ort: Karlskoga

Re: LED-kub likt vattennivå medelst accelerometer?

Inlägg av kodar-holger »

Möjligen kan man använda vektorer och några listiga kryssprodukter, men sån matematik har jag aldrig förstått/lärt mig. Trodde forumets matematiker skulle kastat in en sån lösning redan.

Jag skulle nog gjort ungefär så här tror jag. Utan att ha testat.

Räknat ut lutningen relativt normalvektorn med hjälp av signalerna från tre accelerometrar. Med Atan2 kan man få ut två rotationsvinklar.

Räkna ut en total rotationsmatris Rtot genom att multiplicera de två rotationsmatriserna vi kan kalla Rx och Ry fyllda med data från ovan uträknade rotatinsvinklar. (http://en.wikipedia.org/wiki/Rotation_matrix)

Ge varje lysdiod en koordinat.

Rotera varje lysdiods koordinat med hjälp av Rtot till ett "världskoordinatsystem".

Om resulterande koordinat efter rotation har en z-koordinat <0 skall lysdioden vara tänd, annars släckt. Typ.

Men all matematik skulle jag köra i ett program som maple (kanske går med wolfram alfa) först för att se vilka sin/cos/arctan som tar ut varandra. Det brukar bli en osannolik mängd 0xcos(ditt) och arcsin(sin(datt)) som liksom bara försvinner i såna här sammanhang.
Mr Andersson
Inlägg: 1397
Blev medlem: 29 januari 2011, 21:06:30
Ort: Lapplandet

Re: LED-kub likt vattennivå medelst accelerometer?

Inlägg av Mr Andersson »

Det låter som att http://mathworld.wolfram.com/Point-PlaneDistance.html är vad du vill ha.
Om D är negativ (eller positiv beroende på hur du väljer att ha planets normalvektor) då är punkten under "vattenytan".

X/Y/Z för planet sätter du nånstans i led-kuben beroende på vilken höjd du vill ha på ytan och normalen hade du fått ut direkt från accelerometern om den varit treaxlad. Bara att normalisera till längden 1.
Jag tror att det borde fungera likadant med två axlar, förutom att det går inte att avgöra om kuben är rättvänd eller inverterad.
Användarvisningsbild
Klas-Kenny
Inlägg: 11346
Blev medlem: 17 maj 2010, 19:06:14
Ort: Växjö/Alvesta

Re: LED-kub likt vattennivå medelst accelerometer?

Inlägg av Klas-Kenny »

Tack så mycket för svaren, mycket givande!

kodar-holger: När jag läser ditt inlägg inser jag hur mycket utav vektorgeometrin jag glömt bort, förstår typ principen i det du skriver, men att realisera det hela är värre. Slår som en galning i matteboken för att förstå vidare. :P

För att undvika att bränna hål i hjärnan kollar jag vidare på nästa lösning tills vidare..

Mr Andersson: Det där såg ju högst intressant ut!

Till att börja med, normalvektorn. Den kan jag väl som du säger få direkt ur accelerometern, och om jag inte tolkar det hela fel så blir den alltså (Ax, Ay, Az) där Ax, Ay, Az alltså är utsignalerna ifrån accelerometern (om man tänker en treaxlig istället). Korrekt?
Sen normaliserar jag då den vektorn genom att dividera den med sin längd, vilket ger mig normalvektorn v=(a,b,c).

Om jag sen då vill ha vattennivån i mitten av kuben, väljs ju lämpligen en punkt med koordinaten (1,1,1) (om jag numrerar lysdioderna 0-2 i alla led), vilket ger att d i planets ekvation lätt kan beräknas.

Sen beräknar jag bara huruvida varje lysdiod ska vara tänd eller inte genom formeln (10)
\(D=\frac{a x_0+b y_0+c z_0+d}{\sqrt{a^2+b^2+c^2}}\)

där då x0, y0, z0 sätts till varje lysdiods koordinater.

Om jag har förstått korrekt, så borde det inte vara särskilt svårt att göra programkod utav!

edit: Behöver jag verkligen normalisera normalvektorn förresten? Att inte göra det kommer väl enbart att skala upp alla andra beräkningar så att jag får större tal, men å andra sidan slipper flyttalsberäkningar, eller?
Användarvisningsbild
Klas-Kenny
Inlägg: 11346
Blev medlem: 17 maj 2010, 19:06:14
Ort: Växjö/Alvesta

Re: LED-kub likt vattennivå medelst accelerometer?

Inlägg av Klas-Kenny »

Lödde ihop en kub här hemma, skrev lite kod och visst fungerar det! :bravo:

Tyvärr har jag ingen accelerometer tillgänglig just nu, "simulerar" bara två axlar med två potentiometrar, vilket gör att den inte riktigt fungerar perfekt fullt ut (till exempel fungerar det inte att "lägga den på sidan", då det ju kräver att Z-axeln ger 0 och inte ett fast värde som jag satt den just nu. Får se till att ordna fram en treaxlig accelerometer och testa det också.
kodar-holger
EF Sponsor
Inlägg: 921
Blev medlem: 26 maj 2014, 12:54:35
Ort: Karlskoga

Re: LED-kub likt vattennivå medelst accelerometer?

Inlägg av kodar-holger »

Ännu ett bevis på att man borde lära sig mer matematik. Under mina första dryga 15 år i arbetslivet flyttade jag mest data från en variabel till en annan (databas-system). Sen jobbade jag plötsligt i ett projekt i några år där jag helt enkelt var tvungen att lära mig lite matrisalgebra, koordinattransformer och geodesi. Sen har jag flyttat data mellan variabler igen dom senaste 6 åren... Suck.
Användarvisningsbild
Klas-Kenny
Inlägg: 11346
Blev medlem: 17 maj 2010, 19:06:14
Ort: Växjö/Alvesta

Re: LED-kub likt vattennivå medelst accelerometer?

Inlägg av Klas-Kenny »

Matte är väldigt bra att ha, särskilt då vektorgeometri om man jobbar med någon form utav grafik. :)

Själv skrev jag högsta betyg på tentan i vektorgeometri för ett och ett halvt år sen, men sedan dess så har jag i princip aldrig rört någonting åt det hållet så det mesta har ju fallit bort (men kan ju bringas tillbaka när man sätter sig in i det igen).
Har heller inte använt det i programmering förut, eller tillämpningar över huvud taget, mer än en gång jag testade då jag läste kursen, att generera visare i en klocka på en grafisk display med hjälp av en rotationsmatris.

Hur som helst, programmet tycks som sagt fungera bra, har tweakat en hel del till och snyggat upp koden. Har ju inte testat med en faktisk accelerometer ännu, men chanserna känns mycket goda för att det ska fungera. Testar nu med tre potentiometrar och det beter sig som man kan vänta sig. Bjuder på hela programmet ifall någon annan vill göra något liknande någon gång och råkar hitta tråden, det är alltså för Arduino.
Jag använder en Mega 2560, men det borde fungera lika bra på de flesta.
Har kubens staplar/anoder kopplade (genom strömbegränsningsmotstånd) till utgångarna 2-10 och kubens plan/katoder till var sin NPN-transistor, med basen kopplad (genom strömbegränsningsmotstånd) till utgångarna 11-13. Accelerometern har analoga utgångar, vilka är anslutna till de analoga ingångarna 0-2 (xyz).
Allt det är dock såklart lätt ändrat i kod.

Koden börjar med en beskrivning av teorin bakom och hur programmet fungerar på svenska för förståelse, sen är inline-kommentarerna på engelska av vana.

Kod: Markera allt

/* ************************************************************************************************************************************
*  LED-kub som tänder lysdioder likt vattennivå då kuben vrids.
*  
*  Beräkning av vilka lysdioder som ska vara tända med hjälp av vektorgeometri;
*  X-, Y,- och Z-utgångarna ifrån accelerometern beskriver en vektor v = (X,Y,Z) = (a,b,c)
*  vilken är normal till ett plan, vilket kommer att luta utefter hur kuben lutas.
*  
*  En punkt P (centerPoint) definierar var i rummet planet befinner sig, vilken väljs
*  till mitt i kuben. Vid en 3*3*3-kub då i punkten (1,1,1).
*  Tillsammans med vektorn v ger denna punkt då ett fullständigt bestämt plan ax+by+cz+d = 0
*  där d bestäms med hjälp av punkten till d=-(v*P).
*  
*  För att ta fram vilka koordinater (lysdioder) som befinner sig under planet (ie. under vattenytan, ska lysa)
*  ansätts en vektor w=( (a-x0), (b-y0), (c-z0) ) där x0, y0, z0 är lysdiodens koordinater. Denna vektorn W
*  beskriver alltså då en vektor som går ifrån planet till lysdioden.
*
*  Då kan avståndet ifrån punkten till planet bestämmas genom en projicering utav vektorn w på vektorn v,
*  avståndet D är då lika med D = |v*w|/|v|.
*
*  Före beräkningen av D normaliseras vektorn v, vilket innebär att den får längden 1 och där är 
*  D = |v*w|/|v| = |v*w| = |a*x0 + b*y0 + c*z0 + d|, och där med snabbas exekveringen upp något då någon
*  division ej behöver utföras för varje lysdiod.
*
*  Genom att ta bort absolutbeloppet i täljaren fås D positivt då lysdioden är ovanför planet och
*  negativt då lysdioden är under planet, vilket används för att bestämma vilka lysdioder som ska vara 
*  tända respektive släckta.
*  
*  Mer information om matematiken finnes här: http://mathworld.wolfram.com/Point-PlaneDistance.html
*  
*  Programmet kan lätt modifieras för en större kub (hur stor som helst så länge hårdvaran klarar det)
*  genom att endast ändra några konstanter vid initiering av programmet.
*  
*  Vid programstart körs en kalibrering utav accelerometern för att ta fram dess "nollvärde" så att
*  det går att få ut ett tal som pendlar runt 0, positivt för vridning åt ett håll och negativt åt andra.
*
*  Detta görs genom att placera kuben i plant läge och skicka ett tecken på serieterminalen, vända upp och ned
*  på kuben och åter skicka ett teckan på serieterminalen. Instruktioner ges i terminalen då fönstret öppnas.
*
*  Anledningen till att kuben måste kalibreras i två lägen är att det annars är svårt att hålla Z-axeln kring 0,
*  istället får den testas i båda extremlägen och mittpunkten beräknas.
*
*  Multiplexingen görs genom att tända alla lysdioder som ska vara tända (baserat på 3D-arrayen "diodes") i ett lager i taget, 
*  och då gå igenom alla lager snabbare än ögat hinner att se.
*  Detta görs med en timerstyrd interrupt, för att inte riskera påverka lysdiodernas funktion ifall huvudprogrammet
*  exekveras för långsamt av någon anledning (tunga beräkningar eller någon delay() exempelvis).
*  Timern är inställd till att generera interrupts med ett mellanrum på 5ms.
*
*  Om Timer-biblioteket saknas eller timern av annan anledning inte ska användas avaktiveras den funktionen lätt genom att kommentera
*  bort en rad bland definitionerna (USE_TIMER).
*
*  ************************************************************************************************************************************/

#define USE_TIMER    // Comment away if missing TimerOne library or don't want to use timer interrupt

#define S1 2
#define S2 3
#define S3 4
#define S4 5
#define S5 6
#define S6 7      // Define which column is connected to which pin
#define S7 8
#define S8 9
#define S9 10
#define P1 11
#define P2 12    // Define which plane is connected to which pin
#define P3 13

#define PLANE_ON_STATE HIGH  // Set this to the state of which a plane is turned on. Using transistors on output this is most likely high, otherwise low

#define CUBE_SIZE 3  // Size of used LED-cube

#define X_CHANNEL 0
#define Y_CHANNEL 1  // Define which analog inputs are used for which accelerometer axis
#define Z_CHANNEL 2

// #define X_INVERSE
// #define Y_INVERSE    //Uncomment to inverse any axis based on placement of accelerometer
// #define Z_INVERSE

#define INPUT_DIVIDER 8    // How much to divide the AD-resolution with for preventing of flicker

#if defined(USE_TIMER)
  #include <TimerOne.h>
#endif

int centerPoint[3] = {1, 1, 1};    // Center point of movement, defined as {x, y, z}

int column[9] = {S1, S2, S3, S4, S5, S6, S7, S8, S9};  // Add/remove Sx when changing cube size
int plane[3] = {P1, P2, P3};                           // Add/remove Px when changing cube size

boolean diode[CUBE_SIZE][CUBE_SIZE][CUBE_SIZE];

int xref, yref, zref;
int planeCount = 0;

void setup() {
  
  for(int i=0; i<CUBE_SIZE*3; i++)
    pinMode(column[i], OUTPUT);    // Init all outputs
  for(int i=0; i<CUBE_SIZE; i++)
    pinMode(plane[i], OUTPUT);

  Serial.begin(9600);
  Serial.println("Placera kuben på ett plant underlag och skicka ett tecken.");
  while(!Serial.available());     // Wait for serial receive
  Serial.readString();            // Clear receive buffer
  
  xref = analogRead(X_CHANNEL)/INPUT_DIVIDER;
  yref = analogRead(Y_CHANNEL)/INPUT_DIVIDER;
  int z1 = analogRead(Z_CHANNEL)/INPUT_DIVIDER;
  
  Serial.println("Placera kuben upp och ned på ett plant underlag och skicka ett tecken.");
  while(!Serial.available());     // Wait for serial receive
  Serial.readString();            // Clear receive buffer
  
  int z2 = analogRead(Z_CHANNEL)/INPUT_DIVIDER;
  
  #if defined(Z_INVERSE)
    zref = z2 - ((z2-z1/2);    // Calculate zref different depending on if it is inverse or not
  #else
    zref = z1 - ((z1-z2)/2);
  #endif
  
  #if defined(USE_TIMER)
    Timer1.initialize(5000);
    Timer1.attachInterrupt(timerISR);
  #endif
}


void loop() {
  
  float a = analogRead(X_CHANNEL)/INPUT_DIVIDER - xref;
  float b = analogRead(Y_CHANNEL)/INPUT_DIVIDER - yref;      // Read all accelerometers
  float c = analogRead(Z_CHANNEL)/INPUT_DIVIDER - zref;
  
  #if defined(X_INVERSE)
    a = -a;
  #endif
  #if defined(Y_INVERSE)    // Inverse axis values if defined
    b = -b;
  #endif
  #if defined(Z_INVERSE)
    c = -c;
  #endif
  float length = sqrt(a*a+b*b+c*c);  // Calculate length to normalize vector
  
  a /= length;
  b /= length;    // Normalize vector
  c /= length;
  
  float d = -(a*centerPoint[0]+b*centerPoint[1]+c*centerPoint[2]);  // Calculate constant d for the specified centerpoint of the plane
  
  for(int i=0; i<CUBE_SIZE; i++) 
    for(int j=0; j<CUBE_SIZE; j++)  // Iterate through all LEDs
      for(int k=0; k<CUBE_SIZE; k++) {
        float D = a*i + b*j + c*k + d;  // Calculate distance from point to plane
        if(D>0)
          diode[i][j][k] = LOW;                // Positive distance -> LED over plane, turn it off
        else
          diode[i][j][k] = HIGH;                // Negative or zero distance -> LED under/in plane, turn it on
      }
  
#if defined(USE_TIMER)
}

void timerISR() {
#endif

  for (int i = 0; i < CUBE_SIZE; i++)    // Turn all planes off
  digitalWrite(plane[i], !PLANE_ON_STATE);

  int columnCount = 0;
  for (int i = 0; i < CUBE_SIZE; i++) {
    for (int j = 0; j < CUBE_SIZE; j++) {  // Iterate through all diodes in plane
      digitalWrite(column[columnCount], diode[j][i][planeCount]);  // Turn each LED on/off according to array "diode"
      columnCount++;
    }
  }
  
  digitalWrite(plane[planeCount], PLANE_ON_STATE);    // Turn on one plane at a time
  
  planeCount++;                            // Increase planeCount to go to next plane next time
  if (planeCount >= CUBE_SIZE)
    planeCount = 0;
}
Mr Andersson
Inlägg: 1397
Blev medlem: 29 januari 2011, 21:06:30
Ort: Lapplandet

Re: LED-kub likt vattennivå medelst accelerometer?

Inlägg av Mr Andersson »

> edit: Behöver jag verkligen normalisera normalvektorn förresten? Att inte göra det kommer väl enbart att skala upp alla andra beräkningar så att jag får större tal, men å andra sidan slipper flyttalsberäkningar, eller?

Jo det har du rätt i. Eftersom du bara vill veta vilken sida en punkt är på så spelar det ju ingen roll om avståndet är en faktor för stort.
Snyggt jobbat. Väldigt lättläst kod :)
Användarvisningsbild
Klas-Kenny
Inlägg: 11346
Blev medlem: 17 maj 2010, 19:06:14
Ort: Växjö/Alvesta

Re: LED-kub likt vattennivå medelst accelerometer?

Inlägg av Klas-Kenny »

Javisst ja, just det tänkte jag inte på!

Jag tänkte sen mest att divisionen med längden vid beräkningen av D i länken ju utför just normaliseringen, så det behöver ju inte göras i förväg. Det ger kortare kod än att göra det i förväg, men sen insåg jag ju att det har prestandafördelar att göra det i förväg.

Men du har ju helt rätt i att det inte behöver göras alls nu när man tänker efter, så de raderna kod som normaliserar kan ju lika gärna tas bort. :)
Användarvisningsbild
Andax
Inlägg: 4373
Blev medlem: 4 juli 2005, 23:27:38
Ort: Jönköping

Re: LED-kub likt vattennivå medelst accelerometer?

Inlägg av Andax »

Om man har väldigt få LED så vore det kanske effektfullt att PWM de som ligger på marginalen. Dvs att man inte bara slår på dem när de ligger under planet, utan gradvis ökar intensiteten.
Användarvisningsbild
Klas-Kenny
Inlägg: 11346
Blev medlem: 17 maj 2010, 19:06:14
Ort: Växjö/Alvesta

Re: LED-kub likt vattennivå medelst accelerometer?

Inlägg av Klas-Kenny »

Det är ju ingen dum idé alls! Det är ju bara frågan om att ge varje lysdiod en duty-cykle istället för av/på och att sätta duty-cyklen till (-D+litegrann), då kommer ju duty-cyklen att vara negativ för stora D (dvs. långt över vattenytan), så börjar de tändas strax ovanför vattenytan och lyser starkare och starkare ju "djupare" de är.

Och då såklart för duty-cykle <= 0 är de helt av, för >= 100 är de helt på.

Och så en lite lagom stor skalningsfaktor på D för att göra effekten lagom stor.

Det kluriga är dock att skriva en hyfsat effektiv PWM-rutin för kuben, är ju hela tiden nio individuella PWM-kanaler. Tillbaka till IDE'n! :hacker:
Skriv svar