diff --git a/arduino_code/Test/README.md b/arduino_code/Test/README.md new file mode 100644 index 0000000..e65fa90 --- /dev/null +++ b/arduino_code/Test/README.md @@ -0,0 +1,66 @@ +# MQTT Топики для управления двигателем + +## Общая информация +Система использует MQTT протокол для обмена данными между ESP32 и центральным сервером. Все топики разделены на два типа: +- **Управление** (`motor/control/...`) - для отправки команд +- **Обратная связь** (`motor/feedback/...`) - для получения статуса + +## Управление + +### 1. Команды управления +- **`motor/control/command`** + - Возможные значения: `start`, `stop` + - Описание: Запуск или остановка двигателя + +### 2. RPM (обороты в минуту) +- **`motor/control/rpm`** + - Тип данных: Целое число + - Описание: Установка целевого значения оборотов в минуту + +### 3. Шаги в секунду +- **`motor/control/step_per_sec`** + - Тип данных: Целое число + - Описание: Установка целевого значения шагов в секунду + +### 4. Состояние драйвера +- **`motor/control/driver`** + - Возможные значения: `turnon`, `turnoff` + - Описание: Включение или выключение драйвера двигателя + +### 5. Сброс общего количества шагов +- **`motor/control/totalsteps/reset`** + - Тип данных: Нет полезной нагрузки + - Описание: Сброс общего счетчика шагов + +## Обратная связь + +### 1. RPM (обороты в минуту) +- **`motor/feedback/rpm`** + - Тип данных: Целое число + - Описание: Текущее значение оборотов в минуту + +### 2. Шаги в секунду +- **`motor/feedback/step_per_seconds`** + - Тип данных: Целое число + - Описание: Текущее значение шагов в секунду + +### 3. Общее количество шагов +- **`motor/feedback/totalsteps`** + - Тип данных: Целое число + - Описание: Общее количество выполненных шагов + +### 4. Состояние драйвера +- **`motor/feedback/driver`** + - Возможные значения: `on`, `off` + - Описание: Состояние драйвера (включен/выключен) + +### 5. Состояние работы двигателя +- **`motor/feedback/is_run`** + - Возможные значения: `run`, `stop` + - Описание: Состояние двигателя (работает/стоит) + +## Примечания +- Все числовые значения передаются в строковом формате +- Для управления драйвером используется логический уровень: LOW = включен, HIGH = выключен +- Обратная связь отправляется с интервалом не менее 200 мс +- Система автоматически обрабатывает плавный разгон/торможение при изменении скорости \ No newline at end of file diff --git a/arduino_code/Test/src/config.cpp b/arduino_code/Test/src/config.cpp new file mode 100644 index 0000000..c887886 --- /dev/null +++ b/arduino_code/Test/src/config.cpp @@ -0,0 +1,20 @@ +#include "config.h" + +// WiFi & MQTT +const char* WIFI_SSID = "Home"; +const char* WIFI_PASS = "88888888qwE"; +const char* MQTT_SERVER = "192.168.31.225"; +const int MQTT_PORT = 1883; +const char* MQTT_USER = "test"; +const char* MQTT_PASS = "1234"; +const char* MQTT_CLIENT_ID = "ESP32_Stepper"; + +// Pins +const int DIR_PIN = 18; +const int STEP_PIN = 19; +const int EN_PIN = 21; + +// Motor Params +const int STEPS_PER_REVOLUTION = 200; +const int MICROSTEP_FACTOR = 1; +const unsigned long RAMP_DURATION_MS = 2000; \ No newline at end of file diff --git a/arduino_code/Test/src/config.h b/arduino_code/Test/src/config.h new file mode 100644 index 0000000..b435207 --- /dev/null +++ b/arduino_code/Test/src/config.h @@ -0,0 +1,23 @@ +#ifndef CONFIG_H +#define CONFIG_H + +// WiFi & MQTT +extern const char* WIFI_SSID; +extern const char* WIFI_PASS; +extern const char* MQTT_SERVER; +extern const int MQTT_PORT; +extern const char* MQTT_USER; +extern const char* MQTT_PASS; +extern const char* MQTT_CLIENT_ID; + +// Pins +extern const int DIR_PIN; +extern const int STEP_PIN; +extern const int EN_PIN; + +// Motor Params +extern const int STEPS_PER_REVOLUTION; +extern const int MICROSTEP_FACTOR; +extern const unsigned long RAMP_DURATION_MS; + +#endif \ No newline at end of file diff --git a/arduino_code/Test/src/main.cpp b/arduino_code/Test/src/main.cpp index 83a9f1d..b643de1 100644 --- a/arduino_code/Test/src/main.cpp +++ b/arduino_code/Test/src/main.cpp @@ -1,177 +1,30 @@ #include -#include -#include - -// WiFi credentials -const char* ssid = "Home"; -const char* password = "88888888qwE"; - -// MQTT Server details -const char* mqtt_server = "192.168.31.225"; -const char* mqtt_user = "test"; -const char* mqtt_password = "1234"; - -WiFiClient espClient; -PubSubClient client(espClient); - -// Пины для управления TMC2209 -const int DIR_PIN = 18; -const int STEP_PIN = 19; -const int EN_PIN = 21; -int rpm = 200; - -// --- НАСТРОЙКИ ДВИГАТЕЛЯ --- -const int STEPS_PER_REVOLUTION = 200; // Стандартный шаг 1.8 градуса -const int MICROSTEP_FACTOR = 1; // Настройка перемычек на TMC2209 (1, 2, 4, 8, 16, 32...) -// -------------------------++ - -bool motor_running = false; -unsigned long step_interval_us = 0; // Интервал между шагами в микросекундах -unsigned long last_feedback_time = 0; // Время последней публикации обратной связи -unsigned long total_steps = 0; // Общее количество шагов - -void setup_wifi() { - delay(10); - WiFi.begin(ssid, password); - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - Serial.println("\nWiFi Connected"); -} - -void startMotor() { - motor_running = true; - Serial.println("Motor started"); -} - -void stopMotor() { - motor_running = false; - digitalWrite(STEP_PIN, LOW); - Serial.println("Motor stopped"); -} - -void reconnect() { - while (!client.connected()) { - if (client.connect("ESP32Client", mqtt_user, mqtt_password)) { - client.subscribe("motor/control/command"); - client.subscribe("motor/control/driver"); - client.subscribe("motor/control/step_per_sec"); - client.subscribe("motor/control/rpm"); - } else { - delay(5000); - } - } -} - -// Расчет интервала на основе желаемой скорости в RPM -void setSpeedRPM(int rpm) { - if (rpm <= 0) { - stopMotor(); - return; - } - - // Общее количество шагов на один полный оборот вала - unsigned long total_steps_per_rev = STEPS_PER_REVOLUTION * MICROSTEP_FACTOR; - - // Сколько шагов нужно сделать в секунду для достижения этого RPM - // RPM / 60 = оборотов в секунду. Умножаем на шаги на оборот. - unsigned long steps_per_sec = (rpm * total_steps_per_rev) / 60; - - // Интервал в микросекундах (1 000 000 мкс в секунде) - if (steps_per_sec > 0) { - step_interval_us = 1000000UL / steps_per_sec; - - // Ограничение TMC2209: минимальное время импульса ~1-2 мкс + время восстановления - // Не ставьте скорость слишком высокой сразу. - if (step_interval_us < 10) step_interval_us = 10; - - motor_running = true; - - Serial.print("Set RPM: "); - Serial.print(rpm); - Serial.print(" | Steps/sec: "); - Serial.print(steps_per_sec); - Serial.print(" | Interval: "); - Serial.print(step_interval_us); - Serial.println(" us"); - } -} - -void callback(char* topic, byte* payload, unsigned int length) { - String message; - for (int i = 0; i < length; i++) { - message += (char)payload[i]; - } - - if (strcmp(topic, "motor/control/command") == 0) { - if (message == "start") { - startMotor(); - } else if (message == "stop") { - stopMotor(); - } - } else if (strcmp(topic, "motor/control/driver") == 0) { - if (message == "turnon") { - digitalWrite(EN_PIN, LOW); - } else if (message == "turnoff") { - digitalWrite(EN_PIN, HIGH); - } - } else if (strcmp(topic, "motor/control/step_per_sec") == 0) { - int sps = message.toInt(); - if (sps > 0) { - step_interval_us = 1000000UL / sps; - motor_running = true; - } - } else if (strcmp(topic, "motor/control/rpm") == 0) { - rpm = message.toInt(); - setSpeedRPM(rpm); - } -} - -void publishFeedback() { - unsigned long current_time = millis(); - if (current_time - last_feedback_time >= 500) { // Публикация 2 раз в секунду - // Публикуем обратную связь - client.publish("motor/feedback/rpm", String(rpm).c_str()); - client.publish("motor/feedback/step_per_seconds", String(step_interval_us > 0 ? 1000000UL / step_interval_us : 0).c_str()); - client.publish("motor/feedback/totalsteps", String(total_steps).c_str()); - - last_feedback_time = current_time; - } -} +#include "config.h" +#include "motor.h" +#include "mqtt_handler.h" void setup() { - Serial.begin(115200); - setup_wifi(); - client.setServer(mqtt_server, 1883); - client.setCallback(callback); - - pinMode(DIR_PIN, OUTPUT); - pinMode(STEP_PIN, OUTPUT); - pinMode(EN_PIN, OUTPUT); - - digitalWrite(EN_PIN, LOW); // Включить драйвер - digitalWrite(DIR_PIN, HIGH); - digitalWrite(STEP_PIN, LOW); + Serial.begin(115200); + + // Инициализация мотора (Core 1 context initially) + motorInit(); + + // Запуск задачи MQTT на Core 0 + xTaskCreatePinnedToCore( + mqttTask, + "MQTT_Task", + 8192, + NULL, + 3, + NULL, + 0 // CORE 0 + ); + + Serial.println("System Initialized. Multi-core ready."); } void loop() { - if (!client.connected()) { - reconnect(); - } - client.loop(); - - if (motor_running) { - // Генерируем короткий импульс - digitalWrite(STEP_PIN, HIGH); - delayMicroseconds(step_interval_us); - digitalWrite(STEP_PIN, LOW); - delayMicroseconds(step_interval_us); - - // Увеличиваем счетчик шагов - total_steps++; - } - - // Публикуем обратную связь - publishFeedback(); + // Loop выполняется на Core 1 + motorLoop(); + vTaskDelay(pdMS_TO_TICKS(5)); } \ No newline at end of file diff --git a/arduino_code/Test/src/motor.cpp b/arduino_code/Test/src/motor.cpp new file mode 100644 index 0000000..d5712d3 --- /dev/null +++ b/arduino_code/Test/src/motor.cpp @@ -0,0 +1,134 @@ +#include "motor.h" +#include "config.h" +#include + +volatile bool motor_enabled_logic = false; +volatile unsigned long current_step_interval_us = 100000; +volatile unsigned long total_steps = 0; + +static int target_rpm = 0; +static int current_rpm_display = 0; + +static hw_timer_t *stepTimer = NULL; +static bool is_ramping = false; +static unsigned long ramp_start_ms = 0; +static float start_speed_sps = 0; +static float end_speed_sps = 0; + +// --- ISR --- +void IRAM_ATTR onStepTimer() { + if (digitalRead(EN_PIN) == LOW && motor_enabled_logic) { + GPIO.out_w1ts = (1 << STEP_PIN); + ets_delay_us(1); + GPIO.out_w1tc = (1 << STEP_PIN); + total_steps++; + } +} + +unsigned long getStepIntervalUs() { + return current_step_interval_us; +} + +// --- Private Helpers --- +static void updateRamp() { + unsigned long now = millis(); + unsigned long elapsed = now - ramp_start_ms; + + if (elapsed >= RAMP_DURATION_MS) { + is_ramping = false; + current_step_interval_us = (end_speed_sps > 0) ? (1000000UL / (unsigned long)end_speed_sps) : 100000; + + if (end_speed_sps <= 0) { + motor_enabled_logic = false; + } else { + motor_enabled_logic = true; + } + + timerAlarmWrite(stepTimer, current_step_interval_us, true); + return; + } + + float progress = (float)elapsed / RAMP_DURATION_MS; + float current_sps = start_speed_sps + (end_speed_sps - start_speed_sps) * progress; + if (current_sps < 1) current_sps = 1; + + unsigned long new_interval = 1000000UL / (unsigned long)current_sps; + if (new_interval < 10) new_interval = 10; + + current_step_interval_us = new_interval; + timerAlarmWrite(stepTimer, new_interval, true); + + unsigned long total_steps_per_rev = (unsigned long)STEPS_PER_REVOLUTION * MICROSTEP_FACTOR; + current_rpm_display = ((unsigned long)current_sps * 60) / total_steps_per_rev; +} + +// --- Public API --- +void motorInit() { + pinMode(DIR_PIN, OUTPUT); + pinMode(STEP_PIN, OUTPUT); + pinMode(EN_PIN, OUTPUT); + + digitalWrite(EN_PIN, HIGH); + digitalWrite(DIR_PIN, HIGH); + digitalWrite(STEP_PIN, LOW); + + stepTimer = timerBegin(0, 80, true); + timerAttachInterrupt(stepTimer, &onStepTimer, true); + timerAlarmWrite(stepTimer, 100000, true); + timerAlarmEnable(stepTimer); +} + +void motorLoop() { + if (is_ramping) { + updateRamp(); + } +} + +void resetSteps() { + taskDISABLE_INTERRUPTS(); + total_steps = 0; + taskENABLE_INTERRUPTS(); +} + +unsigned long getMotorSteps() { + unsigned long steps; + taskDISABLE_INTERRUPTS(); + steps = total_steps; + taskENABLE_INTERRUPTS(); + return steps; +} + +int getCurrentRPM() { + return current_rpm_display; +} + +bool isMotorRunning() { + return (motor_enabled_logic && current_step_interval_us < 100000); +} + +void setTargetRPM(int rpm) { + target_rpm = rpm; + + // Сброс шагов при старте с нуля + if (rpm > 0 && current_rpm_display < 5) { + resetSteps(); + } + + unsigned long total_steps_per_rev = (unsigned long)STEPS_PER_REVOLUTION * MICROSTEP_FACTOR; + + if (current_step_interval_us > 0 && current_step_interval_us < 100000) { + start_speed_sps = 1000000.0f / current_step_interval_us; + } else { + start_speed_sps = 0; + } + + if (rpm > 0) { + end_speed_sps = ((float)rpm * total_steps_per_rev) / 60.0f; + } else { + end_speed_sps = 0; + } + + ramp_start_ms = millis(); + is_ramping = true; + motor_enabled_logic = true; +} \ No newline at end of file diff --git a/arduino_code/Test/src/motor.h b/arduino_code/Test/src/motor.h new file mode 100644 index 0000000..01a5488 --- /dev/null +++ b/arduino_code/Test/src/motor.h @@ -0,0 +1,15 @@ +#ifndef MOTOR_H +#define MOTOR_H + +#include + +void motorInit(); +void setTargetRPM(int rpm); +void resetSteps(); +unsigned long getMotorSteps(); +int getCurrentRPM(); +bool isMotorRunning(); +unsigned long getStepIntervalUs(); +void motorLoop(); + +#endif \ No newline at end of file diff --git a/arduino_code/Test/src/mqtt_handler.cpp b/arduino_code/Test/src/mqtt_handler.cpp new file mode 100644 index 0000000..78cc4dc --- /dev/null +++ b/arduino_code/Test/src/mqtt_handler.cpp @@ -0,0 +1,180 @@ +#include "mqtt_handler.h" +#include "config.h" +#include "motor.h" +#include +#include + +// --- ГЛОБАЛЬНЫЕ ОБЪЕКТЫ (Static внутри cpp) --- +static WiFiClient espClient; +static PubSubClient client(espClient); + +// --- ПЕРЕМЕННЫЕ ДЛЯ ОТСЛЕЖИВАНИЯ ИЗМЕНЕНИЙ ТЕЛЕМЕТРИИ --- +static unsigned long last_feedback_time = 0; +static int last_pub_rpm = -1; +static unsigned long last_pub_sps = -1; +static unsigned long last_pub_steps = -1; +static int last_pub_driver_state = -1; // -1: unknown, 0: off, 1: on +static int last_pub_is_run = -1; // -1: unknown, 0: stop, 1: run + +// --- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ --- + +static void setup_wifi() { + Serial.print("Connecting to WiFi"); + WiFi.begin(WIFI_SSID, WIFI_PASS); + while (WiFi.status() != WL_CONNECTED) { + vTaskDelay(pdMS_TO_TICKS(500)); + Serial.print("."); + } + Serial.println("\nWiFi Connected"); +} + +static void reconnect() { + while (!client.connected()) { + Serial.print("Attempting MQTT connection..."); + if (client.connect(MQTT_CLIENT_ID, MQTT_USER, MQTT_PASS)) { + Serial.println("connected"); + + // Сбрасываем кэш телеметрии, чтобы при переподключении + // сразу отправить актуальное состояние, даже если оно не менялось + last_pub_rpm = -1; + last_pub_sps = -1; + last_pub_steps = -1; + last_pub_driver_state = -1; + last_pub_is_run = -1; + + // Подписка на топики управления + client.subscribe("motor/control/command"); + client.subscribe("motor/control/driver"); + client.subscribe("motor/control/step_per_sec"); + client.subscribe("motor/control/rpm"); + client.subscribe("motor/control/totalsteps/reset"); + } else { + Serial.printf("failed, rc=%d, retry in 5s\n", client.state()); + vTaskDelay(pdMS_TO_TICKS(5000)); + } + } +} + +static void callback(char* topic, byte* payload, unsigned int length) { + // Безопасное преобразование payload в строку + char msg[length + 1]; + memcpy(msg, payload, length); + msg[length] = '\0'; + + Serial.printf("MQTT Received [%s]: %s\n", topic, msg); + + if (strcmp(topic, "motor/control/command") == 0) { + if (strcmp(msg, "start") == 0) { + // Если была команда stop (RPM 0), запускаем на дефолтной скорости или последней целевой + // Здесь для простоты запускаем на 60 RPM, если текущая скорость близка к 0 + if (getCurrentRPM() < 5) { + setTargetRPM(60); + } else { + // Если мотор уже крутится, можно просто возобновить рампу к текущей цели, + // но setTargetRPM и так это делает. + setTargetRPM(getCurrentRPM()); + } + } else if (strcmp(msg, "stop") == 0) { + setTargetRPM(0); + } + } + else if (strcmp(topic, "motor/control/rpm") == 0) { + setTargetRPM(atoi(msg)); + } + else if (strcmp(topic, "motor/control/step_per_sec") == 0) { + unsigned long sps = strtoul(msg, NULL, 10); + unsigned long total_steps_per_rev = (unsigned long)STEPS_PER_REVOLUTION * MICROSTEP_FACTOR; + if (sps > 0) { + int calc_rpm = (sps * 60) / total_steps_per_rev; + setTargetRPM(calc_rpm); + } + } + else if (strcmp(topic, "motor/control/driver") == 0) { + // TMC2209: LOW = Enabled, HIGH = Disabled + bool enable = (strcmp(msg, "turnon") == 0); + digitalWrite(EN_PIN, enable ? LOW : HIGH); + } + else if (strcmp(topic, "motor/control/totalsteps/reset") == 0) { + resetSteps(); + // Принудительно публикуем 0 сразу, не дожидаясь цикла телеметрии + if (client.connected()) { + client.publish("motor/feedback/totalsteps", "0"); + } + last_pub_steps = 0; + } +} + +static void publishTelemetry() { + unsigned long now = millis(); + + // Проверяем каждые 200 мс + if (now - last_feedback_time >= 200) { + + // 1. RPM + int current_rpm = getCurrentRPM(); + if (current_rpm != last_pub_rpm) { + client.publish("motor/feedback/rpm", String(current_rpm).c_str()); + last_pub_rpm = current_rpm; + } + + // 2. Steps Per Second (SPS) + unsigned long interval_us = getStepIntervalUs(); + unsigned long current_sps = 0; + if (interval_us > 0 && interval_us < 100000) { + current_sps = 1000000UL / interval_us; + } + + if (current_sps != last_pub_sps) { + client.publish("motor/feedback/step_per_seconds", String(current_sps).c_str()); + last_pub_sps = current_sps; + } + + // 3. Total Steps + unsigned long current_steps = getMotorSteps(); + if (current_steps != last_pub_steps) { + client.publish("motor/feedback/totalsteps", String(current_steps).c_str()); + last_pub_steps = current_steps; + } + + // 4. Driver State (EN_PIN) + // LOW = On, HIGH = Off + int current_driver_state = (digitalRead(EN_PIN) == LOW) ? 1 : 0; + if (current_driver_state != last_pub_driver_state) { + client.publish("motor/feedback/driver", current_driver_state ? "on" : "off"); + last_pub_driver_state = current_driver_state; + } + + // 5. Is Running + int current_is_run = isMotorRunning() ? 1 : 0; + if (current_is_run != last_pub_is_run) { + client.publish("motor/feedback/is_run", current_is_run ? "true" : "false"); + last_pub_is_run = current_is_run; + } + + last_feedback_time = now; + } +} + +// --- ОСНОВНАЯ ЗАДАЧА FREE RTOS --- +void mqttTask(void *parameter) { + // Инициализация WiFi и MQTT клиента + setup_wifi(); + client.setServer(MQTT_SERVER, MQTT_PORT); + client.setCallback(callback); + + for (;;) { + // Поддержание соединения + if (!client.connected()) { + reconnect(); + } + + // Обработка входящих сообщений + client.loop(); + + // Отправка телеметрии + publishTelemetry(); + + // Yield для других задач + vTaskDelay(pdMS_TO_TICKS(10)); + } +} \ No newline at end of file diff --git a/arduino_code/Test/src/mqtt_handler.h b/arduino_code/Test/src/mqtt_handler.h new file mode 100644 index 0000000..075c909 --- /dev/null +++ b/arduino_code/Test/src/mqtt_handler.h @@ -0,0 +1,8 @@ +#ifndef MQTT_HANDLER_H +#define MQTT_HANDLER_H + +#include + +void mqttTask(void *parameter); + +#endif \ No newline at end of file