Загрузка данных
#include <Wire.h>
#include <Servo.h>
#include <SoftwareSerial.h>
// =====================================================
// ПИНЫ
// =====================================================
const int RIGHT_DIR = 2;
const int RIGHT_PWM = 5;
const int LEFT_DIR = 4;
const int LEFT_PWM = 6;
const int SERVO_PIN = A0;
const int TF_RX_PIN = 10; // Arduino RX <- TF-Luna TX
const int TF_TX_PIN = 9; // Arduino TX -> TF-Luna RX
const int REED_PIN = A3; // геркон
const int HALL_PIN = 13; // холл D0
// =====================================================
// НАСТРОЙКИ
// =====================================================
const bool HALL_ACTIVE_LOW = true; // если датчик инверсный — true
// Скорости. Подстрой под свою машинку.
int rightForwardSpeed = 155;
int leftForwardSpeed = 118;
int rightBackwardSpeed = 150;
int leftBackwardSpeed = 112;
// Поворот. Если левое колесо слабее — левый PWM можно выше
int rightTurnSpeed = 168;
int leftTurnSpeed = 188;
// Коррекция курса на прямых
float KpYaw = 2.2;
const int MAX_YAW_CORR = 28;
// Защита от препятствия
const int OBSTACLE_STOP_CM = 18;
// Лидар
const unsigned long SERVO_INTERVAL_MS = 38;
const int SERVO_MIN_ANGLE = 10;
const int SERVO_MAX_ANGLE = 170;
const int SERVO_STEP = 4;
// Маршрут
const int MAX_ROUTE_STEPS = 120;
const int MIN_MOVE_MS = 120;
const int MIN_TURN_DEG = 8;
// Сектора
const int MAX_SECTORS = 8;
const int SECTOR_BINS_HALF = 6;
const int SECTOR_BINS_TOTAL = 12;
// =====================================================
// СОСТОЯНИЕ
// =====================================================
enum MotionState { STOPPED, FORWARD, BACKWARD, LEFT, RIGHT };
MotionState currentMotion = STOPPED;
enum ModeState { MODE_MANUAL = 'M', MODE_RECORD = 'V', MODE_REPLAY = 'P' };
char currentMode = MODE_MANUAL;
struct RouteStep {
char cmd; // F B L R
uint16_t value; // для F/B = время, для L/R = угол
};
RouteStep route[MAX_ROUTE_STEPS];
int routeCount = 0;
uint8_t sectorSaved[MAX_SECTORS];
uint8_t sectorAtStep[MAX_SECTORS];
uint8_t sectorSig[MAX_SECTORS][SECTOR_BINS_TOTAL];
int currentSector = 1;
char activeRecordCmd = 0;
unsigned long activeRecordStartMs = 0;
float activeRecordStartYaw = 0.0;
bool replayAbort = false;
// Датчики
int reedDetected = 0;
int hallDetected = 0;
int magnetDetected = 0;
int frontDistanceCm = 255;
// =====================================================
// MPU6050 / MPU6500 (минимальный raw-драйвер)
// =====================================================
const uint8_t MPU_ADDR = 0x68;
float yawDeg = 0.0;
float gyroZBias = 0.0;
unsigned long lastImuMicros = 0;
// =====================================================
// ЛИДАР + СЕРВО
// =====================================================
SoftwareSerial tfSerial(TF_RX_PIN, TF_TX_PIN);
Servo scanServo;
int servoAngle = 90;
int servoDir = 1;
unsigned long lastServoMoveMs = 0;
bool scanPaused = false;
// =====================================================
// СТАТУС В ESP32
// =====================================================
unsigned long lastStatusMs = 0;
const unsigned long STATUS_INTERVAL_MS = 220;
// =====================================================
// ВСПОМОГАТЕЛЬНОЕ
// =====================================================
float normAngle180(float a) {
while (a > 180.0) a -= 360.0;
while (a < -180.0) a += 360.0;
return a;
}
int clampPWM(int v) {
if (v < 0) return 0;
if (v > 255) return 255;
return v;
}
void sendEvent(const char* msg) {
Serial.print("@EVENT ");
Serial.println(msg);
}
void sendEventSector(const char* msg, int sector) {
Serial.print("@EVENT ");
Serial.print(msg);
Serial.print(" sector=");
Serial.println(sector);
}
void sendScanPoint(int angle, int dist) {
Serial.print("@SCAN a=");
Serial.print(angle);
Serial.print(" d=");
Serial.println(dist);
}
void sendStatus() {
Serial.print("@STATUS mode=");
Serial.print(currentMode);
Serial.print(" motion=");
char m = 'S';
if (currentMotion == FORWARD) m = 'F';
if (currentMotion == BACKWARD) m = 'B';
if (currentMotion == LEFT) m = 'L';
if (currentMotion == RIGHT) m = 'R';
Serial.print(m);
Serial.print(" sector=");
Serial.print(currentSector);
Serial.print(" reed=");
Serial.print(reedDetected);
Serial.print(" hall=");
Serial.print(hallDetected);
Serial.print(" mag=");
Serial.print(magnetDetected);
Serial.print(" dist=");
Serial.println(frontDistanceCm);
}
// =====================================================
// МОТОРЫ
// =====================================================
// ВАЖНО: оставил твою рабочую логику направления
void rightForward(int spd) {
digitalWrite(RIGHT_DIR, HIGH);
analogWrite(RIGHT_PWM, clampPWM(spd));
}
void rightBackward(int spd) {
digitalWrite(RIGHT_DIR, LOW);
analogWrite(RIGHT_PWM, clampPWM(spd));
}
void leftForward(int spd) {
digitalWrite(LEFT_DIR, LOW);
analogWrite(LEFT_PWM, clampPWM(spd));
}
void leftBackward(int spd) {
digitalWrite(LEFT_DIR, HIGH);
analogWrite(LEFT_PWM, clampPWM(spd));
}
void softStop() {
analogWrite(RIGHT_PWM, 0);
analogWrite(LEFT_PWM, 0);
digitalWrite(RIGHT_DIR, LOW);
digitalWrite(LEFT_DIR, LOW);
currentMotion = STOPPED;
}
void applyForwardBase() {
rightBackward(rightForwardSpeed);
leftBackward(leftForwardSpeed);
currentMotion = FORWARD;
}
void applyBackwardBase() {
rightForward(rightBackwardSpeed);
leftForward(leftBackwardSpeed);
currentMotion = BACKWARD;
}
void applyLeftBase() {
rightForward(rightTurnSpeed);
leftBackward(leftTurnSpeed);
currentMotion = LEFT;
}
void applyRightBase() {
rightBackward(rightTurnSpeed);
leftForward(leftTurnSpeed);
currentMotion = RIGHT;
}
void applyForwardHoldYaw(float targetYaw) {
float err = normAngle180(targetYaw - yawDeg);
int corr = (int)(KpYaw * err);
if (corr > MAX_YAW_CORR) corr = MAX_YAW_CORR;
if (corr < -MAX_YAW_CORR) corr = -MAX_YAW_CORR;
int r = rightForwardSpeed - corr;
int l = leftForwardSpeed + corr;
rightBackward(r);
leftBackward(l);
currentMotion = FORWARD;
}
void applyBackwardHoldYaw(float targetYaw) {
float err = normAngle180(targetYaw - yawDeg);
int corr = (int)(KpYaw * err);
if (corr > MAX_YAW_CORR) corr = MAX_YAW_CORR;
if (corr < -MAX_YAW_CORR) corr = -MAX_YAW_CORR;
int r = rightBackwardSpeed + corr;
int l = leftBackwardSpeed - corr;
rightForward(r);
leftForward(l);
currentMotion = BACKWARD;
}
// =====================================================
// MPU
// =====================================================
void mpuWriteByte(uint8_t reg, uint8_t data) {
Wire.beginTransmission(MPU_ADDR);
Wire.write(reg);
Wire.write(data);
Wire.endTransmission();
}
int16_t mpuReadInt16(uint8_t reg) {
Wire.beginTransmission(MPU_ADDR);
Wire.write(reg);
Wire.endTransmission(false);
Wire.requestFrom((int)MPU_ADDR, 2);
int16_t hi = Wire.read();
int16_t lo = Wire.read();
return (hi << 8) | lo;
}
void initMPU() {
delay(100);
mpuWriteByte(0x6B, 0x00); // wake up
delay(50);
mpuWriteByte(0x1A, 0x03); // DLPF
mpuWriteByte(0x1B, 0x08); // gyro ±500 dps
delay(50);
}
float readGyroZ_dps() {
int16_t gzRaw = mpuReadInt16(0x47);
return ((float)gzRaw) / 65.5; // sensitivity for ±500 dps
}
void calibrateGyro() {
const int N = 500;
float sum = 0.0;
for (int i = 0; i < N; i++) {
sum += readGyroZ_dps();
delay(3);
}
gyroZBias = sum / N;
}
void updateIMU() {
unsigned long nowUs = micros();
if (lastImuMicros == 0) {
lastImuMicros = nowUs;
return;
}
float dt = (nowUs - lastImuMicros) / 1000000.0;
lastImuMicros = nowUs;
float gz = readGyroZ_dps() - gyroZBias;
if (gz > -0.4 && gz < 0.4) gz = 0.0; // мёртвая зона
yawDeg += gz * dt;
yawDeg = normAngle180(yawDeg);
}
// =====================================================
// ДАТЧИКИ МАГНИТА
// =====================================================
int readReedStable() {
int hits = 0;
const int samples = 8;
for (int i = 0; i < samples; i++) {
if (digitalRead(REED_PIN) == LOW) hits++;
delay(2);
}
return (hits >= 6) ? 1 : 0;
}
void updateMagSensors() {
reedDetected = readReedStable();
int rawHall = digitalRead(HALL_PIN);
if (HALL_ACTIVE_LOW) {
hallDetected = (rawHall == LOW) ? 1 : 0;
} else {
hallDetected = (rawHall == HIGH) ? 1 : 0;
}
magnetDetected = (reedDetected || hallDetected) ? 1 : 0;
}
// =====================================================
// TF-LUNA
// =====================================================
bool readTfLunaOnce(int &distCm) {
while (tfSerial.available() >= 9) {
if (tfSerial.read() == 0x59) {
if (tfSerial.read() == 0x59) {
uint8_t buf[7];
for (int i = 0; i < 7; i++) {
while (!tfSerial.available()) {}
buf[i] = tfSerial.read();
}
uint16_t dist = buf[0] | (buf[1] << 8);
if (dist > 0 && dist < 1200) {
distCm = (int)dist;
return true;
}
}
}
}
return false;
}
int readTfLunaAverage(int tries, int timeoutMs) {
unsigned long t0 = millis();
long sum = 0;
int count = 0;
int d = 0;
while ((millis() - t0) < (unsigned long)timeoutMs && count < tries) {
if (readTfLunaOnce(d)) {
if (d > 0 && d < 400) {
sum += d;
count++;
}
}
}
if (count == 0) return 255;
return (int)(sum / count);
}
// =====================================================
// НЕПРЕРЫВНЫЙ ЛИДАР-СКАН
// =====================================================
void updateContinuousScan() {
if (scanPaused) return;
unsigned long now = millis();
if (now - lastServoMoveMs < SERVO_INTERVAL_MS) return;
lastServoMoveMs = now;
servoAngle += servoDir * SERVO_STEP;
if (servoAngle >= SERVO_MAX_ANGLE) {
servoAngle = SERVO_MAX_ANGLE;
servoDir = -1;
}
if (servoAngle <= SERVO_MIN_ANGLE) {
servoAngle = SERVO_MIN_ANGLE;
servoDir = 1;
}
scanServo.write(servoAngle);
delay(18);
int d = readTfLunaAverage(1, 8);
if (d > 0 && d < 255) {
frontDistanceCm = d;
sendScanPoint(servoAngle, d);
}
}
// =====================================================
// ЗАПИСЬ МАРШРУТА
// =====================================================
void clearRouteAll() {
routeCount = 0;
activeRecordCmd = 0;
currentSector = 1;
for (int i = 0; i < MAX_SECTORS; i++) {
sectorSaved[i] = 0;
sectorAtStep[i] = 0;
for (int j = 0; j < SECTOR_BINS_TOTAL; j++) sectorSig[i][j] = 0;
}
sendEvent("ROUTE_ERASED");
}
void pushRouteStep(char cmd, uint16_t value) {
if (routeCount >= MAX_ROUTE_STEPS) return;
route[routeCount].cmd = cmd;
route[routeCount].value = value;
routeCount++;
}
void startRecordSegment(char cmd) {
activeRecordCmd = cmd;
activeRecordStartMs = millis();
activeRecordStartYaw = yawDeg;
}
void finalizeRecordSegment() {
if (activeRecordCmd == 0) return;
if (activeRecordCmd == 'F' || activeRecordCmd == 'B') {
unsigned long dt = millis() - activeRecordStartMs;
if (dt >= MIN_MOVE_MS) {
pushRouteStep(activeRecordCmd, (uint16_t)dt);
}
} else if (activeRecordCmd == 'L' || activeRecordCmd == 'R') {
float dYaw = normAngle180(yawDeg - activeRecordStartYaw);
int angle = (int)fabs(dYaw);
if (angle >= MIN_TURN_DEG) {
pushRouteStep(activeRecordCmd, (uint16_t)angle);
}
}
activeRecordCmd = 0;
}
// =====================================================
// ПАНОРАМА СЕКТОРА
// =====================================================
const int sectorAngles[SECTOR_BINS_HALF] = {15, 45, 75, 105, 135, 165};
void captureHalfSignature(uint8_t *dst, int offset) {
for (int i = 0; i < SECTOR_BINS_HALF; i++) {
scanServo.write(sectorAngles[i]);
delay(140);
int d = readTfLunaAverage(3, 35);
if (d < 0) d = 255;
if (d > 255) d = 255;
dst[offset + i] = (uint8_t)d;
}
}
void turnRightByAngle(float angleDeg);
void turnLeftByAngle(float angleDeg);
void capturePanoramaSignature(uint8_t *dst12) {
scanPaused = true;
softStop();
sendEvent("SECTOR_SCAN_START");
// передняя полусфера
captureHalfSignature(dst12, 0);
// поворот корпуса на 180 и задняя полусфера
turnRightByAngle(180.0);
delay(100);
captureHalfSignature(dst12, SECTOR_BINS_HALF);
// вернуть ориентацию как была
turnLeftByAngle(180.0);
delay(60);
scanServo.write(90);
scanPaused = false;
}
int compareSectorSignature(uint8_t *a, uint8_t *b) {
long err = 0;
for (int i = 0; i < SECTOR_BINS_TOTAL; i++) {
err += abs((int)a[i] - (int)b[i]);
}
return (int)err;
}
void saveCurrentSector() {
if (currentSector < 1 || currentSector > MAX_SECTORS) return;
finalizeRecordSegment();
uint8_t temp[SECTOR_BINS_TOTAL];
capturePanoramaSignature(temp);
int idx = currentSector - 1;
for (int i = 0; i < SECTOR_BINS_TOTAL; i++) sectorSig[idx][i] = temp[i];
sectorSaved[idx] = 1;
sectorAtStep[idx] = routeCount;
sendEventSector("SECTOR_CAPTURED", currentSector);
if (currentSector < MAX_SECTORS) {
currentSector++;
sendEventSector("SECTOR_SET", currentSector);
}
}
bool checkSectorIfNeeded(int executedSteps) {
for (int i = 0; i < MAX_SECTORS; i++) {
if (sectorSaved[i] && sectorAtStep[i] == executedSteps) {
uint8_t temp[SECTOR_BINS_TOTAL];
sendEvent("SECTOR_CHECK_START");
capturePanoramaSignature(temp);
int err = compareSectorSignature(temp, sectorSig[i]);
if (err < 240) {
sendEvent("SECTOR_MATCH");
return true;
} else {
sendEvent("SECTOR_MISMATCH");
return false;
}
}
}
return true;
}
// =====================================================
// ФОН / ОБСЛУЖИВАНИЕ
// =====================================================
void serviceBackground() {
updateIMU();
updateMagSensors();
updateContinuousScan();
if (millis() - lastStatusMs >= STATUS_INTERVAL_MS) {
lastStatusMs = millis();
sendStatus();
}
if (magnetDetected) {
sendEvent("MAGNET");
}
}
bool readStopCommandOnly() {
while (Serial.available()) {
char c = (char)Serial.read();
if (c == 'S') return true;
}
return false;
}
// =====================================================
// ПОВТОР МАРШРУТА
// =====================================================
void moveForwardReplay(uint16_t ms) {
float targetYaw = yawDeg;
unsigned long t0 = millis();
while ((millis() - t0) < ms) {
serviceBackground();
if (readStopCommandOnly()) { replayAbort = true; break; }
if (frontDistanceCm > 0 && frontDistanceCm < OBSTACLE_STOP_CM) {
sendEvent("OBSTACLE");
replayAbort = true;
break;
}
applyForwardHoldYaw(targetYaw);
}
softStop();
}
void moveBackwardReplay(uint16_t ms) {
float targetYaw = yawDeg;
unsigned long t0 = millis();
while ((millis() - t0) < ms) {
serviceBackground();
if (readStopCommandOnly()) { replayAbort = true; break; }
applyBackwardHoldYaw(targetYaw);
}
softStop();
}
void turnLeftByAngle(float angleDeg) {
float startYaw = yawDeg;
while (fabs(normAngle180(yawDeg - startYaw)) < angleDeg) {
serviceBackground();
if (currentMode == MODE_REPLAY && readStopCommandOnly()) { replayAbort = true; break; }
applyLeftBase();
}
softStop();
}
void turnRightByAngle(float angleDeg) {
float startYaw = yawDeg;
while (fabs(normAngle180(yawDeg - startYaw)) < angleDeg) {
serviceBackground();
if (currentMode == MODE_REPLAY && readStopCommandOnly()) { replayAbort = true; break; }
applyRightBase();
}
softStop();
}
void replayRoute() {
if (routeCount <= 0) return;
finalizeRecordSegment();
replayAbort = false;
currentMode = MODE_REPLAY;
sendEvent("REPLAY_START");
for (int i = 0; i < routeCount; i++) {
if (replayAbort) break;
char cmd = route[i].cmd;
uint16_t value = route[i].value;
if (cmd == 'F') moveForwardReplay(value);
else if (cmd == 'B') moveBackwardReplay(value);
else if (cmd == 'L') turnLeftByAngle((float)value);
else if (cmd == 'R') turnRightByAngle((float)value);
softStop();
delay(80);
if (!checkSectorIfNeeded(i + 1)) {
replayAbort = true;
sendEvent("REPLAY_ABORT_SECTOR");
break;
}
}
softStop();
if (replayAbort) sendEvent("REPLAY_ABORT_STEP");
else sendEvent("REPLAY_DONE");
currentMode = MODE_MANUAL;
}
// =====================================================
// РУЧНОЕ УПРАВЛЕНИЕ / КОМАНДЫ С ESP32
// =====================================================
void handleMotionCommand(char c) {
if (currentMode == MODE_REPLAY) return;
if (c == 'F') {
if (activeRecordCmd != 'F') {
finalizeRecordSegment();
if (currentMode == MODE_RECORD) startRecordSegment('F');
}
applyForwardBase();
}
else if (c == 'B') {
if (activeRecordCmd != 'B') {
finalizeRecordSegment();
if (currentMode == MODE_RECORD) startRecordSegment('B');
}
applyBackwardBase();
}
else if (c == 'L') {
if (activeRecordCmd != 'L') {
finalizeRecordSegment();
if (currentMode == MODE_RECORD) startRecordSegment('L');
}
applyLeftBase();
}
else if (c == 'R') {
if (activeRecordCmd != 'R') {
finalizeRecordSegment();
if (currentMode == MODE_RECORD) startRecordSegment('R');
}
applyRightBase();
}
else if (c == 'S') {
finalizeRecordSegment();
softStop();
}
}
void processCommand(char c) {
if (c == '\n' || c == '\r') return;
if (c == 'F' || c == 'B' || c == 'L' || c == 'R' || c == 'S') {
handleMotionCommand(c);
return;
}
if (c == 'V') {
if (currentMode != MODE_RECORD) {
currentMode = MODE_RECORD;
activeRecordCmd = 0;
sendEvent("RECORD_ON");
} else {
finalizeRecordSegment();
currentMode = MODE_MANUAL;
sendEvent("RECORD_OFF");
sendEvent("ROUTE_SAVED");
}
return;
}
if (c == 'N') {
if (currentMode == MODE_RECORD) {
saveCurrentSector();
}
return;
}
if (c == 'P') {
if (currentMode != MODE_REPLAY && routeCount > 0) {
replayRoute();
}
return;
}
if (c == 'E') {
if (currentMode != MODE_REPLAY) {
softStop();
currentMode = MODE_MANUAL;
clearRouteAll();
}
return;
}
}
void readESPCommands() {
while (Serial.available()) {
char c = (char)Serial.read();
processCommand(c);
}
}
// =====================================================
// SETUP / LOOP
// =====================================================
void setup() {
Serial.begin(9600); // связь с ESP32
Wire.begin();
tfSerial.begin(115200);
pinMode(RIGHT_DIR, OUTPUT);
pinMode(RIGHT_PWM, OUTPUT);
pinMode(LEFT_DIR, OUTPUT);
pinMode(LEFT_PWM, OUTPUT);
pinMode(REED_PIN, INPUT_PULLUP);
pinMode(HALL_PIN, INPUT);
scanServo.attach(SERVO_PIN);
scanServo.write(90);
softStop();
initMPU();
calibrateGyro();
clearRouteAll();
sendEvent("SYSTEM_READY");
sendStatus();
}
void loop() {
readESPCommands();
serviceBackground();
}