From 7af06f115a8202e8d9961fa4a3008286b722fec7 Mon Sep 17 00:00:00 2001 From: Surya Balaji Date: Sat, 17 Jan 2026 23:31:15 -0600 Subject: [PATCH] Restructure repo: add Python EMG scripts and move ESP32 code to EMG_Arm/ - Add emg_gui.py, learning_data_collection.py, learning_emg_filtering.py - Add collected EMG data and trained models - Move ESP32/PlatformIO code into EMG_Arm/ subdirectory - Add servo control functions for robotic hand (flex/unflex fingers) - Update .gitignore for Python and PlatformIO - Exclude large asset files from repo (>100MB) --- .gitignore | 23 +- .vscode/extensions.json | 8 - EMG_Arm/.gitignore | 5 + CMakeLists.txt => EMG_Arm/CMakeLists.txt | 0 {include => EMG_Arm/include}/README | 0 {lib => EMG_Arm/lib}/README | 0 partitions.csv => EMG_Arm/partitions.csv | 0 platformio.ini => EMG_Arm/platformio.ini | 0 .../sdkconfig.esp32-s3-devkitc1-n16r16 | 0 .../sdkconfig.esp32-s3-devkitc1-n16r16.old | 0 {src => EMG_Arm/src}/CMakeLists.txt | 0 EMG_Arm/src/main.c | 180 ++ {test => EMG_Arm/test}/README | 0 collected_data/user_001_20260108_170626.hdf5 | Bin 0 -> 192174 bytes collected_data/user_001_20260108_171851.hdf5 | Bin 0 -> 198912 bytes collected_data/user_001_20260108_172535.hdf5 | Bin 0 -> 193577 bytes collected_data/user_001_20260108_174542.hdf5 | Bin 0 -> 193649 bytes collected_data/user_001_20260108_174934.hdf5 | Bin 0 -> 191576 bytes collected_data/user_001_20260109_215307.hdf5 | Bin 0 -> 119342 bytes collected_data/user_002_20260108_175610.hdf5 | Bin 0 -> 192050 bytes collected_data/user_002_20260108_220204.hdf5 | Bin 0 -> 193445 bytes collected_data/user_003_20260109_154733.hdf5 | Bin 0 -> 181138 bytes collected_data/user_003_20260109_215459.hdf5 | Bin 0 -> 237178 bytes collected_data/user_004_20260110_154828.hdf5 | Bin 0 -> 247632 bytes emg_gui.py | 1459 +++++++++++ learning_data_collection.py | 2235 +++++++++++++++++ learning_emg_filtering.py | 243 ++ models/emg_lda_classifier.joblib | Bin 0 -> 2581 bytes src/main.c | 46 - 29 files changed, 4140 insertions(+), 59 deletions(-) delete mode 100644 .vscode/extensions.json create mode 100644 EMG_Arm/.gitignore rename CMakeLists.txt => EMG_Arm/CMakeLists.txt (100%) rename {include => EMG_Arm/include}/README (100%) rename {lib => EMG_Arm/lib}/README (100%) rename partitions.csv => EMG_Arm/partitions.csv (100%) rename platformio.ini => EMG_Arm/platformio.ini (100%) rename sdkconfig.esp32-s3-devkitc1-n16r16 => EMG_Arm/sdkconfig.esp32-s3-devkitc1-n16r16 (100%) rename sdkconfig.esp32-s3-devkitc1-n16r16.old => EMG_Arm/sdkconfig.esp32-s3-devkitc1-n16r16.old (100%) rename {src => EMG_Arm/src}/CMakeLists.txt (100%) create mode 100644 EMG_Arm/src/main.c rename {test => EMG_Arm/test}/README (100%) create mode 100644 collected_data/user_001_20260108_170626.hdf5 create mode 100644 collected_data/user_001_20260108_171851.hdf5 create mode 100644 collected_data/user_001_20260108_172535.hdf5 create mode 100644 collected_data/user_001_20260108_174542.hdf5 create mode 100644 collected_data/user_001_20260108_174934.hdf5 create mode 100644 collected_data/user_001_20260109_215307.hdf5 create mode 100644 collected_data/user_002_20260108_175610.hdf5 create mode 100644 collected_data/user_002_20260108_220204.hdf5 create mode 100644 collected_data/user_003_20260109_154733.hdf5 create mode 100644 collected_data/user_003_20260109_215459.hdf5 create mode 100644 collected_data/user_004_20260110_154828.hdf5 create mode 100644 emg_gui.py create mode 100644 learning_data_collection.py create mode 100644 learning_emg_filtering.py create mode 100644 models/emg_lda_classifier.joblib delete mode 100644 src/main.c diff --git a/.gitignore b/.gitignore index 89cc49c..bcf7e0c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,18 @@ -.pio -.vscode/.browse.c_cpp.db* -.vscode/c_cpp_properties.json -.vscode/launch.json -.vscode/ipch +# Python +__pycache__/ +*.py[cod] +*.egg-info/ +.venv/ +venv/ + +# PlatformIO +EMG_Arm/.pio/ +EMG_Arm/.vscode/ + +# Data (uncomment if you want to exclude) +# collected_data/ +# models/ + +# OS +.DS_Store +Thumbs.db diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 29203a4..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "recommendations": [ - "pioarduino.pioarduino-ide" - ], - "unwantedRecommendations": [ - "ms-vscode.cpptools-extension-pack" - ] -} diff --git a/EMG_Arm/.gitignore b/EMG_Arm/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/EMG_Arm/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/CMakeLists.txt b/EMG_Arm/CMakeLists.txt similarity index 100% rename from CMakeLists.txt rename to EMG_Arm/CMakeLists.txt diff --git a/include/README b/EMG_Arm/include/README similarity index 100% rename from include/README rename to EMG_Arm/include/README diff --git a/lib/README b/EMG_Arm/lib/README similarity index 100% rename from lib/README rename to EMG_Arm/lib/README diff --git a/partitions.csv b/EMG_Arm/partitions.csv similarity index 100% rename from partitions.csv rename to EMG_Arm/partitions.csv diff --git a/platformio.ini b/EMG_Arm/platformio.ini similarity index 100% rename from platformio.ini rename to EMG_Arm/platformio.ini diff --git a/sdkconfig.esp32-s3-devkitc1-n16r16 b/EMG_Arm/sdkconfig.esp32-s3-devkitc1-n16r16 similarity index 100% rename from sdkconfig.esp32-s3-devkitc1-n16r16 rename to EMG_Arm/sdkconfig.esp32-s3-devkitc1-n16r16 diff --git a/sdkconfig.esp32-s3-devkitc1-n16r16.old b/EMG_Arm/sdkconfig.esp32-s3-devkitc1-n16r16.old similarity index 100% rename from sdkconfig.esp32-s3-devkitc1-n16r16.old rename to EMG_Arm/sdkconfig.esp32-s3-devkitc1-n16r16.old diff --git a/src/CMakeLists.txt b/EMG_Arm/src/CMakeLists.txt similarity index 100% rename from src/CMakeLists.txt rename to EMG_Arm/src/CMakeLists.txt diff --git a/EMG_Arm/src/main.c b/EMG_Arm/src/main.c new file mode 100644 index 0000000..d78ff57 --- /dev/null +++ b/EMG_Arm/src/main.c @@ -0,0 +1,180 @@ + +#include +#include "driver/gpio.h" +#include "driver/ledc.h" + +// Finger servo pin mappings +#define thumbServoPin GPIO_NUM_1 +#define indexServoPin GPIO_NUM_4 +#define middleServoPin GPIO_NUM_5 +#define ringServoPin GPIO_NUM_6 +#define pinkyServoPin GPIO_NUM_7 + +// LEDC channels (one per servo) +#define thumbChannel LEDC_CHANNEL_0 +#define indexChannel LEDC_CHANNEL_1 +#define middleChannel LEDC_CHANNEL_2 +#define ringChannel LEDC_CHANNEL_3 +#define pinkyChannel LEDC_CHANNEL_4 +#define deg180 2048 +#define deg0 430 + +// Arrays for cleaner initialization +const int servoPins[] = {thumbServoPin, indexServoPin, middleServoPin, ringServoPin, pinkyServoPin}; +const int servoChannels[] = {thumbChannel, indexChannel, middleChannel, ringChannel, pinkyChannel}; +const int numServos = 5; + +void servoInit() { + // LEDC timer configuration (shared by all servos) + ledc_timer_config_t ledc_timer = {}; + ledc_timer.speed_mode = LEDC_LOW_SPEED_MODE; + ledc_timer.timer_num = LEDC_TIMER_0; + ledc_timer.duty_resolution = LEDC_TIMER_14_BIT; + ledc_timer.freq_hz = 50; + ledc_timer.clk_cfg = LEDC_AUTO_CLK; + ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); + + // Initialize each finger servo channel + for (int i = 0; i < numServos; i++) { + ledc_channel_config_t ledc_channel = {}; + ledc_channel.speed_mode = LEDC_LOW_SPEED_MODE; + ledc_channel.channel = servoChannels[i]; + ledc_channel.timer_sel = LEDC_TIMER_0; + ledc_channel.intr_type = LEDC_INTR_DISABLE; + ledc_channel.gpio_num = servoPins[i]; + ledc_channel.duty = deg0; // Start with fingers open + ledc_channel.hpoint = 0; + ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); + } +} + +// Flex functions (move to 180 degrees - finger closed) +void flexThumb() { + ledc_set_duty(LEDC_LOW_SPEED_MODE, thumbChannel, deg180); + ledc_update_duty(LEDC_LOW_SPEED_MODE, thumbChannel); +} + +void flexIndex() { + ledc_set_duty(LEDC_LOW_SPEED_MODE, indexChannel, deg180); + ledc_update_duty(LEDC_LOW_SPEED_MODE, indexChannel); +} + +void flexMiddle() { + ledc_set_duty(LEDC_LOW_SPEED_MODE, middleChannel, deg180); + ledc_update_duty(LEDC_LOW_SPEED_MODE, middleChannel); +} + +void flexRing() { + ledc_set_duty(LEDC_LOW_SPEED_MODE, ringChannel, deg180); + ledc_update_duty(LEDC_LOW_SPEED_MODE, ringChannel); +} + +void flexPinky() { + ledc_set_duty(LEDC_LOW_SPEED_MODE, pinkyChannel, deg180); + ledc_update_duty(LEDC_LOW_SPEED_MODE, pinkyChannel); +} + +// Unflex functions (move to 0 degrees - finger open) +void unflexThumb() { + ledc_set_duty(LEDC_LOW_SPEED_MODE, thumbChannel, deg0); + ledc_update_duty(LEDC_LOW_SPEED_MODE, thumbChannel); +} + +void unflexIndex() { + ledc_set_duty(LEDC_LOW_SPEED_MODE, indexChannel, deg0); + ledc_update_duty(LEDC_LOW_SPEED_MODE, indexChannel); +} + +void unflexMiddle() { + ledc_set_duty(LEDC_LOW_SPEED_MODE, middleChannel, deg0); + ledc_update_duty(LEDC_LOW_SPEED_MODE, middleChannel); +} + +void unflexRing() { + ledc_set_duty(LEDC_LOW_SPEED_MODE, ringChannel, deg0); + ledc_update_duty(LEDC_LOW_SPEED_MODE, ringChannel); +} + +void unflexPinky() { + ledc_set_duty(LEDC_LOW_SPEED_MODE, pinkyChannel, deg0); + ledc_update_duty(LEDC_LOW_SPEED_MODE, pinkyChannel); +} + +// Combo functions +void makeFist() { + // Set all duties first + ledc_set_duty(LEDC_LOW_SPEED_MODE, thumbChannel, deg180); + ledc_set_duty(LEDC_LOW_SPEED_MODE, indexChannel, deg180); + ledc_set_duty(LEDC_LOW_SPEED_MODE, middleChannel, deg180); + ledc_set_duty(LEDC_LOW_SPEED_MODE, ringChannel, deg180); + ledc_set_duty(LEDC_LOW_SPEED_MODE, pinkyChannel, deg180); + // Update all at once + ledc_update_duty(LEDC_LOW_SPEED_MODE, thumbChannel); + ledc_update_duty(LEDC_LOW_SPEED_MODE, indexChannel); + ledc_update_duty(LEDC_LOW_SPEED_MODE, middleChannel); + ledc_update_duty(LEDC_LOW_SPEED_MODE, ringChannel); + ledc_update_duty(LEDC_LOW_SPEED_MODE, pinkyChannel); +} + +void openHand() { + // Set all duties first + ledc_set_duty(LEDC_LOW_SPEED_MODE, thumbChannel, deg0); + ledc_set_duty(LEDC_LOW_SPEED_MODE, indexChannel, deg0); + ledc_set_duty(LEDC_LOW_SPEED_MODE, middleChannel, deg0); + ledc_set_duty(LEDC_LOW_SPEED_MODE, ringChannel, deg0); + ledc_set_duty(LEDC_LOW_SPEED_MODE, pinkyChannel, deg0); + // Update all at once + ledc_update_duty(LEDC_LOW_SPEED_MODE, thumbChannel); + ledc_update_duty(LEDC_LOW_SPEED_MODE, indexChannel); + ledc_update_duty(LEDC_LOW_SPEED_MODE, middleChannel); + ledc_update_duty(LEDC_LOW_SPEED_MODE, ringChannel); + ledc_update_duty(LEDC_LOW_SPEED_MODE, pinkyChannel); +} + +void individualFingerDemo(int delay_ms){ + flexThumb(); + vTaskDelay(pdMS_TO_TICKS(delay_ms)); + unflexThumb(); + vTaskDelay(pdMS_TO_TICKS(delay_ms)); + + flexIndex(); + vTaskDelay(pdMS_TO_TICKS(delay_ms)); + unflexIndex(); + vTaskDelay(pdMS_TO_TICKS(delay_ms)); + + flexMiddle(); + vTaskDelay(pdMS_TO_TICKS(delay_ms)); + unflexMiddle(); + vTaskDelay(pdMS_TO_TICKS(delay_ms)); + + flexRing(); + vTaskDelay(pdMS_TO_TICKS(delay_ms)); + unflexRing(); + vTaskDelay(pdMS_TO_TICKS(delay_ms)); + + flexPinky(); + vTaskDelay(pdMS_TO_TICKS(delay_ms)); + unflexPinky(); + vTaskDelay(pdMS_TO_TICKS(delay_ms)); +} + +void closeOpenDemo(int delay_ms){ + makeFist(); + vTaskDelay(pdMS_TO_TICKS(delay_ms)); + openHand(); + vTaskDelay(pdMS_TO_TICKS(delay_ms)); +} + +void app_main() { + servoInit(); + + // Demo: flex and unflex each finger in sequence + // while(1) { + // individualFingerDemo(1000); + // } + + // Demo: close and open hand + while(1) { + closeOpenDemo(1000); + } +} \ No newline at end of file diff --git a/test/README b/EMG_Arm/test/README similarity index 100% rename from test/README rename to EMG_Arm/test/README diff --git a/collected_data/user_001_20260108_170626.hdf5 b/collected_data/user_001_20260108_170626.hdf5 new file mode 100644 index 0000000000000000000000000000000000000000..6e86c47e91f7907e005746ec8d850e224910b838 GIT binary patch literal 192174 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