Va salut !
Va prezint statia mea de lipit cu Arduino. Realizata cu AI Gemini, updatata cu Vercel si Qwen ; eu doar am formulat cerintele si am testat codul, deci nu am prea multe merite. Inspiratia mea au fost nenumaratele exemple existente.
Pe scurt : ciocan Pensol SL-10 (cel pe care-l am de mai bine de 15 ani si nu renunt la el, e prea bun !), Arduino Nano, display LED 7 segmente 3 digiti, encoder rotativ, 2 butoane pentru memorare 2 temperaturi de lucru, stand-by dupa 30 minute.
Am vrut initial cu PID, dar inertia termica mare a ciocanului m-a facut sa renunt ; Qwen a sugerat un algoritm "predictiv" surprinzator de eficient .
Orice comentariu sau sugestie sunt binevenite. Statia este prezentata si pe Elforum (https://www.elforum.info/topic/165506-statie-de-lipit-cu-arduino/), cine vrea o poate modifica dupa cum doreste (recomand Vercel / Perplexity / Qwen / Gemini, nu neaparat in aceasta ordine). Numai bine !
Update : corectate buguri. Initial am crezut ca nu e mare lucru sa faci o astfel de statie...Realitatea insa mi-a demonstrat contrariul - sunt n factori care o pot face sa mearga prost. In timp cred ca am testat mai mult de 20 de versiuni ; fiecare avea niste neajunsuri. Varianta postata pare sa mearga bine. Doar testarea intensiva imi va confirma asta.

/*
//
// STATIE DE LIPIT CU ARDUINO
// DISPLAY LED CU 7 SEGMENTE
// ENCODER SI 2 MEMORII
// STAND-BY DUPA 30 MINUTE
// pt PENSOL SL-10
//
// revizia N - august 2026
// - 100 nF ceramic intre T+ si T-, fire termocuplu rasucite;
// - IRLZ44N low-side, active-high, pull-down 10 k pe poarta.
//
// www.elforum.info/topic/165506-statie-de-lipit-cu-arduino
*/
#include <Encoder.h>
#include <EEPROM.h>
#include <EasyButton.h>
#include <avr/wdt.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <math.h>
#define CALIBRATE_HOLD_BASE 0 // 1 doar temporar, pentru calibrare
#define HEATER_ACTIVE_LOW 0 // active-high
#define HEATER_QUIET_DURING_READ 1 // heater oprit ~1 ms pe durata citirii
// --- CONFIGURARE HARDWARE ---
const int TC_SCK = 10;
const int TC_CS = 9;
const int TC_SO = 8;
Encoder myEnc(2, 3);
const int segPins[] = {4, 5, 6, 7, 12, 13, A0};
const int digitPins[] = {A3, A4, A5};
const int heaterPin = 11;
// --- CONSTANTE DE SISTEM ---
const unsigned long SAMPLE_INTERVAL_MS = 250UL;
const unsigned long TIMEOUT_OFF = 1800000UL; // 30 minute
const unsigned long BLINK_TIME_MS = 500UL;
const float SETPOINT_MIN = 150.0f;
const float SETPOINT_MAX = 400.0f;
const float MIN_VALID_TEMP = 10.0f;
const float MAX_VALID_TEMP = 480.0f;
const float MAX_SAFE_TEMP = 450.0f;
const float MAX_HEAT_RATE = 15.0f;
const float MAX_COOL_RATE = 5.0f;
const float RATE_MARGIN = 3.0f;
const uint8_t OUTLIER_ACCEPT_STREAK = 3;
const uint8_t INVALID_ERR_COUNT = 8; // ~2 s citiri invalide => E1
const float OVERHEAT_HYST = 20.0f;
const unsigned long OVERHEAT_MIN_OFF_MS = 10000UL;
const unsigned long HEAT_FAULT_MS = 30000UL;
const float HEAT_FAULT_GAP = 60.0f;
const float HEAT_FAULT_RISE = 5.0f;
const unsigned long FAULT_RETRY_MS = 10000UL;
const float CJ_OFFSET = 0.0f;
const float DEADBAND = 1.5f;
const float HOLD_TREND_GAIN = 50.0f;
const float HOLD_ERROR_GAIN = 20.0f;
// --- VARIABILE CONTROL ---
float Setpoint = 380.0f;
float Input = 0.0f;
int pwm = 0;
float lastInput = 0.0f;
float tempTrend = 0.0f;
int pwm_hold_base = 185;
bool inputInitialized = false;
bool needFilterReset = true;
bool sensorError = false;
uint8_t errSource = 0; // 1=E1, 2=E2, 3=E3
uint8_t invalidStreak = 0;
uint8_t outlierStreak = 0;
bool overheatLatch = false;
unsigned long overheatSince = 0;
bool heatFault = false;
unsigned long faultSince = 0;
unsigned long highPwmSince = 0;
float tempAtHighPwm = 0.0f;
long oldPosition = 0;
unsigned long lastUpdate = 0;
unsigned long lastEncoderTime = 0;
unsigned long lastAdjustTime = 0;
// --- MEMORIE, TIMEOUT, BLINK ---
const int EEPROM_ADDR[] = {0, 4};
int savedTemps[] = {0, 0};
volatile unsigned long blinkStart = 0;
unsigned long lastActivityTime = 0;
bool isSystemOff = false;
bool pendingSave = false;
uint8_t pendingSaveSlot = 0;
EasyButton btn1(A1);
EasyButton btn2(A2);
// --- VARIABILE PENTRU DISPLAY ---
volatile uint8_t isrCurrentDigit = 2;
volatile int isrDisplayValue = 0;
volatile bool isrDisplayEnabled = true;
const byte digits[] = {
B00111111, B00000110, B01011011, B01001111, B01100110,
B01101101, B01111101, B00000111, B01111111, B01101111
};
inline void setHeaterPWM(int value) {
if (value < 0) value = 0;
if (value > 255) value = 255;
#if HEATER_ACTIVE_LOW
analogWrite(heaterPin, 255 - value);
#else
analogWrite(heaterPin, value);
#endif
}
// --- DRIVER MAX6675 PROPRIU ---
void tcInit() {
pinMode(TC_SCK, OUTPUT);
digitalWrite(TC_SCK, LOW);
pinMode(TC_CS, OUTPUT);
digitalWrite(TC_CS, HIGH);
pinMode(TC_SO, INPUT);
}
uint16_t tcReadRaw() {
uint16_t v = 0;
digitalWrite(TC_SCK, LOW);
delayMicroseconds(10);
digitalWrite(TC_CS, LOW);
delayMicroseconds(1);
for (int8_t i = 15; i >= 0; i--) {
digitalWrite(TC_SCK, HIGH);
delayMicroseconds(1);
digitalWrite(TC_SCK, LOW);
delayMicroseconds(1);
v <<= 1;
if (digitalRead(TC_SO)) v |= 1;
}
digitalWrite(TC_CS, HIGH);
return v;
}
/*
Trei transferuri SPI cu intreruperile OPRITE + mediana.
~0.7-1 ms total: afisajul sare maxim un tick de 1 ms (invizibil),
millis() pierde <0.5% (neglijabil).
Returneaza NAN la fault de termocuplu sau cadru imposibil.
*/
float tcReadCelsius() {
noInterrupts();
uint16_t a = tcReadRaw();
uint16_t b = tcReadRaw();
uint16_t c = tcReadRaw();
interrupts();
uint16_t t;
if (a > b) { t = a; a = b; b = t; }
if (b > c) { t = b; b = c; c = t; }
if (a > b) { t = a; a = b; b = t; }
uint16_t v = b;
if (v & 0x8000) return NAN; // bit de semn = 1 => cadru corupt
if (v & 0x0004) return NAN; // bit 2 = termocuplu intrerupt
return (float)((v >> 3) & 0x0FFF) * 0.25f;
}
// --- FUNCTII AUXILIARE ---
void triggerBlink() {
blinkStart = millis();
if (blinkStart == 0) blinkStart = 1;
}
void resetActivity() {
lastActivityTime = millis();
}
void wakeSystem() {
if (isSystemOff) {
isSystemOff = false;
needFilterReset = true;
}
heatFault = false;
highPwmSince = 0;
resetActivity();
lastAdjustTime = millis();
}
void saveTempSlot(uint8_t slot) {
if (slot > 1) return;
int newValue = (int)Setpoint;
if (newValue < (int)SETPOINT_MIN || newValue > (int)SETPOINT_MAX) return;
if (savedTemps[slot] != newValue) {
savedTemps[slot] = newValue;
EEPROM.put(EEPROM_ADDR[slot], savedTemps[slot]);
}
lastAdjustTime = millis();
triggerBlink();
}
void onBtn1Short() {
wakeSystem();
if (savedTemps[0] > 0) Setpoint = (float)savedTemps[0];
triggerBlink();
}
void onBtn1Long() {
wakeSystem();
pendingSaveSlot = 0;
pendingSave = true;
}
void onBtn2Short() {
wakeSystem();
if (savedTemps[1] > 0) Setpoint = (float)savedTemps[1];
triggerBlink();
}
void onBtn2Long() {
wakeSystem();
pendingSaveSlot = 1;
pendingSave = true;
}
void filterUpdate(float sample, float dt) {
float newInput = (Input * 0.75f) + (sample * 0.25f);
float up = lastInput + (MAX_HEAT_RATE * dt + RATE_MARGIN);
float dn = lastInput - (MAX_COOL_RATE * dt + RATE_MARGIN);
if (newInput > up) newInput = up;
if (newInput < dn) newInput = dn;
float instantTrend = (newInput - lastInput) / dt;
if (instantTrend > MAX_HEAT_RATE) instantTrend = MAX_HEAT_RATE;
if (instantTrend < -MAX_COOL_RATE) instantTrend = -MAX_COOL_RATE;
tempTrend = (tempTrend * 0.6f) + (instantTrend * 0.4f);
Input = newInput;
lastInput = newInput;
}
// --- ISR DISPLAY ---
ISR(TIMER1_COMPA_vect) {
digitalWrite(digitPins[isrCurrentDigit], LOW);
isrCurrentDigit = (isrCurrentDigit + 1) % 3;
byte segments = 0;
if (!isrDisplayEnabled) {
segments = 0;
} else if (isrDisplayValue == -1) {
// STb
if (isrCurrentDigit == 0) segments = B01101101;
else if (isrCurrentDigit == 1) segments = B01111000;
else segments = B01111100;
} else if (isrDisplayValue <= -111 && isrDisplayValue >= -113) {
// E1 / E2 / E3
if (isrCurrentDigit == 0) segments = B01111001; // E
else if (isrCurrentDigit == 1) segments = digits[-110 - isrDisplayValue]; // 1..3
else segments = 0;
} else {
int t = isrDisplayValue;
if (t < 0) t = -t;
int d;
if (isrCurrentDigit == 0) d = (t / 100) % 10;
else if (isrCurrentDigit == 1) d = (t / 10) % 10;
else d = t % 10;
if (isrCurrentDigit == 0 && d == 0 && t < 100) segments = 0;
else if (isrCurrentDigit == 1 && d == 0 && t < 10) segments = 0;
else segments = digits[d];
}
for (int i = 0; i < 7; i++) {
digitalWrite(segPins[i], (segments & (1 << i)) ? LOW : HIGH);
}
digitalWrite(digitPins[isrCurrentDigit], HIGH);
}
void setupTimer1() {
cli();
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
OCR1A = 249; // 16 MHz / 64 / 250 = 1000 Hz
TCCR1B |= (1 << WGM12);
TCCR1B |= (1 << CS11) | (1 << CS10);
TIFR1 |= (1 << OCF1A);
TIMSK1 |= (1 << OCIE1A);
sei();
}
void setup() {
MCUSR = 0;
wdt_disable();
#if HEATER_ACTIVE_LOW
digitalWrite(heaterPin, HIGH);
#else
digitalWrite(heaterPin, LOW);
#endif
pinMode(heaterPin, OUTPUT);
setHeaterPWM(0);
for (int i = 0; i < 7; i++) {
pinMode(segPins[i], OUTPUT);
digitalWrite(segPins[i], HIGH);
}
for (int i = 0; i < 3; i++) {
pinMode(digitPins[i], OUTPUT);
digitalWrite(digitPins[i], LOW);
}
pinMode(A1, INPUT_PULLUP);
pinMode(A2, INPUT_PULLUP);
tcInit();
isrDisplayValue = (int)Setpoint;
isrDisplayEnabled = true;
setupTimer1();
btn1.begin();
btn2.begin();
btn1.onPressed(onBtn1Short);
btn1.onPressedFor(2000, onBtn1Long);
btn2.onPressed(onBtn2Short);
btn2.onPressedFor(2000, onBtn2Long);
for (uint8_t i = 0; i < 2; i++) {
int val = 0;
EEPROM.get(EEPROM_ADDR[i], val);
if (val >= (int)SETPOINT_MIN && val <= (int)SETPOINT_MAX) savedTemps[i] = val;
else savedTemps[i] = 0;
}
delay(500);
myEnc.write(0);
oldPosition = myEnc.read();
needFilterReset = true;
resetActivity();
lastAdjustTime = millis();
lastUpdate = millis();
wdt_enable(WDTO_2S);
}
void loop() {
wdt_reset();
btn1.read();
btn2.read();
if (pendingSave) {
pendingSave = false;
saveTempSlot(pendingSaveSlot);
}
// Auto-off
if (!isSystemOff && (millis() - lastActivityTime > TIMEOUT_OFF)) {
isSystemOff = true;
needFilterReset = true;
heatFault = false;
highPwmSince = 0;
pwm = 0;
setHeaterPWM(pwm);
}
// --- ENCODER ---
long newPos = myEnc.read();
long deltaPos = newPos - oldPosition;
long absDelta = (deltaPos >= 0) ? deltaPos : -deltaPos;
if (absDelta >= 4 && (millis() - lastEncoderTime > 20)) {
wakeSystem();
long stepsLong = deltaPos / 4;
if (stepsLong > 5) stepsLong = 5;
if (stepsLong < -5) stepsLong = -5;
int steps = (int)stepsLong;
if (steps != 0) {
Setpoint += (float)steps * 5.0f;
if (Setpoint < SETPOINT_MIN) Setpoint = SETPOINT_MIN;
if (Setpoint > SETPOINT_MAX) Setpoint = SETPOINT_MAX;
oldPosition += (long)steps * 4L;
lastEncoderTime = millis();
lastAdjustTime = millis();
}
}
// --- CITIRE SENZOR SI CONTROL ---
if (millis() - lastUpdate >= SAMPLE_INTERVAL_MS) {
unsigned long nowSample = millis();
unsigned long rawDtMs = nowSample - lastUpdate;
lastUpdate = nowSample;
float dt = rawDtMs / 1000.0f;
if (rawDtMs < 50UL || rawDtMs > 10000UL) dt = SAMPLE_INTERVAL_MS / 1000.0f;
if (rawDtMs > 2000UL) needFilterReset = true;
if (isSystemOff) {
pwm = 0;
setHeaterPWM(pwm);
highPwmSince = 0;
} else {
bool hadSensorError = sensorError;
int prevPwm = pwm;
#if HEATER_QUIET_DURING_READ
setHeaterPWM(0);
delay(1);
#endif
float currentRead = tcReadCelsius() + CJ_OFFSET;
#if HEATER_QUIET_DURING_READ
setHeaterPWM(prevPwm);
#endif
bool invalid = isnan(currentRead) || isinf(currentRead)
|| (currentRead < MIN_VALID_TEMP)
|| (currentRead > MAX_VALID_TEMP);
if (invalid) {
pwm = 0;
setHeaterPWM(pwm);
outlierStreak = 0;
highPwmSince = 0;
invalidStreak++;
if (invalidStreak >= INVALID_ERR_COUNT) {
sensorError = true;
errSource = 1; // E1: senzor invalid/intrerupt
}
} else {
invalidStreak = 0;
if (!inputInitialized || needFilterReset || hadSensorError || dt > 2.0f) {
Input = currentRead;
lastInput = currentRead;
tempTrend = 0.0f;
inputInitialized = true;
needFilterReset = false;
outlierStreak = 0;
} else {
float predicted = lastInput + tempTrend * dt;
float upLim = predicted + (MAX_HEAT_RATE * dt + RATE_MARGIN);
float dnLim = predicted - (MAX_COOL_RATE * dt + RATE_MARGIN);
if (currentRead > upLim || currentRead < dnLim) {
outlierStreak++;
if (outlierStreak >= OUTLIER_ACCEPT_STREAK) {
float clamped = currentRead;
if (clamped > upLim) clamped = upLim;
if (clamped < dnLim) clamped = dnLim;
filterUpdate(clamped, dt);
}
} else {
outlierStreak = 0;
filterUpdate(currentRead, dt);
}
}
// --- E2: SUPRAÎNCĂLZIRE pe Input FILTRAT ---
if (!overheatLatch && (Input > MAX_SAFE_TEMP)) {
overheatLatch = true;
overheatSince = millis();
}
if (overheatLatch &&
(Input < (MAX_SAFE_TEMP - OVERHEAT_HYST)) &&
(millis() - overheatSince > OVERHEAT_MIN_OFF_MS)) {
overheatLatch = false;
}
if (overheatLatch) {
pwm = 0;
setHeaterPWM(pwm);
sensorError = true;
errSource = 2; // E2
highPwmSince = 0;
} else {
float dif = Setpoint - Input;
#if CALIBRATE_HOLD_BASE
pwm = pwm_hold_base;
#else
if (dif > DEADBAND) {
pwm = 255;
} else if (dif < -DEADBAND) {
pwm = 0;
} else {
int trendCorrection = (int)(-tempTrend * HOLD_TREND_GAIN);
int errorCorrection = (int)(dif * HOLD_ERROR_GAIN);
pwm = pwm_hold_base + trendCorrection + errorCorrection;
}
#endif
if (pwm < 0) pwm = 0;
if (pwm > 255) pwm = 255;
// --- E3: DEFECT HEATER, doar in faza de incalzire ---
if (heatFault) {
pwm = 0;
sensorError = true;
errSource = 3; // E3
if (millis() - faultSince > FAULT_RETRY_MS) {
heatFault = false;
highPwmSince = 0;
}
} else if (inputInitialized) {
bool heatingPhase = (Setpoint - Input) > HEAT_FAULT_GAP;
if (pwm >= 250 && heatingPhase) {
if (highPwmSince == 0) {
highPwmSince = millis();
tempAtHighPwm = Input;
} else if (millis() - highPwmSince > HEAT_FAULT_MS) {
if (Input >= tempAtHighPwm + HEAT_FAULT_RISE) {
highPwmSince = millis();
tempAtHighPwm = Input;
} else {
heatFault = true;
faultSince = millis();
}
}
} else {
highPwmSince = 0;
}
}
setHeaterPWM(pwm);
if (!heatFault && !overheatLatch && invalidStreak == 0) {
sensorError = false; // auto-recuperare
}
}
}
}
}
// --- LOGICA AFISARE ---
noInterrupts();
float inputSnap = Input;
float setpointSnap = Setpoint;
interrupts();
int valToDisplay;
if (isSystemOff) {
valToDisplay = -1; // STb
} else if (sensorError) {
valToDisplay = -110 - errSource; // -111..-113 => E1..E3
} else if (!inputInitialized || (millis() - lastAdjustTime < 2000UL)) {
valToDisplay = (int)(setpointSnap + 0.5f);
} else {
if (fabs(inputSnap - setpointSnap) < 5.1f) {
valToDisplay = (int)(setpointSnap + 0.5f);
} else {
valToDisplay = (int)(inputSnap + 0.5f);
}
}
noInterrupts();
unsigned long nowMs = millis();
bool blinkActive = (blinkStart != 0) && ((nowMs - blinkStart) < BLINK_TIME_MS);
bool blinkPhase = (((nowMs - blinkStart) / 100UL) % 2UL) == 0UL;
isrDisplayEnabled = blinkActive ? blinkPhase : true;
isrDisplayValue = valToDisplay;
interrupts();
}

Niciun comentariu:
Trimiteți un comentariu