Manufaturação industrial
Internet das coisas industrial | Materiais industriais | Manutenção e reparo de equipamentos | Programação industrial |
home  MfgRobots >> Manufaturação industrial >  >> Manufacturing Technology >> Processo de manufatura

Controle de mão robótico usando EMG

Componentes e suprimentos

dispositivo uECG
× 3
mão inMoov
× 1
Arduino Nano R3
× 1
Driver PWM de 16 canais Adafruit PCA9685
× 1
Módulo nRF24 (genérico)
× 1

Sobre este projeto


Nossa equipe tem uma longa história com mãos robóticas. Por um tempo tentamos fazer uma prótese de mão confiável, mas para este projeto estou usando um bom exemplo de mão de código aberto existente:inMoov.

Não vou entrar em detalhes sobre a montagem manual - está bem descrita no site do projeto e é bastante complicada. Vou me concentrar no controle aqui, já que isso é completamente novo :)
Além disso, verifique como essa tecnologia evoluiu ao longo do tempo no próximo projeto:https://www.hackster.io/the_3d6/seeing-muscles-at -work-8-channel-emg-with-leds-039d69





1. Processamento de sinal


O controle é baseado na EMG - atividade elétrica dos músculos. O sinal EMG é obtido por três dispositivos uECG (eu sei, é suposto ser um monitor de ECG, mas como é baseado em um ADC genérico, pode medir qualquer biossinal - incluindo EMG). Para o processamento EMG, o uECG tem um modo especial no qual envia dados do espectro de 32 bin e média da "janela muscular" (intensidade espectral média entre 75 e 440 Hz). As imagens de espectro têm esta aparência:

Aqui, a frequência está no eixo vertical (em cada um dos 3 gráficos, baixa frequência na parte inferior, alta no topo - de 0 a 488 Hz com passos de ~ 15 Hz), o tempo está na horizontal (dados antigos à esquerda aqui é de cerca de 10 segundos na tela). A intensidade é codificada com a cor:azul - baixo, verde - médio, amarelo - alto, vermelho - ainda mais alto. Para um reconhecimento confiável de gestos, é necessário um processamento adequado dessas imagens no PC. Mas para a ativação simples dos dedos robóticos da mão, é suficiente apenas usar o valor médio em 3 canais - o uECG fornece convenientemente em determinados bytes de pacote para que o esboço do Arduino possa analisá-lo. Esses valores parecem muito mais simples:

Os gráficos vermelhos, verdes e azuis são valores brutos de dispositivos uECG em diferentes grupos de músculos quando estou apertando o polegar, o anelar e os dedos médios de maneira correspondente. Para o nosso olho, esses casos são claramente diferentes, mas precisamos transformar esses valores em "pontuação do dedo" de alguma forma para que um programa possa enviar valores para servos manuais. O problema é que os sinais dos grupos musculares são "mistos":no primeiro e no terceiro caso, a intensidade do sinal azul é quase a mesma - mas o vermelho e o verde são diferentes. No segundo e terceiro casos, os sinais verdes são iguais - mas o azul e o vermelho são diferentes. Para "desmisturá-los", usei uma fórmula relativamente simples:

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

onde S0 - pontuação para o canal 0, V0, V1, V2 - valores brutos para os canais 0, 1, 2 e a, b, c, d - coeficientes que ajustei manualmente (a e c foram de 0,3 a 2,0, b e d fossem 15 e 20, você precisaria alterá-los para se ajustar ao seu posicionamento específico do sensor de qualquer maneira). A mesma pontuação foi calculada para os canais 1 e 2. Depois disso, os gráficos ficaram quase perfeitamente separados:

Para os mesmos gestos (desta vez, dedo anular, médio e polegar), os sinais são claros e podem ser facilmente traduzidos em movimentos servo apenas comparando com o limiar.





2. Esquemas


O esquema é bastante simples, você precisa apenas do módulo nRF24, PCA9685 ou controlador I2C PWM semelhante e fonte de alimentação de 5V de alto ampere que seria o suficiente para mover todos esses servos de uma vez (portanto, requer pelo menos 5A de potência nominal para operação estável).

Lista de conexões:
nRF24 pino 1 (GND) - GND do Arduino
nRF24 pino 2 (Vcc) - 3.3v do Arduino
nRF24 pino 3 (habilitar chip) - D9 do Arduino
nRF24 pino 4 (SPI:CS) - D8 do Arduino
nRF24 pino 5 (SPI:SCK) - D13 do Arduino
nRF24 pino 6 (SPI:MOSI) - D11 do Arduino
nRF24 pino 7 (SPI:MISO) - D12 do Arduino
PCA9685 SDA - A4 do Arduino
PCA9685 SCL - A5 do Arduino
PCA9685 Vcc - 5v do Arduino
PCA9685 GND - GND do Arduino
PCA9685 V + - amplificador alto 5V
PCA9685 GND - alto amp GND
Servos de dedo:para canais PCA 0-4, em minha notação polegar - canal 0, dedo indicador - canal 1 etc.





3. Colocação dos sensores EMG


Para obter leituras razoáveis, é importante colocar os dispositivos uECG, que registram a atividade muscular, nos lugares certos. Embora muitas opções diferentes sejam possíveis aqui, cada uma requer uma abordagem de processamento de sinal diferente - então, estou compartilhando o que usei:

Pode ser contra-intuitivo, mas o sinal do músculo do polegar é melhor visível no lado oposto do braço, então um dos sensores é colocado lá, e todos eles são colocados perto do cotovelo (os músculos têm a maior parte de seu corpo nessa área , mas você deseja verificar onde exatamente o seu está localizado - há uma grande diferença individual)





4. Código


Antes de executar o programa principal, você precisará descobrir IDs de unidade de seus dispositivos uECG específicos (isso é feito removendo o comentário da linha 101 e ligando os dispositivos um por um) e preenchê-los na matriz unit_ids (linha 37).
  #include  
#include
#include
#include
#include
#include
#define SERVOMIN 150 // esta é a contagem de comprimento de pulso 'mínimo' (de 4096)
#define SERVOMAX 600 // esta é a contagem de comprimento de pulso 'máximo' (de 4096)
Adafruit_PWMServoDriver pwm =Adafruit_PWMServoDriver ();
int rf_cen =9; // pino de habilitação do chip nRF24
int rf_cs =8; // pino CS nRF24
RF24 rf (rf_cen, rf_cs);
// endereço do pipe - codificado no lado uECG
uint8_t pipe_rx [8] ={0x0E, 0xE6, 0x0D, 0xA7, 0 , 0, 0, 0};
uint8_t swapbits (uint8_t a) {// o endereço do pipe uECG usa a ordem dos bits trocados
// reverter a ordem dos bits em um único 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;
return v;
}
long last_servo_upd =0; // hora em que atualizamos os valores do servo pela última vez - não quero fazer isso com muita frequência
byte in_pack [32]; // matriz para pacote RF de entrada
unidades_ids longas não assinadas [3] ={4294963881, 4294943100, 28358}; // array de IDs uECG conhecidos - precisa preencher com seus próprios IDs de unidade
int unit_vals [3] ={0, 0, 0}; // array de valores uECG com esses IDs
float tgt_angles [5]; // ângulos alvo para 5 dedos
float cur_angles [5]; // ângulos atuais para 5 dedos
float angle_open =30; // ângulo que corresponde ao dedo aberto
float angle_closed =150; // ângulo que corresponde ao dedo fechado
void setup () {
// nRF24 requer SPI relativamente lento, provavelmente também funcionaria a 2 MHz
SPI.begin ();
SPI .setBitOrder (MSBFIRST);
SPI.beginTransaction (SPISettings (1000000, MSBFIRST, SPI_MODE0));
for (int x =0; x <8; x ++) // nRF24 e uECG têm ordem de bits diferente para endereço de pipe
pipe_rx [x] =swapbits (pipe_rx [x]);
// configurar parâmetros de rádio
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 (); // escuta os dados do uECG
// Observe que o uECG deve ser alternado para o modo de dados brutos (pressionando longamente o botão)
// para enviar pacotes compatíveis, por padrão ele envia dados no modo BLE
// que não pode ser recebido por nRF24
Serial.begin (115200); // saída serial - muito útil para depuração
pwm.begin (); // inicia o driver PWM
pwm.setPWMFreq (60); // Servos analógicos executados em atualizações de ~ 60 Hz
para (int i =0; i <5; i ++) // definir as posições iniciais dos dedos
{
tgt_angles [i] =ângulo_open;
cur_angles [i] =ângulo_open;
}
}
void setAngle (int n, ângulo de flutuação) {// envia o valor do ângulo para determinado canal
pwm.setPWM (n, 0, SERVOMIN + ângulo * 0,005556 * (SERVOMAX - SERVOMIN));
}
float ângulo_velocidade =15; // quão rápido os dedos se moveriam
float v0 =0, v1 =0, v2 =0; // valores de atividade muscular filtrada por 3 canais
void loop ()
{
if (rf.available ())
{
rf.read (in_pack, 32 ); // processamento do pacote
byte u1 =in_pack [3]; // ID da unidade de 32 bits, exclusivo para cada dispositivo uECG
byte u2 =in_pack [4];
byte u3 =in_pack [ 5];
byte u4 =in_pack [6];
id longo sem sinal =(u1 <<24) | (u2 <<16) | (u3 <<8) | u4;
//Serial.println(id); // descomente esta linha para fazer uma lista de seus IDs uECG
if (in_pack [7]! =32) id =0; // tipo de pacote errado:no modo EMG este byte deve ser 32
int val =in_pack [10]; // valor da atividade muscular
if (val! =in_pack [11]) id =0; // o valor é duplicado em 2 bytes porque o ruído RF pode corromper o pacote, e não temos CRC com nRF24
// encontre qual ID corresponde ao ID atual e valor de preenchimento
para (int n =0; n <3; n ++)
if (id ==unit_ids [n])
unit_vals [n] =val;
}
long ms =millis ();
if (ms - last_servo_upd> 20) // não atualiza servos com muita frequência
{
last_servo_upd =ms;
para (int n =0; n <5; n ++) / / passar pelos dedos, se os ângulos alvo e atual não combinarem - ajuste-os
{
if (cur_angles [n] if (cur_angles [n]> tgt_angles [n] + ângulo_velocidade / 2) cur_angles [n] - =ângulo_velocidade;
}
para (int n =0; n <5; n ++) // aplicar ângulos aos dedos
setAngle (n, cur_angles [n]);
// cálculo da média exponencial:evita que picos únicos afetem o estado do dedo
v0 =v0 * 0,7 + 0,3 * (flutuante ) unit_vals [0];
v1 =v1 * 0,7 + 0,3 * (float) unit_vals [1];
v2 =v2 * 0,7 + 0,3 * (float) unit_vals [2];
// pontuação de cálculo s de valores brutos
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 pontuações para depuração
Serial.print (scor0);
Serial.print ('');
Serial.print (scor1);
Serial.print (' ');
Serial.println (scor2);
// compare cada pontuação com o limiar e altere os estados dos dedos de forma correspondente
if (scor2 <0,5) // sinal fraco - dedo aberto
tgt_angles [0] =angle_open;
if (scor2> 1.0) // sinal forte - dedo fechado
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 ] =ângulo_fechado;
tgt_angles [2] =ângulo_fechado;
}
if (pontuação0 <0,5)
{
tgt_angles [3] =ângulo_aberto;
tgt_angles [4] =angle_open;
}
if (scor0> 1.0)
{
tgt_angles [3] =angle_closed;
tgt_angles [4] =ângulo_fechado;
}
}
}





5. Resultados


Com alguns experimentos que levaram cerca de 2 horas, consegui obter uma operação bastante confiável (o vídeo mostra um caso típico):

Não se comporta perfeitamente e com este processamento só consegue reconhecer dedos abertos e fechados (e nem mesmo cada um dos 5, detecta apenas 3 grupos musculares:polegar, indicador e médio juntos, anular e mindinho juntos). Mas o "AI" que analisa o sinal leva 3 linhas de código aqui e usa um único valor de cada canal. Acredito que muito mais poderia ser feito analisando imagens espectrais de 32 bin no PC ou smartphone. Além disso, esta versão usa apenas 3 dispositivos uECG (canais EMG). Com mais canais, deve ser possível reconhecer padrões realmente complexos - mas bem, esse é o objetivo do projeto, fornecer algum ponto de partida para qualquer pessoa interessada :) O controle manual definitivamente não é a única aplicação para tal sistema.

Código

  • emg_hand_control2.ino
emg_hand_control2.ino Arduino
 #include  #include  #include  #include  #include  #include  #define SERVOMIN 150 / / esta é a contagem de comprimento de pulso 'mínimo' (em 4096) #define SERVOMAX 600 // esta é a contagem de comprimento de pulso 'máximo' (em 4096) Adafruit_PWMServoDriver pwm =Adafruit_PWMServoDriver (); int rf_cen =9; // chip nRF24 habilita pinint rf_cs =8; // nRF24 CS pinRF24 rf (rf_cen, rf_cs); // endereço do pipe - codificado em uECG sideuint8_t pipe_rx [8] ={0x0E, 0xE6, 0x0D, 0xA7, 0, 0, 0, 0}; uint8_t swapbits (uint8_t a) {// o endereço do pipe uECG usa a ordem dos bits trocados // inverte a ordem dos bits em um único 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; return v;} long last_servo_upd =0; // hora em que atualizamos os valores do servo pela última vez - não quero fazer isso com muita frequênciabyte in_pack [32]; // array para RF packetunsigned long unit_ids [3] ={4294963881, 4294943100, 28358}; // array de IDs uECG conhecidos - precisa ser preenchido com seu próprio IDsint unit_vals [3] ={0, 0, 0}; // array de valores uECG com estes IDsfloat tgt_angles [5]; // ângulos alvo para 5 dedosfloat cur_angles [5]; // ângulos atuais para 5 dedosfloat angle_open =30; // ângulo que corresponde ao fingerfloat aberto angle_closed =150; // ângulo que corresponde a configuração de fingervóide fechada () {// nRF24 requer SPI relativamente lento, provavelmente funcionaria a 2 MHz também SPI.begin (); SPI.setBitOrder (MSBFIRST); SPI.beginTransaction (SPISettings (1000000, MSBFIRST, SPI_MODE0)); for (int x =0; x <8; x ++) // nRF24 e uECG têm ordem de bits diferente para o endereço do pipe pipe_rx [x] =swapbits (pipe_rx [x]); // configura os parâmetros de rádio 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 (); // escuta os dados uECG // Observe que o uECG deve ser alternado para o modo de dados brutos (por meio de um pressionamento longo do botão) // para enviar pacotes compatíveis, por padrão ele envia dados no modo BLE // que não podem ser recebidos pelo nRF24 Serial .begin (115200); // saída serial - muito útil para depuração pwm.begin (); // inicia o driver PWM pwm.setPWMFreq (60); // Servos analógicos rodam em atualizações de ~ 60 Hz para (int i =0; i <5; i ++) // define as posições iniciais dos dedos {tgt_angles [i] =angle_open; cur_angles [i] =ângulo_aberto; }} void setAngle (int n, ângulo de flutuação) {// envia o valor do ângulo para determinado canal pwm.setPWM (n, 0, SERVOMIN + ângulo * 0,005556 * (SERVOMAX - SERVOMIN));} float ângulo_velocidade =15; // quão rápido os dedos se moveriam v0 =0, v1 =0, v2 =0; // valores de atividade muscular filtrados por 3 canaisvoid loop () {if (rf.available ()) {rf.read (in_pack, 32); // processamento do pacote de byte u1 =in_pack [3]; // ID da unidade de 32 bits, exclusivo para cada dispositivo uECG byte u2 =in_pack [4]; byte u3 =in_pack [5]; byte u4 =in_pack [6]; id longo sem sinal =(u1 <<24) | (u2 <<16) | (u3 <<8) | u4; //Serial.println(id); // descomente esta linha para fazer uma lista de seus IDs uECG if (in_pack [7]! =32) id =0; // tipo de pacote errado:no modo EMG este byte deve ser 32 int val =in_pack [10]; // valor da atividade muscular if (val! =in_pack [11]) id =0; // o valor é duplicado em 2 bytes porque o ruído de RF pode corromper o pacote, e não temos CRC com nRF24 // encontre qual ID corresponde ao ID atual e preencha o valor para (int n =0; n <3; n ++) se (id ==unit_ids [n]) unit_vals [n] =val; } ms longo =milis (); if (ms - last_servo_upd> 20) // não atualiza servos com muita frequência {last_servo_upd =ms; for (int n =0; n <5; n ++) // passar pelos dedos, se os ângulos alvo e atual não corresponderem - ajuste-os {if (cur_angles [n]  tgt_angles [n] + ângulo_velocidade / 2) cur_angles [n] - =ângulo_velocidade; } for (int n =0; n <5; n ++) // aplica ângulos aos dedos setAngle (n, cur_angles [n]); // média exponencial:evita que picos únicos afetem o estado do dedo v0 =v0 * 0,7 + 0,3 * (float) unit_vals [0]; v1 =v1 * 0,7 + 0,3 * (flutuante) unit_vals [1]; v2 =v2 * 0,7 + 0,3 * (flutuante) unit_vals [2]; // calculando pontuações a partir de valores brutos 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 pontuações para depuração Serial.print (scor0); Serial.print (''); Serial.print (scor1); Serial.print (''); Serial.println (scor2); // compare cada pontuação com o limite e mude os estados dos dedos correspondentemente if (scor2 <0,5) // sinal fraco - abra o dedo tgt_angles [0] =angle_open; if (scor2> 1.0) // sinal forte - dedo fechado tgt_angles [0] =angle_closed; if (scor1 <0,5) {tgt_angles [1] =ângulo_aberto; tgt_angles [2] =ângulo_aberto; } if (scor1> 1.0) {tgt_angles [1] =ângulo_closed; tgt_angles [2] =angle_closed; } if (scor0 <0,5) {tgt_angles [3] =ângulo_aberto; tgt_angles [4] =ângulo_aberto; } if (scor0> 1.0) {tgt_angles [3] =ângulo_fechado; tgt_angles [4] =angle_closed; }}} 

Esquemas

nrf24_hand_control_5jcEeCP8a3.fzz

Processo de manufatura

  1. Pílula anticoncepcional
  2. Faça um veículo robótico sem fio usando sensores infravermelhos
  3. O projeto de referência simplifica o controle do motor robótico industrial
  4. Sistema de controle de dispositivo baseado em temperatura usando LM35
  5. Usando IA para controlar as propriedades da luz | Geração de supercontínuo
  6. Usando o software de simulação do robô 3DG para planejar a automação robótica
  7. Controle Automático de Trem
  8. Controle remoto universal usando Arduino, 1Sheeld e Android
  9. Usando IoT para controlar remotamente um braço robótico
  10. Estudantes constroem sistema robótico de triagem de lixo usando tecnologia B&R