산업 제조
산업용 사물 인터넷 | 산업자재 | 장비 유지 보수 및 수리 | 산업 프로그래밍 |
home  MfgRobots >> 산업 제조 >  >> Manufacturing Technology >> 제조공정

EMG를 사용한 로봇 손 제어

구성품 및 소모품

uECG 장치
× 3
inMoov 손
× 1
Arduino Nano R3
× 1
Adafruit PCA9685 16채널 PWM 드라이버
× 1
nRF24 모듈(일반)
× 1

이 프로젝트 정보

우리 팀은 로봇 손에 대한 긴 이야기를 가지고 있습니다. 한동안 우리는 신뢰할 수 있는 의수를 만들기 위해 노력했지만 이 프로젝트에서는 기존 오픈 소스 손의 좋은 예인 inMoov를 사용하고 있습니다.

손 조립에 대한 자세한 내용은 다루지 않겠습니다. 프로젝트 사이트에 잘 설명되어 있으며 매우 복잡합니다. 여기서는 완전히 새로운 기능이므로 여기서는 제어에 중점을 두겠습니다.
또한 다음 프로젝트에서 이 기술이 시간이 지남에 따라 어떻게 발전했는지 확인하십시오:https://www.hackster.io/the_3d6/seeing-muscles-at -work-8-channel-emg-with-leds-039d69

1. 신호 처리

제어는 EMG(근육의 전기적 활동)를 기반으로 합니다. EMG 신호는 3개의 uECG 장치에 의해 획득됩니다(ECG 모니터로 가정되지만 일반 ADC를 기반으로 하기 때문에 EMG를 포함한 모든 생체 신호를 측정할 수 있음). EMG 처리를 위해 uECG에는 32-bin 스펙트럼 데이터와 "근육 창" 평균(75~440Hz 사이의 평균 스펙트럼 강도)을 보내는 특수 모드가 있습니다. 스펙트럼 이미지는 다음과 같습니다.

여기에서 주파수는 수직 축(3개의 플롯 각각에서 하단은 저주파수, 상단은 높음 - ~15Hz 단계로 0~488Hz), 시간은 수평(여기서 전체 왼쪽의 이전 데이터) 화면에서 약 10초). 강도는 파란색 - 낮음, 녹색 - 중간, 노란색 - 높음, 빨간색 - 더 높은 색상으로 인코딩됩니다. 안정적인 제스처 인식을 위해서는 이러한 이미지의 적절한 PC 처리가 필요합니다. 그러나 로봇 손 손가락의 간단한 활성화를 위해서는 3개의 채널에서 평균값을 사용하는 것으로 충분합니다. uECG는 특정 패킷 바이트에서 편리하게 이를 제공하므로 Arduino 스케치가 이를 구문 분석할 수 있습니다. 이 값은 훨씬 간단해 보입니다.

빨간색, 녹색, 파란색 차트는 엄지, 약지 및 중지를 상응하게 쥐고 있을 때 다른 근육 그룹에 있는 uECG 장치의 원시 값입니다. 우리 눈에는 이러한 경우가 분명히 다르지만 프로그램이 수동 서보에 값을 출력할 수 있도록 이러한 값을 "손가락 점수"로 어떻게든 변환해야 합니다. 문제는 근육 그룹의 신호가 "혼합"된다는 것입니다. 첫 번째와 세 번째 경우 파란색 신호 강도는 거의 같지만 빨간색과 녹색은 다릅니다. 두 번째와 세 번째 경우 녹색 신호는 동일하지만 파란색과 빨간색은 다릅니다. 이들을 "혼합 해제"하기 위해 비교적 간단한 공식을 사용했습니다.

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

여기서 S0 - 채널 0, V0, V1, V2에 대한 점수 - 채널 0, 1, 2 및 a, b, c, d에 대한 원시 값 - 수동으로 조정한 계수(a 및 c는 0.3에서 2.0, b 및 d는 15와 20이었으며 어쨌든 특정 센서 배치에 맞게 조정하려면 변경해야 합니다. 채널 1과 2에 대해 동일한 점수가 계산되었습니다. 이후 차트는 거의 완벽하게 분리되었습니다.

동일한 제스처(이번에는 약지, 중지, 엄지)에 대해 신호가 명확하고 임계값과 비교하는 것만으로 쉽게 서보 움직임으로 변환될 수 있습니다.

2. 회로도

회로도는 매우 간단합니다. nRF24 모듈, PCA9685 또는 이와 유사한 I2C PWM 컨트롤러 및 이러한 모든 서보를 한 번에 이동하기에 충분한 고암페어 5V 전원 공급 장치만 있으면 됩니다(따라서 안정적인 작동을 위해서는 최소 5A 정격 전원이 필요합니다).

연결 목록:
nRF24 핀 1(GND) - Arduino의 GND
nRF24 핀 2(Vcc) - Arduino의 3.3v
nRF24 핀 3(칩 활성화) - Arduino의 D9
nRF24 핀 4(SPI:CS) - Arduino의 D8
nRF24 핀 5(SPI:SCK) - Arduino의 D13
nRF24 핀 6(SPI:MOSI) - Arduino의 D11
nRF24 핀 7(SPI:MISO) - Arduino의 D12
PCA9685 SDA - Arduino의 A4
PCA9685 SCL - Arduino의 A5
PCA9685 Vcc - Arduino의 5v
PCA9685 GND - Arduino의 GND
PCA9685 V+ - 높음 5V
PCA9685 GND - 고앰프 GND
핑거 서보:PCA 채널 0-4, 내 표기법에서 엄지 - 채널 0, 집게 손가락 - 채널 1 등

3. EMG 센서 배치

합리적인 판독값을 얻으려면 근육 활동을 기록하는 uECG 장치를 올바른 위치에 배치하는 것이 중요합니다. 여기에서 다양한 옵션이 가능하지만 각각 다른 신호 처리 접근 방식이 필요하므로 제가 사용한 것을 공유합니다.

직관적이지 않을 수 있지만 엄지 근육 신호는 팔의 반대쪽에서 더 잘 볼 수 있으므로 센서 중 하나가 거기에 배치되고 모두 팔꿈치에 가깝게 배치됩니다(근육은 해당 영역에 신체의 대부분을 갖습니다. , 하지만 귀하의 위치가 정확히 어디인지 확인하려는 경우 - 개인차가 상당히 큽니다.)

4. 코드

메인 프로그램을 실행하기 전에 특정 uECG 장치의 장치 ID를 찾아(라인 101 주석을 제거하고 장치를 하나씩 켜서 수행) unit_ids 배열(라인 37)에 채워야 합니다.

#include 
#include
#include
#include
#include
#include
#define SERVOMIN 150 // 이것은 '최소' 펄스 길이 카운트입니다(4096개 중)
#define SERVOMAX 600 // 이것은 '최대' 펄스 길이 카운트(4096개 중)입니다.
Adafruit_PWMServoDriver pwm =Adafruit_PWMServoDriver();
int rf_cen =9; //nRF24 칩 활성화 핀
int rf_cs =8; //nRF24 CS 핀
RF24 rf(rf_cen, rf_cs);
//파이프 주소 - uECG 측에 하드코딩됨
uint8_t pipe_rx[8] ={0x0E, 0xE6, 0x0D, 0xA7, 0 , 0, 0, 0};
uint8_t swapbits(uint8_t a){ //uECG 파이프 주소는 교환된 비트 순서를 사용합니다.
// 단일 바이트에서 비트 순서를 반대로 합니다.
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;
}
long last_servo_upd =0; //서보 값을 마지막으로 업데이트한 시간 - 너무 자주 하고 싶지는 않습니다.
byte in_pack[32]; // 들어오는 RF 패킷에 대한 배열
unsigned long unit_ids[3] ={4294963881, 4294943100, 28358}; //알려진 uECG ID 배열 - 고유한 단위 ID로 채워야 함
int unit_vals[3] ={0, 0, 0}; //이 ID를 가진 uECG 값의 배열
float tgt_angles[5]; //5개의 손가락에 대한 목표 각도
float cur_angles[5]; //5개의 손가락에 대한 현재 각도
float angle_open =30; //펼친 손가락에 해당하는 각도
float angle_closed =150; //닫힌 손가락에 해당하는 각도
void setup() {
//nRF24에는 상대적으로 느린 SPI가 필요합니다. 아마도 2MHz에서도 작동할 것입니다.
SPI.begin();
SPI .setBitOrder(MSBFIRST);
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
for(int x =0; x <8; x++) //nRF24와 uECG는 비트 순서가 다릅니다. 파이프 주소용
pipe_rx[x] =swapbits(pipe_rx[x]);
//무선 매개변수 구성
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(); //uECG 데이터 수신
//uECG는 원시 데이터 모드로 전환되어야 합니다(긴 버튼 누름)
//호환 가능한 패킷을 보내려면 기본적으로 BLE 모드로 데이터를 보냅니다
//nRF24에서 수신할 수 없습니다.
Serial.begin(115200); // 직렬 출력 - 디버깅에 매우 유용합니다.
pwm.begin(); //PWM 드라이버 시작
pwm.setPWMFreq(60); // 아날로그 서보는 ~60Hz 업데이트에서 실행됩니다.
for(int i =0; i <5; i++) //초기 손가락 위치 설정
{
tgt_angles[i] =angle_open;
cur_angles[i] =angle_open;
}
}
void setAngle(int n, float angle){ //주어진 채널에 대한 각도 값을 보냅니다.
pwm.setPWM (n, 0, SERVOMIN + 각도 * 0.005556 * (SERVOMAX - SERVOMIN));
}
float angle_speed =15; //손가락이 얼마나 빨리 움직이는지
float v0 =0, v1 =0, v2 =0; //3 채널당 필터링된 근육 활동 값
void loop()
{
if(rf.available())
{
rf.read(in_pack, 32 ); //패킷 처리
byte u1 =in_pack[3];//32비트 단위 ID, 모든 uECG 장치에 대해 고유
byte u2 =in_pack[4];
byte u3 =in_pack[ 5];
바이트 u4 =in_pack[6];
서명되지 않은 긴 ID =(u1<<24) | (u2<<16) | (u3<<8) | u4;
//Serial.println(id); // uECG ID 목록을 만들기 위해 이 줄의 주석 처리를 제거합니다.
if(in_pack[7] !=32) id =0; //잘못된 팩 유형:EMG 모드에서 이 바이트는 32여야 합니다.
int val =in_pack[10]; //근육 활동 값
if(val !=in_pack[11]) id =0; //RF 노이즈가 패킷을 손상시킬 수 있기 때문에 값이 2바이트로 복제되고 nRF24가 있는 CRC가 없습니다.
//현재 ID에 해당하는 ID를 찾고 값을 채우십시오.
for(int n =0; n <3; n++)
if(id ==unit_ids[n])
unit_vals[n] =val;
}
긴 ms =millis();
if(ms - last_servo_upd> 20) //서보를 너무 자주 업데이트하지 마십시오.
{
last_servo_upd =ms;
for(int n =0; n <5; n++) / /목표 각도와 현재 각도가 일치하지 않으면 손가락으로 이동 - 조정
{
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++) //손가락에 각도 적용
setAngle(n, cur_angles[n]);
//지수 평균:단일 피크가 손가락 상태에 영향을 미치지 않도록 방지
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];
//점수 계산 원시 값의 s
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));
//디버깅 점수 인쇄
Serial.print(scor0);
Serial.print(' ');
Serial.print(scor1);
Serial.print(' ');
Serial.println(scor2);
//각 점수를 임계값과 비교하고 그에 따라 손가락 상태를 변경합니다.
if(scor2 <0.5) // 약한 신호 - 열린 손가락
tgt_angles[0] =angle_open;
if(scor2> 1.0) //강한 신호 - 손가락 닫기
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. 결과

약 2시간이 소요되는 몇 가지 실험을 통해 매우 안정적인 작동을 얻을 수 있었습니다(비디오는 일반적인 경우를 보여줍니다).

그것은 완벽하게 작동하지 않으며 이 처리로 열린 손가락과 닫힌 손가락만 인식할 수 있습니다(5개 각각도 인식하지 못하고 엄지, 검지 및 가운데, 약지와 새끼 손가락 함께 3개의 근육 그룹만 감지함). 그러나 신호를 분석하는 "AI"는 여기에서 3줄의 코드를 취하고 각 채널의 단일 값을 사용합니다. PC나 스마트폰에서 32-bin 스펙트럼 이미지를 분석하면 훨씬 더 많은 일을 할 수 있다고 생각합니다. 또한 이 버전은 3개의 uECG 장치(EMG 채널)만 사용합니다. 더 많은 채널을 사용하면 정말 복잡한 패턴을 인식하는 것이 가능해야 합니다. 하지만 관심 있는 모든 사람에게 시작점을 제공하는 것이 프로젝트의 요점입니다. 핸드 컨트롤이 확실히 이러한 시스템의 유일한 응용 프로그램은 아닙니다.

<섹션 클래스="섹션 컨테이너 섹션 축소 가능" id="코드">

코드

<울>
  • emg_hand_control2.ino
  • emg_hand_control2.ino아두이노
    #include #include #include #include #include #include #define SERVOMIN 150 / / 이것은 '최소' 펄스 길이 카운트입니다(4096개 중)#define SERVOMAX 600 // 이것은 '최대' 펄스 길이 카운트입니다(4096개 중)Adafruit_PWMServoDriver pwm =Adafruit_PWMServoDriver();int rf_cen =9; //nRF24 칩 활성화 pinint rf_cs =8; //nRF24 CS pinRF24 rf(rf_cen, rf_cs);//파이프 주소 - uECG에 하드 코딩됨 sideuint8_t pipe_rx[8] ={0x0E, 0xE6, 0x0D, 0xA7, 0, 0, 0, 0};uint8_t_t a)uint { //uECG 파이프 주소는 교환된 비트 순서를 사용합니다. // 단일 바이트에서 비트 순서를 반대로 합니다. 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;}long last_servo_upd =0; //서보 값을 마지막으로 업데이트한 시간 - 너무 자주 하고 싶지 않습니다.byte in_pack[32]; // 들어오는 RF 패킷에 대한 배열 unsigned long unit_ids[3] ={4294963881, 4294943100, 28358}; //알려진 uECG ID 배열 - 고유한 단위 ID로 채워야 함sint unit_vals[3] ={0, 0, 0}; //이 ID를 가진 uECG 값의 배열float tgt_angles[5]; // 5개의 손가락에 대한 목표 각도float cur_angles[5]; // 5개의 손가락에 대한 현재 각도float angle_open =30; //오픈 핑거플로트에 해당하는 각도 angle_closed =150; //닫힌 fingervoid에 해당하는 각도 setup() { //nRF24에는 상대적으로 느린 SPI가 필요합니다. 아마도 2MHz에서도 작동할 것입니다. SPI.begin(); SPI.setBitOrder(MSBFIRST); SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)); for(int x =0; x <8; x++) //nRF24와 uECG는 파이프 주소에 대한 비트 순서가 다릅니다. pipe_rx[x] =swapbits(pipe_rx[x]); // 라디오 매개변수 구성 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, 파이프_rx); rf.setCRC길이(RF24_CRC_DISABLED); rf.disableCRC(); rf.startListening(); //uECG 데이터 수신 //uECG는 원시 데이터 모드(긴 버튼 누름)로 전환되어야 합니다. //호환되는 패킷을 보내려면 기본적으로 BLE 모드로 데이터를 보냅니다. //nRF24 직렬에서 수신할 수 없습니다. .시작(115200); // 직렬 출력 - 디버깅에 매우 유용합니다. pwm.begin(); // PWM 드라이버 시작 pwm.setPWMreq(60); // 아날로그 서보는 ~60Hz 업데이트에서 실행 for(int i =0; i <5; i++) //초기 손가락 위치 설정 { tgt_angles[i] =angle_open; cur_angles[i] =angle_open; }}void setAngle(int n, float angle){ //주어진 채널에 대한 각도 값을 보냅니다. pwm.setPWM(n, 0, SERVOMIN + angle * 0.005556 * (SERVOMAX - SERVOMIN));}float angle_speed =15; //손가락이 얼마나 빨리 움직이는지 float v0 =0, v1 =0, v2 =0; //3 채널당 필터링된 근육 활동 값void loop() { if(rf.available()) { rf.read(in_pack, 32); //패킷 바이트 처리 u1 =in_pack[3];//모든 uECG 장치에 대해 고유한 32비트 단위 ID 바이트 u2 =in_pack[4]; 바이트 u3 =in_pack[5]; 바이트 u4 =in_pack[6]; 서명되지 않은 긴 ID =(u1<<24) | (u2<<16) | (u3<<8) | u4; //Serial.println(id); // uECG ID 목록을 만들려면 이 줄의 주석 처리를 제거하십시오. if(in_pack[7] !=32) id =0; //잘못된 팩 유형:EMG 모드에서 이 바이트는 32여야 합니다. int val =in_pack[10]; //근육 활동 값 if(val !=in_pack[11]) id =0; //RF 노이즈가 패킷을 손상시킬 수 있기 때문에 값이 2바이트로 중복되고 nRF24가 있는 CRC가 없습니다. // 현재 ID에 해당하는 ID를 찾고 값을 채우십시오. (id ==unit_ids[n]) unit_vals[n] =발; } 긴 ms =millis(); if(ms - last_servo_upd> 20) //서보를 너무 자주 업데이트하지 않음 { last_servo_upd =ms; for(int n =0; n <5; n++) // 목표 각도와 현재 각도가 일치하지 않으면 손가락으로 이동 - 조정 { if(cur_angles[n]  tgt_angles[n] + angle_speed/2) cur_angles[n] -=angle_speed; } for(int n =0; n <5; n++) //손가락에 각도를 적용합니다. setAngle(n, cur_angles[n]); //지수 평균:단일 피크가 손가락 상태에 영향을 미치지 않도록 방지 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]; //원시 값에서 점수 계산 float scor0 =4.0*v0*v0/((v1*0.3 + 20)*(v2*1.3 + 15)); 부동 점수 1 =4.0*v1*v1/((v0*2.0 + 20)*(v2*2.0 + 20)); 부동 점수2 =4.0*v2*v2/((v0*1.2 + 20)*(v1*0.5 + 15)); //디버깅 점수 인쇄 Serial.print(scor0); Serial.print(' '); Serial.print(scor1); Serial.print(' '); Serial.println(scor2); //각 점수를 임계값과 비교하고 그에 따라 손가락 상태를 변경합니다. if(scor2 <0.5) // 약한 신호 - 열린 손가락 tgt_angles[0] =angle_open; if(scor2> 1.0) // 강한 신호 - 손가락 닫기 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; } }}

    회로도

    nrf24_hand_control_5jcEeCP8a3.fzz

    제조공정

    1. 피임약
    2. IR 센서를 사용하여 무선 로봇 차량 만들기
    3. 산업용 로봇 모터 제어를 단순화하는 레퍼런스 디자인
    4. LM35를 사용한 온도 기반 장치 제어 시스템
    5. AI를 사용하여 빛의 속성 제어 | 초연속 세대
    6. 3DG 로봇 시뮬레이션 소프트웨어를 사용하여 로봇 자동화 계획
    7. 자동 열차 제어
    8. Arduino, 1Sheeld 및 Android를 사용한 범용 원격 제어
    9. IoT를 사용하여 로봇 팔 원격 제어
    10. B&R 기술을 사용하여 로봇 쓰레기 분류 시스템을 구축하는 학생들