duminică, 1 martie 2026

O simpla statie de lipit cu Arduino

 

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 testat-o cateva zile, pe parcursul carora am rezolvat diverse buguri. In prezent functioneza destul de bine, zic eu. 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.



 

 

 


/*
//
// STATIE DE LIPIT CU ARDUINO
// DISPLAY LED CU 7 SEGMENTE
// ENCODER SI 2 MEMORII
// STAND-BY DUPA 30 MINUTE
// pt PENSOL SL-10
//
//
// mai multe info aici :
//
// www.elforum.info/topic/165506-statie-de-lipit-cu-arduino
*/


#include <max6675.h>
#include <Encoder.h>
#include <EEPROM.h>
#include <EasyButton.h>
#include <avr/wdt.h>
#include <avr/io.h>
#include <avr/interrupt.h>

// --- CONFIGURARE HARDWARE ---
MAX6675 thermocouple(10, 9, 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;

// --- VARIABILE CONTROL ---
float Setpoint = 380.0;
float Input = 0;
int pwm = 0;
long oldPosition = 0;
unsigned long lastUpdate = 0;
unsigned long lastEncoderTime = 0;
unsigned long lastAdjustTime = 0;
bool sensorError = false;
int errorCounter = 0;
const int MAX_ERROR_COUNT = 4;

float lastInput = 0;
float tempTrend = 0;
int pwm_hold_base = 185;

bool inputInitialized = false;

// --- LOGICA MEMORIE SI TIMER ---
const int EEPROM_ADDR[] = {0, 4};
int savedTemps[] = {0, 0};
unsigned long blinkUntil = 0;
unsigned long lastActivityTime = 0;
const unsigned long TIMEOUT_OFF = 1800000;
bool isSystemOff = false;

EasyButton btn1(A1);
EasyButton btn2(A2);

// --- VARIABILE PENTRU DISPLAY ---
volatile uint8_t isrCurrentDigit = 0;
volatile int isrDisplayValue = 0;
volatile bool isrDisplayEnabled = true;

byte const digits[] = {
B00111111, B00000110, B01011011, B01001111, B01100110,
B01101101, B01111101, B00000111, B01111111, B01101111
};

// --- FUNCTII AUXILIARE ---
void triggerBlink() { blinkUntil = millis() + 500; }
void resetActivity() { lastActivityTime = millis(); }
void wakeSystem() { if (isSystemOff) isSystemOff = false; resetActivity(); }

void onBtn1Short() { wakeSystem(); if (savedTemps[0] > 0) { Setpoint = savedTemps[0]; lastAdjustTime = millis(); triggerBlink(); } }
void onBtn1Long() { wakeSystem(); savedTemps[0] = (int)Setpoint; EEPROM.put(EEPROM_ADDR[0], savedTemps[0]); triggerBlink(); }
void onBtn2Short() { wakeSystem(); if (savedTemps[1] > 0) { Setpoint = savedTemps[1]; lastAdjustTime = millis(); triggerBlink(); } }
void onBtn2Long() { wakeSystem(); savedTemps[1] = (int)Setpoint; EEPROM.put(EEPROM_ADDR[1], savedTemps[1]); triggerBlink(); }

// --- INTERRUPT SERVICE ROUTINE (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) { // Err
if (isrCurrentDigit == 0) segments = B01111001;
else if (isrCurrentDigit == 1) segments = B01010000;
else segments = B01010000;
} else {
int t = abs(isrDisplayValue);
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 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;
TCCR1B |= (1 << WGM12);
TCCR1B |= (1 << CS11) | (1 << CS10);
TIMSK1 |= (1 << OCIE1A);
sei();
}

void setup() {
// Fix WDT boot-loop: dezactiveaza WDT imediat la pornire
// (bootloader vechi nu il dezactiveaza dupa reset WDT → boot-loop)
MCUSR = 0;
wdt_disable();

for (int i = 0; i < 7; i++) pinMode(segPins[i], OUTPUT);
for (int i = 0; i < 3; i++) pinMode(digitPins[i], OUTPUT);
pinMode(heaterPin, OUTPUT);
analogWrite(heaterPin, 0);

// Afiseaza Setpoint imediat la pornire via ISR
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 (int i = 0; i < 2; i++) {
int val; EEPROM.get(EEPROM_ADDR[i], val);
if (val >= 150 && val <= 400) savedTemps[i] = val; else savedTemps[i] = 0;
}

// Delay 500ms pentru stabilizare SPI MAX6675 la pornire
// ISR-ul ruleaza deja si afiseaza Setpoint in acest timp
delay(500);

myEnc.write(0);
resetActivity();
lastAdjustTime = millis();

// WDT activat DUPA delay-ul de stabilizare, pentru a nu reseta la pornire
wdt_enable(WDTO_2S);
}

void loop() {
wdt_reset();
btn1.read();
btn2.read();

if (!isSystemOff && (millis() - lastActivityTime > TIMEOUT_OFF)) isSystemOff = true;

// --- ENCODER ---
long newPos = myEnc.read();
long deltaPos = newPos - oldPosition;
if (abs(deltaPos) >= 4 && millis() - lastEncoderTime > 20) {
wakeSystem();
oldPosition = newPos;
lastEncoderTime = millis();
lastAdjustTime = millis();
if (deltaPos > 0) Setpoint += 5; else Setpoint -= 5;
Setpoint = constrain(Setpoint, 150, 400);
}

// --- CITIRE SENZOR SI CONTROL INCALZIRE (250ms) ---
if (millis() - lastUpdate > 250) {
lastUpdate = millis();

if (isSystemOff) {
pwm = 0;
analogWrite(heaterPin, 0);
} else {
float currentRead = thermocouple.readCelsius();

// isnan repus: detecteaza termocuplu deconectat (MAX6675 returneaza NAN)
// NAN < 5 si NAN > 500 sunt AMBELE false, deci fara isnan eroarea nu e prinsa
if (isnan(currentRead) || currentRead < 5 || currentRead > 500) {
errorCounter++;
if (errorCounter >= MAX_ERROR_COUNT) {
sensorError = true;
pwm = 0;
}
} else {
errorCounter = 0;
sensorError = false;

if (!inputInitialized) {
Input = currentRead;
lastInput = currentRead;
inputInitialized = true;
} else {
// Alpha 0.25 in loc de 0.2: raspuns mai rapid la schimbari reale
// dar filtreaza mai bine spike-urile izolate SPI
Input = (Input * 0.75) + (currentRead * 0.25);
}

float instantTrend = (Input - lastInput) / 0.25;
// Clamp instantTrend: ignora spike-uri mai mari de 8 grade/secunda
// (imposibile fizic la un fier de 48W in regim stabil)
instantTrend = constrain(instantTrend, -8.0, 8.0);
tempTrend = (tempTrend * 0.6) + (instantTrend * 0.4);
lastInput = Input;

float dif = Setpoint - Input;
if (dif > 1.5) pwm = 255;
else if (dif > -1.5) pwm = pwm_hold_base + (tempTrend < 0 ? (int)(fabs(tempTrend) * 50) : 0);
else pwm = 0;
}

pwm = constrain(pwm, 0, 255);
analogWrite(heaterPin, pwm);
}
}

// --- LOGICA AFISARE ---
// noInterrupts() la citirea Input/Setpoint: sunt float (4 bytes),
// scriere non-atomica pe AVR 8-bit → posibila coruptie daca ISR intrerupe
noInterrupts();
float inputSnap = Input;
float setpointSnap = Setpoint;
interrupts();

int valToDisplay;
if (isSystemOff) {
valToDisplay = -1;
} else if (sensorError) {
valToDisplay = -111;
} else if (!inputInitialized || millis() - lastAdjustTime < 2000) {
valToDisplay = (int)setpointSnap;
} else {
if (fabs(inputSnap - setpointSnap) < 5.1) valToDisplay = (int)setpointSnap;
else valToDisplay = (int)(inputSnap + 0.5);
}

noInterrupts();
unsigned long nowMs = millis();
isrDisplayEnabled = (nowMs < blinkUntil) ? ((nowMs / 100) % 2 == 0) : true;
isrDisplayValue = valToDisplay;
interrupts();
}

Niciun comentariu:

Trimiteți un comentariu