Göra en krypande Robot Zombie med avhuggna ben (19 / 22 steg)
Steg 19: Kod: krypa
Arduino verktyg och kunskap för stiftelsen
Om du letar efter en bra introduktion till Arduinos, då det är en hel del av Instructables för dig, här är en stor en.
Jag ska anta att du har laddat ner och installera Arduino IDE och kan ladda upp koden till din Arduino, den tidigare nämnda instructable kommer att guida dig genom allt detta och mycket mer.
Kodöversikt
I föregående steg beslutade vi att vi skulle genomföra genomsökning av cykling genom "ramar" av animation som skulle definiera var och en av servon vid en viss position i tid.
Den fullständiga koden är ansluten som en fil, men jag kommer gå igenom de flesta avdelningar här så att du kan förstå dem. Om du redan förstår dem, är detta bra, eftersom du kan förmodligen komma på ett bättre sätt att göra detta som jag gjorde.
Importera krävs bibliotek
Det enda bibliotek som krävs för denna koden är "Servo.h" som är full av praktiska funktioner som gör det extremt enkelt att kontrollera servon från en Arduino.
#include < Servo.h >
Definiera globala variabler
Detta är variabler som kan nås från någonstans i koden. Vi använder dem för att lagra information som kommer att krävas i ett senare skede
Timer variabler
Kommandot millis() returnerar antalet millisekunder sedan Arduino styrelsen började köra nuvarande program. Vi kan lagra resultatet av millis() och sedan jämföra den till resultatet i ett senare skede bestämma hur länge det har funnits mellan de två samtalen.
lång previousFrameMillis = 0;
lång previousInterpolationMillis = 0;
Servo ingående variabler
Dessa variabler lagra vilken pin varje servo är ansluten till, samt hur många micros motsvarar min (indragen eller nedsänkta) och max (upp/extended)
servo pins
int LNpin = 7. vänstra hals muskel
int RNpin = 6; rätt hals muskel
int LSpin = 5; vänster axel
int LEpin = 4; vänster armbåge
int RSpin = 2; höger axel
int REpin = 3; höger armbåge
dessa är de 'min' och 'max' vinklar för varje servo
kan inverteras för servon i motsatt riktning
int LSMin = 1000;
int LSMax = 1700;
int RSMin = 2000.
int RSMax = 1300;
int LEMin = 1700;
int LEMax = 1300;
int REMin = 1300;
int ordet REMax = 1700;
int LNMin = 1400;
int LNMax = 1600;
int RNMin = 1600;
int RNMax = 1400;
Ramar
Sin första intryck kan vara att vi precis som servon till de position som har definierats av den första bildrutan, vänta en viss fördröjning (frameDuration) och sedan ange servon till nästa position.
Detta kommer att resultera i en hemsk animering dock eftersom servon kommer att hoppa så snabbt som möjligt till den angivna position och sedan vänta där på deras nästa instruktion.
Sättet runt detta är att interpolera mellan ramar. Med andra ord, om min ram längd är 2 sekunder, vill efter 1,75 sekunder jag servon till vara tre fjärdedelar (eller 75%) väg mellan bildruta 1 och bild 2.
Det är lite triviala av matematik att räkna ut där servo bör vara om vi vet hur mycket av ramen har förflutit som förhållande. Ord är det bara (föregående ram) +(the difference between the next and previous frames) * (andelen ram varaktighet förflutit), detta är känt som "linjär interpolation".
int frameDuration = 800; längden på en bildruta i millisekunder
int frameElapsed = 0; räknaren millisekunder av denna ram som har förflutit
int interpolationDuration = 30. antalet millisekunder mellan interpolation steg
Här definierar vi de faktiska ramarna. Obs att jag använt 0-1000, där 0 anger indragen och sänkas och 1000 anger extended/upp. Jag kunde valt något nummer, och det kan väl vara mer logiskt val, men intervallet gav mig en tillfredsställande kompromiss mellan upplösning och läsbarhet.
Vi kommer att använda funktionen map() senare till karta 0 till LSMin variabel och 1000 till LSMax variabel som vi definierat tidigare (självklart detta exemplet är för vänster axel, men det skulle vara samma process för alla de andra servon).
Om du vill definiera mer komplexa eller mjukare animeringar kan du enkelt lägga till fler bilder och också använda siffror än min/max. Ett alternativ skulle vara att använda ca 8 ramar för att göra en trevlig elliptisk rörelse.
ramar för den krypande gångarten lagras här
int currentFrameIndex = 0;
int numFrames = 4;
int crawlGaitRS [] = {0,1000,1000,0}.
int crawlGaitRE [] = {0,0,1000,1000}.
int crawlGaitLE [] = {1000,0,0,1000}.
int crawlGaitLS [] = {0,0,1000,1000}.
int crawlGaitLN [] = {1000,1000,0,1000}.
int crawlGaitRN [] = {0,1000,1000,1000}.
För att genomföra denna interpolation beräkning måste vi hålla reda på den föregående bildrutan och följande ram, så vi satt upp vissa variabler till gör så pass.
servo sista bildruta micros
int LSlast = 0;
int RSlast = 0;
int LElast = 0;
int RElast = 0;
int LNlast = 0;
int RNlast = 0;
servo nästa bildruta micros
int LSnext = 0;
int RSnext = 0;
int LEnext = 0;
int REnext = 0;
int LNnext = 0;
int RNnext = 0;
variabeln som används för att lagra den aktuella bildrutan i animeringen
int currentFrameIndex = 0;
Servo objekt
Slutligen, vi skapar vissa servo objekt och tilldela dem till variabler. Dessa är förekomster av klassen servo som vi ingår i Servo.h och kommer att ge användbara funktioner för att styra varje servo.
skapa servo objekt för att kontrollera servon
Servo LS;
Servo RS;
Servo LE;
Servo RE;
Servo LN;
Servo RN;
Definiera funktioner
Arduino Setup-funktionen
Arduino setup() funktion är den första lite kod som får köra efter de globala variablerna har definierats. Allt som behövs här för nu är att koppla servo objekt till deras stift och starta upp den seriella porten, om vi vill rapportera något för debuggging.
void setup()
{
Serial.BEGIN(9600);
LS.attach(LSpin);
RS.attach(RSpin);
LE.attach(LEpin);
RE.attach(REpin);
LN.attach(LNpin);
RN.attach(RNpin);
}
Ange nästa bildruta
Denna funktion kallas när våra servon kommer till slutet av en ram. Allt som den gör är:
- Öka den "currentFrameIndex" (om vi inte har nått den sista bildrutan, i vilket fall det loopar tillbaka till ram 0)
- Lagra aktuell ram position som "sista bildrutan"
- Hämta nästa ram position från arrayen animation
void setNextFrame()
{
om detta var den sista bildrutan, börja om igen
om (currentFrameIndex < numFrames - 1) {
currentFrameIndex ++;
}
annat {
currentFrameIndex = 0;
}
Vi har nått målbildrutan, så spara det som "sista bildrutan"
LSlast = LSnext;
RSlast = RSnext;
LElast = LEnext;
RElast = REnext;
LNlast = LNnext;
RNlast = RNnext;
generera nya "nästa frame"
LSnext = crawlGaitLS [currentFrameIndex];
RSnext = crawlGaitRS [currentFrameIndex];
LEnext = crawlGaitLE [currentFrameIndex];
REnext = crawlGaitRE [currentFrameIndex];
LNnext = crawlGaitLN [currentFrameIndex];
RNnext = crawlGaitRN [currentFrameIndex];
}
Interpolation funktion
Som beskrivits tidigare, kommer att vi använda en linjär interpolation för att avgöra exakt vilken ställning en servo bör vara i vid en given tidpunkt mellan två ramar.
Min dator vetenskap föreläsare sa alltid att vara en bra programmerare handlade om att vara lat, om du kan undvika skriva kod flera gånger genom att sätta det i en funktion, då göra det.
Denna funktion helt enkelt genomför linjär interpolation ekvationen mellan två bildrutor och sedan kartor ram position till en servo position och tillämpar det till servo-objektet.
void writeInterpolatMicros (Servo servo, int prevFrame, int nextFrame, int servoMin, int servoMax, float elapsedRatio) {
int interpolerad = prevFrame + int (float (nextFrame - prevFrame) * elapsedRatio);
servo.writeMicroseconds(map(interpolated,0,1000,servoMin,servoMax));
}
Servo uppdateringsfunktionen
Denna funktion gör koden snyggare genom att ta bort en bit av det från den huvudsakliga loopen.
Först beräknas förhållandet mellan den ram som redan är klar med antalet millisekunder som har förflutit sedan ramen började, dividerat med antal millisekunder det tar för att slutföra en ram.
Detta förhållande är skickas vidare till funktionen interpolation för varje servo, uppdatera varje position.
void updateServos()
{
flyta frameElapsedRatio = float(frameElapsed)/float(frameDuration);
writeInterpolatMicros(LS,LSlast,LSnext,LSMin,LSMax,frameElapsedRatio);
writeInterpolatMicros(LE,LElast,LEnext,LEMin,LEMax,frameElapsedRatio);
writeInterpolatMicros(RS,RSlast,RSnext,RSMin,RSMax,frameElapsedRatio);
writeInterpolatMicros(RE,RElast,REnext,REMin,REMax,frameElapsedRatio);
writeInterpolatMicros(LN,LNlast,LNnext,LNMin,LNMax,frameElapsedRatio);
writeInterpolatMicros(RN,RNlast,RNnext,RNMin,RNMax,frameElapsedRatio);
}
Huvudloop
Den huvudsakliga loop() är där all action händer, så snart den är klar att köra alla koden som finns inuti den hoppar tillbaka till början och börjar om igen.
Det första steget i den huvudsakliga slingan är att spela in det aktuella antalet millisekunder sedan programmet startade kör) så att vi kan avgöra hur mycket tid har förflutit sedan den senaste iterationen av loopen.
Använda denna tid kan vi avgöra om förfluten tid är större än den period som vi definierade för interpolation steg, så anropa funktionen updateServos() att generera nya interpolerade positioner.
Vi kontrollerar också om förfluten tid är större än vad ram, då vi behöver att anropa funktionen setNextFrame().
void loop()
{
osignerade långa currentMillis = millis();
om (currentMillis - previousInterpolationMillis > interpolationDuration) {
Spara sista gången att vi uppdaterat servon
previousInterpolationMillis = currentMillis;
Increment ramen gått coutner
frameElapsed += interpolationDuration;
uppdatera servon
updateServos();
}
om (currentMillis - previousFrameMillis > frameDuration) {
Spara sista gången att vi uppdaterat servon
previousFrameMillis = currentMillis;
återställa förfluten ram tiem till 0
frameElapsed = 0;
uppdatera servon
setNextFrame();
}