Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
86944cc
erste version rising protection (ungetested)
Dec 21, 2018
afa4ae6
FAST_BOOT feature
Dec 22, 2018
1adbf12
In loop() it should be ifdef ADC_TEST, not ifndef...
Dec 22, 2018
dd886f3
Move the up and down triangles a tad to the left (they were cut on my
Dec 22, 2018
fc77a1a
Use the real voltage for power calculation, do not assume min of 15V.
Dec 22, 2018
83a6a96
Define macro COLOR(r,g,b) to easy color defintions out of RGB
Dec 22, 2018
cfbb807
Do not enter standby mode when the soldering iron is off.
Dec 22, 2018
b16b7bc
Be able to turn the soldering iron on with only a short press on
Dec 22, 2018
110fd40
Tuning the protection parameter for the RT-11 tip
Dec 22, 2018
a389a9c
Tweak displaying the input voltage and use defines for cell voltages …
Dec 22, 2018
113c9d0
Tune my PID parameters
Dec 22, 2018
54647ea
Speed up the voltage measurements (Especially when switching from ext…
Dec 22, 2018
2d74eb9
Tweak GUI a bit
Dec 22, 2018
ca5fdf4
Small change in artwork for 100% full
Dec 22, 2018
aede67d
Tune PID
Dec 22, 2018
a705140
Fix for battery percentage ..
Dec 22, 2018
f19cae1
Feature: Power limitation
Dec 22, 2018
b186d66
Tune the battery display
Dec 22, 2018
1d24a75
Ensure that the temperature is rising when it should.
coldtobi Dec 22, 2018
3806af9
Bugfix and better documentation.
Nov 19, 2025
8f63929
Merge branch 'tobi' into feature_protection_tempnotrising
coldtobi Jan 1, 2026
c862db7
Improve temperature protection.
Jan 2, 2026
e5ebea5
Add missing commit-part.
Jan 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 181 additions & 32 deletions Maiskolben_TFT/Maiskolben_TFT.ino
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
*/
//#define USE_TFT_RESET

/* show splash screen only a short time; to enter options, press power button when turning the maiskolben on. */
#define FAST_BOOT

/* display the input voltage (define to true to show, to false to hide */
#define SHOW_INPUT_VOLTS true

/*
* If red is blue and blue is red change this
* If not sure, leave commented, you will be shown a setup screen
Expand Down Expand Up @@ -42,7 +48,7 @@ volatile uint8_t pwm, threshold_counter;
volatile int16_t cur_t, last_measured;
volatile error_type error = NO_ERROR;
error_type error_old;
int16_t stored[3] = {300, 350, 450}, set_t = TEMP_MIN, set_t_old, cur_t_old, target_t;
int16_t stored[3] = { 300, 350, 450 }, set_t = TEMP_MIN, set_t_old, cur_t_old, target_t, old_target_t;
double pid_val, cur_td, set_td;
uint8_t store_to = 255;
p_source power_source, power_source_old = NO_INIT;
Expand Down Expand Up @@ -72,15 +78,18 @@ TFT_ILI9163C tft = TFT_ILI9163C(TFT_CS, TFT_DC, STBY_NO);
#else
TFT_ILI9163C tft = TFT_ILI9163C(TFT_CS, TFT_DC);
#endif
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
#define GRAY 0x94B2

#define COLOR(r,g,b) ((((r &0xFFu)>>3u)<<11u) + (((g&0xFFu)>>2u)<<5u) + ((b&0xFFu)>>3u))

#define BLACK COLOR(0,0,0)
#define BLUE COLOR(0,0,255)
#define RED COLOR(255,20,20)
#define GREEN COLOR(0, 255, 0)
#define CYAN COLOR(0, 255, 255)
#define MAGENTA COLOR(255, 0, 255)
#define YELLOW COLOR(255, 255, 0)
#define WHITE COLOR(255,255,255)
#define GRAY COLOR(144,148,140)

PID heaterPID(&cur_td, &pid_val, &set_td, kp, ki, kd, DIRECT);

Expand Down Expand Up @@ -222,6 +231,9 @@ void setup(void) {
if (force_menu) optionMenu();
else {
updateRevision();
#ifdef FAST_BOOT
attachInterrupt(digitalPinToInterrupt(SW_STBY), optionMenu, LOW);
#endif
tft.drawBitmap(0, 20, maiskolben, 160, 64, YELLOW);
tft.setCursor(20,86);
tft.setTextColor(YELLOW);
Expand All @@ -234,13 +246,18 @@ void setup(void) {
tft.setCursor(46,120);
tft.print("HW Revision ");
tft.print(revision);

#ifdef FAST_BOOT
//Allow Options to be set at startup
delay(200);
#else
delay(100);
attachInterrupt(digitalPinToInterrupt(SW_STBY), optionMenu, LOW);
for (int i = 0; i < 10 && !menu_dismissed; i++) {
digitalWrite(HEAT_LED, i % 2);
delay(250);
}
#endif
detachInterrupt(digitalPinToInterrupt(SW_STBY));
}
/*
Expand Down Expand Up @@ -499,7 +516,11 @@ void timer_sw_poll(void) {
cnt_off_press = min(201, cnt_off_press+1);
} else {
if (cnt_off_press > 0 && cnt_off_press <= 100) {
setStandby(!stby);
if (!off) setStandby(!stby);
if (off) {
setStandby(false);
setOff(false);
}
}
cnt_off_press = 0;
}
Expand Down Expand Up @@ -608,6 +629,37 @@ void printTemp(float t) {
tft.print((int)t);
}


const unsigned char* get_battery_symbol(float v_bat, bool charging, uint8_t *red, uint8_t *green, uint8_t *percent_out ) {
uint8_t percent;
if (charging) {
float D_MAX = NUM_CELLS * (MAX_CHARGE_PER_CELL - MIN_VOLTS_PER_CELL);
float delta_v = (v_bat) - (NUM_CELLS * MIN_VOLTS_PER_CELL);
// Ladezustand
percent = min(100,max(0,(delta_v / D_MAX ) * 100.0));
} else {
float D_MAX = NUM_CELLS * (MAX_VOLTS_PER_CELL - MIN_VOLTS_PER_CELL);
float delta_v = (v_bat) - (NUM_CELLS * MIN_VOLTS_PER_CELL);
// Ladezustand
percent = min(100,max(0,(delta_v / D_MAX ) * 100.0));
}

if (red && green) {
*green = ((float) percent) * 2.55;
*red = 255-*green;
}

if (percent_out) {
*percent_out = percent;
}

if(percent >= 90) return battery_100;
if(percent >= 75) return battery_75;
if(percent >= 50) return battery_50;
if(percent >= 25) return battery_25;
return battery_0;
}

void display(void) {
if (force_redraw) tft.fillScreen(BLACK);
int16_t temperature = cur_t; //buffer volatile value
Expand Down Expand Up @@ -638,6 +690,10 @@ void display(void) {
case NO_TIP:
tft.print(F("Error: No tip connected\nTip slipped out?"));
break;
case FAILED_TO_HEAT:
tft.print(F("Error: Heating\nTemp not increasing."));
break;

}
tft.setTextSize(2);
tft.setTextColor(YELLOW, BLACK);
Expand Down Expand Up @@ -669,17 +725,17 @@ void display(void) {
if (stby || stby_layoff) {
old_stby = true;
tft.setTextColor(YELLOW, BLACK);
tft.print(F("STBY "));
tft.print(F("STBY "));
} else {
old_stby = false;
set_t_old = set_t;
tft.setTextColor(WHITE, BLACK);
tft.write(' ');
//tft.write(' ');
printTemp(set_t);
tft.write(247);
tft.write(fahrenheit?'F':'C');
tft.fillTriangle(149, 50, 159, 50, 154, 38, (set_t < TEMP_MAX) ? WHITE : GRAY);
tft.fillTriangle(149, 77, 159, 77, 154, 90, (set_t > TEMP_MIN) ? WHITE : GRAY);
tft.fillTriangle(140, 50, 150, 50, 145, 38, (set_t < TEMP_MAX) ? WHITE : GRAY);
tft.fillTriangle(140, 77, 150, 77, 145, 90, (set_t > TEMP_MIN) ? WHITE : GRAY);
}
}
if (!off) {
Expand Down Expand Up @@ -722,7 +778,7 @@ void display(void) {
if (temperature < TEMP_COLD) {
tft.print(F("COLD "));
} else {
tft.write(' ');
//tft.write(' ');
printTemp(temperature);
tft.write(247);
tft.write(fahrenheit?'F':'C');
Expand All @@ -741,7 +797,7 @@ void display(void) {
tft.setTextColor(YELLOW, BLACK);
tft.setCursor(122,5);
tft.setTextSize(2);
int power = min(15,v)*min(15,v)/4.8*pwm/255;
int power =v*v/4.8*pwm/255;
if (power < 10) tft.write(' ');
tft.print(power);
tft.write('W');
Expand Down Expand Up @@ -776,15 +832,45 @@ void display(void) {
power_source_old = power_source;
}
if (power_source == POWER_CORD) {
/*if (v > v_c3) {
tft.setTextSize(2);
tft.setTextColor(GREEN, BLACK);
tft.setCursor(0,5);
tft.print(v);
uint8_t red = 0;
uint8_t green = 0;
uint8_t percent = 0;
tft.setTextSize(1);
tft.setCursor(30,5);

if (v > (v_c3+1.5)) {
if (v_c3 > 6.0) {
// on wall power, with battery present.
// we print a cyan symbol to indicate that we're (likely) charging, and a green one if completed.
const unsigned char *symbol = get_battery_symbol(v_c3+0.35, true, nullptr, nullptr, &percent);
uint16_t color = tft.Color565(red, green, 0);
if (percent > 98) {
color = GREEN;
tft.setTextColor(GREEN,BLACK);
tft.print("DC: "); tft.print(v, 1); tft.print("V");
tft.fillRect(77, 5, 40, 9, BLACK);
} else {
color = CYAN;
tft.setTextColor(CYAN,BLACK);
tft.print("BAT: "); tft.print(v_c3 + 0.25 ,2); tft.print("V");
tft.fillRect(89, 5, 32, 9, BLACK);
}
tft.drawBitmap(0, 5, symbol, 24, 9 , color, BLACK);
} else {
tft.drawBitmap(0, 5, power_cord, 24, 9 , GREEN, BLACK);
}
} else {
// on battery.
const unsigned char *symbol = get_battery_symbol(v_c3+0.35, false, &red, &green, &percent);
uint16_t color = tft.Color565(red, green, 0);
tft.drawBitmap(0, 5, symbol, 24, 9 , color, BLACK);
tft.setTextColor(WHITE,BLACK);
tft.print("BAT: ");
tft.print(v_c3+0.35, 1);
tft.print("V ");
} else {*/
tft.drawBitmap(0, 5, power_cord, 24, 9, tft.Color565(max(0, min(255, (14.5-v)*112)), max(0, min(255, (v-11)*112)), 0));
//}
tft.print(percent);
tft.print("% ");
}
} else if (power_source == POWER_LIPO || power_source == POWER_CHARGING) {
float volt[] = {v_c1, v_c2-v_c1, v_c3-v_c2};
uint8_t volt_disp[] = {max(1,min(16,(volt[0]-3.0)*14.2)), max(1,min(16,(volt[1]-3.0)*14.2)), max(1,min(16,(volt[2]-3.0)*14.2))};
Expand All @@ -795,11 +881,11 @@ void display(void) {
}
}
for (uint8_t i = 0; i < 3; i++) {
if (volt[i] < 3.20) {
if (volt[i] < MIN_VOLTS_PER_CELL) {
setError(BATTERY_LOW);
tft.fillRect(13, 7+14*i, volt_disp[i], 8, blink?RED:BLACK);
} else {
tft.fillRect(13, 7+14*i, volt_disp[i], 8, tft.Color565(250-min(250, max(0, (volt[i]-3.4)*1000.0)), max(0,min(250, (volt[i]-3.15)*1000.0)), 0));
tft.fillRect(13, 7+14*i, volt_disp[i], 8, tft.Color565(250-min(250, max(0, (volt[i]-MIN_VOLTS_PER_CELL)*1000.0)), max(0,min(250, (volt[i]-MIN_VOLTS_PER_CELL)*1000.0)), 0));
}
tft.fillRect(13+volt_disp[i], 7+14*i, 17-volt_disp[i], 8, BLACK);
}
Expand Down Expand Up @@ -838,12 +924,19 @@ void display(void) {
}

void compute(void) {
static int16_t rising_protection_milestone_temperature = 0;
static int16_t rising_protection_timeout = WATCH_TEMP_PERIOD;
static int16_t rising_rebound_timeout = WATCH_TEMP_REBOUND;
static bool rising_protection_target_reached = false;

#ifndef USE_TFT_RESET
setStandbyLayoff(!digitalRead(STBY_NO)); //do not measure while heater is active, potential is not neccessary == GND
#endif
cur_t = getTemperature();
if (off) {
target_t = 0;
rising_protection_milestone_temperature = 0;
rising_protection_timeout = WATCH_TEMP_PERIOD;
if (cur_t < adc_offset + TEMP_RISE) {
threshold_counter = TEMP_UNDER_THRESHOLD; //reset counter
}
Expand All @@ -856,6 +949,53 @@ void compute(void) {
if (cur_t-last_measured <= -30 && last_measured != 999) {
setError(EXCESSIVE_FALL); //decrease of more than 30 degree is uncommon, short of ring and gnd is possible.
}

// if target_t has been lowered, make sure that we also lower that milestone temperature
if (target_t < rising_protection_milestone_temperature) {
rising_protection_milestone_temperature = target_t;
}

// if target_t has been changed, timeouts needs to be reset.
if(old_target_t != target_t) {
rising_protection_timeout = WATCH_TEMP_PERIOD ;
rising_rebound_timeout = WATCH_TEMP_REBOUND;
old_target_t = target_t;
}

// ensure that the temperature is actually rising when it should.
if(target_t - cur_t > WATCH_TEMP_DEACTIVATE ) {
// temperature is lower than setpoint by WATCH_TEMP_DEACTIVATE °C.
if (rising_protection_target_reached) {
// if previously we've been at target, e.g cleaning the tip might drop the temp significantly in a short time.
// so we need to temporarily suspend the protection and also continue it with a lower milestone.
if(rising_rebound_timeout) { rising_rebound_timeout--; }
else {
// rebound timeout expired, arm protection again.
rising_protection_milestone_temperature = cur_t + WATCH_TEMP_INCREASE;
rising_protection_target_reached = false;
}
} else {
// target was previously not reached, see if next milestone has been reached.
if (cur_t >= rising_protection_milestone_temperature + WATCH_TEMP_INCREASE) {
rising_protection_milestone_temperature = cur_t + WATCH_TEMP_INCREASE; // Yes, raise the bar.
rising_protection_timeout = WATCH_TEMP_PERIOD; // and give a new time window.
} else {
rising_protection_timeout--; // milestone not reached.
}
}
} else {
// we are near the target, time to disarm the protection and rearm the rebound timer.
rising_protection_timeout = WATCH_TEMP_PERIOD;
rising_rebound_timeout = WATCH_TEMP_REBOUND;
rising_protection_target_reached = true;
rising_protection_milestone_temperature = target_t - WATCH_TEMP_DEACTIVATE; // in case temperature has been lowered, follow the target.
}

if(0 == rising_protection_timeout) {
// milestone not reached, E-STOP.
setError(FAILED_TO_HEAT);
}

if (cur_t < adc_offset + TEMP_RISE) {
if (threshold_counter == 0) {
setError(NOT_HEATING); //temperature is not reached in desired time, short of sensor and gnd too?
Expand All @@ -872,10 +1012,19 @@ void compute(void) {
last_measured = cur_t;

heaterPID.Compute();
if (error != NO_ERROR || off)

// Power limitation.
// Tips are rated for 40W, do not exceed that.
// note t-hat we have inherently only 50% PWM, as we have it on 10ms and then wait with pwm off for another 10ms.
// Formula: P = 0.5 * ( U^2 / ( R )) * (pwm)
float pwm_max = 2 * PMAX / ((v*v) / 2.4);
if (pwm_max > pid_val) pwm_max = pid_val;

if (error != NO_ERROR || off) {
pwm = 0;
else
pwm = min(255,pid_val*255);
} else {
pwm = min(255, pwm_max * 255);
}
analogWrite(HEATER_PWM, pwm);
}

Expand Down Expand Up @@ -915,7 +1064,7 @@ void loop(void) {

if (sendNext <= millis()) {
sendNext += 100;
#ifndef TEST_ADC
#ifdef TEST_ADC
Serial.print(stored[0]);
Serial.print(";");
Serial.print(stored[1]);
Expand All @@ -941,8 +1090,8 @@ void loop(void) {
Serial.print(v_c2);
Serial.print(";");
Serial.println(v);
#endif
Serial.flush();
#endif
display();
}
if (Serial.available()) {
Expand Down
Loading