Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
home  MfgRobots >> Промышленное производство >  >> Manufacturing Technology >> Производственный процесс

Роботизированное ручное управление с использованием ЭМГ

Компоненты и расходные материалы

устройство УЭКГ
× 3
inMoov hand
× 1
Arduino Nano R3
× 1
16-канальный драйвер PWM Adafruit PCA9685
× 1
Модуль nRF24 (общий)
× 1

Об этом проекте

У нашей команды долгая история с роботизированными руками. Некоторое время мы пытались сделать надежный протез руки, но для этого проекта я использую хороший пример существующей руки с открытым исходным кодом:inMoov.

Не буду вдаваться в подробности ручной сборки - она ​​хорошо описана на сайте проекта и довольно сложна. Я сосредоточусь на контроле здесь, так как это совершенно новый вариант :)
Также, посмотрите, как эта технология развивалась с течением времени, в следующем проекте:https://www.hackster.io/the_3d6/seeing-muscles-at -work-8-channel-emg-with-leds-039d69

1. Обработка сигналов

Управление основано на ЭМГ - электрической активности мышц. Сигнал ЭМГ получается с помощью трех устройств УЭКГ (я знаю, что это должен быть монитор ЭКГ, но, поскольку он основан на общем АЦП, он может измерять любые биосигналы, включая ЭМГ). Для обработки ЭМГ у ЭКГ есть специальный режим, в котором он отправляет данные спектра с 32 ячейками и среднее значение «мышечного окна» (средняя спектральная интенсивность от 75 до 440 Гц). Спектральные изображения выглядят так:

Здесь частота по вертикальной оси (на каждом из 3 графиков, низкая частота внизу, высокая вверху - от 0 до 488 Гц с шагом ~ 15 Гц), время по горизонтали (здесь старые данные слева в целом на экране около 10 секунд). Интенсивность кодируется цветом:синий - низкий, зеленый - средний, желтый - высокий, красный - еще более высокий. Для надежного распознавания жестов требуется правильная обработка этих изображений на ПК. Но для простой активации пальцев руки робота достаточно просто использовать усредненное значение по 3 каналам - uECG удобно предоставляет его в определенных байтах пакета, чтобы скетч Arduino мог его проанализировать. Эти значения выглядят намного проще:

Красные, зеленые и синие диаграммы - это необработанные значения с устройств УЭКГ для различных групп мышц, когда я сжимаю соответственно большой, безымянный и средний пальцы. Для нашего глаза эти случаи явно различны, но нам нужно каким-то образом превратить эти значения в «оценку пальца», чтобы программа могла выводить значения на ручные сервоприводы. Проблема в том, что сигналы от групп мышц «смешанные»:в 1-м и 3-м случаях интенсивность синего сигнала примерно одинакова, а красный и зеленый - разные. Во 2-м и 3-м случаях зеленые сигналы одинаковы, но синий и красный разные. Чтобы «размешать» их, я использовал относительно простую формулу:

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 и источник питания с высоким усилителем 5 В, которого будет достаточно для одновременного перемещения всех этих сервоприводов (поэтому для стабильной работы требуется номинальная мощность не менее 5 А).

Список подключений:
nRF24 контакт 1 (GND) - Arduino GND
nRF24 контакт 2 (Vcc) - Arduino 3.3v
nRF24 контакт 3 (Chip Enable) - 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 + - high amp 5V
PCA9685 GND - GND высокого усилителя
Сервоприводы пальцев:к каналам PCA 0–4, в моем обозначении большой палец - канал 0, указательный палец - канал 1 и т. Д.

3. Размещение датчиков ЭМГ

Чтобы получить разумные показания, важно размещать устройства ЭКГ, которые регистрируют мышечную активность, в нужных местах. Хотя здесь возможно множество различных вариантов, для каждого из них требуется свой подход к обработке сигналов, поэтому я делюсь тем, что использовал:

Это может показаться нелогичным, но сигнал мышцы большого пальца лучше виден на противоположной стороне руки, поэтому один из датчиков размещен там, и все они расположены близко к локтю (большая часть тела мышц находится в этой области. , но вы хотите проверить, где именно находятся ваши - индивидуальная разница довольно большая)

4. Код

Перед запуском основной программы вам нужно будет узнать идентификаторы устройств ваших конкретных устройств uECG (это делается раскомментированием строки 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;
return v;
}
long last_servo_upd =0; // время, когда мы в последний раз обновляли значения сервопривода - не хочу делать это слишком часто
byte in_pack [32]; // массив для входящего RF-пакета
unsigned long unit_ids [3] ={4294963881, 4294943100, 28358}; // массив известных идентификаторов ЭКГ - необходимо заполнить идентификаторами ваших модулей
int unit_val [3] ={0, 0, 0}; // массив значений uЭКГ с этими идентификаторами
float tgt_angles [5]; // целевые углы для 5 пальцев
float cur_angles [5]; // текущие углы для 5 пальцев
float angle_open =30; // угол, соответствующий открытому пальцу
float angle_closed =150; // угол, соответствующий закрытому пальцу
void setup () {
// nRF24 требует относительно медленного SPI, вероятно, будет работать и на 2 МГц
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.setPWMFreq (60); // Аналоговые сервоприводы работают с частотой ~ 60 Гц. Обновления
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 ); // обработка пакета
byte u1 =in_pack [3]; // 32-битный идентификатор блока, уникальный для каждого устройства uECG
byte u2 =in_pack [4];
byte u3 =in_pack [ 5];
байт u4 =in_pack [6];
длинный идентификатор без знака =(u1 <<24) | (u2 <<16) | (u3 <<8) | u4;
//Serial.println(id); // раскомментируйте эту строку, чтобы составить список ваших идентификаторов uECG
if (in_pack [7]! =32) id =0; // неправильный тип упаковки:в режиме EMG этот байт должен быть 32
int val =in_pack [10]; // значение мышечной активности
if (val! =in_pack [11]) id =0; // значение дублируется в 2 байта, потому что радиочастотный шум может испортить пакет, и у нас нет CRC с nRF24
// найти, какой идентификатор соответствует текущему идентификатору и заполнить значение
для (int n =0; n <3; n ++)
if (id ==unit_ids [n])
unit_val [n] =val;
}
long 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_val [0];
v1 =v1 * 0.7 + 0.3 * (float) unit_val [1];
v2 =v2 * 0.7 + 0.3 * (float) unit_val [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] =угол_закрытого;
}
}
}

5. Результаты

С некоторыми экспериментами, которые длились около 2 часов, я смог получить довольно надежную работу (видео показывает типичный случай):

Он ведет себя не идеально и с этой обработкой может распознавать только открытые и закрытые пальцы (и даже не каждую из 5, он обнаруживает только 3 группы мышц:большой, указательный и средний вместе, безымянный и мизинец вместе). Но «AI», который анализирует сигнал, берет здесь 3 строки кода и использует одно значение из каждого канала. Я считаю, что можно сделать гораздо больше, анализируя 32-биновые спектральные изображения на ПК или смартфоне. Также в этой версии используются всего 3 аппарата УЭКГ (каналы ЭМГ). С большим количеством каналов должно быть возможно распознавать действительно сложные шаблоны - но что ж, в этом и заключается суть проекта, предоставить некоторую отправную точку для всех, кто интересуется :) Ручное управление определенно не единственное приложение для такой системы.

Код

  • emg_hand_control2.ino
emg_hand_control2.ino Arduino
 #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 swapbits (uint8_t a) {// адрес канала uECG использует перестановку битов // инвертировать битовый порядок в одном байте uint8_t v =0; если (a &0x80) v | =0x01; если (a &0x40) v | =0x02; если (a &0x20) v | =0x04; если (a &0x10) v | =0x08; если (a &0x08) v | =0x10; если (a &0x04) v | =0x20; если (a &0x02) v | =0x40; если (a &0x01) v | =0x80; return v;} long last_servo_upd =0; // время, когда мы в последний раз обновляли значения сервоприводов - не нужно делать это слишком частоbyte in_pack [32]; // массив для входящих пакетов RF без подписи long unit_ids [3] ={4294963881, 4294943100, 28358}; // массив известных идентификаторов ЭКГ - необходимо заполнить собственными идентификаторами модуляsint unit_vals [3] ={0, 0, 0}; // массив значений uЭКГ с этими IDsfloat tgt_angles [5]; // целевые углы для пяти пальцев cur_angles [5]; // текущие углы для 5 пальцев смещения angle_open =30; // угол, соответствующий открытому пальцу смещения angle_closed =150; // угол, соответствующий закрытому fingervoid setup () {// nRF24 требует относительно медленного SPI, вероятно, будет работать и на 2 МГц 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.setPWMFreq (60); // Аналоговые сервоприводы работают с частотой ~ 60 Гц, обновления для (int i =0; i <5; i ++) // устанавливаем начальные положения пальцев {tgt_angles [i] =angle_open; cur_angles [я] =угол_открытый; }} 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]; // 32-битный идентификатор блока, уникальный для каждого байта устройства uECG u2 =in_pack [4]; байт u3 =in_pack [5]; байт u4 =in_pack [6]; беззнаковый длинный id =(u1 <<24) | (u2 <<16) | (u3 <<8) | u4; //Serial.println(id); // раскомментируйте эту строку, чтобы составить список ваших идентификаторов uECG if (in_pack [7]! =32) id =0; // неправильный тип пакета:в режиме EMG этот байт должен быть 32 int val =in_pack [10]; // значение мышечной активности if (val! =in_pack [11]) id =0; // значение дублируется в 2 байта, потому что радиочастотный шум может повредить пакет, и у нас нет CRC с nRF24 // найти, какой идентификатор соответствует текущему идентификатору и заполнить значение для (int n =0; n <3; n ++) if (id ==unit_ids [n]) unit_val [n] =val; } long 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_val [0]; v1 =v1 * 0,7 + 0,3 * (с плавающей запятой) unit_val [1]; v2 =v2 * 0,7 + 0,3 * (с плавающей запятой) unit_val [2]; // вычисление оценок по необработанным значениям 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 (scor1); Серийный.принт (''); Serial.println (scor2); // сравниваем каждую оценку с порогом и соответственно меняем состояния пальцев if (scor2 <0.5) // слабый сигнал - открыть палец tgt_angles [0] =angle_open; if (scor2> 1.0) // сильный сигнал - закрыть палец tgt_angles [0] =angle_closed; если (scor1 <0,5) {tgt_angles [1] =angle_open; tgt_angles [2] =угол_открытый; } если (scor1> 1.0) {tgt_angles [1] =angle_closed; tgt_angles [2] =закрытый_угол; } если (scor0 <0,5) {tgt_angles [3] =angle_open; tgt_angles [4] =угол_открытый; } если (scor0> 1.0) {tgt_angles [3] =angle_closed; tgt_angles [4] =угол_закрытый; }}} 

Схема

nrf24_hand_control_5jcEeCP8a3.fzz

Производственный процесс

  1. Противозачаточные таблетки
  2. Создание беспроводного роботизированного автомобиля с использованием ИК-датчиков
  3. Эталонный дизайн упрощает управление двигателем промышленного робота
  4. Система управления устройством на основе температуры с использованием LM35
  5. Использование ИИ для управления свойствами света | Генерация суперконтинуума
  6. Использование программного обеспечения для моделирования роботов 3DG для планирования автоматизации роботов
  7. Автоматическое управление поездом
  8. Универсальный пульт дистанционного управления с использованием Arduino, 1Sheeld и Android
  9. Использование Интернета вещей для удаленного управления манипулятором
  10. Студенты создают роботизированную систему сортировки мусора с использованием технологии B&R