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;
}