Airborne Adornments Lightweight compagnions BA Projekt Tobias Bieri XS Schmuck 2023
INHALTSVERZEICHNIS
- ANFÄNGE : INSPIRATIONSQUELLE UND ZIELSETZUNG
- STUDIUM DER MECHANIK
- AUFTRIEB
- FORMSTUDIEN
- ZUSAMMENARBEIT MIT SARNA PLASTEC AG
- ENDFORM UND SCHNITTMUSTER
- FLÜGEL
- STEUERUNG
- TECHNIK
- PROGRAMMIERUNG
- FOTOS
Inspirationsquelle: Putzer und Pilotfische, wie Schmuck begeleiten sie die grösseren Tiere.
ZIEL: DAS ERSTELLEN EINES FLIEGENDEN OBJEKTES, DAS EINE INTERAKTIVITÄT
ERMÖGLICHT UND SICH DURCH EINE FANTASIEVOLLE ÄSTHETIK AUSZEICHNET.
- OUTFIT EXTENSION
- DAS INNERE KIND ANSPRECHEN UND FANTASIEWELTEN ENTSTEHEN LASSEN.
HAUPTFRAGEN:
WIE KÖNNEN NATÜRLICHE BEWEGUNGEN SIMULIERT WERDEN, UM EINE
LEBENDIGKEIT ZU VERMITTELN?
- STUDIUM VON MECHANISCHEN PRINZIPIEN (KINETIK )
- BIONISCHE ANSÄTZE ( FINRAY EFFEKT)
WIE BRINGE ICH DAS OBJEKT ZUM FLIEGEN ?
- AUFTRIEB DURCH THERMIK
- AUFTRIEB DURCH HELIUM
- STUDIUM AERODYNAMIK
-ANTRIEB DURCH UNABHÄNGIGE ENERGIEQUELLEN WIE WIND
-INTERNE ENERGIEQUELLE (AKKU )
WIE FUNKTIONIERT DIE INTERAKTION / STEUERUNG?
- LENKUNG DURCH DRITTE
- AUTOPILOT
-GESTIK
STUDIUM MECHANISCHER PRINZIPIEN
In der ersten Woche habe ich mich vor allem mit mechanischen Prinzipien und Kinetik befasst. Da ich auf diesem Gebiet keine Erfahrung habe, wollte ich mich mit den Grundsätzen vertraut machen, um die Machbarkeit von späteren Konzepten besser einschätzen zu können. Ich habe mich darauf konzentriert, einen möglichst natürlich wirkenden Flügelschlag mithilfe eines einzelnen Motors zu erzeugen.
WIE FLIEGT DAS OBJEKT?
AUFTRIEB DURCH HELIUM
AUFTRIEB:
Dichte Helium = 0.16 Kg/m3
Dichte Luft = 1.2 Kg/ m3
Ausserdem spielen Temperatur und Höhe eine nicht geringe Rolle. All meine Berrechnungen gelten für die zu erwartenden Temperaturen in Innenräumen auf 420 Meter
über Meer ( Emmenbrücke)
Referenzliste für Schätzungen. ( Latexballon )
ÜBERLEGUNGEN:
Helium ist relativ teuer ( Bei Hornbach 130 Fr für 10L = 2m3. Aufgrund der kleinen Molekülen entweicht Helium schnell aus Latexballons. Zudem ist Latex schwer. Flugzeit ca 2 Stunden mit maximal Last.
Aluminium Beschichtete Pe Folie hält länger ( Ca.1 Tag ) allerdings gestaltet sich das schweissen einer Dichten
Gasbedarf Flugzeit Tragkraft 23 cm 0,007 m³ ca. 6 Std ca. 0 g 25 cm 0,008 m³ ca. 6 Std ca. 0 g 28 cm 0,012 m³ ca. 12 Std ca. 2 g 30 cm 0,015 m³ ca. 20 Std ca. 20 g 60 cm 0,100 m³ ca. 36 Std ca. 90 g 80 cm 0,400 m³ ca. 48 Std ca. 220 g 120 cm 1,000 m³ ca. 72 Std ca. 880 g 160 cm 2,000 m³ ca. 96 Std ca. 1800 g 210 cm 5,000 m³ ca. 168 Std ca. 4500 g
Ballongröße
Hülle Schwierig.
FORMSTUDIE 1
Die Struktur zur Formgebung inspiriert sich an Topologie optimierten bionischem Design. Die Streben haben eine Stärke von 3 mm. Verwendetes
Material: Lightweigt PLA = 0,6 g/cm3
Volumen der Struktur = 281 cm3
Totalgewicht = 186 g
Flossen aus Styropor. Dichte 0.06 g/ cm3
Totalgewicht etwa 20g
Da der Auftrieb exponentiell zu dem Durchmesser des Ballons wächst, habe ich mich für einen grossen Ballon mit 90 cm Durchmesser entschieden. Errechneter Auftrieb = ca. 390 g
Pro: Der Latexballon kann so gekauft werden.
FLUGFÄHIGKEIT:
LATEX BALLON 25 G +RAHMEN 186 G + FLOSSEN 20 G + ELEKTRONIK 50 G = 263 G
DAMIT DER BALLON STABIL SCHWEBT, MUSS DER SCHWERPUNKT UNTEN AM OBJEKT LIEGEN. DAS BEDEUTET, DIE OBERE HÄLFTE DARF NICHT MEHR WIEGEN ALS DIE UNTERE UND DER SCHWANZ
MUSS AUSTARIERT WERDEN. AUCH WENN EIN GROSSTEIL DER ELEKTRONIK UNTEN ANGEBRACHT WERDEN KANN, IST DER OBERE TEIL DENNOCH ZU SCHWER. DIE KONSTRUKTION KANN AUCH NICHT DÜNNER GESTALTET WERDEN, DA SIE DANN NICHT MEHR HERSTELLBAR WÄRE.
EINE ALTERNATIVE BIETEN CARBONSTANGEN, DIE SIND ABER TEUER.
Formstudie 2
Inspiriert von Riemenfisch. Hülle aus Aluminiumbeschichteter Folie. ( Selber verschweisst)
Volumen Gaszelle =0.028m3
Auftrieb=30 g
Flugfähigkeit.
Die Hülle würde insgesamt 20 g wiegen. Bei einem Auftrieb von 30 g bleiben noch 10 g für Flossen und Elektronik, was nicht umsetzbar ist.
130 cm 40cm
Leichtgewichts Materialreserche
Styropor = 0.06 g/cm3
Sikablock m80 = 0.08 g/cm3
Balsaholz = 0.15-0.3g/cm3
Fichtenholz = 0.6g/cm3
Leichte Verarbeitung
Stabiler als Polyuteranschaum / Teuer
e Sun Lightweigt Filament = 0.6 g/cm3 mögliche wandstärke 0.4 mm, Frei formbar
Pla = 1.2 g/cm3
KFC =1.5 g/cm3 extrem Stabil
Aluminium Rettungsdecke 25g/m2
Solafirm Pet Folie 18-20g /m2
Formstudie 3 Orka
Gesamtlänge 160cm
Gaszelle 140cm
Volumen 0.21m3
Auftrieb ca.200 g
Die Gaszelle wird aus aluminiumbeschichteter Folie selbst gemacht. Sie ist umgeben von einem Rahmen aus Hartschaum, an der Technik und Flossen befestigt sind. Da der Rahmen an der Unterseite verläuft, sollte eine Austarierung der Schwanzflosse kein Problem sein. Ich bin skeptisch, was die Machbarkeit der Hülle angeht. Der Vortriebt wird durch einen kleinen Propeller im Rahmen erzeugt. Schwanz und Brustflossen dienen als Höhen und Seitenruder.
Vorteil ist, dass die Technik im Rahmen verbaut und getestet werden kann.
Rahmen
Volumen ca 400cm3
Erstellt in Sikablock m80 ( 0.08g/cm3 ) = 32 g
Flugfähikeit
Rahmen 32g + 50g Folie + Flossen ( Folie+ Sika/ Balsa) 60 g +30 g Elektonik = 172g damit würden 28g Spielraum Verbleiben.
Probleme: Herstellung der Gaszelle/ Kolorierung Relativ gross
BESUCH IN DER SARNA PLASTEC AG
Spezialist für Folientechnik
Der Besuch war sehr aufschlussreich, der Abteilungsleiter Daniel Bucher ist sehr hilfsbereit und bietet mir seine Mithilfe bei dem Verschweissen der Hülle an. Die Firma Sarna Plastec hat sich auf maschinelle Fertigung, vor allem mittels Hochfrequenzschweissen spezialisiert. (Prinzip ähnlich einer Induktionsplatte oder einer Mikrowelle. Das heisst allerdings auch, dass sich die Auswahl an Materialien für die Gaszelle reduziert, möglich sind PVC, TPU und PET
Da mir nun in der Formgebung der Gaszelle mehr Optionen zur Verfügung stehen, muss ich das Designkonzept anpassen. TPU würde sich zum Beispiel anbieten, jedoch nur in transparenter Form, die Färbung oder Bedruckung wäre im Rahmen dieser Bachelor Arbeit zu teuer. Auch hat TPU eine relativ hohe Dichte von 1.22gr/cm2. Was es einerseits perfekt für den Einsatz mit Helium macht, da es dies relativ gut hält, anderseits aber auch eine Menge Gewicht bedeutet.
Nächste Schritte:
Damit Daniel mir den Prototypen schweissen kann muss ich ihm ein Schnittmuster vorbereiten. Da ich in der Formgebung der Gaszelle nun mehr möglichkeiten habe will ich mir das Gewicht eines Gestells wie beim Kugelfisch sparen indem ich die Gaszelle bereits so Forme dass sich ein fisch ergibt. ( Konkret habe ich mich für einen Kofferfisch entschieden.
Konkret bedeutet dies:
Schritt 1:
Erstellen eines 3D Models ohne geolte Flächen ( Da diese probleme bei der Abwicklung erzeugen würden ), Ziel so wenig Schweissnähte wie möglich, Laschen für Elektronik und Flossen bereits intergieren. Rollenbreite von 1.35m nicht überschreiten.
Schritt 2:
Volumen, Foliengewicht und Auftrieb berrechnen, Materialauswahl treffen. ( mindestens 100gr für Flossen und Elektronik einberrechnen)
Schritt 3:
Stimmt die Form und der Auftrieb 3D Render erstellen, ( Blender Tool aufblasen um zu simulieren wie sich die Form unter Druck verhält. Dann Schnittmuster erstellen und TFX File An Daniel schicken.
Schritt 4:
Erstellung Prototyp n.1
Nebenbei: Bemalungstests durchführen.
Sketches
Wal-ähnlicher Fisch 160cm
Volumen 0.3309m3
Luftdichte: 1,225kg m3 = 0.405kg
Heliumdichte: 0.178 kg m3 = 0.0591kg
Tragkraft ohne Hülle = 346 g
Oberfläche: 27013 cm2
Dichte 1.22gcm3, Dicke: 0.05mm = 164 g
346 - 164 = 182g
Schnittmuster
Das Schnittmuster wurde so erstellt, dass möglichst wenig Schweissnähte benötigt werden, des Weiteren sind die Schweissnähte so ausgelegt, dass sie als optisches Element zur Formgebung beitragen (Seiten) und unten eine Befestigungsmöglichkeit für Elektronik und Gewichte zur Austarierung sowie vielleicht zusätzliche Flossen bieten. Das Schnittmuster wurde mithilfe eines 3D Drucks mit der Skalierung 1:15 erstellt. Bevor ich das Schnittmuster an die Sarna Plastec AG weitergeleitet habe, wurde die Form mithilfe eines Stoffmodells überprüft.
Flossen
Die Flossen mussten steif genug sein, um eine akkurate Lenkung zu ermöglichen, zugleich sollten sie so leicht wie möglich sein und ein Gesamtgewicht von 50 g nicht überschreiten. Um eine natürliche Erscheinung zu erhalten, ist eine leichte Flexibilität erforderlich.
Dies wurde mit einer Kombination aus 3D Druck, Plastikfolie und Karbonstangen erreicht.
70cm 50cm
Inspirationsquelle
3D Druck zur Formgestaltung und als Rahmen für den Karbon rahmen, Material: E-Sun Lightweigt PLA (250
Karbonrahmen
PET Folie 0.05mm
3D Druck Pla Lightweight
Schwanzflosse vs.2
Der Ansatz ist korrekt, allerdings bringt der SG90 Servo nicht genug Kraft auf, die Flosse zuverlässig zu bewegen.
Drehegelenk
Aufhängung am Ballong
Brustflosse
Dieser Ansatz hat von Anfang an gut funktioniert, das Drehgelenk nimmt das Gewicht des Flügels auf und der Servo hat keine Mühe, die Flosse zu drehen.
Stabilisisator
Drehgelenk zur Stabilisierung und um die Last vom Servo zu nehmen
Befestigung direkt am Ballon in einer Tasche, ermöglicht eine gewisse Flexibilität für eine natürliche Bewegung
Schwanzflosse vs.3
- Vergrösserte Hebelkraft
- unterstützung durch Feder
STEUERUNG / ELEKTRONIK
ein Vortrieb, kann sowohl durch Bewegung (Flossenschlag) als auch durch Rotoren erzeugt werden.
Zu beachten ist dabei, dass die Stabilisierung des Objekts durch Rotoren einfacher ist als durch Flossenschlag. Ziel ist eine möglichst natürliche Bewegungsästhetik. Auch sollten störende Geräusche minimiert werden. Allerdings ist Gewicht das Hauptproblem. Ich habe mich dazu entschieden, mir bei der Elektronik und Programmierung Hilfe zu holen, da ich erfahrungsgemäss dort aufgrund meiner fehlenden Kenntnisse am meisten Zeit verliere. Thomas Knüsel aus dem Media-Dock hat sich bereit erklärt, mich dabei zu unterstützen.
Sketch eines Stystems unter 10g
Nach Vorbild eines Micro Rc Flugzeugs.
Idee:
durch Infrarot Distanzsensoren kann das Objekt Kollision sicher gemacht werden. Da der Auftrieb durch Helium gegeben ist, wäre braucht es nur kurze Steuerungsimpulse um das Objekt zu lenken. Dadurch kann auch eine Interaktion zwischen Objekt und Mensch entstehen, da das Objekt auf äussere Einflüsse reagiert.
Leider hat sich herausgestellt, dass die Verwendung dieser Sensoren nicht möglich ist. Die kleinen Sensoren, die ich in Spielzeug gefunden habe, sind nicht mit der restlichen Elektronik kompatibel. Kompatible Sensoren gäbe es zwar, aber diese sind zu schwer.
1.2g
1.5g Micro
Micro
Micro Reciever (3-4 Kanal)
1S Lipo Batterie 50 mAh
servo 2g
servo 2g
Brushless motor 3g
VERSCHIEDENE ANTRIEB SYSTEME
1: Vortrieb durch Rotor. Flossen dienen als Als Lenkruder. ?
2: Vortrieb und Lenkung durch Flossenschag ( Rückflosse) Durch eine verschiebung des Schwerpunkts erfolgt die ausrichtung nach oben oder unten. ?
3: Auftrieb und Vortrieb durch Flossenschlag ?
Die Ziele die ich zusammen mit Thomas definiert habe sind also:
- Maximale Gewichtsreduktion
- Adaptiver Reciever ( ESP NOW ) kann als Sender unf Empfänger genutzt werden und ermöglicht die Steuerung verschiedener Motoren.
- Steuerung erfolgt Manuel ( Vieleicht mittels Drucksensoren die auf der Haut angebracht werden um aunauffälliges Steuern zu ermöglichen ohne eine Fernsteuerung halten zu müssen
- Thomas beschäftigt sich mit den ESP Now Prozessoren und hilft bei der programmierung wärend ich mich um die Mechanik, Flossen und Fine-Tuning beschäftige.
Wir arbeiten mit Folgenden Komponenten.
- Arduino ESP NOW ( X2)
- Micro Servos 2 und 9g
- Micro Brushless motor
- Druck/ Bend Sensoren
- Aktuatoren
- Lipo Akku
Steuerungs-test mit Arduino uno
Made with Tinkercad®
Title: Incredible Curcan-Stantia
Date: 8.4.2023, 22:01:08 Sheet: 1/1
A B C D E 1 2 3 4 5 6 A B C D E
R5 10k R3 10k R1 R2 R6 10k R4 PWR SIG GND SERVO1 U1_5V U1_GND A0 A1 A2 A3 A4 A5 IOREF RES VIN 5V 3.3V AREF GND RX TX D2 D13 D3 D4 D5 D6 D7 D8 D9 D10 D11 D12 SDA SCL Arduino UNO U1 PWR SIG GND SERVO3 PWR SIG GND SERVO2 R10 10k R8 R7 R9 10k U1_GND U1_GND U1_A1 U1_A1 U1_A0 U1_A0 U1_5V U1_5V U1_A2 U1_A2 U1_D9 U1_D9
Steuerungsprinzip vs.1
Gyrosensor
Pressure sensor 1
Pressure sensor 2
Pressure sensor 3
Servo 1 = A0
Servo 2 = A1
servo 3 = A2
Vorwärts = Sensor 1 = Servo 1 ( A0)-- Loop( -45,+45)
Links = Sensor 2 = Servo1 (A0)---Loop( -45, +45) + Servo2( A1) ( 45)
Rechts = Sensor 3 = Servo1 (A0)---Loop( -45, +45) + Servo3( A2) ( 45)
Auf = Gyrosensor(+127) = Servo1(A0) (-45)+ Servo 2(-45)+Servo 3(-45)
Ab = Gyrosensor (-127) = Servo1(A0)(+45)+ Servo2 (+45)+Servo 3 (+45)
Das ESP 32 hat nur 4 analog Inputs da wir aber 5 Inputs brauchen (vorwärts. Auf, ab, links, rechts) muss ein Pin 2 Inputs abdecken. Die Lösung ist ein Gyro-Sensor mit 3 Achsen (wir verwenden aber nur eine. )
Programmierung des Esp32 ( Sender)
Das programmieren war nur Dank Thomas möglich ( ein riesiges Dankeschön !)
FUNKTIONSPRINZIP SENDER
Als Basis benutzen wir das QT PY ESP32-S2, Die Wahl viel auf diese Board wegen des Formfaktors und der Möglichkeit einer direkten WIFI Verbindung zu dem Empänger ( XIAO32-S2) herzustellen. Angeschlsossen Elemente bestehen in 3 Drucksensoren und einem Gyrosensor sowie einem LiPO Akku.
Der Code lässt sich in mehrere Hauptbereiche gliedern, Bibliotheken und globale Variablen: Am Anfang des Codes werden verschiedene Bibliotheken importiert, die für die Kommunikation mit den Sensoren und die Kommunikation über ESP-NOW benötigt werden. Es werden auch einige globale Variablen und Strukturen erstellt, die später im Code verwendet werden.
Funktionen zur Sensorwertanpassung: mapAndAdjustPressureSensorValues und mapAndAdjustAccelerometerSensorValues sind Funktionen, die zum Anpassen und Skalieren der von den Sensoren gelesenen Werte dienen.
ESP-NOW Rückruffunktion: OnDataSent ist eine Funktion, die aufgerufen wird, nachdem ein Datenpaket gesendet wurde. Sie gibt Informationen darüber aus, ob das Senden erfolgreich war oder nicht.
Setup-Funktion: Die setup Funktion wird einmal ausgeführt, wenn das Programm startet. Sie initialisiert das Serial-Interface, das WiFi-Interface, registriert die ESP-NOW Rückruffunktion, fügt den Empfänger als Peer hinzu und initialisiert den Beschleunigungssensor.
Loop-Funktion: Die loop Funktion wird immer wieder ausgeführt, solange das Gerät läuft. In dieser Funktion werden die Sensorwerte gelesen, in das richtige Format gebracht und über ESP-NOW an den Empfänger gesendet.
Der allgemeine Ablauf des Programms ist also wie folgt:
Beim Start wird die setup Funktion ausgeführt, um alles für den Betrieb vorzubereiten.
Danach wird die loop Funktion immer wieder ausgeführt. Bei jedem Durchlauf liest sie die Sensorwerte, bereitet die Daten für das Senden vor und sendet sie dann über ESP-NOW.
Jedes Mal, wenn Daten gesendet werden, wird die OnDataSent Funktion aufgerufen, um das Ergebnis des Sendevorgangs anzuzeigen.
}
*/
Based on: Seeedstudio XIAO ESP32-C3
The reason we chose this board, was it`s small formfactor and its external antenna for better signal transmission. Based on the following examples: https://dronebotworkshop.com/esp-now/ https://github.com/un0038998/ESPNOW_Transmitter_Receiver
/* ESP-NOW GLOBALS *********************************************************/
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <Wire.h>
Adafruit_MPU6050 mpu;
/* ESP-NOW GLOBALS *********************************************************/
#include <esp_now.h>
#include <WiFi.h>
// REPLACE WITH YOUR RECEIVER MAC Address
// QT Py ESP32-S2 Receiver MAC-Adress: D4:F9:8D:71:1E:9C
// QT Py ESP32-S2 Receiver MAC-Adress: D4:F9:8D:70:88:98 (Clone)
uint8_t receiverMacAddress[] = { 0xD4, 0xF9, 0x8D, 0x71, 0x1E, 0x9C };
struct PacketData {
byte ForwardForce0;
byte RightForce1; byte LeftForce2;
byte UpDownForce3; // int RightForce4;
}; PacketData data;
// Pins for the force sens ors
// const int ForwardForceSensor1 = A0; // const int UpForceSensor1 = A1; // ... not sure if necessary to define this
// Force threshold const int ForceThreshold = 500;
/* GET Force Sensor Data FUNCTION ******************************************************/ int mapAndAdjustPressureSensorValues(int value, bool reverse) {
// Check force on sensor if (value > ForceThreshold) {
Serial.print(„Pressure Sensor Value:“); Serial.println(value); return value;
delay(50);
}
/* GET Accelerometer Sensor Data FUNCTION ********************************************* *********/ int mapAndAdjustAccelerometerSensorValues(int value, bool reverse)
{ if (value >= 4)
{ value = map(value, 4.0, 10.0, 127, 254);
} else if (value <= -4)
{ value = (value == 0 ? 0 : map(value, -4.0, -10.0, 127, 0));
} else
{ value = 127;
} if (reverse)
{ value = 254 - value;
} Serial.println(value); return value;
}
/* ESP-NOW CALLBACK *********************************************************/
// callback when data
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
Serial.print(„\r\nLast Packet Send Status:\t „); Serial.println(status); Serial.println(status == ESP_NOW_SEND_SUCCESS ? „Message sent“ „Message failed“);
}
/* SETUP FUNCTION *************************************************************/
void setup() {
// Initialize the force sensors as input // pinMode(ForwardForceSensor1, INPUT); // ... not sure if necessary to define this delay(50); Serial.begin(115200); WiFi.mode(WIFI_STA);
// Init ESP-NOW if (esp_now_init() != ESP_OK) { Serial.println(„Error initializing ESP-NOW“); return; } else { Serial.println(„Succes: Initialized ESP-NOW“); } esp_now_register_send_cb(OnDataSent);
// Register peer esp_now_peer_info_t peerInfo; memcpy(peerInfo.peer_addr, receiverMacAddress, 6); peerInfo.channel = 0; peerInfo.encrypt = false;
// Add peer if (esp_now_add_peer(&peerInfo) != ESP_OK) { Serial.println(„Failed to add peer“); return;
} else { Serial.println(„Succes: Added peer“); }
// Initialize Accelerometer if (!mpu.begin()) { Serial.println(„Failed to find MPU6050 chip“); while (1) { delay(10);
}
} Serial.println(„MPU6050 Found!“); mpu.setAccelerometerRange(MPU6050_RANGE_16_G); delay(100);
}
void loop() {
// Get new Accelerometer sensor events with the readings sensors_event_t a, g, temp; mpu.getEvent(&a, &g, &temp); data.ForwardForce0 = mapAndAdjustPressureSensorValues(analogRead(A0), false); Serial.print(„Pressure Sensor0 LoopValue:“); Serial.println(analogRead(A0)); data.RightForce1 = mapAndAdjustPressureSensorValues(analogRead(A1), false); Serial.print(„Pressure Sensor3 LoopValue:“); Serial.println(analogRead(A1)); data.LeftForce2 = mapAndAdjustPressureSensorValues(analogRead(A2), false); Serial.print(„Pressure Sensor3 LoopValue:“); Serial.println(analogRead(A2)); data.UpDownForce3 = mapAndAdjustAccelerometerSensorValues(a.acceleration.y, false); Serial.print(„Pressure Sensor1 LoopValue:“); Serial.println(a.acceleration.y);
esp_err_t result = esp_now_send(receiverMacAddress, (uint8_t *)&data, sizeof(data)); if (result == ESP_OK) { Serial.println(„Sent with success“); } else { Serial.println(„Error sending the data“); } delay(1000); }
is sent
/* LOOP FUNCTION *************************************************************/
Sendercode
Programmierung des Esp32 ( Sender)
Der Code für den Empfänger ist etwas komplizierter. Eine Schwierigkeit die es zu überwinden gab war es zu gewährleisten dass es keine Konflikte bei der Auslesung gleichtzeitig Empfanger Sensordaten gibt. Als Basis verwenden wir ein Xiao32-S2 in verbindung mit einem Lipo Akku und 3 Servomotren.
Datendefinition und -struktur: Es gibt eine Struktur namens PacketData, die Datenfelder für vier verschiedene Kräfte (Vorwärts, Rechts, Links, Hoch/Runter) enthält. Diese Struktur wird verwendet, um Daten vom Sender zu empfangen. Jede Kraft wird als Byte gespeichert.
Setup der Servomotoren: In der Funktion setUpServos() werden die Servomotoren initialisiert und ihre Positionen festgelegt.
ESP-NOW Callback: Die Funktion OnDataRecv ist eine Callback-Funktion, die aufgerufen wird, wenn Daten von einem anderen ESP32-Gerät empfangen werden. Die empfangenen Daten werden in die receiverData-Struktur kopiert. Danach werden die Funktionen mapAndWriteValues() und moveServosBasedOnValues() aufgerufen.
Werte zuordnen und schreiben: In der Funktion mapAndWriteValues() werden die empfangenen Daten analysiert und an die Funktionen zur Steuerung der Servomotoren weitergeleitet.
Servomotoren bewegen: In der Funktion moveServosBasedOnValues() werden die Servomotoren basierend auf den empfangenen Daten bewegt.
Setup-Funktion: In der setup()-Funktion wird die serielle Kommunikation initialisiert, die Servomotoren werden eingerichtet und ESP-NOW wird gestartet. Außerdem wird die Funktion OnDataRecv als Callback-Funktion für den Empfang von Daten registriert.
/* ESP-NOW GLOBALS *********************************************** **********/
#include <esp_now.h>
#include <WiFi.h>
// This is signal timeout in milli seconds. We will reset the data if no signal
#define SIGNAL_TIMEOUT 1000
unsigned long lastRecvTime = 0;
typedef struct PacketData { byte ForwardForce0; byte RightForce1; byte LeftForce2; byte UpDownForce3; } PacketData;
PacketData receiverData;
/* SERVO GLOBALS ************************************************** *******/
#include „ESP32_S2_ISR_Servo.h“
#define TIMER_INTERRUPT_DEBUG 1
#define ISR_SERVO_DEBUG 1
// Select different ESP32 timer number (0-3) to avoid conflict
#define USE_ESP32_TIMER_NO 3
//See Adafruit QT Py ESP32-S2 : https://cdn-learn.adafruit.com/assets/ assets/000/107/493/large1024/adafruit_products_Adafruit_QT_Py_ESP32S2_Pinout.png?1640130293
#define PIN_D5 5 // TX = PIN_D5
#define PIN_D6 6 // SCL = PIN_D6
#define PIN_D7 7 // SDA = PIN_D7
#define PIN_D8 8 // A3 = PIN_D8
#define PIN_D9 9 // A2 = PIN_D9
#define PIN_D17 17 // A1 = PIN_D17
#define PIN_D18 18 // A0 = PIN_D18
// Published values for SG90 servos; adjust if needed
#define MIN_MICROS 800 //544
#define MAX_MICROS 2450
int servoA0 = -1;
int servoA1 = -1;
int servoA2 = -1;
int servoA3 = -1;
int valForwardForce0 = 0;
int valRightForce1 = 0; int valLeftForce2 = 0; int valUpDownForce3 = 0;
int maxSensorValue = valForwardForce0; // Assume sensor1 is the largest initially int maxSensorIndex = 1; // Variable to store the sensor number int UpDownNeutralIndex = 1; int position = 40; int backFinDeg = 80;
/* SETUP DEFAULT **************************************************
*********/
void setInputDefaultValues() { // The middle position for joystick. (254/2=127)
receiverData.ForwardForce0 = valForwardForce0; //receiverData.UpForce2 = valUpForce1; }
Code des ESP Empfänger
/* RECEIVED DATA: MAP_AND_WRITE_VALUES ******************** *****************/
void mapAndWriteValues() { valForwardForce0 = receiverData.ForwardForce0; valRightForce1 = receiverData.RightForce1; valLeftForce2 = receiverData.LeftForce2; valUpDownForce3 = receiverData.UpDownForce3; maxSensorValue = valForwardForce0; // Assume sensor1 is the largest initially maxSensorIndex = 1; // Variable to store the sensor number //valForwardForce0 = map(receiverData.ForwardForce0, 0, 255, 0, 180); //Serial.print(„valForwardForce0Mapped: „); Serial.print(„valForwardForce0: „); Serial.println(valForwardForce0); Serial.print(„valRightForce1: „); Serial.println(valRightForce1); Serial.print(„valLeftForce2: „); Serial.println(valLeftForce2); Serial.print(„valUpDownForce3: „); Serial.println(valUpDownForce3); delay(100);
// Is Up or Down bigger? Map and Index it! if (valUpDownForce3 >= 128) { valUpDownForce3 = map(valUpDownForce3, 128, 255, 0, 255); // Index Up UpDownNeutralIndex = 2; Serial.print(„Up-Down-Index:“); Serial.println(UpDownNeutralIndex);
}
else if (valUpDownForce3 <= 126) { valUpDownForce3 = map(valUpDownForce3, 0, 126, 0, 255); // Index Down UpDownNeutralIndex = 0; Serial.print(„Up-Down-Index:“); Serial.println(UpDownNeutralIndex); }
else { valUpDownForce3 = 127; // Index Neutral UpDownNeutralIndex = 1; Serial.print(„Up-Down-Index:“); Serial.println(UpDownNeutralIndex); }
if (valRightForce1 > maxSensorValue) { maxSensorValue = valRightForce1; maxSensorIndex = 2; Serial.print(„maxSensorValue: „); Serial.println(maxSensorValue); Serial.print(„maxSensorIndex: „); Serial.println(maxSensorIndex); } delay(100);
if (valLeftForce2 > maxSensorValue) { maxSensorValue = valLeftForce2; maxSensorIndex = 3; Serial.print(„maxSensorValue: „); Serial.println(maxSensorValue); Serial.print(„maxSensorIndex: „); Serial.println(maxSensorIndex); } delay(100);
ESP32_ISR_Servos.useTimer(USE_ESP32_TIMER_NO); // Select ESP32 timer USE_ESP32_TIMER_NO servoA0 = ESP32_ISR_Servos.setupServo(PIN_D18, MIN_MICROS, MAX_MICROS); servoA1 = ESP32_ISR_Servos.setupServo(PIN_D17, MIN_MICROS, MAX_MICROS); servoA2 = ESP32_ISR_Servos.setupServo(PIN_D9, MIN_MICROS, MAX_MICROS);
/* UNCOMMENT / ADD AS FOLLOWS - IF MORE SERVOS ARE NEEDED */ // servoA3 = ESP32_ISR_Servos.setupServo(PIN_D8, MIN_ MICROS, MAX_MICROS);
if (servoA0 != -1)
Serial.println(F(„Setup Servo0 OK“)); else
Serial.println(F(„Setup Servo0 failed“)); if (servoA1 != -1)
Serial.println(F(„Setup Servo1 OK“)); else
Serial.println(F(„Setup Servo1 failed“));
if (servoA2 != -1)
Serial.println(F(„Setup Servo2 OK“)); else
Serial.println(F(„Setup Servo2 failed“)); }
/* MOVE THE SERVOS ************************************************* ***********/
void moveServosBasedOnValues() {
/* ServoA0 THROTTLE (FORWARD- FOR CE)*******************************************/
// Full Swing
if ((servoA0 != -1) && (valForwardForce0 > 70)) { for (position = 40; position <= backFinDeg; position += 5) { // goes from 40 degrees to 80 degrees // in steps of 1 degree if (position % 30 == 0) { Serial.print(F(„Servo0 pos = „)); Serial.print(position); }
ESP32_ISR_Servos.setPosition(servoA0, position); // waits 30ms for the servo to reach the position delay(30); }
delay(100);
for (position = backFinDeg; position >= 0; position -= 5) { // goes from 80 degrees to 0 degrees if (position % 30 == 0) { Serial.print(F(„Servo0 pos = „)); Serial.print(position); }
ESP32_ISR_Servos.setPosition(servoA0, position); // waits 30ms for the servo to reach the position delay(30); }
ESP32_ISR_Servos.setPosition(servoA2, 145); // old val 135 delay(50); Serial.println(„Servo Up“); }
// Servo Index Neutral else if ((servoA1 != -1) && (servoA2 != -1) && (maxSensorIndex == 4) && (UpDownNeutralIndex == 1)) {
ESP32_ISR_Servos.setPosition(servoA1, 85); //old val 90 delay(50);
ESP32_ISR_Servos.setPosition(servoA2, 95); //old val 90 delay(50);
Serial.println(„Servo Neutral“); }
/***** UP DOWN NEUTRAL ************************************* ***************************************/
// Servo Index Right
if ((servoA1 != -1) && (servoA2 != -1) && (maxSensorIndex == 2)) { ESP32_ISR_Servos.setPosition(servoA1, 5); //old val 135 delay(50); ESP32_ISR_Servos.setPosition(servoA2, 85); //old val 90 delay(50);
Serial.println(„Servo Down“); }
// Servo Index Left
else if ((servoA1 != -1) && (servoA2 != -1) && (maxSensorIndex == 3)) { ESP32_ISR_Servos.setPosition(servoA1, 85); //old val 90 delay(50); ESP32_ISR_Servos.setPosition(servoA2, 165); // 135 delay(50);
Serial.println(„Servo Up“); }
/* UNCOMMENT / ADD AS FOLLOWS - IF MORE SERVOS ARE NEEDED */
// ESP32_ISR_Servos.setPosition(servoA1, valUpForce1); // ESP32_ISR_Servos.setPosition(servoA1, valUpForce1); }
/* SETUP FUNCTION ******************************************** *****************/
void setup() { Serial.begin(115200); setUpServos();
// SETUP Wifi
WiFi.mode(WIFI_STA);
// Init ESP-NOW if (esp_now_init() != ESP_OK) { Serial.println(„Error initializing ESP-NOW“); return;
} esp_now_register_recv_cb(OnDataRecv); }
/* LOOP FUNCTION ********************************************* ****************/
/* ESP-NOW CALLBACK *********************************************
************/
/* callback function that will be executed when data is received
************/
void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) { unsigned long now = millis(); if (now - lastRecvTime > SIGNAL_TIMEOUT) { if (len == 0) { return; } if (len > 0) {
memcpy(&receiverData, incomingData, sizeof(receiverData));
mapAndWriteValues(); lastRecvTime = millis(); moveServosBasedOnValues(); } }
if (valUpDownForce3 > maxSensorValue) { maxSensorValue = valUpDownForce3; maxSensorIndex = 4; Serial.print(„maxSensorValue: „); Serial.println(maxSensorValue); Serial.print(„maxSensorIndex: „); Serial.println(maxSensorIndex); } delay(100); }
/* SETUP SERVOS *****************************************************
********/
void setUpServos() { while (!Serial && millis() < 5000)
; setInputDefaultValues(); delay(500);
Serial.print(F(„\nStarting ISR_MultiServos on „)); Serial.println(ARDUINO_BOARD); Serial.println(ESP32_S2_ISR_SERVO_VERSION);
delay(100);
for (position = 0; position <= 40; position += 5) { // goes from 0 degrees to 40 degrees if (position % 30 == 0) { Serial.print(F(„Servo0 pos = „)); Serial.print(position); }
ESP32_ISR_Servos.setPosition(servoA0, position); // waits 30ms for the servo to reach the position delay(30); } }
/***** UP DOWN NEUTRAL ******************************************** ********************************/
// Servo Index Down
if ((servoA1 != -1) && (servoA2 != -1) && (maxSensorIndex == 4) && (UpDownNeutralIndex == 0)) { ESP32_ISR_Servos.setPosition(servoA1, 145); delay(50); ESP32_ISR_Servos.setPosition(servoA2, 30); delay(50); Serial.println(„Servo Down“);
void loop() { }