Restructure ESP32 firmware into layered architecture with serial streaming
Architecture changes: - config/: Centralized pin definitions and settings - hal/: Hardware abstraction layer (servo PWM control) - drivers/: Device drivers (hand, emg_sensor) - core/: Business logic (gestures) - app/: Application entry point New features: - Fake EMG data streaming over USB serial at ~100 Hz - FEATURE_FAKE_EMG flag to switch between fake/real sensors - Python RealSerialStream class for receiving serial data Data format: timestamp_ms,ch0,ch1,ch2,ch3
This commit is contained in:
@@ -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
|
||||
.
|
||||
)
|
||||
|
||||
114
EMG_Arm/src/app/main.c
Normal file
114
EMG_Arm/src/app/main.c
Normal file
@@ -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 <stdio.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#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();
|
||||
}
|
||||
109
EMG_Arm/src/config/config.h
Normal file
109
EMG_Arm/src/config/config.h
Normal file
@@ -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 */
|
||||
122
EMG_Arm/src/core/gestures.c
Normal file
122
EMG_Arm/src/core/gestures.c
Normal file
@@ -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 <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
/*******************************************************************************
|
||||
* 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));
|
||||
}
|
||||
74
EMG_Arm/src/core/gestures.h
Normal file
74
EMG_Arm/src/core/gestures.h
Normal file
@@ -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 <stdint.h>
|
||||
#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 */
|
||||
55
EMG_Arm/src/drivers/emg_sensor.c
Normal file
55
EMG_Arm/src/drivers/emg_sensor.c
Normal file
@@ -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 <stdlib.h>
|
||||
|
||||
/*******************************************************************************
|
||||
* 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);
|
||||
}
|
||||
56
EMG_Arm/src/drivers/emg_sensor.h
Normal file
56
EMG_Arm/src/drivers/emg_sensor.h
Normal file
@@ -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 <stdint.h>
|
||||
#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 */
|
||||
48
EMG_Arm/src/drivers/hand.c
Normal file
48
EMG_Arm/src/drivers/hand.c
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
59
EMG_Arm/src/drivers/hand.h
Normal file
59
EMG_Arm/src/drivers/hand.h
Normal file
@@ -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 */
|
||||
@@ -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 <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
/*******************************************************************************
|
||||
* 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));
|
||||
}
|
||||
@@ -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 <stdint.h>
|
||||
|
||||
/*******************************************************************************
|
||||
* 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 */
|
||||
87
EMG_Arm/src/hal/servo_hal.c
Normal file
87
EMG_Arm/src/hal/servo_hal.c
Normal file
@@ -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;
|
||||
}
|
||||
47
EMG_Arm/src/hal/servo_hal.h
Normal file
47
EMG_Arm/src/hal/servo_hal.h
Normal file
@@ -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 <stdint.h>
|
||||
#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 */
|
||||
@@ -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 <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 */
|
||||
270
serial_stream.py
Normal file
270
serial_stream.py
Normal file
@@ -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}")
|
||||
Reference in New Issue
Block a user