Загрузка данных
#include "motion.h"
#include "config.h"
#include "pinout.h"
#include "gcode_program.h"
#include "state_manager.h"
// Физическое положение станка (изменяется только в прерывании)
volatile long currentStepsX = 0;
volatile long currentStepsY = 0;
volatile long currentStepsZ = 0;
volatile bool isVectorMoving = false;
static CorrectionPoint curvatureMap[250];
// --- КОЛЬЦЕВОЙ БУФЕР ШАГОВ ---
#define STEP_BUFFER_SIZE 4096 // Увеличим буфер для стабильности
struct StepCmd {
uint8_t stepX : 1;
uint8_t dirX : 1;
uint8_t stepY : 1;
uint8_t dirY : 1;
uint8_t stepZ : 1;
uint8_t dirZ : 1;
uint8_t valid : 1;
};
// Убираем volatile со всего массива, оставляем атомарными только указатели
static StepCmd stepRingBuffer[STEP_BUFFER_SIZE];
static volatile uint32_t bufferHead = 0;
static volatile uint32_t bufferTail = 0;
// Переменные плавного изменения скорости
static float targetMultiplier = 1.0f;
static unsigned long lastFadingTime = 0;
const unsigned long FADING_DURATION = 300; // Плавный стоп/разгон за 300 мс
// Глобальная переменная для ползунка скорости из Python (определена в motion.h)
float pythonFeedrateOverride = 1.0f;
struct InputSegment {
float x, y, z, f, a;
volatile bool hasNewData;
};
static volatile InputSegment nextSeg = {0, 0, 0, 0, 0, false};
void initMotion() {
isVectorMoving = false;
currentStepsX = 0; currentStepsY = 0; currentStepsZ = 0;
bufferHead = 0;
bufferTail = 0;
for (int i = 0; i < 250; i++) {
curvatureMap[i].y_offset = 0.0f;
curvatureMap[i].z_offset = 0.0f;
}
for (int i = 0; i < STEP_BUFFER_SIZE; i++) {
stepRingBuffer[i].valid = 0;
}
}
void updateCurvaturePoint(int index, float y_off, float z_off) {
if (index >= 0 && index < cfg.laserMapSize) {
curvatureMap[index].y_offset = y_off;
curvatureMap[index].z_offset = z_off;
}
}
CorrectionPoint getCorrection(float currentX) {
if (currentX < 0) currentX = 0;
float max_len = (cfg.laserMapSize - 1) * cfg.laserGridStep;
if (currentX > max_len) currentX = max_len;
int index = (int)(currentX / cfg.laserGridStep);
if (index >= cfg.laserMapSize - 1) return curvatureMap[cfg.laserMapSize - 1];
float t = (currentX - (index * cfg.laserGridStep)) / cfg.laserGridStep;
CorrectionPoint corr;
corr.y_offset = curvatureMap[index].y_offset + t * (curvatureMap[index+1].y_offset - curvatureMap[index].y_offset);
corr.z_offset = curvatureMap[index].z_offset + t * (curvatureMap[index+1].z_offset - curvatureMap[index].z_offset);
return corr;
}
// Вызывается Ядром 0: теперь она работает мгновенно (не ждет физического доезда!)
void prepareVectorSegment(float newX, float newY, float newZ, float feedRateMM_Min, float accelMM_Sec2) {
// Ждем ТОЛЬКО если поток Ядра 1 еще не успел забрать ПРЕДЫДУЩУЮ команду из ячейки-посредника
while (nextSeg.hasNewData) {
delay(1);
}
nextSeg.x = newX;
nextSeg.y = newY;
nextSeg.z = newZ;
nextSeg.f = feedRateMM_Min;
nextSeg.a = accelMM_Sec2;
nextSeg.hasNewData = true; // Пинаем Ядро 1
// Взводим флаг движения. Теперь Ядро 0 мгновенно выходит из функции и возвращается в loop()
isVectorMoving = true;
}
// --- ПОТОК ПЛАНИРОВЩИКА НА ЯДРЕ 1 ---
// --- ПОТОК ПЛАНИРОВЩИКА НА ЯДРЕ 1 ---
void motionTask(void * parameter) {
const float DT = 0.00001f;
// Переменные положения планировщика (живут на протяжении всей работы потока)
long localStepsX = 0;
long localStepsY = 0;
long localStepsZ = 0;
while (true) {
if (!nextSeg.hasNewData) {
if (bufferTail == bufferHead) {
isVectorMoving = false;
}
vTaskDelay(pdMS_TO_TICKS(1));
continue;
}
// Синхронизируем виртуальный счетчик с реальным положением ПЕРЕД началом нового кадра!
localStepsX = currentStepsX;
localStepsY = currentStepsY;
localStepsZ = currentStepsZ;
float nX = nextSeg.x; float nY = nextSeg.y; float nZ = nextSeg.z;
float feed = nextSeg.f; float accel = nextSeg.a;
nextSeg.hasNewData = false;
if (isHomed) {
if (nX < cfg.minX) nX = cfg.minX; if (nX > cfg.maxX) nX = cfg.maxX;
if (nY < cfg.minY) nY = cfg.minY; if (nY > cfg.maxY) nY = cfg.maxY;
if (nZ < cfg.minZ) nZ = cfg.minZ; if (nZ > cfg.maxZ) nZ = cfg.maxZ;
}
CorrectionPoint corr = getCorrection(nX);
float realMachineY = nY + corr.y_offset;
float realMachineZ = nZ + corr.z_offset;
float dx = nX - ((float)localStepsX / cfg.stepsPerMmX);
float dy = realMachineY - ((float)localStepsY / cfg.stepsPerMmY);
float dz = realMachineZ - ((float)localStepsZ / cfg.stepsPerMmZ);
float totalLength = sqrt(dx*dx + dy*dy + dz*dz);
if (totalLength < 0.001f) { continue; }
float Kx = fabs(dx) / totalLength; int dirX = (dx >= 0) ? 1 : -1;
float Ky = fabs(dy) / totalLength; int dirY = (dy >= 0) ? 1 : -1;
float dz_val = fabs(dz) / totalLength; int dirZ = (dz >= 0) ? 1 : -1;
float maxSpeed = feed / 60.0f;
float minSpeed = cfg.minVectorSpeed;
float currentVectorSpeed = minSpeed;
float t_accel = (maxSpeed - minSpeed) / accel;
float s_accel = minSpeed * t_accel + 0.5f * accel * t_accel * t_accel;
long totalTicks = 0; long accelTicks = 0, decelTicks = 0;
if (s_accel * 2.0f > totalLength) {
float halfLen = totalLength / 2.0f;
float t_real = (-minSpeed + sqrt(minSpeed*minSpeed + 2.0f*accel*halfLen)) / accel;
totalTicks = (long)((t_real * 2.0f) / DT);
accelTicks = (long)(t_real / DT); decelTicks = accelTicks;
} else {
float s_cruise = totalLength - (s_accel * 2.0f);
float t_cruise = s_cruise / maxSpeed;
totalTicks = (long)((t_accel * 2.0f + t_cruise) / DT);
accelTicks = (long)(t_accel / DT); decelTicks = accelTicks;
}
// Инициализируем идеальные координаты строго от текущей точки локального счетчика кадра!
float idealX = (float)localStepsX / cfg.stepsPerMmX;
float idealY = (float)localStepsY / cfg.stepsPerMmY;
float idealZ = (float)localStepsZ / cfg.stepsPerMmZ;
if (cfg.feedMultiplier < 0.01f) cfg.feedMultiplier = 1.0f;
// ГЕНЕРАЦИЯ ТАКТОВ ВРЕМЕНИ
for (long tick = 0; tick < totalTicks; tick++) {
while (cfg.feedMultiplier <= 0.001f) {
currentVectorSpeed = minSpeed;
vTaskDelay(pdMS_TO_TICKS(5));
}
float targetSpeed = maxSpeed * cfg.feedMultiplier;
long ticksToExtremity = totalTicks - tick;
if (ticksToExtremity <= decelTicks) {
targetSpeed = minSpeed * cfg.feedMultiplier;
}
if (currentVectorSpeed < targetSpeed) {
currentVectorSpeed += accel * DT;
if (currentVectorSpeed > targetSpeed) currentVectorSpeed = targetSpeed;
} else if (currentVectorSpeed > targetSpeed) {
currentVectorSpeed -= accel * DT;
if (currentVectorSpeed < targetSpeed) currentVectorSpeed = targetSpeed;
}
float deltaS = currentVectorSpeed * DT;
idealX += deltaS * Kx * dirX;
idealY += deltaS * Ky * dirY;
idealZ += deltaS * dz_val * dirZ;
long tStepsX = idealX * cfg.stepsPerMmX;
long tStepsY = idealY * cfg.stepsPerMmY;
long tStepsZ = idealZ * cfg.stepsPerMmZ;
// ИСПРАВЛЕНО: Ждем место в буфере ДО генерации шага кадра.
// Никаких вложенных бесконечных while! Только чистый пошаговый контроль.
uint32_t nextHead = (bufferHead + 1) % STEP_BUFFER_SIZE;
while (nextHead == bufferTail) {
vTaskDelay(pdMS_TO_TICKS(1));
}
StepCmd cmd = {0, 0, 0, 0, 0, 0, 0};
bool stateChanged = false;
// За один такт времени (10 мкс) мы делаем максимум 1 дискретный шаг (через IF)
if (localStepsX != tStepsX) { cmd.stepX = 1; cmd.dirX = (tStepsX > localStepsX) ? 1 : 0; localStepsX += (tStepsX > localStepsX) ? 1 : -1; stateChanged = true; }
if (localStepsY != tStepsY) { cmd.stepY = 1; cmd.dirY = (tStepsY > localStepsY) ? 1 : 0; localStepsY += (tStepsY > localStepsY) ? 1 : -1; stateChanged = true; }
if (localStepsZ != tStepsZ) { cmd.stepZ = 1; cmd.dirZ = (tStepsZ > localStepsZ) ? 1 : 0; localStepsZ += (tStepsZ > localStepsZ) ? 1 : -1; stateChanged = true; }
// Если физический шаг мотора нужен на этом такте таймера — укладываем его в буфер
if (stateChanged) {
cmd.valid = 1;
stepRingBuffer[bufferHead] = cmd;
bufferHead = nextHead;
}
}
vTaskDelay(pdMS_TO_TICKS(1));
}
}
void startMotionTask() {
xTaskCreatePinnedToCore(motionTask, "CNC_Motion", 4096, NULL, 24, NULL, 1);
}
// --- ФИНАЛЬНОЕ ПРЕРЫВАНИЕ ТАЙМЕРА (100 кГц) ---
void IRAM_ATTR onTimerInterrupt() {
// Таймер больше не зависит от флага и кэша ядер. Он работает всегда!
// Проверка физической безопасности
if (digitalRead(ESTOP_PIN) == HIGH || digitalRead(MOTORS_ALARM) == HIGH || digitalRead(ENDSTOPS_PIN) == HIGH) {
digitalWrite(MOTORS_ENABLE, HIGH);
bufferTail = bufferHead; // Сброс буфера
return;
}
// Если указатели не равны — в буфере ЕСТЬ шаги, выполняем их мгновенно!
if (bufferTail != bufferHead) {
StepCmd cmd = stepRingBuffer[bufferTail];
if (cmd.valid) {
bool stepIssued = false;
if (cmd.stepX) {
digitalWrite(X_DIR_PIN, cmd.dirX ? HIGH : LOW);
digitalWrite(X_STEP_PIN, HIGH);
currentStepsX += cmd.dirX ? 1 : -1;
stepIssued = true;
}
if (cmd.stepY) {
digitalWrite(Y_DIR_PIN, cmd.dirY ? HIGH : LOW);
digitalWrite(Y_STEP_PIN, HIGH);
currentStepsY += cmd.dirY ? 1 : -1;
stepIssued = true;
}
if (cmd.stepZ) {
digitalWrite(Z_DIR_PIN, cmd.dirZ ? HIGH : LOW);
digitalWrite(Z_STEP_PIN, HIGH);
currentStepsZ += cmd.dirZ ? 1 : -1;
stepIssued = true;
}
if (stepIssued) {
delayMicroseconds(2);
digitalWrite(X_STEP_PIN, LOW);
digitalWrite(Y_STEP_PIN, LOW);
digitalWrite(Z_STEP_PIN, LOW);
}
}// Сдвигаем указатель чтения (освобождая место для потока)bufferTail = (bufferTail + 1) % STEP_BUFFER_SIZE;
}
}
// Обычные функции хоуминга (работают в контексте задач, а не прерываний)
bool homeAxis(int stepPin, int dirPin, int dirSign, volatile long &axisSteps, long pullOffSteps) {
digitalWrite(dirPin, (dirSign == 1) ? HIGH : LOW);
while (digitalRead(ENDSTOPS_PIN) == LOW) {
digitalWrite(stepPin, HIGH);
delayMicroseconds(50);
digitalWrite(stepPin, LOW);
delayMicroseconds(50);
if (digitalRead(ESTOP_PIN) == HIGH) return false;
}
digitalWrite(dirPin, (dirSign == 1) ? LOW : HIGH);
for (long i = 0; i < pullOffSteps; i++) {
digitalWrite(stepPin, HIGH);
delayMicroseconds(100);
digitalWrite(stepPin, LOW);
delayMicroseconds(100);
}
delay(200);
digitalWrite(dirPin, (dirSign == 1) ? HIGH : LOW);
while (digitalRead(ENDSTOPS_PIN) == LOW) {
digitalWrite(stepPin, HIGH);
delayMicroseconds(200);
digitalWrite(stepPin, LOW);
delayMicroseconds(200);
}
axisSteps = 0;
digitalWrite(dirPin, (dirSign == 1) ? LOW : HIGH);
for (long i = 0; i < pullOffSteps; i++) {
digitalWrite(stepPin, HIGH);
delayMicroseconds(100);
digitalWrite(stepPin, LOW);
delayMicroseconds(100);
}
delay(200);
return true;
}
void runFullHoming() {
isVectorMoving = false;
bufferTail = bufferHead;
if (!homeAxis(Z_STEP_PIN, Z_DIR_PIN, 1, currentStepsZ, 1200)) {
isHomed = false;
return;
}
if (!homeAxis(Y_STEP_PIN, Y_DIR_PIN, -1, currentStepsY, 1200)) {
isHomed = false;
return;
}
if (!homeAxis(X_STEP_PIN, X_DIR_PIN, -1, currentStepsX, 1200)) {
isHomed = false;
return;
}
isHomed = true;
}
void updateFeedrateFading() {
// 1. Определяем целевую скорость на основе состояния автомата ЧПУ
if (currentMachineState == STATE_HOLD || currentMachineState == STATE_ALARM) {
targetMultiplier = 0.0f; // Пауза или Авария -> плавно гасим скорость в ноль
} else {
targetMultiplier = pythonFeedrateOverride; // Штатный режим -> берем значение с ПК
}
// 2. Линейная рампа изменения коэффициента каждые 5 миллисекунд
if (millis() - lastFadingTime > 5) {
lastFadingTime = millis();
float step = 5.0f / (float)FADING_DURATION;
if (cfg.feedMultiplier < targetMultiplier) {
cfg.feedMultiplier += step;
if (cfg.feedMultiplier > targetMultiplier) cfg.feedMultiplier = targetMultiplier;
}
else if (cfg.feedMultiplier > targetMultiplier) {
cfg.feedMultiplier -= step;
if (cfg.feedMultiplier < targetMultiplier) cfg.feedMultiplier = targetMultiplier;
}
}
}