Introduction: Servo Vlinder Prototype Voor Malenia Cosplay

Voor dit project heb ik een prototype gemaakt van vlinders die ik uiteindelijk ga gebruiken voor mijn Malenia, goddess of rot cosplay. Malenia komt uit de game Elden ring, één van mijn favoriete games. Malenia heeft grote vleugels waarop veel levende vlinders zitten. In de tweede cutscene van haar bossfight zie je dat er veel vlinders van haar af vliegen. Met dit project wou ik die levenden vlinders namaken met behulp van Arduino. Hiermee geef ik mijn cosplay extra dimensie en kan ik de cutscene nabootsen in het echt

Supplies

Elektronisch

16 stokjes uit 3D printer

16 Servo's.

Arduino UNO r3.

2 ORTONIC 16 kanaals 12-bit PWM Servo Driver I2C.

Voeding

USB-kabel

Voedingskabel


Materialen afwerking

MDF plaat (22,5 cm x 75 cm x 0,6 cm)

3 Houten pootjes (11 cm x 2 cm x 6 cm)

Dik mat papier

Stickerpapier

Tie wraps

Blokje voor de Tie wraps

Dubbelzijdige tape

Talens Expert Acrylic verf

kleine kwast


Gereedschap

Hamer

Spijkers

Zaag

Workmate

Boormachine

Printer

3D printer

Hot glue

Secondelijm

Schaar

Schroevendraaier

Soldeerbout

Soldeertin

Wacom tekentablet


Software

Autodesk fusion 360

Arduino IDE 2.3.0

Photoshop

Step 1: Hoorcollege Servo

Tijdens een hoorcollege over Arduino's kregen we de opdracht om te spelen met een servo motor. Daarvoor kregen we deze code. Terwijl ik met de waardes aan het spelen was, had ik het idee om met servo's een project te maken. Ik heb altijd al een cosplay willen maken, dus ik was op zoek naar karakters waarbij ik makkelijk een bewegend component kon nabootsen. Ik kwam al snel op Elden ring, maar na wat onderzoek had ik bedacht dat ik Malenia's vleugels wou maken. Eerst wou ik dat de vleugels zelf bewogen, maar dat bleek niet zo realistisch te zijn vanwege de grootte van de motors en accu die ik daarvoor nodig zou hebben. Dus had ik bedacht dat ik met servo's de vlinders wou nabootsen die op de vleugels van Malenia zitten. En met de kennis die ik deze hoorcollege had opgelopen ben ik aan de slag gegaan met het codere van de vlinders.


#include <Servo.h>

Servo servo1;

void setup() {
 Serial.begin(9600);

 pinMode(A0, INPUT);

 servo1.attach(6);

}

void loop() {
 int sensorValue = analogRead(A0);
 int servoPos = map(sensorValue, 0, 1023, 0, 180);
 servo1.write(servoPos);

 delay(50);

}




Step 2: Beginnen Code

Dit is de code voor het eerste vlinder concept. Met deze code wou ik ervoor zorgen dat de vlinders op een zo natuurlijk mogelijke manier bewogen. Hieronder staat de code met notities.

Animaties = De mogelijke animaties die een vlinder kan afpelen.

Iteraties = Hoe vaak een vlinder bepaalde animaties achter elkaar afspeelt.

Steps = Animatieframes

// Servo motor library importeren
#include <Servo.h>


// Servo motor aanmaken
Servo myservo;


// Type definiëren voor servodata
typedef struct servoData {
  uint8_t currentAnimationId;     // Animatietype
  uint8_t currentAnimationStep;   // Huidige animatieframe
  int currentStepWaitTime;        // Wachttijd van de huidige animatieframe
 
  uint8_t currentIteration;       // Huidige iteratie
  int currentIterationWaitTime;   // Huidige wachttijd tijdens iteratie
  uint8_t iterationCount;         // Aantal iteraties
  int iterationWaitTime;          // Wachttijd tussen iteraties


  uint8_t animationSpeedup;       // Animatie versneller


  int currentAnimationWaitTime;   // Wachttijd tussen animaties
} servoData;


// Animaties
const uint8_t animations[][2] =
                      {
                        {2,50},{4,50},{6,50},{8,50},{10,50},{12,50},{14,50},{16,15},{18,15},{20,15},{22,15},{24,15},{26,15},{28,15},{30,15},         // animation 1, {positie, wachttijd}
                        {32,15},{34,10},{36,10},{38,10},{40,10},{42,15},{44,15},{46,15},{48,15},{50,15},{52,50},{54,50},{56,50},{58,50},{60,50},     // animation 1  
                       
                        {60,50},{58,50},{56,50},{54,50},{52,50},{50,15},{48,15},{46,15},{44,15},{42,15},{40,10},{38,10},{36,10},{34,10},{32,15},     // animation 1
                        {30,15},{28,15},{26,15},{24,15},{22,15},{20,15},{18,15},{16,15},{14,50},{12,50},{10,50},{8,50},{6,50},{4,50},{2,50},         // animation 1

                        {2,50},{4,50},{6,50},{8,50},{10,50},{12,50},{14,50},{16,15},{18,15},{20,15},{22,15},{24,15},{26,15},{28,15},{30,15},         // animation 2
                        {32,15},{34,10},{36,10},{38,10},{40,10},                                                                                     // animation 2
                       
                        {40,10},{38,10},{36,10},{34,10},{32,15},{30,15},{28,15},{26,15},{24,15},{22,15},{20,15},{18,15},{16,15},{14,50},{12,50},     // animation 2
                        {10,50},{8,50},{6,50},{4,50},{2,50}                                                                                          // animation 2
                      };


// Frames per animatie
const uint8_t animationSettings[] = { 60, 40 };


// Startwaarden servo
servoData servosData[] = {
  { .currentAnimationId = 0, .currentAnimationStep = 0, .currentStepWaitTime = 0, .currentIteration = 0, .currentIterationWaitTime = 0, .iterationCount = 0, .iterationWaitTime = 0, .animationSpeedup = 1, .currentAnimationWaitTime = 0 }
};


// Berekent aantal aanwezige servo's
uint8_t numberOfServosConfigured = 1;


void setup()
{
  Serial.begin(115200);


  // Pin servo
  myservo.attach(9);


  // Lees analoge waarde van ingang 1 (deze is niet aangesloten en levert dus een willekeurige waarde op)
  int analogInputValue = analogRead(0);
 
  // Randomnummer genorator configureren
  randomSeed(analogInputValue);
}


void loop()
{


  // Loop over alle servo's heen
  for (uint8_t servoIndex = 0; servoIndex < numberOfServosConfigured; servoIndex++)
  {
    // Is de pauze tussen 2 animaties voorbij?
    if (servosData[servoIndex].currentAnimationWaitTime > 0)
    {
      servosData[servoIndex].currentAnimationWaitTime--; // Nee, tel wachttijd af met 1
    }
    else
    {
      // Is de wachttijd tussen frames voorbij?
      if (servosData[servoIndex].currentStepWaitTime > 0)
      {
        servosData[servoIndex].currentStepWaitTime--; // Nee, tel wachttijd af met 1
      }
      else
      {
        // Is de wachttijd tussen iteraties voorbij?
        if (servosData[servoIndex].currentIterationWaitTime > 0)
        {
          servosData[servoIndex].currentIterationWaitTime--; // Nee, tel wachttijd af met 1
        }
        else
        {
          // Checkt of alle frames voorbij zijn van de huidige animatie
          if (servosData[servoIndex].currentAnimationStep == animationSettings[servosData[servoIndex].currentAnimationId])
          {
            // Zo ja, zijn alle iteraties voorbij?
            if (servosData[servoIndex].currentIteration != servosData[servoIndex].iterationCount)
            {
              //Nee, selecteer de volgende iteratie
              servosData[servoIndex].currentIteration++;
              servosData[servoIndex].currentIterationWaitTime = servosData[servoIndex].iterationWaitTime; // Laad currentIterationWaittime met bewaarde iterationWaitTime
              servosData[servoIndex].currentAnimationStep = 0; // Animatie start bij eerste frame
            }
            else
            {
              // Animatie afgerond, maak nieuwe animatie aan


              // Bepaal wachttijd tussen 2 animaties
              servosData[servoIndex].currentAnimationWaitTime = random(4000, 8000);


              // Selecteer de voglende animatie
              servosData[servoIndex].currentAnimationId = random(0, 1);


              // Selecteer versnelling animatie
              servosData[servoIndex].animationSpeedup = random(1, 3);


              // Reset iteratie teller
              servosData[servoIndex].currentIteration = 0;
              // Bepaal hoeveelheid iteraties
              servosData[servoIndex].iterationCount = random(2, 4);
              // Bepaal wachttijd tussen 2 iteraties
              servosData[servoIndex].iterationWaitTime = random(100, 200);
              // Laad currentIterationWaittime met bewaarde iterationWaitTime
              servosData[servoIndex].currentIterationWaitTime = servosData[servoIndex].iterationWaitTime;


              // Animatie start bij eerste frame
              servosData[servoIndex].currentAnimationStep = 0;
            }
          }
          else
          {
            // Selecteer volgende frame


            // Bepaal positie animatieframe in lijst met animatie informatie
            int animationsPointer = 0;
            for(uint8_t animation = 0; animation < servosData[servoIndex].currentAnimationId; animation++)
            {
              // Skip stappen voorliggende animaties
              animationsPointer += animationSettings[animation];
            }
            // Selecteer volgende frame
            animationsPointer += servosData[servoIndex].currentAnimationStep;


            // Bepaal positie servo
            uint8_t stepSize = animations[animationsPointer][0];


            // Stuur servo aan
            myservo.write(stepSize);


            // Bepaal wachttijd tussen frames en deel deze door versnelling
            servosData[servoIndex].currentStepWaitTime = (animations[animationsPointer][1] / servosData[servoIndex].animationSpeedup);


            // Selecteer volgende frame
            servosData[servoIndex].currentAnimationStep++;
          }
        }
      }
    }
  }
// Wacht 1 miliseconde per stap
  delay(1);
}


Step 3: Meerdere Servo's

Nadat ik de code klaar had om één servo te laten bewegen, heb ik de code aangepast zodat ik meerdere servo's tegelijkertijd kan aansturen. Ook heb ik een servodriver (Adadruit_PWMServoDriver) toegevoegd, omdat ik uiteindelijk meer servo's nodig had dan dat er passen in een arduino uno. Ook zit er in deze iteratie van de code een extra animatie.


// Importeren driver voor ondersteunen meerdere servo's
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>


#define SERVOBASEOFFSET 300


// Ondersteuning meerdere servo's
Adafruit_PWMServoDriver servoDrivers[] = { Adafruit_PWMServoDriver(0x40) };


typedef struct servoData {
  uint8_t servoControllerId;
  uint8_t servoId;


  uint8_t currentAnimationId;
  uint8_t currentAnimationStep;
  int currentStepWaitTime;
 
  uint8_t currentIteration;
  int currentIterationWaitTime;
  uint8_t iterationCount;
  int iterationWaitTime;


  uint8_t animationSpeedup;


  int currentAnimationWaitTime;
} servoData;


const uint8_t animations[][2] =
                      {
                        {2,40},{4,40},{6,40},{8,40},{10,40},{12,40},{14,40},{16,15},{18,15},{20,15},{22,15},{24,15},{26,15},{28,15},{30,15},                // animation 1
                        {32,15},{34,10},{36,10},{38,10},{40,10},{42,15},{44,15},{46,15},{48,15},{50,15},{52,40},{54,40},{56,40},{58,40},{60,40},            // animation 1
                       
                        {60,40},{58,40},{56,40},{54,40},{52,40},{50,15},{48,15},{46,15},{44,15},{42,15},{40,10},{38,10},{36,10},{34,10},{32,15},            // animation 1
                        {30,15},{28,15},{26,15},{24,15},{22,15},{20,15},{18,15},{16,15},{14,40},{12,40},{10,40},{8,40},{6,40},{4,40},{2,40},                // animation 1

                        {2,40},{4,40},{6,40},{8,40},{10,15},{12,15},{14,15},{16,15},{18,10},{20,10},{22,10},{24,10},{26,15},{28,15},{30,15},                // animation 2
                        {32,15},{34,40},{36,40},{38,40},{40,40},                                                                                            // animation 2
                       
                        {40,40},{38,40},{36,40},{34,40},{32,15},{30,15},{28,15},{26,15},{24,10},{22,10},{20,10},{18,10},{16,15},{14,15},{12,15},            // animation 2
                        {10,15},{8,40},{6,40},{4,40},{2,40},                                                                                                // animation 2

                        {2,40},{4,40},{6,40},{8,40},{10,40},{12,40},{14,40},{16,40},{18,40},{20,40},{22,15},{24,15},{26,15},{28,15},{30,15},                // animation 3
                        {32,15},{34,10},{36,10},{38,10},{40,10},{42,15},{44,15},{46,15},{48,15},{50,15},{52,15},{54,15},{56,15},{58,15},{60,15},            // animation 3
                        {62,15},{64,15},{66,15},{68,15},{70,15},{72,15},{74,15},{76,15},{78,15},{80,15},{82,15},{84,15},{86,15},{88,15},{90,15},            // animation 3
                        {92,15},{94,15},{96,15},{98,15},{100,15},{102,40},{104,40},{106,40},{108,40},{110,40},{112,40},{114,40},{116,40},{118,40},{120,40}, // animation 3
                       
                        {120,40},{118,40},{116,40},{114,40},{112,40},{110,40},{108,40},{106,40},{104,40},{102,40},{100,15},{98,15},{96,15},{94,15},{92,15}, // animation 3
                        {90,15},{88,15},{86,15},{84,15},{82,15},{80,15},{78,15},{76,15},{74,15},{72,15},{70,15},{68,15},{66,15},{64,15},{62,15},            // animation 3
                        {60,15},{58,15},{56,15},{54,15},{52,15},{50,15},{48,15},{46,15},{44,15},{42,15},{40,15},{38,15},{36,15},{34,10},{32,15},            // animation 3
                        {30,15},{28,15},{26,15},{24,15},{22,15},{20,40},{18,40},{16,40},{14,40},{12,40},{10,40},{8,40},{6,40},{4,40},{2,40}                 // animation 3                        
                      };


// Extra animatie
const uint8_t animationSettings[] = { 60, 40, 120 };


// Meerdere servo's
servoData servosData[] = {
  { .servoControllerId = 0, .servoId = 0, .currentAnimationId = 0, .currentAnimationStep = 0, .currentStepWaitTime = 0, .currentIteration = 0, .currentIterationWaitTime = 0, .iterationCount = 0, .iterationWaitTime = 0, .animationSpeedup = 1, .currentAnimationWaitTime = 0 },
  { .servoControllerId = 0, .servoId = 1, .currentAnimationId = 0, .currentAnimationStep = 0, .currentStepWaitTime = 0, .currentIteration = 0, .currentIterationWaitTime = 0, .iterationCount = 0, .iterationWaitTime = 0, .animationSpeedup = 1, .currentAnimationWaitTime = 0 },
  { .servoControllerId = 0, .servoId = 2, .currentAnimationId = 0, .currentAnimationStep = 0, .currentStepWaitTime = 0, .currentIteration = 0, .currentIterationWaitTime = 0, .iterationCount = 0, .iterationWaitTime = 0, .animationSpeedup = 1, .currentAnimationWaitTime = 0 }
};


// Aantal servodrivers
uint8_t numberOfServoControllersConfigured = 1;
// Aantal servo's
uint8_t numberOfServosConfigured = 3;
// Aantal animaties
uint8_t numberOfAnimationsAvailable = 3;


void setup()
{
  Serial.begin(115200);


  int analogInputValue = analogRead(0);
  randomSeed(analogInputValue);


  // Klaarzetten servo's
  for (uint8_t servoControllerIndex = 0; servoControllerIndex < numberOfServoControllersConfigured; servoControllerIndex++)
  {
    servoDrivers[servoControllerIndex].begin();
    servoDrivers[servoControllerIndex].setOscillatorFrequency(27000000);
    servoDrivers[servoControllerIndex].setPWMFreq(50);
  }
}


void loop()
{
  for (uint8_t servoIndex = 0; servoIndex < numberOfServosConfigured; servoIndex++)
  {
    if (servosData[servoIndex].currentAnimationWaitTime > 0)
    {
      servosData[servoIndex].currentAnimationWaitTime--;
    }
    else
    {
      if (servosData[servoIndex].currentStepWaitTime > 0)
      {
        servosData[servoIndex].currentStepWaitTime--;
      }
      else
      {
        if (servosData[servoIndex].currentIterationWaitTime > 0)
        {
          servosData[servoIndex].currentIterationWaitTime--;
        }
        else
        {
          if (servosData[servoIndex].currentAnimationStep == animationSettings[servosData[servoIndex].currentAnimationId])
          {
           
            if (servosData[servoIndex].currentIteration != servosData[servoIndex].iterationCount)
            {
              servosData[servoIndex].currentIteration++;
              servosData[servoIndex].currentIterationWaitTime = servosData[servoIndex].iterationWaitTime;
              servosData[servoIndex].currentAnimationStep = 0;
            }
            else
            {
              servosData[servoIndex].currentAnimationWaitTime = random(4000, 8000);


             
              servosData[servoIndex].currentAnimationId = random(0, numberOfAnimationsAvailable);


              servosData[servoIndex].animationSpeedup = random(1, 3);


              servosData[servoIndex].currentIteration = 0;
              servosData[servoIndex].iterationCount = random(1, 4);
              servosData[servoIndex].iterationWaitTime = random(100, 200);
              servosData[servoIndex].currentIterationWaitTime = servosData[servoIndex].iterationWaitTime;


             
              servosData[servoIndex].currentAnimationStep = 0;
            }
          }
          else
          {
            int animationsPointer = 0;
            for(uint8_t animation = 0; animation < servosData[servoIndex].currentAnimationId; animation++)
              animationsPointer += animationSettings[animation];
            animationsPointer += servosData[servoIndex].currentAnimationStep;


            uint8_t stepSize = animations[animationsPointer][0];


            // Stuur servo aan via servodriver
            servoDrivers[servosData[servoIndex].servoControllerId].setPWM(servosData[servoIndex].servoId, 0, SERVOBASEOFFSET + stepSize);


            servosData[servoIndex].currentStepWaitTime = (animations[animationsPointer][1] / servosData[servoIndex].animationSpeedup);


            servosData[servoIndex].currentAnimationStep++;
          }
        }
      }
    }
  }
  // Wacht 1 miliseconde per stap
  delay(1);
}

Step 4: Eerste Concept Vlinder

Eerst had ik bedacht door middel van een touwtje tussen de servo en vleugel toe te voegen, dat ik met één servo twee vleugels kon besturen. De reden waarom ik het met één servo per vlinder wou maken, is dat het dan veel minder stroom per vlinder gebruikt. In de praktijk viel dit concept tegen. Het touwtje bleek niet stevig genoeg om de vleugels op en neer te laten bewegen. Vanwege de kleine schaal van de vlinders was het ook erg moeilijk om in elkaar te zetten. Ik moest dus een nieuwe manier verzinnen waarop ik de vleugels kon laten bewegen.

Step 5: Tweede Concept Vlinder

Uiteindelijk ben ik op het idee gekomen om twee servo's per vlinder te gebruiken. Minder optimaal, maar het bracht wel het resultaat die ik nodig had. Verder heb ik nog stokjes ge-3D-print en aan de servo gelijmd, zodat ik daar straks de vlinders overheen kan schuiven.

Step 6: Monteren Vlinders Op MDF

Nu dat de vlinders werkten ben ik ze gaan monteren op een MDF plaat. het idee was om alle hardware op de achterkant te plakken en de voorkant mooi af te werken. Door met een boormachine gaten in het MDF de boren kunnen de draadjes van de servo erin zodat ze uit het zicht zijn. Ook heb ik uit een lat hout 3 houten pootjes (11 cm x 2 cm x 6 cm) gesneden zodat de onderkant vrij is voor de andere onderdelen.

Step 7: Vlinders Aansluiten

Nadat alle servo's op de voorkant van het MDF waren gemonteerd ben ik aan de slag gegaan met alle onderdelen aansluiten. Alle onderdelen aan de onderkant zijn door middel van tie wraps om plakzetels aangebracht. Ook heb ik een pushbutton aangebracht zodat ik straks de vlinders op pauze kan zetten. (De tweede servodriver is adres 41.)

Step 8: Interactie En Twee Servo's Per Vlinder

Dit is de code voor één vlinder die bestaat uit 2 servo's. De pushbutton functionaliteit zit in deze iteratie van de code ook verwerkt. Door op de pushbutton te klikken stoppen de servo's. Deze code is ook de uiteindelijke code die ik gebruik voor mijn project.

#include <Wire.h>


// Importeer pushbutton functionaliteit
#include <Pushbutton.h>
#include <Adafruit_PWMServoDriver.h>


#define SERVOBASEOFFSET 325


// Maak pusbutton aan en koppel deze aan A0
Pushbutton button(A0);


// Op de ene 10 en op de andere 6 servo's
Adafruit_PWMServoDriver servoDrivers[] = { Adafruit_PWMServoDriver(0x40), Adafruit_PWMServoDriver(0x41) };


// Staan de animaties aan?
bool sequenceOn = true;


typedef struct servoData {
  uint8_t servoControllerId;
  uint8_t servoIds[2];


  uint8_t currentAnimationId;
  uint8_t currentAnimationStep;
  int currentStepWaitTime;
 
  uint8_t currentIteration;
  int currentIterationWaitTime;
  uint8_t iterationCount;
  int iterationWaitTime;


  uint8_t animationSpeedup;


  int currentAnimationWaitTime;
} servoData;


const uint8_t animations[][2] =
                      {
                        {2,40},{4,40},{6,40},{8,40},{10,40},{12,40},{14,40},{16,15},{18,15},{20,15},{22,15},{24,15},{26,15},{28,15},{30,15},                // animation 1
                        {32,15},{34,10},{36,10},{38,10},{40,10},{42,15},{44,15},{46,15},{48,15},{50,15},{52,40},{54,40},{56,40},{58,40},{60,40},            // animation 1
                       
                        {60,40},{58,40},{56,40},{54,40},{52,40},{50,15},{48,15},{46,15},{44,15},{42,15},{40,10},{38,10},{36,10},{34,10},{32,15},            // animation 1
                        {30,15},{28,15},{26,15},{24,15},{22,15},{20,15},{18,15},{16,15},{14,40},{12,40},{10,40},{8,40},{6,40},{4,40},{2,40},                // animation 1

                        {2,40},{4,40},{6,40},{8,40},{10,15},{12,15},{14,15},{16,15},{18,10},{20,10},{22,10},{24,10},{26,15},{28,15},{30,15},                // animation 2
                        {32,15},{34,40},{36,40},{38,40},{40,40},                                                                                            // animation 2
                       
                        {40,40},{38,40},{36,40},{34,40},{32,15},{30,15},{28,15},{26,15},{24,10},{22,10},{20,10},{18,10},{16,15},{14,15},{12,15},            // animation 2
                        {10,15},{8,40},{6,40},{4,40},{2,40},                                                                                                // animation 2

                        {2,40},{4,40},{6,40},{8,40},{10,40},{12,40},{14,40},{16,40},{18,40},{20,40},{22,15},{24,15},{26,15},{28,15},{30,15},                // animation 3
                        {32,15},{34,10},{36,10},{38,10},{40,10},{42,15},{44,15},{46,15},{48,15},{50,15},{52,15},{54,15},{56,15},{58,15},{60,15},            // animation 3
                        {62,15},{64,15},{66,15},{68,15},{70,15},{72,15},{74,15},{76,15},{78,15},{80,15},{82,15},{84,15},{86,15},{88,15},{90,15},            // animation 3


                        {90,15},{88,15},{86,15},{84,15},{82,15},{80,15},{78,15},{76,15},{74,15},{72,15},{70,15},{68,15},{66,15},{64,15},{62,15},            // animation 3
                        {60,15},{58,15},{56,15},{54,15},{52,15},{50,15},{48,15},{46,15},{44,15},{42,15},{40,15},{38,15},{36,15},{34,10},{32,15},            // animation 3
                        {30,15},{28,15},{26,15},{24,15},{22,15},{20,40},{18,40},{16,40},{14,40},{12,40},{10,40},{8,40},{6,40},{4,40},{2,40}                 // animation 3                        
                      };


const uint8_t animationSettings[] = { 60, 40, 90 };


// 2 servo's per servodata
servoData servosData[] = {
  { .servoControllerId = 0, .servoIds = { 0, 1 }, .currentAnimationId = 0, .currentAnimationStep = 0, .currentStepWaitTime = 0, .currentIteration = 0, .currentIterationWaitTime = 0, .iterationCount = 0, .iterationWaitTime = 0, .animationSpeedup = 1, .currentAnimationWaitTime = 0 },
  { .servoControllerId = 0, .servoIds = { 2, 3 }, .currentAnimationId = 0, .currentAnimationStep = 0, .currentStepWaitTime = 0, .currentIteration = 0, .currentIterationWaitTime = 0, .iterationCount = 0, .iterationWaitTime = 0, .animationSpeedup = 1, .currentAnimationWaitTime = 0 },
  { .servoControllerId = 0, .servoIds = { 4, 5 }, .currentAnimationId = 0, .currentAnimationStep = 0, .currentStepWaitTime = 0, .currentIteration = 0, .currentIterationWaitTime = 0, .iterationCount = 0, .iterationWaitTime = 0, .animationSpeedup = 1, .currentAnimationWaitTime = 0 },
  { .servoControllerId = 0, .servoIds = { 6, 7 }, .currentAnimationId = 0, .currentAnimationStep = 0, .currentStepWaitTime = 0, .currentIteration = 0, .currentIterationWaitTime = 0, .iterationCount = 0, .iterationWaitTime = 0, .animationSpeedup = 1, .currentAnimationWaitTime = 0 },
  { .servoControllerId = 0, .servoIds = { 8, 9 }, .currentAnimationId = 0, .currentAnimationStep = 0, .currentStepWaitTime = 0, .currentIteration = 0, .currentIterationWaitTime = 0, .iterationCount = 0, .iterationWaitTime = 0, .animationSpeedup = 1, .currentAnimationWaitTime = 0 },
  { .servoControllerId = 1, .servoIds = { 0, 1 }, .currentAnimationId = 0, .currentAnimationStep = 0, .currentStepWaitTime = 0, .currentIteration = 0, .currentIterationWaitTime = 0, .iterationCount = 0, .iterationWaitTime = 0, .animationSpeedup = 1, .currentAnimationWaitTime = 0 },
  { .servoControllerId = 1, .servoIds = { 2, 3 }, .currentAnimationId = 0, .currentAnimationStep = 0, .currentStepWaitTime = 0, .currentIteration = 0, .currentIterationWaitTime = 0, .iterationCount = 0, .iterationWaitTime = 0, .animationSpeedup = 1, .currentAnimationWaitTime = 0 },
  { .servoControllerId = 1, .servoIds = { 4, 5 }, .currentAnimationId = 0, .currentAnimationStep = 0, .currentStepWaitTime = 0, .currentIteration = 0, .currentIterationWaitTime = 0, .iterationCount = 0, .iterationWaitTime = 0, .animationSpeedup = 1, .currentAnimationWaitTime = 0 },
};


uint8_t numberOfServoControllersConfigured = 2;
uint8_t numberOfServosConfigured = 8;
uint8_t numberOfAnimationsAvailable = 3;


void setup()
{
  Serial.begin(115200);


  int analogInputValue = analogRead(0);
 
  randomSeed(analogInputValue);


  for (uint8_t servoControllerIndex = 0; servoControllerIndex < numberOfServoControllersConfigured; servoControllerIndex++)
  {
    servoDrivers[servoControllerIndex].begin();
    servoDrivers[servoControllerIndex].setOscillatorFrequency(27000000);
    servoDrivers[servoControllerIndex].setPWMFreq(50);
  }
}


void loop()
{
  // Haal pushbutton status op en verander of animaties aan of uit staan
  if (button.getSingleDebouncedRelease()) sequenceOn = !sequenceOn;


  // Animaties staan aan?
  if (sequenceOn)
  {
    // Behandel animaties
    for (uint8_t servoIndex = 0; servoIndex < numberOfServosConfigured; servoIndex++)
    {
      if (servosData[servoIndex].currentAnimationWaitTime > 0)
      {
        servosData[servoIndex].currentAnimationWaitTime--;
      }
      else
      {
        if (servosData[servoIndex].currentStepWaitTime > 0)
        {
          servosData[servoIndex].currentStepWaitTime--;
        }
        else
        {
          if (servosData[servoIndex].currentIterationWaitTime > 0)
          {
            servosData[servoIndex].currentIterationWaitTime--;
          }
          else
          {
            if (servosData[servoIndex].currentAnimationStep == animationSettings[servosData[servoIndex].currentAnimationId])
              // handle iteration ?
              if (servosData[servoIndex].currentIteration != servosData[servoIndex].iterationCount)
              {
                servosData[servoIndex].currentIteration++;
                servosData[servoIndex].currentIterationWaitTime = servosData[servoIndex].iterationWaitTime;
                servosData[servoIndex].currentAnimationStep = 0;
              }
              else
              {
                servosData[servoIndex].currentAnimationWaitTime = random(4000, 8000);


                // select next animation randomly
                servosData[servoIndex].currentAnimationId = random(0, numberOfAnimationsAvailable);


                servosData[servoIndex].animationSpeedup = random(1, 3);


                servosData[servoIndex].currentIteration = 0;
                servosData[servoIndex].iterationCount = random(1, 4);
                servosData[servoIndex].iterationWaitTime = random(100, 200);
                servosData[servoIndex].currentIterationWaitTime = servosData[servoIndex].iterationWaitTime;


                // prepare servoData for new animation
                servosData[servoIndex].currentAnimationStep = 0;
              }
            }
            else
            {
              int animationsPointer = 0;
              for(uint8_t animation = 0; animation < servosData[servoIndex].currentAnimationId; animation++)
                animationsPointer += animationSettings[animation];
              animationsPointer += servosData[servoIndex].currentAnimationStep;


              uint8_t stepSize = animations[animationsPointer][0];
              // Voor linker en rechter vleugel
              for (uint8_t serviceIdIndex = 0; serviceIdIndex < 2; serviceIdIndex++)
              {
                servoDrivers[servosData[servoIndex].servoControllerId].setPWM(servosData[servoIndex].servoIds[serviceIdIndex], 0, SERVOBASEOFFSET + stepSize);
              }


              servosData[servoIndex].currentStepWaitTime = (animations[animationsPointer][1] / servosData[servoIndex].animationSpeedup);


              servosData[servoIndex].currentAnimationStep++;
            }
          }
        }
      }
    }
  }


  delay(1);
}

Step 9: Afwerking

Nadat alle onderdelen op hun plaats zaten, begon ik met het tekenen van vlinders in Photoshop. Tijdens het tekenen had ik een referentie van Malenia's vleugels zodat de kleuren en vorm zo accuraat mogelijk zijn. Daarna maakte ik een bestand aan per kleur en achterkant en begon ik met het uitprinten van de vlinders op dik A3 papier. Met hot glue plakte ik de voor- en achterkanten van de vlinders tegen elkaar en schoof ze over het ge-3D-printen stokjes aan de servo's. Voor het bedekken van de plaat zelf had ik de bruine en donkergroene vlinders op stickerpapier geprint zodat ik ze gelijk op het MDF kon plakken. Nadat alle vlinders aan de servo's en op de plaat zaten, ben ik met acrylverf over de randen van de vlinders gegaan met zwarte verf zodat het witte van het papier niet meer te zien is. Ook heb ik wat kleuren verf verdunt met water en op de vlinders geschilderd zodat er meer dimensie in de vleugels zit. Soms zag je nog wat servo onderdelen door de vlinder heen en die heb ik ook geschilderd.

Step 10: Compleet

En toen was het af! In deze video's laat ik zien hoe ze bewegen.

Step 11: Conclusie

Tijdens dit project heb ik erg veel geleerd over arduino's en wat je er allemaal mee kan doen. Het monteren en afwerken van het project vond ik het leukst, omdat dan echt alles mooi samen komt. Het coderen vind ik altijd wel minder leuk, maar doordat je snel feedback krijgt vanuit je onderdelen vond ik het wel leuker dan normaal. Ik ga verder dit prototype uitwerken zodat ik straks een volledige Malenia, goddess of rot cosplay heb.