diff --git a/EMG_Arm/src/CMakeLists.txt b/EMG_Arm/src/CMakeLists.txt index 483bc0c..3ca814c 100644 --- a/EMG_Arm/src/CMakeLists.txt +++ b/EMG_Arm/src/CMakeLists.txt @@ -1,6 +1,38 @@ -# This file was automatically generated for projects -# without default 'CMakeLists.txt' file. +# CMakeLists.txt for Bucky Arm EMG Robotic Hand +# +# Project structure: +# src/ +# ├── config/ Configuration headers +# ├── hal/ Hardware Abstraction Layer +# ├── drivers/ Device drivers +# ├── core/ Core business logic +# └── app/ Application entry point -FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/*.*) +# Collect all source files from each layer +set(HAL_SOURCES + hal/servo_hal.c +) -idf_component_register(SRCS ${app_sources}) +set(DRIVER_SOURCES + drivers/hand.c + drivers/emg_sensor.c +) + +set(CORE_SOURCES + core/gestures.c +) + +set(APP_SOURCES + app/main.c +) + +# Register component with ESP-IDF +idf_component_register( + SRCS + ${HAL_SOURCES} + ${DRIVER_SOURCES} + ${CORE_SOURCES} + ${APP_SOURCES} + INCLUDE_DIRS + . +) diff --git a/EMG_Arm/src/app/main.c b/EMG_Arm/src/app/main.c new file mode 100644 index 0000000..57dfad1 --- /dev/null +++ b/EMG_Arm/src/app/main.c @@ -0,0 +1,114 @@ +/** + * @file main.c + * @brief Application entry point for the EMG-controlled robotic hand. + * + * This is the top-level application that initializes all subsystems + * and runs the main loop. Currently configured to stream EMG data + * over USB serial for Python to receive. + * + * @note This is Layer 4 (Application). + */ + +#include +#include +#include +#include "esp_timer.h" + +#include "config/config.h" +#include "drivers/hand.h" +#include "drivers/emg_sensor.h" +#include "core/gestures.h" + +/******************************************************************************* + * Private Functions + ******************************************************************************/ + +/** + * @brief Stream EMG data over USB serial. + * + * Outputs data in format: "timestamp_ms,ch0,ch1,ch2,ch3\n" + * This matches what Python's SimulatedEMGStream produces. + */ +static void stream_emg_data(void) +{ + emg_sample_t sample; + + printf("\n[EMG] Starting data stream at %d Hz...\n", EMG_SAMPLE_RATE_HZ); + printf("[EMG] Format: timestamp_ms,ch0,ch1,ch2,ch3\n\n"); + + /* + * FreeRTOS tick rate is typically 100Hz (10ms per tick). + * We must delay at least 1 tick to yield to the scheduler. + * This limits our max rate to ~100 Hz, which is fine for testing. + */ + const TickType_t delay_ticks = 1; /* Minimum 1 tick (~10ms) */ + + while (1) { + /* Read EMG (fake or real depending on FEATURE_FAKE_EMG) */ + emg_sensor_read(&sample); + + /* Output in CSV format matching Python expectation */ + printf("%lu,%u,%u,%u,%u\n", + (unsigned long)sample.timestamp_ms, + sample.channels[0], + sample.channels[1], + sample.channels[2], + sample.channels[3]); + + /* Yield to FreeRTOS scheduler - prevents watchdog timeout */ + vTaskDelay(delay_ticks); + } +} + +/** + * @brief Run demo mode - cycle through gestures. + */ +static void run_demo(void) +{ + printf("\n[DEMO] Running gesture demo...\n"); + + while (1) { + gestures_demo_fist(1000); + } +} + +/******************************************************************************* + * Application Entry Point + ******************************************************************************/ + +void app_main(void) +{ + printf("\n"); + printf("========================================\n"); + printf(" Bucky Arm - EMG Robotic Hand\n"); + printf(" Firmware v1.0.0\n"); + printf("========================================\n\n"); + + /* Initialize subsystems */ + printf("[INIT] Initializing hand (servos)...\n"); + hand_init(); + + printf("[INIT] Initializing EMG sensor...\n"); + emg_sensor_init(); + +#if FEATURE_FAKE_EMG + printf("[INIT] Using FAKE EMG data (sensors not connected)\n"); +#else + printf("[INIT] Using REAL EMG sensors\n"); +#endif + + printf("[INIT] Done!\n\n"); + + /* + * Choose what to run: + * - stream_emg_data(): Send EMG data to laptop (Phase 1) + * - run_demo(): Test servo movement + * + * For now, we stream EMG data. + * Comment out and use run_demo() to test servos. + */ + stream_emg_data(); + + /* Alternative: run servo demo */ + // run_demo(); +} diff --git a/EMG_Arm/src/config/config.h b/EMG_Arm/src/config/config.h new file mode 100644 index 0000000..5044d87 --- /dev/null +++ b/EMG_Arm/src/config/config.h @@ -0,0 +1,109 @@ +/** + * @file config.h + * @brief Centralized configuration for the EMG-controlled robotic hand. + * + * All hardware pin definitions and system constants in one place. + * Modify this file to adapt to different hardware configurations. + */ + +#ifndef CONFIG_H +#define CONFIG_H + +#include "driver/gpio.h" +#include "driver/ledc.h" + +/******************************************************************************* + * Feature Flags + * + * Compile-time switches to enable/disable features. + * Set to 1 to enable, 0 to disable. + ******************************************************************************/ + +/** + * @brief Use fake EMG data (random values) instead of real ADC reads. + * + * Set to 1 while waiting for EMG sensors to arrive. + * Set to 0 when ready to use real sensors. + */ +#define FEATURE_FAKE_EMG 1 + +/******************************************************************************* + * GPIO Pin Definitions - Servos + ******************************************************************************/ + +#define PIN_SERVO_THUMB GPIO_NUM_1 +#define PIN_SERVO_INDEX GPIO_NUM_4 +#define PIN_SERVO_MIDDLE GPIO_NUM_5 +#define PIN_SERVO_RING GPIO_NUM_6 +#define PIN_SERVO_PINKY GPIO_NUM_7 + +/******************************************************************************* + * Servo PWM Configuration + ******************************************************************************/ + +#define SERVO_PWM_FREQ_HZ 50 /**< Standard servo frequency */ +#define SERVO_PWM_RESOLUTION LEDC_TIMER_14_BIT /**< 14-bit = 16384 levels */ +#define SERVO_PWM_TIMER LEDC_TIMER_0 /**< LEDC timer for servos */ +#define SERVO_PWM_SPEED_MODE LEDC_LOW_SPEED_MODE /**< ESP32-S3 uses low-speed */ + +#define SERVO_DUTY_MIN 430 /**< Duty cycle for 0 degrees (extended) */ +#define SERVO_DUTY_MAX 2048 /**< Duty cycle for 180 degrees (flexed) */ + +/******************************************************************************* + * LEDC Channel Assignments + ******************************************************************************/ + +#define LEDC_CH_THUMB LEDC_CHANNEL_0 +#define LEDC_CH_INDEX LEDC_CHANNEL_1 +#define LEDC_CH_MIDDLE LEDC_CHANNEL_2 +#define LEDC_CH_RING LEDC_CHANNEL_3 +#define LEDC_CH_PINKY LEDC_CHANNEL_4 + +/******************************************************************************* + * EMG Configuration + ******************************************************************************/ + +#define EMG_NUM_CHANNELS 4 /**< Number of EMG sensor channels */ +#define EMG_SAMPLE_RATE_HZ 1000 /**< Samples per second per channel */ + +/******************************************************************************* + * Common Type Definitions + ******************************************************************************/ + +/** + * @brief Finger identification. + */ +typedef enum { + FINGER_THUMB = 0, + FINGER_INDEX, + FINGER_MIDDLE, + FINGER_RING, + FINGER_PINKY, + FINGER_COUNT /**< Total number of fingers (5) */ +} finger_t; + +/** + * @brief Recognized gestures. + */ +typedef enum { + GESTURE_NONE = 0, + GESTURE_REST, + GESTURE_FIST, + GESTURE_OPEN, + GESTURE_HOOK_EM, + GESTURE_THUMBS_UP, + GESTURE_COUNT +} gesture_t; + +/** + * @brief System operating modes. + */ +typedef enum { + MODE_IDLE = 0, /**< Waiting for commands */ + MODE_DATA_STREAM, /**< Streaming EMG data to laptop */ + MODE_COMMAND, /**< Executing gesture commands from laptop */ + MODE_DEMO, /**< Running demo sequence */ + MODE_COUNT +} system_mode_t; + +#endif /* CONFIG_H */ diff --git a/EMG_Arm/src/core/gestures.c b/EMG_Arm/src/core/gestures.c new file mode 100644 index 0000000..7aa4db6 --- /dev/null +++ b/EMG_Arm/src/core/gestures.c @@ -0,0 +1,122 @@ +/** + * @file gestures.c + * @brief Named gesture implementation. + * + * Implements gesture functions using the hand driver. + */ + +#include "gestures.h" +#include "drivers/hand.h" +#include +#include + +/******************************************************************************* + * Private Data + ******************************************************************************/ + +/** @brief Gesture name lookup table. */ +static const char* gesture_names[GESTURE_COUNT] = { + "NONE", + "REST", + "FIST", + "OPEN", + "HOOK_EM", + "THUMBS_UP" +}; + +/******************************************************************************* + * Public Functions + ******************************************************************************/ + +void gestures_execute(gesture_t gesture) +{ + switch (gesture) { + case GESTURE_REST: + gesture_rest(); + break; + case GESTURE_FIST: + gesture_fist(); + break; + case GESTURE_OPEN: + gesture_open(); + break; + case GESTURE_HOOK_EM: + gesture_hook_em(); + break; + case GESTURE_THUMBS_UP: + gesture_thumbs_up(); + break; + default: + break; + } +} + +const char* gestures_get_name(gesture_t gesture) +{ + if (gesture >= GESTURE_COUNT) { + return "UNKNOWN"; + } + return gesture_names[gesture]; +} + +/******************************************************************************* + * Individual Gesture Functions + ******************************************************************************/ + +void gesture_open(void) +{ + hand_unflex_all(); +} + +void gesture_fist(void) +{ + hand_flex_all(); +} + +void gesture_hook_em(void) +{ + /* Index and pinky extended, others flexed */ + hand_flex_finger(FINGER_THUMB); + hand_unflex_finger(FINGER_INDEX); + hand_flex_finger(FINGER_MIDDLE); + hand_flex_finger(FINGER_RING); + hand_unflex_finger(FINGER_PINKY); +} + +void gesture_thumbs_up(void) +{ + /* Thumb extended, others flexed */ + hand_unflex_finger(FINGER_THUMB); + hand_flex_finger(FINGER_INDEX); + hand_flex_finger(FINGER_MIDDLE); + hand_flex_finger(FINGER_RING); + hand_flex_finger(FINGER_PINKY); +} + +void gesture_rest(void) +{ + /* Rest is same as open - neutral position */ + gesture_open(); +} + +/******************************************************************************* + * Demo Functions + ******************************************************************************/ + +void gestures_demo_fingers(uint32_t delay_ms) +{ + for (int finger = 0; finger < FINGER_COUNT; finger++) { + hand_flex_finger((finger_t)finger); + vTaskDelay(pdMS_TO_TICKS(delay_ms)); + hand_unflex_finger((finger_t)finger); + vTaskDelay(pdMS_TO_TICKS(delay_ms)); + } +} + +void gestures_demo_fist(uint32_t delay_ms) +{ + gesture_fist(); + vTaskDelay(pdMS_TO_TICKS(delay_ms)); + gesture_open(); + vTaskDelay(pdMS_TO_TICKS(delay_ms)); +} diff --git a/EMG_Arm/src/core/gestures.h b/EMG_Arm/src/core/gestures.h new file mode 100644 index 0000000..a77e9ea --- /dev/null +++ b/EMG_Arm/src/core/gestures.h @@ -0,0 +1,74 @@ +/** + * @file gestures.h + * @brief Named gesture definitions and execution. + * + * This module provides named gestures (fist, open, hook_em, etc.) + * that map to specific finger configurations. These are the gestures + * that the ML model will predict. + * + * @note This is Layer 3 (Core). Uses drivers/hand internally. + */ + +#ifndef GESTURES_H +#define GESTURES_H + +#include +#include "config/config.h" + +/******************************************************************************* + * Public Functions + ******************************************************************************/ + +/** + * @brief Execute a gesture by its enum value. + * + * @param gesture Which gesture to perform + */ +void gestures_execute(gesture_t gesture); + +/** + * @brief Get the name of a gesture as a string. + * + * @param gesture Which gesture + * @return Pointer to gesture name string (e.g., "FIST", "OPEN") + */ +const char* gestures_get_name(gesture_t gesture); + +/******************************************************************************* + * Individual Gesture Functions + ******************************************************************************/ + +/** @brief Open hand - all fingers extended. */ +void gesture_open(void); + +/** @brief Fist - all fingers flexed. */ +void gesture_fist(void); + +/** @brief Hook 'em Horns - index and pinky extended, others flexed. */ +void gesture_hook_em(void); + +/** @brief Thumbs up - thumb extended, others flexed. */ +void gesture_thumbs_up(void); + +/** @brief Rest - same as open (neutral position). */ +void gesture_rest(void); + +/******************************************************************************* + * Demo Functions + ******************************************************************************/ + +/** + * @brief Demo: cycle each finger individually. + * + * @param delay_ms Milliseconds between movements + */ +void gestures_demo_fingers(uint32_t delay_ms); + +/** + * @brief Demo: open and close fist repeatedly. + * + * @param delay_ms Milliseconds between open/close + */ +void gestures_demo_fist(uint32_t delay_ms); + +#endif /* GESTURES_H */ diff --git a/EMG_Arm/src/drivers/emg_sensor.c b/EMG_Arm/src/drivers/emg_sensor.c new file mode 100644 index 0000000..b63df5e --- /dev/null +++ b/EMG_Arm/src/drivers/emg_sensor.c @@ -0,0 +1,55 @@ +/** + * @file emg_sensor.c + * @brief EMG sensor driver implementation. + * + * Provides EMG readings - fake data for now, real ADC when sensors arrive. + */ + +#include "emg_sensor.h" +#include "esp_timer.h" +#include + +/******************************************************************************* + * Public Functions + ******************************************************************************/ + +void emg_sensor_init(void) +{ +#if FEATURE_FAKE_EMG + /* Seed random number generator for fake data */ + srand((unsigned int)esp_timer_get_time()); +#else + /* TODO: Configure ADC channels when sensors arrive */ + /* adc1_config_width(EMG_ADC_WIDTH); */ + /* adc1_config_channel_atten(ADC_EMG_CH0, EMG_ADC_ATTEN); */ + /* ... */ +#endif +} + +void emg_sensor_read(emg_sample_t *sample) +{ + sample->timestamp_ms = emg_sensor_get_timestamp_ms(); + +#if FEATURE_FAKE_EMG + /* + * Generate fake EMG data: + * - Base value around 512 (middle of 10-bit range, matching Python sim) + * - Random noise of +/- 50 + * - Mimics real EMG baseline noise + */ + for (int i = 0; i < EMG_NUM_CHANNELS; i++) { + int noise = (rand() % 101) - 50; /* -50 to +50 */ + sample->channels[i] = (uint16_t)(512 + noise); + } +#else + /* TODO: Real ADC reads when sensors arrive */ + /* sample->channels[0] = adc1_get_raw(ADC_EMG_CH0); */ + /* sample->channels[1] = adc1_get_raw(ADC_EMG_CH1); */ + /* ... */ +#endif +} + +uint32_t emg_sensor_get_timestamp_ms(void) +{ + return (uint32_t)(esp_timer_get_time() / 1000); +} diff --git a/EMG_Arm/src/drivers/emg_sensor.h b/EMG_Arm/src/drivers/emg_sensor.h new file mode 100644 index 0000000..cec5752 --- /dev/null +++ b/EMG_Arm/src/drivers/emg_sensor.h @@ -0,0 +1,56 @@ +/** + * @file emg_sensor.h + * @brief EMG sensor driver for reading muscle signals. + * + * This module provides EMG data acquisition. Currently generates fake + * data for testing (FEATURE_FAKE_EMG=1). When sensors arrive, the + * implementation switches to real ADC reads without changing the interface. + * + * @note This is Layer 2 (Driver). + */ + +#ifndef EMG_SENSOR_H +#define EMG_SENSOR_H + +#include +#include "config/config.h" + +/******************************************************************************* + * Data Types + ******************************************************************************/ + +/** + * @brief Single EMG reading from all channels. + */ +typedef struct { + uint32_t timestamp_ms; /**< Timestamp in milliseconds */ + uint16_t channels[EMG_NUM_CHANNELS]; /**< ADC values for each channel */ +} emg_sample_t; + +/******************************************************************************* + * Public Functions + ******************************************************************************/ + +/** + * @brief Initialize the EMG sensor system. + * + * If FEATURE_FAKE_EMG is enabled, just seeds the random generator. + * Otherwise, configures ADC channels for real sensor reading. + */ +void emg_sensor_init(void); + +/** + * @brief Read current values from all EMG channels. + * + * @param sample Pointer to struct to fill with current readings + */ +void emg_sensor_read(emg_sample_t *sample); + +/** + * @brief Get the current timestamp in milliseconds. + * + * @return Milliseconds since boot + */ +uint32_t emg_sensor_get_timestamp_ms(void); + +#endif /* EMG_SENSOR_H */ diff --git a/EMG_Arm/src/drivers/hand.c b/EMG_Arm/src/drivers/hand.c new file mode 100644 index 0000000..9731428 --- /dev/null +++ b/EMG_Arm/src/drivers/hand.c @@ -0,0 +1,48 @@ +/** + * @file hand.c + * @brief Hand driver implementation. + * + * Provides finger-level control using the servo HAL. + */ + +#include "hand.h" +#include "hal/servo_hal.h" + +/******************************************************************************* + * Public Functions + ******************************************************************************/ + +void hand_init(void) +{ + servo_hal_init(); +} + +void hand_flex_finger(finger_t finger) +{ + servo_hal_set_duty(finger, SERVO_DUTY_MAX); +} + +void hand_unflex_finger(finger_t finger) +{ + servo_hal_set_duty(finger, SERVO_DUTY_MIN); +} + +void hand_set_finger_angle(finger_t finger, float degrees) +{ + uint32_t duty = servo_hal_degrees_to_duty(degrees); + servo_hal_set_duty(finger, duty); +} + +void hand_flex_all(void) +{ + for (int i = 0; i < FINGER_COUNT; i++) { + hand_flex_finger((finger_t)i); + } +} + +void hand_unflex_all(void) +{ + for (int i = 0; i < FINGER_COUNT; i++) { + hand_unflex_finger((finger_t)i); + } +} diff --git a/EMG_Arm/src/drivers/hand.h b/EMG_Arm/src/drivers/hand.h new file mode 100644 index 0000000..7b2f171 --- /dev/null +++ b/EMG_Arm/src/drivers/hand.h @@ -0,0 +1,59 @@ +/** + * @file hand.h + * @brief Hand driver for individual finger control. + * + * This module provides an intuitive interface for controlling + * individual fingers - flex, unflex, or set to a specific angle. + * + * @note This is Layer 2 (Driver). Uses hal/servo_hal internally. + */ + +#ifndef HAND_H +#define HAND_H + +#include "config/config.h" + +/******************************************************************************* + * Public Functions + ******************************************************************************/ + +/** + * @brief Initialize the hand (all finger servos). + * + * Sets up PWM for all servos. All fingers start extended (open). + */ +void hand_init(void); + +/** + * @brief Flex a finger (close it). + * + * @param finger Which finger to flex + */ +void hand_flex_finger(finger_t finger); + +/** + * @brief Unflex a finger (extend/open it). + * + * @param finger Which finger to unflex + */ +void hand_unflex_finger(finger_t finger); + +/** + * @brief Set a finger to a specific angle. + * + * @param finger Which finger to move + * @param degrees Angle (0 = extended, 180 = fully flexed) + */ +void hand_set_finger_angle(finger_t finger, float degrees); + +/** + * @brief Flex all fingers at once. + */ +void hand_flex_all(void); + +/** + * @brief Unflex all fingers at once. + */ +void hand_unflex_all(void); + +#endif /* HAND_H */ diff --git a/EMG_Arm/src/gestures.c b/EMG_Arm/src/gestures.c deleted file mode 100644 index 4154fea..0000000 --- a/EMG_Arm/src/gestures.c +++ /dev/null @@ -1,140 +0,0 @@ -/** - * @file gestures.c - * @brief Gesture control implementation for the EMG-controlled robotic hand. - * - * This module implements high-level gesture functions using the low-level - * servo control interface. Each gesture function translates intuitive - * commands into appropriate servo positions. - */ - -#include "gestures.h" -#include "servo.h" -#include -#include - -/******************************************************************************* - * Individual Finger Control - Flex Functions - ******************************************************************************/ - -void flex_thumb(void) -{ - servo_set_finger(FINGER_THUMB, SERVO_POS_MAX); -} - -void flex_index(void) -{ - servo_set_finger(FINGER_INDEX, SERVO_POS_MAX); -} - -void flex_middle(void) -{ - servo_set_finger(FINGER_MIDDLE, SERVO_POS_MAX); -} - -void flex_ring(void) -{ - servo_set_finger(FINGER_RING, SERVO_POS_MAX); -} - -void flex_pinky(void) -{ - servo_set_finger(FINGER_PINKY, SERVO_POS_MAX); -} - -/******************************************************************************* - * Individual Finger Control - Unflex Functions - ******************************************************************************/ - -void unflex_thumb(void) -{ - servo_set_finger(FINGER_THUMB, SERVO_POS_MIN); -} - -void unflex_index(void) -{ - servo_set_finger(FINGER_INDEX, SERVO_POS_MIN); -} - -void unflex_middle(void) -{ - servo_set_finger(FINGER_MIDDLE, SERVO_POS_MIN); -} - -void unflex_ring(void) -{ - servo_set_finger(FINGER_RING, SERVO_POS_MIN); -} - -void unflex_pinky(void) -{ - servo_set_finger(FINGER_PINKY, SERVO_POS_MIN); -} - -/******************************************************************************* - * Composite Gestures - ******************************************************************************/ - -void gesture_make_fist(void) -{ - /* Flex all fingers simultaneously */ - flex_thumb(); - flex_index(); - flex_middle(); - flex_ring(); - flex_pinky(); -} - -void gesture_open_hand(void) -{ - /* Extend all fingers simultaneously */ - unflex_thumb(); - unflex_index(); - unflex_middle(); - unflex_ring(); - unflex_pinky(); -} - -/******************************************************************************* - * Demo/Test Sequences - ******************************************************************************/ - -void demo_individual_fingers(uint32_t delay_ms) -{ - /* Thumb */ - flex_thumb(); - vTaskDelay(pdMS_TO_TICKS(delay_ms)); - unflex_thumb(); - vTaskDelay(pdMS_TO_TICKS(delay_ms)); - - /* Index */ - flex_index(); - vTaskDelay(pdMS_TO_TICKS(delay_ms)); - unflex_index(); - vTaskDelay(pdMS_TO_TICKS(delay_ms)); - - /* Middle */ - flex_middle(); - vTaskDelay(pdMS_TO_TICKS(delay_ms)); - unflex_middle(); - vTaskDelay(pdMS_TO_TICKS(delay_ms)); - - /* Ring */ - flex_ring(); - vTaskDelay(pdMS_TO_TICKS(delay_ms)); - unflex_ring(); - vTaskDelay(pdMS_TO_TICKS(delay_ms)); - - /* Pinky */ - flex_pinky(); - vTaskDelay(pdMS_TO_TICKS(delay_ms)); - unflex_pinky(); - vTaskDelay(pdMS_TO_TICKS(delay_ms)); -} - -void demo_close_open(uint32_t delay_ms) -{ - gesture_make_fist(); - vTaskDelay(pdMS_TO_TICKS(delay_ms)); - gesture_open_hand(); - vTaskDelay(pdMS_TO_TICKS(delay_ms)); -} diff --git a/EMG_Arm/src/gestures.h b/EMG_Arm/src/gestures.h deleted file mode 100644 index c1b869f..0000000 --- a/EMG_Arm/src/gestures.h +++ /dev/null @@ -1,128 +0,0 @@ -/** - * @file gestures.h - * @brief Gesture control interface for the EMG-controlled robotic hand. - * - * This module provides high-level gesture functions for controlling the - * robotic hand. It builds upon the low-level servo control module to - * implement intuitive finger movements and hand gestures. - * - * Gesture Categories: - * - Individual finger control (flex/unflex each finger) - * - Composite gestures (fist, open hand, etc.) - * - Demo/test sequences - */ - -#ifndef GESTURES_H -#define GESTURES_H - -#include - -/******************************************************************************* - * Individual Finger Control - Flex Functions - * - * Flex functions move a finger to the closed (180 degree) position. - ******************************************************************************/ - -/** - * @brief Flex the thumb (move to closed position). - */ -void flex_thumb(void); - -/** - * @brief Flex the index finger (move to closed position). - */ -void flex_index(void); - -/** - * @brief Flex the middle finger (move to closed position). - */ -void flex_middle(void); - -/** - * @brief Flex the ring finger (move to closed position). - */ -void flex_ring(void); - -/** - * @brief Flex the pinky finger (move to closed position). - */ -void flex_pinky(void); - -/******************************************************************************* - * Individual Finger Control - Unflex Functions - * - * Unflex functions move a finger to the extended (0 degree) position. - ******************************************************************************/ - -/** - * @brief Unflex the thumb (move to extended position). - */ -void unflex_thumb(void); - -/** - * @brief Unflex the index finger (move to extended position). - */ -void unflex_index(void); - -/** - * @brief Unflex the middle finger (move to extended position). - */ -void unflex_middle(void); - -/** - * @brief Unflex the ring finger (move to extended position). - */ -void unflex_ring(void); - -/** - * @brief Unflex the pinky finger (move to extended position). - */ -void unflex_pinky(void); - -/******************************************************************************* - * Composite Gestures - * - * These functions control multiple fingers simultaneously to form gestures. - ******************************************************************************/ - -/** - * @brief Close all fingers to form a fist. - * - * All five fingers move to the flexed position simultaneously. - */ -void gesture_make_fist(void); - -/** - * @brief Open the hand fully. - * - * All five fingers move to the extended position simultaneously. - */ -void gesture_open_hand(void); - -/******************************************************************************* - * Demo/Test Sequences - * - * These functions provide demonstration sequences for testing servo operation. - ******************************************************************************/ - -/** - * @brief Demo sequence: flex and unflex each finger individually. - * - * Cycles through each finger, flexing and unflexing with a delay - * between each movement. Useful for testing individual servo operation. - * - * @param delay_ms Delay in milliseconds between each movement. - */ -void demo_individual_fingers(uint32_t delay_ms); - -/** - * @brief Demo sequence: repeatedly close and open the hand. - * - * Alternates between making a fist and opening the hand. - * Useful for testing simultaneous servo operation. - * - * @param delay_ms Delay in milliseconds between fist and open positions. - */ -void demo_close_open(uint32_t delay_ms); - -#endif /* GESTURES_H */ diff --git a/EMG_Arm/src/hal/servo_hal.c b/EMG_Arm/src/hal/servo_hal.c new file mode 100644 index 0000000..729554e --- /dev/null +++ b/EMG_Arm/src/hal/servo_hal.c @@ -0,0 +1,87 @@ +/** + * @file servo_hal.c + * @brief Hardware Abstraction Layer for servo PWM control. + * + * Implements low-level LEDC peripheral configuration and control. + */ + +#include "servo_hal.h" +#include "driver/ledc.h" +#include "esp_err.h" + +/******************************************************************************* + * Private Data + ******************************************************************************/ + +/** @brief GPIO pin for each finger servo. */ +static const int servo_pins[FINGER_COUNT] = { + PIN_SERVO_THUMB, + PIN_SERVO_INDEX, + PIN_SERVO_MIDDLE, + PIN_SERVO_RING, + PIN_SERVO_PINKY +}; + +/** @brief LEDC channel for each finger servo. */ +static const ledc_channel_t servo_channels[FINGER_COUNT] = { + LEDC_CH_THUMB, + LEDC_CH_INDEX, + LEDC_CH_MIDDLE, + LEDC_CH_RING, + LEDC_CH_PINKY +}; + +/******************************************************************************* + * Public Functions + ******************************************************************************/ + +void servo_hal_init(void) +{ + /* Configure LEDC timer (shared by all servo channels) */ + ledc_timer_config_t timer_config = { + .speed_mode = SERVO_PWM_SPEED_MODE, + .timer_num = SERVO_PWM_TIMER, + .duty_resolution = SERVO_PWM_RESOLUTION, + .freq_hz = SERVO_PWM_FREQ_HZ, + .clk_cfg = LEDC_AUTO_CLK + }; + ESP_ERROR_CHECK(ledc_timer_config(&timer_config)); + + /* Configure each finger's LEDC channel */ + for (int i = 0; i < FINGER_COUNT; i++) { + ledc_channel_config_t channel_config = { + .speed_mode = SERVO_PWM_SPEED_MODE, + .channel = servo_channels[i], + .timer_sel = SERVO_PWM_TIMER, + .intr_type = LEDC_INTR_DISABLE, + .gpio_num = servo_pins[i], + .duty = SERVO_DUTY_MIN, /* Start extended (open) */ + .hpoint = 0 + }; + ESP_ERROR_CHECK(ledc_channel_config(&channel_config)); + } +} + +void servo_hal_set_duty(finger_t finger, uint32_t duty) +{ + if (finger >= FINGER_COUNT) { + return; + } + + ledc_set_duty(SERVO_PWM_SPEED_MODE, servo_channels[finger], duty); + ledc_update_duty(SERVO_PWM_SPEED_MODE, servo_channels[finger]); +} + +uint32_t servo_hal_degrees_to_duty(float degrees) +{ + /* Clamp to valid range */ + if (degrees < 0.0f) { + degrees = 0.0f; + } else if (degrees > 180.0f) { + degrees = 180.0f; + } + + /* Linear interpolation: duty = min + (degrees/180) * (max - min) */ + float duty = SERVO_DUTY_MIN + (degrees / 180.0f) * (SERVO_DUTY_MAX - SERVO_DUTY_MIN); + return (uint32_t)duty; +} diff --git a/EMG_Arm/src/hal/servo_hal.h b/EMG_Arm/src/hal/servo_hal.h new file mode 100644 index 0000000..45e7a60 --- /dev/null +++ b/EMG_Arm/src/hal/servo_hal.h @@ -0,0 +1,47 @@ +/** + * @file servo_hal.h + * @brief Hardware Abstraction Layer for servo PWM control. + * + * This module provides low-level access to the ESP32's LEDC peripheral + * for generating PWM signals to control servo motors. + * + * @note This is Layer 1 (HAL). Only drivers/ should use this directly. + */ + +#ifndef SERVO_HAL_H +#define SERVO_HAL_H + +#include +#include "config/config.h" + +/******************************************************************************* + * Public Functions + ******************************************************************************/ + +/** + * @brief Initialize the LEDC peripheral for all servo channels. + * + * Configures the timer and all 5 channels for 50Hz PWM output. + * All servos start in the extended (open) position. + * + * @note Must be called once before any other servo_hal functions. + */ +void servo_hal_init(void); + +/** + * @brief Set the duty cycle for a specific finger's servo. + * + * @param finger Which finger (use finger_t enum from config.h) + * @param duty Duty cycle value (SERVO_DUTY_MIN to SERVO_DUTY_MAX) + */ +void servo_hal_set_duty(finger_t finger, uint32_t duty); + +/** + * @brief Convert degrees to duty cycle value. + * + * @param degrees Angle in degrees (0 to 180) + * @return Corresponding duty cycle value + */ +uint32_t servo_hal_degrees_to_duty(float degrees); + +#endif /* SERVO_HAL_H */ diff --git a/EMG_Arm/src/main.c b/EMG_Arm/src/main.c deleted file mode 100644 index 6f32d43..0000000 --- a/EMG_Arm/src/main.c +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @file main.c - * @brief Main application entry point for the EMG-controlled robotic hand. - * - * This application controls a 5-finger robotic hand using servo motors. - * The servos are driven by PWM signals generated through the ESP32's LEDC - * peripheral. Future versions will integrate EMG signal processing to - * translate muscle activity into hand gestures. - * - * Hardware Platform: ESP32-S3-DevKitC-1 - * Framework: ESP-IDF with FreeRTOS - */ - -#include -#include -#include "servo.h" -#include "gestures.h" - -/******************************************************************************* - * Configuration - ******************************************************************************/ - -/** @brief Delay between demo movements in milliseconds. */ -#define DEMO_DELAY_MS 1000 - -/******************************************************************************* - * Application Entry Point - ******************************************************************************/ - -/** - * @brief Main application entry point. - * - * Initializes the servo hardware and runs a demo sequence. - * The demo can be configured to test individual finger control - * or simultaneous hand gestures. - */ -void app_main(void) -{ - /* Initialize servo motors */ - servo_init(); - - /* Run demo sequence */ - while (1) { - /* Option 1: Test individual finger control */ - // demo_individual_fingers(DEMO_DELAY_MS); - - /* Option 2: Test simultaneous hand gestures */ - demo_close_open(DEMO_DELAY_MS); - } -} diff --git a/EMG_Arm/src/servo.c b/EMG_Arm/src/servo.c deleted file mode 100644 index 345bc70..0000000 --- a/EMG_Arm/src/servo.c +++ /dev/null @@ -1,125 +0,0 @@ -/** - * @file servo.c - * @brief Servo motor control implementation for the EMG-controlled robotic hand. - * - * This module implements low-level servo control using the ESP32's LEDC peripheral. - * The LEDC is configured to generate 50Hz PWM signals suitable for standard hobby servos. - */ - -#include "servo.h" -#include "esp_err.h" - -/******************************************************************************* - * Private Constants - ******************************************************************************/ - -/** @brief PWM frequency for servo control (standard is 50Hz). */ -#define SERVO_PWM_FREQ_HZ 50 - -/** @brief PWM resolution in bits (14-bit = 16384 levels). */ -#define SERVO_PWM_RESOLUTION LEDC_TIMER_14_BIT - -/** @brief LEDC speed mode (ESP32-S3 only supports low-speed mode). */ -#define SERVO_SPEED_MODE LEDC_LOW_SPEED_MODE - -/** @brief LEDC timer used for all servos. */ -#define SERVO_TIMER LEDC_TIMER_0 - -/******************************************************************************* - * Private Data - ******************************************************************************/ - -/** - * @brief Mapping of finger indices to GPIO pins. - */ -static const int servo_pins[FINGER_COUNT] = { - THUMB_SERVO_PIN, - INDEX_SERVO_PIN, - MIDDLE_SERVO_PIN, - RING_SERVO_PIN, - PINKY_SERVO_PIN -}; - -/** - * @brief Mapping of finger indices to LEDC channels. - */ -static const ledc_channel_t servo_channels[FINGER_COUNT] = { - THUMB_CHANNEL, - INDEX_CHANNEL, - MIDDLE_CHANNEL, - RING_CHANNEL, - PINKY_CHANNEL -}; - -/******************************************************************************* - * Public Function Implementations - ******************************************************************************/ - -void servo_init(void) -{ - /* Configure LEDC timer (shared by all servo channels) */ - ledc_timer_config_t timer_config = { - .speed_mode = SERVO_SPEED_MODE, - .timer_num = SERVO_TIMER, - .duty_resolution = SERVO_PWM_RESOLUTION, - .freq_hz = SERVO_PWM_FREQ_HZ, - .clk_cfg = LEDC_AUTO_CLK - }; - ESP_ERROR_CHECK(ledc_timer_config(&timer_config)); - - /* Configure LEDC channel for each finger servo */ - for (int i = 0; i < FINGER_COUNT; i++) { - ledc_channel_config_t channel_config = { - .speed_mode = SERVO_SPEED_MODE, - .channel = servo_channels[i], - .timer_sel = SERVO_TIMER, - .intr_type = LEDC_INTR_DISABLE, - .gpio_num = servo_pins[i], - .duty = SERVO_POS_MIN, /* Start with fingers extended */ - .hpoint = 0 - }; - ESP_ERROR_CHECK(ledc_channel_config(&channel_config)); - } -} - -void servo_set_position(ledc_channel_t channel, uint32_t duty) -{ - ledc_set_duty(SERVO_SPEED_MODE, channel, duty); - ledc_update_duty(SERVO_SPEED_MODE, channel); -} - -void servo_set_finger(finger_t finger, uint32_t duty) -{ - if (finger >= FINGER_COUNT) { - return; /* Invalid finger index */ - } - servo_set_position(servo_channels[finger], duty); -} - -uint32_t servo_degrees_to_duty(float degrees) -{ - /* Clamp input to valid range */ - if (degrees < 0.0f) { - degrees = 0.0f; - } else if (degrees > 180.0f) { - degrees = 180.0f; - } - - /* - * Linear interpolation formula: - * duty = min + (degrees / 180) * (max - min) - * - * Where: - * - min = SERVO_POS_MIN (duty at 0 degrees) - * - max = SERVO_POS_MAX (duty at 180 degrees) - */ - float duty = SERVO_POS_MIN + (degrees / 180.0f) * (SERVO_POS_MAX - SERVO_POS_MIN); - - return (uint32_t)duty; -} - -void servo_set_finger_degrees(finger_t finger, float degrees) -{ - uint32_t duty = servo_degrees_to_duty(degrees); - servo_set_finger(finger, duty); -} diff --git a/EMG_Arm/src/servo.h b/EMG_Arm/src/servo.h deleted file mode 100644 index ff0ae0b..0000000 --- a/EMG_Arm/src/servo.h +++ /dev/null @@ -1,135 +0,0 @@ -/** - * @file servo.h - * @brief Servo motor control interface for the EMG-controlled robotic hand. - * - * This module provides low-level servo motor control using the ESP32's LEDC - * (LED Controller) peripheral for PWM generation. It handles initialization - * and position control for all five finger servos. - * - * Hardware Configuration: - * - Thumb: GPIO 1, LEDC Channel 0 - * - Index: GPIO 4, LEDC Channel 1 - * - Middle: GPIO 5, LEDC Channel 2 - * - Ring: GPIO 6, LEDC Channel 3 - * - Pinky: GPIO 7, LEDC Channel 4 - * - * @note All servos share a single LEDC timer configured for 50Hz (standard servo frequency). - */ - -#ifndef SERVO_H -#define SERVO_H - -#include "driver/ledc.h" -#include "driver/gpio.h" - -/******************************************************************************* - * GPIO Pin Definitions - ******************************************************************************/ - -#define THUMB_SERVO_PIN GPIO_NUM_1 -#define INDEX_SERVO_PIN GPIO_NUM_4 -#define MIDDLE_SERVO_PIN GPIO_NUM_5 -#define RING_SERVO_PIN GPIO_NUM_6 -#define PINKY_SERVO_PIN GPIO_NUM_7 - -/******************************************************************************* - * LEDC Channel Definitions - ******************************************************************************/ - -#define THUMB_CHANNEL LEDC_CHANNEL_0 -#define INDEX_CHANNEL LEDC_CHANNEL_1 -#define MIDDLE_CHANNEL LEDC_CHANNEL_2 -#define RING_CHANNEL LEDC_CHANNEL_3 -#define PINKY_CHANNEL LEDC_CHANNEL_4 - -/******************************************************************************* - * Servo Position Definitions - * - * These duty cycle values correspond to servo positions at 14-bit resolution. - * At 50Hz with 14-bit resolution (16384 counts per 20ms period): - * - 1ms pulse (~0 degrees) = ~819 counts - * - 2ms pulse (~180 degrees) = ~1638 counts - * - * Actual values may vary based on specific servo characteristics. - ******************************************************************************/ - -#define SERVO_POS_MIN 430 /**< Duty cycle for 0 degrees (finger extended) */ -#define SERVO_POS_MAX 2048 /**< Duty cycle for 180 degrees (finger flexed) */ - -/******************************************************************************* - * Finger Index Enumeration - ******************************************************************************/ - -/** - * @brief Enumeration for finger identification. - */ -typedef enum { - FINGER_THUMB = 0, - FINGER_INDEX, - FINGER_MIDDLE, - FINGER_RING, - FINGER_PINKY, - FINGER_COUNT /**< Total number of fingers (5) */ -} finger_t; - -/******************************************************************************* - * Public Function Declarations - ******************************************************************************/ - -/** - * @brief Initialize all servo motors. - * - * Configures the LEDC timer and channels for all five finger servos. - * All servos are initialized to the extended (open) position. - * - * @note Must be called before any other servo functions. - */ -void servo_init(void); - -/** - * @brief Set a specific servo to a given duty cycle position. - * - * @param channel The LEDC channel corresponding to the servo. - * @param duty The duty cycle value (use SERVO_POS_MIN to SERVO_POS_MAX). - */ -void servo_set_position(ledc_channel_t channel, uint32_t duty); - -/** - * @brief Set a finger servo to a specific position. - * - * @param finger The finger to control (use finger_t enumeration). - * @param duty The duty cycle value (use SERVO_POS_MIN to SERVO_POS_MAX). - */ -void servo_set_finger(finger_t finger, uint32_t duty); - -/** - * @brief Convert an angle in degrees to the corresponding duty cycle value. - * - * Performs linear interpolation between SERVO_POS_MIN (0 degrees) and - * SERVO_POS_MAX (180 degrees). Input values are clamped to the valid range. - * - * @param degrees The desired angle in degrees (0 to 180). - * @return The corresponding duty cycle value for the LEDC peripheral. - * - * @example - * uint32_t duty = servo_degrees_to_duty(90); // Get duty for 90 degrees - * servo_set_finger(FINGER_INDEX, duty); - */ -uint32_t servo_degrees_to_duty(float degrees); - -/** - * @brief Set a finger servo to a specific angle in degrees. - * - * Convenience function that combines degree-to-duty conversion with - * finger positioning. This is the recommended function for most use cases. - * - * @param finger The finger to control (use finger_t enumeration). - * @param degrees The desired angle in degrees (0 = extended, 180 = flexed). - * - * @example - * servo_set_finger_degrees(FINGER_THUMB, 90); // Move thumb to 90 degrees - * servo_set_finger_degrees(FINGER_INDEX, 45); // Move index to 45 degrees - */ -void servo_set_finger_degrees(finger_t finger, float degrees); - -#endif /* SERVO_H */ diff --git a/serial_stream.py b/serial_stream.py new file mode 100644 index 0000000..6c10c97 --- /dev/null +++ b/serial_stream.py @@ -0,0 +1,270 @@ +""" +@file serial_stream.py +@brief Real serial stream for reading EMG data from ESP32. + +This module provides a serial communication interface for receiving +EMG data from the ESP32 microcontroller over USB. It implements the +same interface as SimulatedEMGStream, making it a drop-in replacement. + +@section usage Usage Example +@code{.py} + from serial_stream import RealSerialStream + + # Create stream (auto-detects port, or specify manually) + stream = RealSerialStream(port='COM3') + stream.start() + + # Read data (same interface as SimulatedEMGStream) + while True: + line = stream.readline() + if line: + print(line) # "1234,512,489,501,523" + + stream.stop() +@endcode + +@section format Data Format + The ESP32 sends data as CSV lines: + "timestamp_ms,ch0,ch1,ch2,ch3\\n" + + Example: + "12345,512,489,501,523\\n" + +@author Bucky Arm Project +""" + +import serial +import serial.tools.list_ports +from typing import Optional, List + + +class RealSerialStream: + """ + @brief Reads EMG data from ESP32 over USB serial. + + This class provides the same interface as SimulatedEMGStream: + - start() : Open serial connection + - stop() : Close serial connection + - readline() : Read one line of data + + This allows it to be used as a drop-in replacement for testing + with real hardware instead of simulated data. + + @note Requires pyserial: pip install pyserial + """ + + def __init__(self, port: str = None, baud_rate: int = 115200, timeout: float = 1.0): + """ + @brief Initialize the serial stream. + + @param port Serial port name (e.g., 'COM3' on Windows, '/dev/ttyUSB0' on Linux). + If None, will attempt to auto-detect the ESP32. + @param baud_rate Communication speed in bits per second. Default 115200 matches ESP32. + @param timeout Read timeout in seconds. Returns None if no data within this time. + """ + self.port = port + self.baud_rate = baud_rate + self.timeout = timeout + self.serial: Optional[serial.Serial] = None + self.running = False + + def start(self) -> None: + """ + @brief Open the serial connection to the ESP32. + + If no port was specified in __init__, attempts to auto-detect + the ESP32 by looking for common USB-UART chip identifiers. + + @throws RuntimeError If no port specified and auto-detect fails. + @throws RuntimeError If unable to open the serial port. + """ + # Auto-detect port if not specified + if self.port is None: + self.port = self._auto_detect_port() + + if self.port is None: + raise RuntimeError( + "No serial port specified and auto-detect failed.\n" + "Use RealSerialStream.list_ports() to see available ports." + ) + + # Open serial connection + try: + self.serial = serial.Serial( + port=self.port, + baudrate=self.baud_rate, + timeout=self.timeout + ) + self.running = True + + # Clear any stale data in the buffer + self.serial.reset_input_buffer() + + print(f"[SERIAL] Connected to {self.port} at {self.baud_rate} baud") + + except serial.SerialException as e: + raise RuntimeError(f"Failed to open {self.port}: {e}") + + def stop(self) -> None: + """ + @brief Close the serial connection. + + Safe to call even if not connected. + """ + self.running = False + + if self.serial and self.serial.is_open: + self.serial.close() + print(f"[SERIAL] Disconnected from {self.port}") + + def readline(self) -> Optional[str]: + """ + @brief Read one line of data from the ESP32. + + Blocks until a complete line is received or timeout occurs. + This matches the interface of SimulatedEMGStream.readline(). + + @return Line string including newline, or None if timeout/error. + + @note Lines from ESP32 are in format: "timestamp_ms,ch0,ch1,ch2,ch3\\n" + """ + if not self.serial or not self.serial.is_open: + return None + + try: + line_bytes = self.serial.readline() + if line_bytes: + return line_bytes.decode('utf-8', errors='ignore') + return None + + except serial.SerialException: + return None + + def _auto_detect_port(self) -> Optional[str]: + """ + @brief Attempt to auto-detect the ESP32 serial port. + + Looks for common USB-UART bridge chips used on ESP32 dev boards: + - CP210x (Silicon Labs) + - CH340 (WCH) + - FTDI + + @return Port name if found, None otherwise. + """ + ports = serial.tools.list_ports.comports() + + # Known USB-UART chip identifiers + known_chips = ['cp210', 'ch340', 'ftdi', 'usb-serial', 'usb serial'] + + for port in ports: + description_lower = port.description.lower() + if any(chip in description_lower for chip in known_chips): + print(f"[SERIAL] Auto-detected ESP32 on {port.device}") + return port.device + + # Fallback: use first available port + if ports: + print(f"[SERIAL] No ESP32 detected, using first port: {ports[0].device}") + return ports[0].device + + return None + + @staticmethod + def list_ports() -> List[str]: + """ + @brief List all available serial ports on the system. + + Useful for finding the correct port name for your ESP32. + + @return List of port names (e.g., ['COM3', 'COM4'] on Windows). + """ + ports = serial.tools.list_ports.comports() + + if not ports: + print("No serial ports found.") + return [] + + print("\nAvailable serial ports:") + print("-" * 50) + + for port in ports: + print(f" {port.device}") + print(f" Description: {port.description}") + print() + + return [p.device for p in ports] + + +# ============================================================================= +# Standalone Test +# ============================================================================= + +if __name__ == "__main__": + """ + @brief Quick test to verify ESP32 serial communication. + + Run this file directly to test: + python serial_stream.py [port] + + If port is not specified, auto-detection is attempted. + """ + import sys + + print("=" * 50) + print(" ESP32 Serial Stream Test") + print("=" * 50) + print() + + # Show available ports + ports = RealSerialStream.list_ports() + + if not ports: + print("No ports found. Is the ESP32 plugged in?") + sys.exit(1) + + # Determine which port to use + if len(sys.argv) > 1: + port = sys.argv[1] + print(f"Using specified port: {port}") + else: + port = None # Will auto-detect + print("No port specified, will auto-detect.") + + print() + print("Starting stream... (Ctrl+C to stop)") + print("-" * 50) + + # Create and start stream + stream = RealSerialStream(port=port) + + try: + stream.start() + + sample_count = 0 + + while True: + line = stream.readline() + + if line: + line = line.strip() + + # Check if this is a data line (starts with digit = timestamp) + if line and line[0].isdigit(): + sample_count += 1 + + # Print every 500th sample to avoid flooding terminal + if sample_count % 500 == 0: + print(f" [{sample_count:6d} samples] Latest: {line}") + + else: + # Print startup/info messages from ESP32 + print(f" {line}") + + except KeyboardInterrupt: + print("\n") + print("-" * 50) + print("Stopped by user (Ctrl+C)") + + finally: + stream.stop() + print(f"Total samples received: {sample_count}")