Motor control implemented

This commit is contained in:
2025-07-19 22:55:58 -06:00
parent fef8da2de2
commit 5a4c91fbd3
4 changed files with 316 additions and 17 deletions

View File

@ -12,6 +12,9 @@
#include "motor_control.h"
#include "sdkconfig.h"
// Uncomment this line to enable motor test mode with shorter intervals
// #define MOTOR_TEST_MODE
static const char *TAG = "MAIN";
// Application version
@ -76,6 +79,7 @@ static void mqtt_connected_callback(void)
"plant_watering/pump/+/speed",
"plant_watering/commands/test_pump/+",
"plant_watering/commands/emergency_stop",
"plant_watering/commands/test_mode",
"plant_watering/settings/+/+",
NULL
};
@ -161,17 +165,37 @@ static void mqtt_data_callback(const char* topic, const char* data, int data_len
// Parse JSON configuration here
} else if (strcmp(topic, "plant_watering/commands/test_pump/1") == 0) {
uint32_t duration = atoi(data);
if (duration > 0 && duration <= 10000) { // Max 10 seconds for test
if (duration > 0 && duration <= 30000) { // Max 30 seconds for test
ESP_LOGI(TAG, "Test pump 1 for %lu ms", duration);
motor_test_run(MOTOR_PUMP_1, duration);
} else {
ESP_LOGW(TAG, "Invalid test duration: %lu (max 30000ms)", duration);
}
} else if (strcmp(topic, "plant_watering/commands/test_pump/2") == 0) {
uint32_t duration = atoi(data);
if (duration > 0 && duration <= 10000) { // Max 10 seconds for test
if (duration > 0 && duration <= 30000) { // Max 30 seconds for test
ESP_LOGI(TAG, "Test pump 2 for %lu ms", duration);
motor_test_run(MOTOR_PUMP_2, duration);
} else {
ESP_LOGW(TAG, "Invalid test duration: %lu (max 30000ms)", duration);
}
} else if (strcmp(topic, "plant_watering/commands/emergency_stop") == 0) {
ESP_LOGW(TAG, "Emergency stop command received!");
motor_emergency_stop();
} else if (strcmp(topic, "plant_watering/commands/test_mode") == 0) {
if (strncmp(data, "on", data_len) == 0) {
ESP_LOGW(TAG, "Enabling test mode - short intervals");
motor_set_min_interval(MOTOR_PUMP_1, 5000); // 5 seconds
motor_set_min_interval(MOTOR_PUMP_2, 5000);
motor_set_max_runtime(MOTOR_PUMP_1, 30000); // 30 seconds
motor_set_max_runtime(MOTOR_PUMP_2, 30000);
} else if (strncmp(data, "off", data_len) == 0) {
ESP_LOGI(TAG, "Disabling test mode - normal intervals");
motor_set_min_interval(MOTOR_PUMP_1, CONFIG_WATERING_MIN_INTERVAL_MS);
motor_set_min_interval(MOTOR_PUMP_2, CONFIG_WATERING_MIN_INTERVAL_MS);
motor_set_max_runtime(MOTOR_PUMP_1, CONFIG_WATERING_MAX_DURATION_MS);
motor_set_max_runtime(MOTOR_PUMP_2, CONFIG_WATERING_MAX_DURATION_MS);
}
} else if (strncmp(topic, "plant_watering/settings/pump/", 29) == 0) {
// Parse settings commands like:
// plant_watering/settings/pump/1/max_runtime
@ -373,11 +397,21 @@ void app_main(void)
motor_register_state_callback(motor_state_change_callback);
motor_register_error_callback(motor_error_callback);
// Configure motor safety limits from Kconfig
// Configure motor safety limits
#ifdef MOTOR_TEST_MODE
// Use shorter limits for testing
ESP_LOGI(TAG, "MOTOR TEST MODE - Using short intervals for testing");
motor_set_max_runtime(MOTOR_PUMP_1, 30000); // 30 seconds max
motor_set_max_runtime(MOTOR_PUMP_2, 30000);
motor_set_min_interval(MOTOR_PUMP_1, 5000); // 5 seconds for testing
motor_set_min_interval(MOTOR_PUMP_2, 5000);
#else
// Use production values from Kconfig
motor_set_max_runtime(MOTOR_PUMP_1, CONFIG_WATERING_MAX_DURATION_MS);
motor_set_max_runtime(MOTOR_PUMP_2, CONFIG_WATERING_MAX_DURATION_MS);
motor_set_min_interval(MOTOR_PUMP_1, CONFIG_WATERING_MIN_INTERVAL_MS);
motor_set_min_interval(MOTOR_PUMP_2, CONFIG_WATERING_MIN_INTERVAL_MS);
#endif
// Start WiFi connection
esp_err_t ret = wifi_manager_start();

View File

@ -182,7 +182,10 @@ esp_err_t motor_control_init(void)
// Create safety timers
for (int i = 0; i < MOTOR_PUMP_MAX - 1; i++) {
s_motors[i].safety_timer = xTimerCreate("motor_safety",
char timer_name[32];
snprintf(timer_name, sizeof(timer_name), "motor_safety_%d", i + 1);
s_motors[i].safety_timer = xTimerCreate(timer_name,
pdMS_TO_TICKS(1000),
pdFALSE,
(void*)(i + 1),
@ -193,7 +196,8 @@ esp_err_t motor_control_init(void)
goto error;
}
s_motors[i].soft_start_timer = xTimerCreate("motor_soft_start",
snprintf(timer_name, sizeof(timer_name), "motor_soft_%d", i + 1);
s_motors[i].soft_start_timer = xTimerCreate(timer_name,
pdMS_TO_TICKS(50),
pdTRUE,
(void*)(i + 1),
@ -627,13 +631,22 @@ esp_err_t motor_test_run(motor_id_t id, uint32_t duration_ms)
static void motor_safety_timer_callback(TimerHandle_t xTimer)
{
motor_id_t id = (motor_id_t)(intptr_t)pvTimerGetTimerID(xTimer);
motor_t *motor = &s_motors[id - 1];
ESP_LOGW(TAG, "Safety timer expired for motor %d", id);
motor_stop(id);
if (s_error_callback) {
s_error_callback(id, "Maximum runtime exceeded");
}
// Do minimal work in timer callback to avoid stack overflow
// Just stop the PWM and update state
ledc_set_duty(LEDC_LOW_SPEED_MODE, motor->pwm_channel, 0);
ledc_update_duty(LEDC_LOW_SPEED_MODE, motor->pwm_channel);
// Update basic state
motor->speed_percent = 0;
motor->state = MOTOR_STATE_STOPPED;
// Stats update can be deferred or done in main context
// For now, just record the stop time
motor->last_stop_time = get_time_ms();
}
static void motor_soft_start_timer_callback(TimerHandle_t xTimer)
@ -661,7 +674,10 @@ static void motor_soft_start_timer_callback(TimerHandle_t xTimer)
}
uint8_t duty = (motor->speed_percent * MOTOR_PWM_MAX_DUTY) / 100;
motor_update_pwm(id, duty);
// Update PWM directly without taking mutex (atomic operation)
ledc_set_duty(LEDC_LOW_SPEED_MODE, motor->pwm_channel, duty);
ledc_update_duty(LEDC_LOW_SPEED_MODE, motor->pwm_channel);
ESP_LOGD(TAG, "Soft start motor %d: %d%% (target: %d%%)",
id, motor->speed_percent, motor->target_speed);

View File

@ -21,9 +21,16 @@
// Safety Configuration
#define MOTOR_DEFAULT_SPEED 80 // Default pump speed (%)
#define MOTOR_MIN_SPEED 20 // Minimum pump speed (%)
#define MOTOR_MAX_RUNTIME_MS 30000 // Maximum runtime (30 seconds)
#define MOTOR_MIN_INTERVAL_MS 300000 // Minimum interval between runs (5 minutes)
#define MOTOR_SOFT_START_TIME_MS 1000 // Soft start ramp time
// Default safety limits (can be overridden at runtime)
#define MOTOR_MAX_RUNTIME_MS 30000 // Default maximum runtime (30 seconds)
#define MOTOR_MIN_INTERVAL_MS 300000 // Default minimum interval between runs (5 minutes)
// Test mode limits (shorter for testing)
#define MOTOR_TEST_MAX_RUNTIME_MS 30000 // Test mode max runtime (30 seconds)
#define MOTOR_TEST_MIN_INTERVAL_MS 5000 // Test mode min interval (5 seconds)
#define MOTOR_SOFT_START_TIME_MS 500 // Soft start ramp time
// Motor IDs
typedef enum {

242
main/motor_test.c Normal file
View File

@ -0,0 +1,242 @@
/**
* Motor Control Hardware Test Program
*
* This is a standalone test program to verify TB6612FNG motor driver
* connections before integrating with the full system.
*
* Test sequence:
* 1. Initialize motor control
* 2. Test each pump individually
* 3. Test PWM speed control
* 4. Test safety features
* 5. Test both pumps together
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "motor_control.h"
static const char *TAG = "MOTOR_TEST";
// Test callbacks
static void test_state_callback(motor_id_t id, motor_state_t state)
{
const char *state_str[] = {"STOPPED", "RUNNING", "ERROR", "COOLDOWN"};
ESP_LOGI(TAG, "Motor %d state: %s", id, state_str[state]);
}
static void test_error_callback(motor_id_t id, const char* error)
{
ESP_LOGE(TAG, "Motor %d error: %s", id, error);
}
void app_main(void)
{
ESP_LOGI(TAG, "=== TB6612FNG Motor Driver Test Program ===");
ESP_LOGI(TAG, "GPIO Connections:");
ESP_LOGI(TAG, " AIN1 (Pump1 Dir): GPIO%d", MOTOR_AIN1_GPIO);
ESP_LOGI(TAG, " AIN2 (Pump1 Dir): GPIO%d", MOTOR_AIN2_GPIO);
ESP_LOGI(TAG, " BIN1 (Pump2 Dir): GPIO%d", MOTOR_BIN1_GPIO);
ESP_LOGI(TAG, " BIN2 (Pump2 Dir): GPIO%d", MOTOR_BIN2_GPIO);
ESP_LOGI(TAG, " PWMA (Pump1 PWM): GPIO%d", MOTOR_PWMA_GPIO);
ESP_LOGI(TAG, " PWMB (Pump2 PWM): GPIO%d", MOTOR_PWMB_GPIO);
ESP_LOGI(TAG, " STBY (Standby): GPIO%d", MOTOR_STBY_GPIO);
// Initialize motor control
ESP_LOGI(TAG, "\n--- Initializing Motor Control ---");
esp_err_t ret = motor_control_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize motor control: %s", esp_err_to_name(ret));
return;
}
// Register callbacks
motor_register_state_callback(test_state_callback);
motor_register_error_callback(test_error_callback);
// Wait a bit
vTaskDelay(pdMS_TO_TICKS(2000));
// Test 1: Basic ON/OFF for Pump 1
ESP_LOGI(TAG, "\n--- Test 1: Pump 1 Basic ON/OFF ---");
ESP_LOGI(TAG, "Starting Pump 1 at default speed (%d%%)", MOTOR_DEFAULT_SPEED);
motor_start(MOTOR_PUMP_1, MOTOR_DEFAULT_SPEED);
vTaskDelay(pdMS_TO_TICKS(3000));
ESP_LOGI(TAG, "Stopping Pump 1");
motor_stop(MOTOR_PUMP_1);
vTaskDelay(pdMS_TO_TICKS(2000));
// Test 2: Basic ON/OFF for Pump 2
ESP_LOGI(TAG, "\n--- Test 2: Pump 2 Basic ON/OFF ---");
ESP_LOGI(TAG, "Starting Pump 2 at default speed (%d%%)", MOTOR_DEFAULT_SPEED);
motor_start(MOTOR_PUMP_2, MOTOR_DEFAULT_SPEED);
vTaskDelay(pdMS_TO_TICKS(3000));
ESP_LOGI(TAG, "Stopping Pump 2");
motor_stop(MOTOR_PUMP_2);
vTaskDelay(pdMS_TO_TICKS(2000));
// Test 3: PWM Speed Control
ESP_LOGI(TAG, "\n--- Test 3: PWM Speed Control (Pump 1) ---");
int speeds[] = {30, 50, 70, 90, 100};
for (int i = 0; i < sizeof(speeds)/sizeof(speeds[0]); i++) {
ESP_LOGI(TAG, "Testing speed: %d%%", speeds[i]);
motor_start(MOTOR_PUMP_1, speeds[i]);
vTaskDelay(pdMS_TO_TICKS(2000));
}
motor_stop(MOTOR_PUMP_1);
vTaskDelay(pdMS_TO_TICKS(2000));
// Test 4: Timed Run
ESP_LOGI(TAG, "\n--- Test 4: Timed Run (5 seconds) ---");
ESP_LOGI(TAG, "Starting Pump 1 for 5 seconds");
motor_start_timed(MOTOR_PUMP_1, 70, 5000);
vTaskDelay(pdMS_TO_TICKS(7000)); // Wait for completion
// Test 5: Both pumps together
ESP_LOGI(TAG, "\n--- Test 5: Both Pumps Together ---");
ESP_LOGI(TAG, "Starting both pumps");
motor_start(MOTOR_PUMP_1, 60);
motor_start(MOTOR_PUMP_2, 80);
vTaskDelay(pdMS_TO_TICKS(3000));
ESP_LOGI(TAG, "Stopping both pumps");
motor_stop_all();
vTaskDelay(pdMS_TO_TICKS(2000));
// Test 6: Safety - Cooldown Period
ESP_LOGI(TAG, "\n--- Test 6: Cooldown Period Test ---");
motor_set_min_interval(MOTOR_PUMP_1, 5000); // 5 second cooldown for test
ESP_LOGI(TAG, "Starting pump 1");
motor_start(MOTOR_PUMP_1, 50);
vTaskDelay(pdMS_TO_TICKS(2000));
motor_stop(MOTOR_PUMP_1);
ESP_LOGI(TAG, "Attempting to restart immediately (should fail)");
ret = motor_start(MOTOR_PUMP_1, 50);
if (ret != ESP_OK) {
ESP_LOGI(TAG, "Good! Cooldown protection working");
}
ESP_LOGI(TAG, "Waiting for cooldown period...");
vTaskDelay(pdMS_TO_TICKS(6000));
ESP_LOGI(TAG, "Attempting to start after cooldown");
ret = motor_start(MOTOR_PUMP_1, 50);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "Good! Pump started after cooldown");
vTaskDelay(pdMS_TO_TICKS(2000));
motor_stop(MOTOR_PUMP_1);
}
// Test 7: Speed change while running
ESP_LOGI(TAG, "\n--- Test 7: Speed Change While Running ---");
ESP_LOGI(TAG, "Starting at 30%%");
motor_start(MOTOR_PUMP_1, 30);
vTaskDelay(pdMS_TO_TICKS(2000));
ESP_LOGI(TAG, "Changing to 70%%");
motor_set_speed(MOTOR_PUMP_1, 70);
vTaskDelay(pdMS_TO_TICKS(2000));
ESP_LOGI(TAG, "Changing to 100%%");
motor_set_speed(MOTOR_PUMP_1, 100);
vTaskDelay(pdMS_TO_TICKS(2000));
motor_stop(MOTOR_PUMP_1);
// Test 8: Emergency Stop
ESP_LOGI(TAG, "\n--- Test 8: Emergency Stop ---");
ESP_LOGI(TAG, "Starting both pumps");
motor_start(MOTOR_PUMP_1, 80);
motor_start(MOTOR_PUMP_2, 80);
vTaskDelay(pdMS_TO_TICKS(2000));
ESP_LOGI(TAG, "Triggering emergency stop!");
motor_emergency_stop();
ESP_LOGI(TAG, "Checking states after emergency stop");
if (!motor_is_running(MOTOR_PUMP_1) && !motor_is_running(MOTOR_PUMP_2)) {
ESP_LOGI(TAG, "Good! Both pumps stopped");
}
vTaskDelay(pdMS_TO_TICKS(2000));
// Test 9: Get Statistics
ESP_LOGI(TAG, "\n--- Test 9: Runtime Statistics ---");
motor_stats_t stats;
for (int i = 1; i <= 2; i++) {
if (motor_get_stats(i, &stats) == ESP_OK) {
ESP_LOGI(TAG, "Pump %d Statistics:", i);
ESP_LOGI(TAG, " Total runtime: %lu ms", stats.total_runtime_ms);
ESP_LOGI(TAG, " Run count: %lu", stats.run_count);
ESP_LOGI(TAG, " Last duration: %lu ms", stats.last_run_duration_ms);
ESP_LOGI(TAG, " Error count: %lu", stats.error_count);
}
}
// Test 10: Maximum runtime safety
ESP_LOGI(TAG, "\n--- Test 10: Maximum Runtime Safety ---");
motor_set_max_runtime(MOTOR_PUMP_1, 3000); // 3 second max for test
ESP_LOGI(TAG, "Starting pump 1 (should auto-stop after 3 seconds)");
motor_start(MOTOR_PUMP_1, 50);
// Wait for safety timer
vTaskDelay(pdMS_TO_TICKS(5000));
if (!motor_is_running(MOTOR_PUMP_1)) {
ESP_LOGI(TAG, "Good! Safety timer stopped the pump");
}
// Test 11: Soft Start Observation
ESP_LOGI(TAG, "\n--- Test 11: Soft Start Observation ---");
ESP_LOGI(TAG, "Watch/listen for gradual speed increase over 500ms");
motor_start(MOTOR_PUMP_1, 100);
vTaskDelay(pdMS_TO_TICKS(3000));
motor_stop(MOTOR_PUMP_1);
// Final test summary
ESP_LOGI(TAG, "\n=== All Tests Complete ===");
ESP_LOGI(TAG, "Test Summary:");
ESP_LOGI(TAG, " ✓ Basic ON/OFF control");
ESP_LOGI(TAG, " ✓ PWM speed control");
ESP_LOGI(TAG, " ✓ Timed operations");
ESP_LOGI(TAG, " ✓ Dual pump control");
ESP_LOGI(TAG, " ✓ Safety features");
ESP_LOGI(TAG, " ✓ Emergency stop");
ESP_LOGI(TAG, " ✓ Statistics tracking");
ESP_LOGI(TAG, "");
ESP_LOGI(TAG, "Monitor the pumps to ensure they responded correctly to all commands");
ESP_LOGI(TAG, "Check for any unusual noises, heating, or behavior");
// Keep running and print status periodically
ESP_LOGI(TAG, "\nEntering monitoring mode - System status every 10 seconds");
while (1) {
vTaskDelay(pdMS_TO_TICKS(10000));
ESP_LOGI(TAG, "--- System Status ---");
ESP_LOGI(TAG, "Free heap: %d bytes", esp_get_free_heap_size());
for (int i = 1; i <= 2; i++) {
motor_stats_t current_stats;
if (motor_get_stats(i, &current_stats) == ESP_OK) {
const char *state = "IDLE";
if (motor_is_running(i)) {
state = "RUNNING";
} else if (motor_is_cooldown(i)) {
state = "COOLDOWN";
}
ESP_LOGI(TAG, "Pump %d: State=%s, Total runs=%lu, Total time=%lu s",
i, state, current_stats.run_count,
current_stats.total_runtime_ms / 1000);
}
}
}
}