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
|
# CMakeLists.txt for Bucky Arm EMG Robotic Hand
|
||||||
# without default 'CMakeLists.txt' file.
|
#
|
||||||
|
# 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