Fabrication industrielle
Internet des objets industriel | Matériaux industriels | Entretien et réparation d'équipement | Programmation industrielle |
home  MfgRobots >> Fabrication industrielle >  >> Manufacturing Technology >> Processus de fabrication

Contrôle robotique à l'aide d'EMG

Composants et fournitures

appareil uECG
× 3
inMoov hand
× 1
Arduino Nano R3
× 1
Pilote PWM 16 canaux Adafruit PCA9685
× 1
Module nRF24 (Générique)
× 1

À propos de ce projet

Notre équipe a une longue histoire avec les mains robotiques. Pendant un moment, nous avons essayé de fabriquer une main prothétique fiable, mais pour ce projet, j'utilise un bon exemple de main open source existante :inMoov.

Je n'entrerai pas dans les détails de l'assemblage à la main - c'est bien décrit sur le site du projet et c'est assez compliqué. Je vais me concentrer sur le contrôle ici, car c'est complètement nouveau :)
Aussi, découvrez comment cette technologie a évolué au fil du temps dans le prochain projet :https://www.hackster.io/the_3d6/seeing-muscles-at -work-8-channel-emg-with-leds-039d69

1. Traitement du signal

Le contrôle est basé sur l'EMG - activité électrique des muscles. Le signal EMG est obtenu par trois appareils uECG (je sais, c'est censé être un moniteur ECG, mais comme il est basé sur un ADC générique, il peut mesurer n'importe quel biosignal - y compris l'EMG). Pour le traitement EMG, uECG dispose d'un mode spécial dans lequel il envoie des données spectrales à 32 bins et une moyenne de « fenêtre musculaire » (intensité spectrale moyenne entre 75 et 440 Hz). Les images du spectre ressemblent à ceci :

Ici la fréquence est sur un axe vertical (sur chacun des 3 tracés, basse fréquence en bas, haute en haut - de 0 à 488 Hz avec des pas de ~ 15 Hz), le temps est sur une horizontale (anciennes données sur la gauche globalement ici est d'environ 10 secondes à l'écran). L'intensité est codée avec la couleur :bleu - faible, vert - moyen, jaune - élevé, rouge - encore plus élevé. Pour une reconnaissance gestuelle fiable, un traitement PC approprié de ces images est requis. Mais pour une activation simple des doigts de la main robotique, il suffit d'utiliser la valeur moyenne sur 3 canaux - uECG la fournit commodément à certains octets de paquet afin que Arduino Sketch puisse l'analyser. Ces valeurs semblent beaucoup plus simples :

Les graphiques rouges, verts et bleus sont des valeurs brutes des appareils uECG sur différents groupes musculaires lorsque je serre le pouce, l'annulaire et le majeur en conséquence. Pour notre œil, ces cas sont clairement différents, mais nous devons transformer ces valeurs en "score de doigt" d'une manière ou d'une autre afin qu'un programme puisse envoyer des valeurs aux servos manuels. Le problème est que les signaux des groupes musculaires sont « mélangés » :dans les 1er et 3e cas, l'intensité du signal bleu est à peu près la même - mais le rouge et le vert sont différents. Dans les 2e et 3e cas, les signaux verts sont les mêmes - mais le bleu et le rouge sont différents. Afin de les "démixer", j'ai utilisé une formule relativement simple :

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

où S0 - score pour le canal 0, V0, V1, V2 - valeurs brutes pour les canaux 0, 1, 2 et a, b, c, d - coefficients que j'ai ajustés manuellement (a et c étaient de 0,3 à 2,0, b et d étaient 15 et 20, vous auriez besoin de les modifier pour ajuster de toute façon l'emplacement particulier de votre capteur). Le même score a été calculé pour les canaux 1 et 2. Après cela, les graphiques sont devenus presque parfaitement séparés :

Pour les mêmes gestes (cette fois l'annulaire, le majeur, puis le pouce), les signaux sont clairs et peuvent être facilement traduits en mouvements d'asservissement simplement en comparant avec le seuil.

2. Schémas

Le schéma est assez simple, vous n'avez besoin que d'un module nRF24, d'un PCA9685 ou d'un contrôleur I2C PWM similaire et d'une alimentation 5 V à intensité élevée qui suffirait à déplacer tous ces servos à la fois (il nécessite donc au moins une puissance nominale de 5 A pour un fonctionnement stable).

Liste des connexions :
nRF24 broche 1 (GND) - GND d'Arduino
nRF24 broche 2 (Vcc) - Arduino 3.3v
nRF24 broche 3 (Chip Enable) - D9 d'Arduino
nRF24 broche 4 (SPI:CS) - D8
nRF24 d'Arduino broche 5 (SPI:SCK) - D13 d'Arduino
nRF24 broche 6 (SPI:MOSI) - D11
nRF24 d'Arduino broche 7 (SPI :MISO) - Arduino D12
PCA9685 SDA - Arduino A4
PCA9685 SCL - Arduino A5
PCA9685 Vcc - Arduino 5v
PCA9685 GND - Arduino GND
PCA9685 V+ - haut ampli 5V
PCA9685 GND - High amp GND
Servos de doigt :vers PCA canaux 0-4, dans ma notation pouce - canal 0, index - canal 1 etc.

3. Emplacement des capteurs EMG

Afin d'obtenir des lectures raisonnables, il est important de placer les appareils uECG, qui enregistrent l'activité musculaire, aux bons endroits. Bien que de nombreuses options différentes soient possibles ici, chacune nécessite une approche de traitement du signal différente - je partage donc ce que j'ai utilisé :

Cela peut être contre-intuitif, mais le signal musculaire du pouce est mieux visible du côté opposé du bras, donc un des capteurs est placé là, et tous sont placés près du coude (les muscles ont la majeure partie de leur corps dans cette zone , mais vous voulez vérifier où se trouvent exactement les vôtres - il y a une assez grande différence individuelle)

4. Coder

Avant d'exécuter le programme principal, vous devrez trouver les ID d'unité de vos appareils uECG particuliers (cela se fait en décommentant la ligne 101 et en allumant les appareils un par un) et les remplir dans le tableau unit_ids (ligne 37).

#include 
#include
#include
#include
#include
#include
#define SERVOMIN 150 // c'est la longueur d'impulsion "minimum" (sur 4096)
#define SERVOMAX 600 // c'est le nombre de longueur d'impulsion 'maximum' (sur 4096)
Adafruit_PWMServoDriver pwm =Adafruit_PWMServoDriver();
int rf_cen =9; //puce nRF24 activer la broche
int rf_cs =8 ; //nRF24 CS pin
RF24 rf(rf_cen, rf_cs);
//adresse du tuyau - codée en dur côté uECG
uint8_t pipe_rx[8] ={0x0E, 0xE6, 0x0D, 0xA7, 0 , 0, 0, 0} ;
uint8_t swapbits(uint8_t a){ //l'adresse du canal uECG utilise l'ordre des bits échangés
// inverse l'ordre des bits dans un seul octet
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;
retourner v;
}
long last_servo_upd =0; //heure à laquelle nous avons mis à jour les valeurs d'asservissement pour la dernière fois - je ne veux pas le faire trop souvent
byte in_pack[32] ; //tableau pour les paquets RF entrants
unsigned long unit_ids[3] ={4294963881, 4294943100, 28358} ; //tableau d'identifiants uECG connus - à remplir avec vos propres identifiants d'unité
int unit_vals[3] ={0, 0, 0} ; //tableau de valeurs uECG avec ces identifiants
float tgt_angles[5] ; //angles cibles pour 5 doigts
float cur_angles[5]; //angles actuels pour 5 doigts
float angle_open =30; //angle qui correspond au doigt ouvert
float angle_closed =150; //angle qui correspond au doigt fermé
configuration vide() {
//nRF24 nécessite un SPI relativement lent, fonctionnerait probablement aussi à 2MHz
SPI.begin();
SPI .setBitOrder(MSBFIRST);
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
for(int x =0; x <8; x++) //nRF24 et uECG ont un ordre de bits différent pour l'adresse du tuyau
pipe_rx[x] =swapbits(pipe_rx[x]);
//configurer les paramètres radio
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(); //écouter les données uECG
//Notez que uECG doit être basculé en mode données brutes (via un appui long)
//afin d'envoyer des paquets compatibles, par défaut, il envoie des données en mode BLE
//qui ne peut pas être reçu par nRF24
Serial.begin(115200); //sortie série - très utile pour le débogage
pwm.begin(); //démarrer le pilote PWM
pwm.setPWMFreq(60); // Les servos analogiques fonctionnent à ~60 Hz mises à jour
for(int i =0; i <5; i++) //définir les positions initiales des doigts
{
tgt_angles[i] =angle_open;
cur_angles[i] =angle_open;
}
}
void setAngle(int n, float angle){ //envoie la valeur de l'angle pour un canal donné
pwm.setPWM (n, 0, SERVOMIN + angle * 0,005556 * (SERVOMAX - SERVOMIN));
}
float angle_speed =15 ; //à quelle vitesse les doigts se déplaceraient
float v0 =0, v1 =0, v2 =0 ; //valeurs d'activité musculaire filtrées par 3 canaux
boucle vide()
{
if(rf.available())
{
rf.read(in_pack, 32 ); //traitement du paquet
byte u1 =in_pack[3];//ID d'unité 32 bits, unique pour chaque périphérique uECG
byte u2 =in_pack[4];
byte u3 =in_pack[ 5];
byte u4 =in_pack[6];
id long non signé =(u1<<24) | (u2<<16) | (u3<<8) | u4;
//Serial.println(id); //décommentez cette ligne pour faire la liste de vos identifiants uECG
if(in_pack[7] !=32) id =0; //type de pack incorrect :en mode EMG, cet octet doit être 32
int val =in_pack[10] ; //valeur d'activité musculaire
if(val !=in_pack[11]) id =0; //la valeur est dupliquée dans 2 octets car le bruit RF peut corrompre le paquet, et nous n'avons pas de CRC avec nRF24
//trouver quel ID correspond à l'ID actuel et remplir la valeur
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) //ne pas mettre à jour les servos trop souvent
{
last_servo_upd =ms;
for(int n =0; n <5; n++) / /passer entre les doigts, si les angles cible et actuel ne correspondent pas - ajustez-les
{
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++) //appliquer des angles aux doigts
setAngle(n, cur_angles[n]);
//moyenne exponentielle :empêche les pics uniques d'affecter l'état du doigt
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];
//calcul du score s à partir des valeurs brutes
scor0 flottant =4.0*v0*v0/((v1*0.3 + 20)*(v2*1.3 + 15));
scor flottant1 =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));
//print scores pour le débogage
Serial.print(scor0);
Serial.print(' ');
Serial.print(scor1);
Serial.print(' ');
Serial.println(scor2);
//comparez chaque score avec le seuil et changez les états des doigts en conséquence
if(scor2 <0.5) //signal faible - doigt ouvert
tgt_angles[0] =angle_open;
if(scor2> 1.0) //signal fort - doigt de fermeture
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. Résultats

Avec quelques expériences qui ont duré environ 2 heures, j'ai pu obtenir un fonctionnement assez fiable (la vidéo montre un cas typique) :

Il ne se comporte pas parfaitement et avec ce traitement ne peut reconnaître que les doigts ouverts et fermés (et même pas chacun des 5, il ne détecte que 3 groupes musculaires :pouce, index et majeur ensemble, annulaire et auriculaire ensemble). Mais "AI" qui analyse le signal prend ici 3 lignes de code et utilise une seule valeur de chaque canal. Je pense que beaucoup plus pourrait être fait en analysant des images spectrales de 32 bins sur un PC ou un smartphone. De plus, cette version n'utilise que 3 appareils uECG (canaux EMG). Avec plus de canaux, il devrait être possible de reconnaître des modèles vraiment complexes - mais bon, c'est le but du projet, de fournir un point de départ à toute personne intéressée :) La commande manuelle n'est certainement pas la seule application pour un tel système.

Code

  • emg_hand_control2.ino
emg_hand_control2.inoArduino
#include #include #include #include #include #include #define SERVOMIN 150 / / c'est le nombre "minimum" de longueur d'impulsion (sur 4096)#define SERVOMAX 600 // c'est le nombre "maximum" de longueur d'impulsion (sur 4096)Adafruit_PWMServoDriver pwm =Adafruit_PWMServoDriver();int rf_cen =9; // La puce nRF24 active le pinint rf_cs =8; //nRF24 CS pinRF24 rf(rf_cen, rf_cs);//adresse du tuyau - codée en dur sur uECG sideuint8_t pipe_rx[8] ={0x0E, 0xE6, 0x0D, 0xA7, 0, 0, 0, 0};uint8_t swapbits(uint8_t a) { //l'adresse du canal uECG utilise l'ordre des bits échangés // inverse l'ordre des bits dans un seul octet uint8_t v =0; si(a &0x80) v |=0x01; si(a &0x40) v |=0x02; si(a &0x20) v |=0x04; si(a &0x10) v |=0x08; si(a &0x08) v |=0x10; si(a &0x04) v |=0x20; si(a &0x02) v |=0x40; si(a &0x01) v |=0x80; return v;}long last_servo_upd =0; // heure de la dernière mise à jour des valeurs d'asservissement - je ne veux pas le faire trop souventbyte in_pack[32] ; // tableau pour les paquets RF entrants long unit_ids[3] ={4294963881, 4294943100, 28358} ; // tableau d'identifiants uECG connus - à remplir avec votre propre identifiant d'unitésint unit_vals[3] ={0, 0, 0} ; //tableau de valeurs uECG avec ces IDsfloat tgt_angles[5] ; //angles cibles pour 5 doigts flottant cur_angles[5]; //angles actuels pour 5 doigts flottant angle_open =30; //angle qui correspond au fingerfloat ouvert angle_closed =150; //angle qui correspond à la configuration fermée du fingervoid() { //nRF24 nécessite un SPI relativement lent, fonctionnerait probablement aussi à 2MHz SPI.begin(); SPI.setBitOrder(MSBFIRST); SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)); for(int x =0; x <8; x++) //nRF24 et uECG ont un ordre de bits différent pour l'adresse de pipe pipe_rx[x] =swapbits(pipe_rx[x]); //configuration des paramètres radio 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(); //écouter les données uECG //Notez que uECG doit être basculé en mode données brutes (via un appui long sur un bouton) //afin d'envoyer des paquets compatibles, par défaut, il envoie des données en mode BLE //qui ne peuvent pas être reçues par nRF24 Serial .begin(115200); //sortie série - très utile pour déboguer pwm.begin(); //démarrer le pilote PWM pwm.setPWMFreq(60); // Les servos analogiques fonctionnent à ~60 Hz, les mises à jour for(int i =0; i <5; i++) //définir les positions initiales des doigts { tgt_angles[i] =angle_open; cur_angles[i] =angle_open; }}void setAngle(int n, float angle){ //envoie la valeur de l'angle pour le canal donné pwm.setPWM(n, 0, SERVOMIN + angle * 0.005556 * (SERVOMAX - SERVOMIN));}float angle_speed =15; //à quelle vitesse les doigts bougeraientfloat v0 =0, v1 =0, v2 =0; //valeurs d'activité musculaire filtrées par 3 canauxvoid loop() { if(rf.available()) { rf.read(in_pack, 32); //Traitement de l'octet de paquet u1 =in_pack[3];//ID d'unité 32 bits, unique pour chaque octet de périphérique uECG u2 =in_pack[4] ; octet u3 =in_pack[5] ; octet u4 =in_pack[6] ; id long non signé =(u1<<24) | (u2<<16) | (u3<<8) | u4 ; //Série.println(id); //décommentez cette ligne pour faire la liste de vos identifiants uECG if(in_pack[7] !=32) id =0; //type de pack incorrect :en mode EMG, cet octet doit être 32 int val =in_pack[10] ; //valeur d'activité musculaire if(val !=in_pack[11]) id =0; //la valeur est dupliquée dans 2 octets car le bruit RF peut corrompre le paquet, et nous n'avons pas de CRC avec nRF24 //trouver quel ID correspond à l'ID actuel et remplir la valeur pour (int n =0; n <3; n++) si (id ==unit_ids[n]) unit_vals[n] =val; } ms long =millis(); if(ms - last_servo_upd> 20) //ne pas mettre à jour les servos trop souvent { last_servo_upd =ms; for(int n =0; n <5; n++) //parcourir les doigts, si les angles cible et actuel ne correspondent pas - ajustez-les { if(cur_angles[n]  tgt_angles[n] + angle_speed/2) cur_angles[n] -=angle_speed; } for(int n =0; n <5; n++) //appliquer des angles aux doigts setAngle(n, cur_angles[n]); //moyenne exponentielle :empêche les pics uniques d'affecter l'état du doigt 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] ; //calcul des scores à partir des valeurs brutes 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)); // imprime les scores pour le débogage Serial.print(scor0) ; Serial.print(' '); Serial.print(scor1) ; Serial.print(' '); Serial.println(scor2) ; //comparez chaque score avec le seuil et modifiez les états des doigts en conséquence if(scor2 <0,5) //signal faible - doigt ouvert tgt_angles[0] =angle_open; if(scor2> 1.0) //signal fort - fermer le doigt tgt_angles[0] =angle_closed; if(scor1 <0.5) { tgt_angles[1] =angle_open; objectif_angles[2] =angle_ouvert ; } if(scor1> 1.0) { tgt_angles[1] =angle_closed; objectif_angles[2] =angle_fermé ; } if(scor0 <0.5) { tgt_angles[3] =angle_open; objectifs_angles[4] =angle_ouvert ; } if(scor0> 1.0) { tgt_angles[3] =angle_closed; objectifs_angles[4] =angle_fermé ; } }}

Schémas

nrf24_hand_control_5jcEeCP8a3.fzz

Processus de fabrication

  1. pilule contraceptive
  2. Créer un véhicule robotique sans fil à l'aide de capteurs infrarouges
  3. La conception de référence simplifie le contrôle de moteur robotique industriel
  4. Système de contrôle d'appareil basé sur la température utilisant LM35
  5. Utiliser l'IA pour contrôler les propriétés de la lumière | Génération de supercontinuum
  6. Utilisation du logiciel de simulation de robot 3DG pour planifier l'automatisation robotique
  7. Contrôle automatique des trains
  8. Télécommande universelle utilisant Arduino, 1Sheeld et Android
  9. Des étudiants construisent un système robotisé de tri des déchets à l'aide de la technologie B&R