refactoring codebase

This commit is contained in:
2026-07-06 23:11:34 +07:00
parent 77997736f1
commit 9c522fb95c
8 changed files with 469 additions and 170 deletions

View File

@@ -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 мс
- Система автоматически обрабатывает плавный разгон/торможение при изменении скорости

View File

@@ -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;

View File

@@ -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

View File

@@ -1,177 +1,30 @@
#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
// 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));
}

View File

@@ -0,0 +1,134 @@
#include "motor.h"
#include "config.h"
#include <driver/timer.h>
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;
}

View File

@@ -0,0 +1,15 @@
#ifndef MOTOR_H
#define MOTOR_H
#include <Arduino.h>
void motorInit();
void setTargetRPM(int rpm);
void resetSteps();
unsigned long getMotorSteps();
int getCurrentRPM();
bool isMotorRunning();
unsigned long getStepIntervalUs();
void motorLoop();
#endif

View File

@@ -0,0 +1,180 @@
#include "mqtt_handler.h"
#include "config.h"
#include "motor.h"
#include <WiFi.h>
#include <PubSubClient.h>
// --- ГЛОБАЛЬНЫЕ ОБЪЕКТЫ (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));
}
}

View File

@@ -0,0 +1,8 @@
#ifndef MQTT_HANDLER_H
#define MQTT_HANDLER_H
#include <Arduino.h>
void mqttTask(void *parameter);
#endif