#include <Arduino.h>
#include <max6675.h>
#include <AccelStepper.h>
#include <Wire.h>
#include "SSD1306Ascii.h"
#include "SSD1306AsciiWire.h"
// 1. Инициализация ультралегкого текстового дисплея
#define SCREEN_ADDRESS 0x3C
SSD1306AsciiWire display;
// Настройки температуры
const float SETPOINT = 100.0; // Целевая температура в °C
const float HYSTERESIS = 1.0; // Коридор безопасности в °C
// 2. Настройки кастомного чипа термопары MAX6675
int thermoDO = 4;
int thermoCS = 5;
int thermoCLK = 6;
MAX6675 thermocouple(thermoCLK, thermoCS, thermoDO);
// 3. Настройки шагового двигателя и драйвера DRV8825 / A4988
const int stepPin = 2;
const int dirPin = 3;
const int enablePin = 7;
AccelStepper stepper(AccelStepper::DRIVER, stepPin, dirPin);
// Настройки геометрии (200 шагов на 360°)
const float STEPS_PER_REV = 200.0;
const long POS_ON = (45.0 / 360.0) * STEPS_PER_REV; // Позиция +45° (ВКЛ)
const long POS_OFF = (-45.0 / 360.0) * STEPS_PER_REV; // Позиция -45° (ВЫКЛ)
// Переменные состояния системы
bool isHeaterOn = false;
int sensorCounter = 0;
volatile float currentTemp = 0.0;
volatile bool sensorError = false;
// НА ТИВНОЕ ПРЕРЫВАНИЕ ТАЙМЕРА 2 (вызывается аппаратно ровно каждые 1.0 мс)
ISR(TIMER2_COMPA_vect) {
// 1. Управление мотором (МГНОВЕННЫЙ "ЧИК": шаг каждую 1 мс, если не доехали)
if (stepper.targetPosition() != stepper.currentPosition()) {
stepper.enableOutputs();
if (stepper.targetPosition() > stepper.currentPosition()) {
stepper.setCurrentPosition(stepper.currentPosition() + 1);
} else {
stepper.setCurrentPosition(stepper.currentPosition() - 1);
}
} else {
stepper.disableOutputs(); // Приехали — полностью обесточиваем обмотки
}
// 2. Опрос датчика каждые 500 мс
sensorCounter++;
if (sensorCounter >= 500) {
sensorCounter = 0;
digitalWrite(thermoCLK, LOW);
digitalWrite(thermoCS, HIGH);
delayMicroseconds(10);
float tempReading = thermocouple.readCelsius();
if (isnan(tempReading) || (tempReading == 0.0 && millis() < 2000)) {
return;
}
if (tempReading == 0.0 && millis() >= 2000) {
sensorError = true;
currentTemp = 0.0;
stepper.enableOutputs();
stepper.moveTo(POS_OFF);
isHeaterOn = false;
return;
}
sensorError = false;
currentTemp = tempReading;
// Релейное управление «чиками»
if (currentTemp < (SETPOINT - HYSTERESIS) && !isHeaterOn) {
stepper.enableOutputs();
stepper.moveTo(POS_ON);
isHeaterOn = true;
}
else if (currentTemp > (SETPOINT + HYSTERESIS) && isHeaterOn) {
stepper.enableOutputs();
stepper.moveTo(POS_OFF);
isHeaterOn = false;
}
}
}
void setup() {
Serial.begin(9600);
// Инициализация легкого дисплея
Wire.begin();
Wire.setClock(400000L);
display.begin(&Adafruit128x64, SCREEN_ADDRESS);
display.setFont(Adafruit5x7);
display.clear();
// Настройка пина Enable для драйвера мотора
stepper.setEnablePin(enablePin);
stepper.setPinsInverted(false, false, true);
stepper.setMaxSpeed(1000);
stepper.setCurrentPosition(0);
// Стартовое выключение
stepper.enableOutputs();
stepper.moveTo(POS_OFF);
// НАСТРОЙКА НАТИВНЫХ РЕГИСТРОВ ТАЙМЕРА 2 (1 мс)
cli();
TCCR2A = 0;
TCCR2B = 0;
TCNT2 = 0;
OCR2A = 249;
TCCR2A |= (1 << WGM21);
TCCR2B |= (1 << CS22);
TIMSK2 |= (1 << OCIE2A);
sei();
}
void loop() {
// Вывод текста без графического буфера
display.home();
if (sensorError) {
display.set2X(); // Крупный шрифт
display.println("ERROR: ");
display.println("NO SENSOR! ");
display.set1X();
display.println(" ");
display.println(" ");
}
else {
// Строка 1: Крупно текущая температура
display.set2X();
display.print("Temp: ");
display.print((int)currentTemp);
display.print(".");
display.print((int)(currentTemp * 10) % 10);
display.println(" C "); // Пробелы затирают старые хвосты цифр
display.set1X(); // Обычный шрифт
display.println("---------------------");
// Строка 2: Цель
display.print("Target: ");
display.print(SETPOINT, 1);
display.println(" C ");
// Строка 3: Состояние ручки
display.print("Status: ");
if (isHeaterOn) {
display.println("HEATING [ON] ");
} else {
display.println("IDLE [OFF] ");
}
}
delay(200); // loop() никуда не спешит и не мешает таймеру
}