Industrielle Fertigung
Industrielles Internet der Dinge | Industrielle Materialien | Gerätewartung und Reparatur | Industrielle Programmierung |
home  MfgRobots >> Industrielle Fertigung >  >> Manufacturing Technology >> Herstellungsprozess

Robotische Handsteuerung mit EMG

Komponenten und Verbrauchsmaterialien

uEKG-Gerät
× 3
inMoov-Hand
× 1
Arduino Nano R3
× 1
Adafruit PCA9685 16-Kanal-PWM-Treiber
× 1
nRF24-Modul (generisch)
× 1

Über dieses Projekt

Unser Team hat eine lange Geschichte mit Roboterhänden. Eine Zeitlang haben wir versucht, eine zuverlässige Handprothese herzustellen, aber für dieses Projekt verwende ich ein gutes Beispiel einer bestehenden Open-Source-Hand:inMoov.

Ich werde nicht auf Details der Handmontage eingehen - sie ist auf der Projektseite gut beschrieben und ziemlich kompliziert. Ich konzentriere mich hier auf die Kontrolle, da das völlig neu ist :)
Sehen Sie sich auch im nächsten Projekt an, wie sich diese Technologie im Laufe der Zeit entwickelt hat:https://www.hackster.io/the_3d6/seeing-muscles-at -work-8-channel-emg-with-leds-039d69

1. Signalverarbeitung

Die Kontrolle basiert auf EMG - der elektrischen Aktivität der Muskeln. Das EMG-Signal wird von drei uECG-Geräten erhalten (ich weiß, es soll ein EKG-Monitor sein, aber da er auf einem generischen ADC basiert, kann er alle Biosignale messen - einschließlich EMG). Für die EMG-Verarbeitung verfügt uECG über einen speziellen Modus, in dem es 32-Bin-Spektrumdaten und den "Muskelfenster"-Durchschnitt (durchschnittliche spektrale Intensität zwischen 75 und 440 Hz) aussendet. Spektrumbilder sehen so aus:

Hier ist die Frequenz auf einer vertikalen Achse (auf jedem der 3 Diagramme, niedrige Frequenz unten, hohe oben - von 0 bis 488 Hz mit ~15 Hz-Schritten), die Zeit ist auf einer horizontalen (alte Daten links insgesamt hier .) beträgt ca. 10 Sekunden auf dem Bildschirm). Die Intensität wird mit Farbe kodiert:blau - niedrig, grün - mittel, gelb - hoch, rot - noch höher. Für eine zuverlässige Gestenerkennung ist eine ordnungsgemäße PC-Verarbeitung dieser Bilder erforderlich. Aber für die einfache Aktivierung von Roboterhandfingern reicht es aus, nur den Durchschnittswert auf 3 Kanälen zu verwenden - uECG stellt ihn bequem bei bestimmten Paketbytes bereit, damit Arduino Sketch ihn analysieren kann. Diese Werte sehen viel einfacher aus:

Rote, grüne, blaue Diagramme sind Rohwerte von uEKG-Geräten an verschiedenen Muskelgruppen, wenn ich Daumen, Ring- und Mittelfinger entsprechend drücke. Für unser Auge sind diese Fälle eindeutig anders, aber wir müssen diese Werte irgendwie in "Finger-Score" umwandeln, damit ein Programm Werte an Handservos ausgeben kann. Das Problem ist, dass Signale von Muskelgruppen "gemischt" sind:Im 1. und 3. Fall ist die blaue Signalintensität ungefähr gleich - aber rot und grün sind unterschiedlich. Im 2. und 3. Fall sind grüne Signale gleich - aber blaue und rote sind unterschiedlich. Um sie zu "entmischen", habe ich eine relativ einfache Formel verwendet:

S0=V0^2 / (( V1 *a0 +b0)( V2 * c0+d0))

wobei S0 - Score für Kanal 0, V0, V1, V2 - Rohwerte für die Kanäle 0, 1, 2 und a, b, c, d - Koeffizienten, die ich manuell angepasst habe (a und c waren von 0,3 bis 2,0, b und d waren 15 und 20, Sie müssten sie ohnehin ändern, um sie an Ihre spezielle Sensorposition anzupassen). Für Kanal 1 und 2 wurde die gleiche Punktzahl berechnet. Danach wurden die Charts fast perfekt getrennt:

Für die gleichen Gesten (diesmal Ringfinger, Mittelfinger und dann Daumen) sind die Signale klar und können einfach durch Vergleich mit dem Schwellenwert in Servobewegungen übersetzt werden.

2. Schaltpläne

Der Schaltplan ist recht einfach, Sie benötigen nur ein nRF24-Modul, einen PCA9685 oder einen ähnlichen I2C-PWM-Controller und eine 5-V-Hochleistungsversorgung, die ausreichen würde, um alle diese Servos auf einmal zu bewegen (für einen stabilen Betrieb sind also mindestens 5 A Nennleistung erforderlich).

Liste der Anschlüsse:
nRF24 Pin 1 (GND) - Arduinos GND
nRF24 Pin 2 (Vcc) - Arduinos 3.3v
nRF24 Pin 3 (Chip Enable) - Arduinos D9
nRF24 Pin 4 (SPI:CS) - Arduinos D8
nRF24 Pin 5 (SPI:SCK) - Arduinos D13
nRF24 Pin 6 (SPI:MOSI) - Arduinos D11
nRF24 Pin 7 (SPI:MISO) - Arduinos D12
PCA9685 SDA - Arduinos A4
PCA9685 SCL - Arduinos A5
PCA9685 Vcc - Arduinos 5v
PCA9685 GND - Arduinos GND
PCA9685 V+ - High Amp 5V
PCA9685 GND - High Amp GND
Fingerservos:auf PCA Kanäle 0-4, in meiner Schreibweise Daumen - Kanal 0, Zeigefinger - Kanal 1 etc.

3. Platzierung der EMG-Sensoren

Um vernünftige Messwerte zu erhalten, ist es wichtig, uEKG-Geräte, die die Muskelaktivität aufzeichnen, an den richtigen Stellen zu platzieren. Obwohl hier viele verschiedene Optionen möglich sind, erfordert jede einen anderen Signalverarbeitungsansatz - daher teile ich meine Verwendung mit:

Es mag nicht intuitiv sein, aber das Daumenmuskelsignal ist auf der gegenüberliegenden Seite des Arms besser sichtbar, daher wird einer der Sensoren dort platziert, und alle befinden sich in der Nähe des Ellenbogens (Muskeln haben den größten Teil ihres Körpers in diesem Bereich). , aber Sie möchten überprüfen, wo sich Ihre genau befinden - es gibt einen ziemlich großen individuellen Unterschied)

4. Code

Bevor Sie das Hauptprogramm ausführen, müssen Sie die Unit-IDs Ihrer jeweiligen uECG-Geräte herausfinden (dies geschieht durch Entkommentieren von Zeile 101 und Einschalten der Geräte nacheinander) und sie in das unit_ids-Array (Zeile 37) füllen.

#include 
#include
#include
#include
#include
#include
#define SERVOMIN 150 // Dies ist die 'minimale' Impulslänge (von 4096)
#define SERVOMAX 600 // dies ist die 'maximale' Impulslänge (von 4096)
Adafruit_PWMServoDriver pwm =Adafruit_PWMServoDriver();
int rf_cen =9; // nRF24-Chip-Aktivierungsstift
int rf_cs =8; //nRF24 CS Pin
RF24 rf(rf_cen, rf_cs);
//Pipe-Adresse - hartcodiert auf uECG-Seite
uint8_t pipe_rx[8] ={0x0E, 0xE6, 0x0D, 0xA7, 0 , 0, 0, 0};
uint8_t swapbits(uint8_t a){ //uECG-Pipe-Adresse verwendet vertauschte Bitreihenfolge
// Kehrt die Bitreihenfolge in einem einzelnen Byte um
uint8_t v =0;
if(a &0x80) v |=0x01;
if(a &0x40) v |=0x02;
if(a &0x20) v |=0x04;
if(a &0x10) v |=0x08;
if(a &0x08) v |=0x10;
if(a &0x04) v |=0x20;
if(a &0x02 ) v |=0x40;
if(a &0x01) v |=0x80;
zurück v;
}
long last_servo_upd =0; //Zeit, als wir die Servowerte zuletzt aktualisiert haben - möchte dies nicht zu oft tun
byte in_pack[32]; //Array für eingehendes RF-Paket
unsigned long unit_ids[3] ={4294963881, 4294943100, 28358}; //Array bekannter uECG-IDs - muss mit Ihren eigenen Geräte-IDs gefüllt werden
int unit_vals[3] ={0, 0, 0}; //Array von uECG-Werten mit diesen IDs
float tgt_angles[5]; //Zielwinkel für 5 Finger
float cur_angles[5]; //aktuelle Winkel für 5 Finger
float angle_open =30; //Winkel, der dem offenen Finger entspricht
float angle_closed =150; //Winkel, der einem geschlossenen Finger entspricht
void setup() {
//nRF24 erfordert relativ langsames SPI, würde wahrscheinlich auch bei 2MHz funktionieren
SPI.begin();
SPI .setBitOrder(MSBFIRST);
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
for(int x =0; x <8; x++) //nRF24 und uECG haben unterschiedliche Bitreihenfolge für Pipe-Adresse
pipe_rx[x] =swapbits(pipe_rx[x]);
//Funkparameter konfigurieren
rf.begin();
rf.setDataRate(RF24_1MBPS);
rf.setAddressWidth(4);
rf.setChannel(22);
rf.setRetries(0, 0);
rf.setAutoAck(0);
rf.disableDynamicPayloads();
rf.setPayloadSize(32);
rf.openReadingPipe(0, pipe_rx);
rf.setCRCLength(RF24_CRC_DISABLED);
rf.disableCRC();
rf.startListening(); //auf uECG-Daten hören
//Beachten Sie, dass uECG in den Rohdatenmodus geschaltet werden sollte (durch langes Drücken der Taste)
//um kompatible Pakete zu senden, sendet es standardmäßig Daten im BLE-Modus
//die von nRF24 nicht empfangen werden kann
Serial.begin(115200); //serielle Ausgabe - sehr nützlich zum Debuggen
pwm.begin(); // PWM-Treiber starten
pwm.setPWMFreq (60); // Analoge Servos laufen mit ~60 Hz Updates
for(int i =0; i <5; i++) //setze die anfänglichen Fingerpositionen
{
tgt_angles[i] =angle_open;
cur_angles[i] =angle_open;
}
}
void setAngle(int n, float angle){ //sendet den Winkelwert für den gegebenen Kanal
pwm.setPWM (n, 0, SERVOMIN + Winkel * 0,005556 * (SERVOMAX - SERVOMIN));
}
float angle_speed =15; // wie schnell sich die Finger bewegen würden
schweben v0 =0, v1 =0, v2 =0; //gefilterte Muskelaktivitätswerte pro 3 Kanäle
void loop()
{
if(rf.available())
{
rf.read(in_pack, 32 ); //Verarbeitungspaket
byte u1 =in_pack[3];//32-Bit-Unit-ID, eindeutig für jedes uECG-Gerät
byte u2 =in_pack[4];
byte u3 =in_pack[ 5];
byte u4 =in_pack[6];
unsigned long id =(u1<<24) | (u2<<16) | (u3<<8) | u4;
//Serial.println(id); //Entkommentieren Sie diese Zeile, um eine Liste Ihrer uECG-IDs zu erstellen
if(in_pack[7] !=32) id =0; //falscher Packtyp:im EMG-Modus muss dieses Byte 32 sein
int val =in_pack[10]; //Wert der Muskelaktivität
if(val !=in_pack[11]) id =0; //Wert wird in 2 Byte dupliziert, da HF-Rauschen das Paket beschädigen kann und wir keinen CRC mit nRF24 haben
// Finden Sie heraus, welche ID der aktuellen ID und dem Füllwert entspricht
for(int n =0; n <3; n++)
if(id ==unit_ids[n])
unit_vals[n] =val;
}
long ms =millis();
if(ms - last_servo_upd> 20) // Servos nicht zu oft aktualisieren
{
last_servo_upd =ms;
for(int n =0; n <5; n++) / /Gehen Sie durch die Finger, wenn Ziel- und aktueller Winkel nicht übereinstimmen - passen Sie sie an
{
if(cur_angles[n] if(cur_angles[n]> tgt_angles[n] + angle_speed/2) cur_angles[n] -=angle_speed;
}
for(int n =0; n <5; n++) //Winkel auf Finger anwenden
setAngle(n, cur_angles[n]);
//exponentielle Mittelwertbildung:verhindert, dass einzelne Spitzen den Fingerzustand beeinflussen
v0 =v0*0.7 + 0.3*(float )unit_vals[0];
v1 =v1*0.7 + 0.3*(float)unit_vals[1];
v2 =v2*0.7 + 0.3*(float)unit_vals[2];
//Berechnung der Punktzahl s aus Rohwerten
float scor0 =4.0*v0*v0/((v1*0.3 + 20)*(v2*1.3 + 15));
float scor1 =4.0*v1*v1/(( v0*2.0 + 20)*(v2*2.0 + 20));
float scor2 =4.0*v2*v2/((v0*1.2 + 20)*(v1*0.5 + 15));
//Ergebnisse zum Debuggen drucken
Serial.print(scor0);
Serial.print(' ');
Serial.print(scor1);
Serial.print(' ');
Serial.println(scor2);
//jede Punktzahl mit Schwellenwert vergleichen und Fingerzustände entsprechend ändern
if(scor2 <0.5) //schwaches Signal - offener Finger
tgt_angles[0] =angle_open;
if(scor2> 1.0) //starkes Signal - Finger schließen
tgt_angles[0] =angle_closed;
if(scor1 <0.5)
{
tgt_angles[1] =angle_open;
tgt_angles[2] =angle_open;
}
if(scor1> 1.0)
{
tgt_angles[1 ] =angle_closed;
tgt_angles[2] =angle_closed;
}
if(scor0 <0.5)
{
tgt_angles[3] =angle_open;
tgt_angles[4] =angle_open;
}
if(scor0> 1.0)
{
tgt_angles[3] =angle_closed;
tgt_angles[4] =angle_closed;
}
}
}

5. Ergebnisse

Mit einigen Experimenten, die ungefähr 2 Stunden dauerten, konnte ich einen recht zuverlässigen Betrieb erzielen (Video zeigt einen typischen Fall):

Es verhält sich nicht perfekt und kann bei dieser Verarbeitung nur offene und geschlossene Finger erkennen (und nicht einmal jede der 5, es erkennt nur 3 Muskelgruppen:Daumen, Zeige- und Mittelfinger zusammen, Ring- und kleiner Finger zusammen). Aber "AI", das das Signal analysiert, benötigt hier 3 Codezeilen und verwendet einen einzelnen Wert von jedem Kanal. Ich glaube, dass durch die Analyse von 32-bin-Spektralbildern auf einem PC oder Smartphone viel mehr erreicht werden könnte. Außerdem verwendet diese Version nur 3 uECG-Geräte (EMG-Kanäle). Mit mehr Kanälen sollte es möglich sein, wirklich komplexe Muster zu erkennen - aber gut, das ist der Sinn des Projekts, um allen Interessierten einen Ansatzpunkt zu bieten :) Handsteuerung ist definitiv nicht die einzige Anwendung für ein solches System.

Code

  • emg_hand_control2.ino
emg_hand_control2.inoArduino
#include #include #include #include #include #include #define SERVOMIN 150 / / dies ist die 'minimale' Impulslänge (von 4096)#define SERVOMAX 600 // dies ist die 'maximale' Impulslänge (von 4096)Adafruit_PWMServoDriver pwm =Adafruit_PWMServoDriver();int rf_cen =9; // nRF24-Chip aktivieren Pinint rf_cs =8; // nRF24 CS pinRF24 rf (rf_cen, rf_cs); // Pipe-Adresse - hartcodiert auf der uECG-Seiteuint8_t pipe_rx[8] ={0x0E, 0xE6, 0x0D, 0xA7, 0, 0, 0, 0};uint8_t swapbits(uint8_t a) { // uECG-Pipe-Adresse verwendet vertauschte Bitreihenfolge // Umkehren der Bitreihenfolge in einem einzelnen Byte uint8_t v =0; if(a &0x80) v |=0x01; if(a &0x40) v |=0x02; if(a &0x20) v |=0x04; if(a &0x10) v |=0x08; if(a &0x08) v |=0x10; if(a &0x04) v |=0x20; if(a &0x02) v |=0x40; if(a &0x01) v |=0x80; v zurückgeben;}long last_servo_upd =0; // Zeit, als wir die Servowerte zuletzt aktualisiert haben - möchte dies nicht zu oft tunbyte in_pack[32]; //Array für eingehende RF-Paketeunsigned long unit_ids[3] ={4294963881, 4294943100, 28358}; //Array bekannter uECG-IDs - muss mit Ihren eigenen Einheiten-IDs gefüllt werdenint unit_vals[3] ={0, 0, 0}; //Array von uECG-Werten mit diesen IDsfloat tgt_angles[5]; // Zielwinkel für 5 Fingerfloat cur_angles[5]; // aktuelle Winkel für 5 Fingerfloat angle_open =30; // Winkel, der dem offenen Fingerfloat entspricht angle_closed =150; // Winkel, der einem geschlossenen Fingervoid-Setup entspricht () { // nRF24 erfordert relativ langsames SPI, würde wahrscheinlich auch bei 2 MHz funktionieren SPI.begin (); SPI.setBitOrder(MSBFIRST); SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)); for(int x =0; x <8; x++) //nRF24 und uECG haben unterschiedliche Bitreihenfolge für die Pipe-Adresse pipe_rx[x] =swapbits(pipe_rx[x]); // Funkparameter konfigurieren rf.begin (); rf.setDataRate(RF24_1MBPS); rf.setAddressWidth(4); rf.setChannel (22); rf.setRetries(0, 0); rf.setAutoAck(0); rf.disableDynamicPayloads(); rf.setPayloadSize(32); rf.openReadingPipe(0, pipe_rx); rf.setCRCLength(RF24_CRC_DISABLED); rf.disableCRC(); rf.startListening(); // auf uECG-Daten lauschen // Beachten Sie, dass uECG in den Rohdatenmodus geschaltet werden sollte (durch langes Drücken der Taste) // um kompatible Pakete zu senden, sendet es standardmäßig Daten im BLE-Modus // die von nRF24 Serial nicht empfangen werden können .begin(115200); //serielle Ausgabe - sehr nützlich zum Debuggen von pwm.begin(); // PWM-Treiber starten pwm.setPWMFreq (60); // Analoge Servos laufen mit ~60 Hz Updates für (int i =0; i <5; i++) // anfängliche Fingerpositionen einstellen {tgt_angles[i] =angle_open; cur_angles[i] =angle_open; }}void setAngle (int n, float angle) {// sendet den Winkelwert für den angegebenen Kanal aus pwm.setPWM (n, 0, SERVOMIN + angle * 0,005556 * (SERVOMAX - SERVOMIN));} float angle_speed =15; // wie schnell sich die Finger bewegen würdenfloat v0 =0, v1 =0, v2 =0; // gefilterte Muskelaktivitätswerte pro 3 Kanälevoid loop () { if (rf.available ()) { rf.read (in_pack, 32); //Verarbeitungspaketbyte u1 =in_pack[3];//32-Bit-Unit-ID, eindeutig für jedes uECG-Gerät Byte u2 =in_pack[4]; Byte u3 =in_pack[5]; Byte u4 =in_pack[6]; unsigned long id =(u1<<24) | (u2<<16) | (u3<<8) | u4; //Seriell.println(id); // Kommentieren Sie diese Zeile, um eine Liste Ihrer uECG-IDs zu erstellen if(in_pack[7] !=32) id =0; // Falscher Packtyp:Im EMG-Modus muss dieses Byte 32 sein int val =in_pack[10]; //Wert der Muskelaktivität if(val!=in_pack[11]) id =0; // Wert wird in 2 Byte dupliziert, da HF-Rauschen das Paket beschädigen kann und wir keinen CRC mit nRF24 haben // Finden Sie heraus, welche ID der aktuellen ID entspricht und füllen Sie den Wert für (int n =0; n <3; n++) if (id ==unit_ids[n]) unit_vals[n] =val; } lange ms =millis(); if (ms - last_servo_upd> 20) // Servos nicht zu oft aktualisieren { last_servo_upd =ms; for(int n =0; n <5; n++) // durch die Finger gehen, wenn Ziel- und aktueller Winkel nicht übereinstimmen - passen Sie sie an { if(cur_angles[n]  tgt_angles[n] + angle_speed/2) cur_angles[n] -=angle_speed; } for(int n =0; n <5; n++) //Winkel auf die Finger anwenden setAngle(n, cur_angles[n]); //exponentielle Mittelung:verhindert, dass einzelne Peaks den Fingerzustand beeinflussen v0 =v0*0.7 + 0.3*(float)unit_vals[0]; v1 =v1*0,7 + 0,3*(float)unit_vals[1]; v2 =v2*0,7 + 0,3*(float)unit_vals[2]; //Berechnung von Scores aus Rohwerten float scor0 =4.0*v0*v0/((v1*0.3 + 20)*(v2*1.3 + 15)); Gleitkommazahl1 =4,0*v1*v1/((v0*2,0 + 20)*(v2*2,0 + 20)); Gleitkommawert2 =4,0*v2*v2/((v0*1,2 + 20)*(v1*0,5 + 15)); // Ergebnisse zum Debuggen von Serial.print (scor0) drucken; Serial.print (' '); Serial.print (scor1); Serial.print (' '); Serial.println (scor2); // jeden Score mit dem Schwellenwert vergleichen und die Fingerzustände entsprechend ändern if(scor2 <0,5) //schwaches Signal - offener Finger tgt_angles[0] =angle_open; if(scor2> 1.0) //starkes Signal - Finger schließen tgt_angles[0] =angle_closed; if(scor1 <0,5) { tgt_angles[1] =angle_open; tgt_angles[2] =angle_open; } if(scor1> 1.0) {tgt_angles[1] =angle_closed; tgt_angles[2] =angle_closed; } if (scor0 <0,5) { tgt_angles [3] =angle_open; tgt_angles[4] =angle_open; } if(scor0> 1.0) {tgt_angles[3] =angle_closed; tgt_angles[4] =angle_closed; } }}

Schaltpläne

nrf24_hand_control_5jcEeCP8a3.fzz

Herstellungsprozess

  1. Antibabypille
  2. Bauen Sie ein drahtloses Roboterfahrzeug mit IR-Sensoren
  3. Referenzdesign vereinfacht die Motorsteuerung von Industrierobotern
  4. Temperaturbasiertes Gerätesteuerungssystem mit LM35
  5. Einsatz von KI zur Steuerung der Lichteigenschaften | Superkontinuumsgeneration
  6. Verwenden der 3DG Robotersimulationssoftware zur Planung der Roboterautomatisierung
  7. Automatische Zugbeeinflussung
  8. Universelle Fernbedienung mit Arduino, 1Sheeld und Android
  9. Einsatz des IoT zur Fernsteuerung eines Roboterarms
  10. Die Schüler bauen ein Roboter-Müllsortiersystem mit B&R-Technologie