1
0
mirror of https://github.com/ArduinoHannover/Maiskolben.git synced 2025-09-01 17:12:35 +02:00

Version 2.0

This commit is contained in:
Luca Zimmermann
2016-05-30 16:09:47 +02:00
parent 1a792734f2
commit e1d34c9aee
5 changed files with 940 additions and 64 deletions

View File

@@ -0,0 +1,89 @@
import processing.serial.*;
import java.lang.reflect.Array;
import javax.swing.*;
static int gWidth = 400;
static int gHeight = 450;
static int frequency = 10; // receive data every 1/f seconds
Serial Maiskolben;
java.util.LinkedList<Integer> temperature = new java.util.LinkedList<Integer>();
float pidValue;
int setPoint;
int secs;
boolean off, standby, standby_layoff;
int[] stored = new int[3];
PGraphics history;
JToggleButton on = new JToggleButton();
JToggleButton stby = new JToggleButton();
void setup() {
size(50,1);
surface.setResizable(true);
surface.setSize(gWidth+200,gHeight);
Object[] list = Serial.list();
JComboBox serialPort = new JComboBox(list);
Object[] params = {"Select serial Port:",serialPort};
Object[] opts = {"Select","Exit"};
int opt = JOptionPane.showOptionDialog(null, params, "Select Maiskolben", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, opts, opts[0]);
if (opt == JOptionPane.CANCEL_OPTION) System.exit(0);
else {
Maiskolben = new Serial(this, (String)serialPort.getSelectedItem(), 115200);
Maiskolben.bufferUntil(10);
}
history = createGraphics(gWidth,gHeight);
}
void draw() {
if (Maiskolben != null && Maiskolben.available() > 0) {
//println("Received something!");
String line = Maiskolben.readStringUntil(10);
if (line != null) {
String values[] = line.split(";");
if (values.length < 9) println("zu wenig!");
else {
try {
secs = (secs+1)%frequency;
for (int i = 0; i < 3; stored[i] = Integer.parseInt(values[i++]));
off = values[3].equals("1");
standby = values[4].equals("1");
standby_layoff = values[5].equals("1");
setPoint = Integer.parseInt(values[6]);
temperature.add(Integer.parseInt(values[7]));
if (temperature.size() > gWidth) temperature.remove(0);
pidValue = Float.parseFloat(values[8]);
history.beginDraw();
history.background(50);
history.stroke(150);
for (int i = 50; i < gHeight; i+=50) {
history.line(0,i,gWidth,i);
}
for (int i = (temperature.size() == gWidth)?secs:0; i < gWidth; i+=frequency) {
history.line(gWidth-i,0,gWidth-i,gHeight);
}
history.stroke(255);
int lastV = temperature.getFirst();
int x = 0;
for (int temp : temperature) {
history.line(x,gHeight-lastV,++x,gHeight-(lastV = temp));
}
history.endDraw();
} catch (Exception e) {
}
}
println(line);
}
}
background(0);
image(history,0,0);
text(off?"Off":"On",gWidth+10,10);
text(standby?"Standby":"Active",gWidth+10,30);
text(standby_layoff?"In Holder":"",gWidth+10,50);
text("Set Temperature "+setPoint,gWidth+10,70);
if (temperature.size() != 0)
text("Current Temperature "+temperature.getLast(),gWidth+10,90);
}

View File

@@ -1,15 +1,17 @@
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>
#include <PID_v1.h>
#include <EEPROM.h>
#include "TimerOne.h"
#define VERSION 100
#define EEPROM_CHECK 42
//#define HAS_BATTERY
#define STBY_TEMP 150
//SOFTWARE CAN'T MEASURE MORE THAN 422, IF SET TO >= 422 IT'S LIKELY TO KILL YOUR TIP!
//If read 1024 on Analog in, the tip is turned off.
//SOFTWARE CAN'T MEASURE MORE THAN 422 DUE TO RESISTOR CONFIGURATION, IF SET TO >= 422 IT'S LIKELY TO KILL YOUR TIP!
//If read 1024 on Analog in, the tip is turned off
#define MAX_TEMP 400
#define MIN_TEMP 100
@@ -30,10 +32,15 @@
#define SW_UP A5
#define BATTERY_IN A6
#define kp 0.1
#define ki 0.0001
#define kd 0.0
#define TIME_DISP_REFRESH_IN_MS 300
#define TIME_SW_POLL_IN_MS 10
#define DELAY_BEFORE_MEASURE 10
#define DELAY_MAIN_LOOP 10
#define PID_SAMPLE_TIME 10
#define ADC_TO_TEMP_GAIN 0.39
#define ADC_TO_TEMP_OFFSET 23.9
@@ -41,17 +48,21 @@
#define NUM_DIFFS 8 //Dividable by 8
boolean off = true, stby = true, stby_layoff = true, sw_stby_old = false, sw_up_old = false, sw_down_old = false, clear_display = true, store_invalid = true, error = false, menu = false;
volatile boolean off = true, stby = true, stby_layoff = true, sw_stby_old = false, sw_up_old = false, sw_down_old = false, clear_display = true, store_invalid = true, error = false, menu = false;
uint16_t stored[3] = {250, 300, 350}, set_t = 150, cur_t, set_t_old, cur_t_old;
double pid_val, cur_td, set_td;
uint8_t pwm, store_to = 255, contrast = 50;
uint16_t cnt_disp_refresh = TIME_DISP_REFRESH_IN_MS, cnt_sw_poll, cnt_but_press, cnt_off_press, cnt_but_store;
float battery_voltage;
uint16_t last_measured;
int16_t last_diffs[NUM_DIFFS];
uint8_t array_index, array_count;
uint32_t sendNext;
Adafruit_PCD8544 display = Adafruit_PCD8544(10, 9, -1);
PID heaterPID(&cur_td, &pid_val, &set_td, kp, ki, kd, DIRECT);
void setup() {
digitalWrite(HEATER_PWM, LOW);
pinMode(HEATER_PWM, OUTPUT);
@@ -70,9 +81,10 @@ void setup() {
}
//PWM Prescaler = 1024
TCCR2B = TCCR2B & 0xFF;
TCCR2B = TCCR2B & 0b11111000 | 7;
delay(500);
display.begin();
delay(500);
display.clearDisplay();
if (EEPROM.read(0) != EEPROM_CHECK) {
EEPROM.update(0, EEPROM_CHECK);
@@ -86,14 +98,15 @@ void setup() {
set_t = EEPROM.read(8) << 8;
set_t |= EEPROM.read(9);
setContrast(EEPROM.read(10));
delay(500);
Serial.begin(115200);
Serial.println("DIFF SUM;SET TEMPERATURE;IS TEMPERATURE;DUTY CYCLE;VOLTAGE");
//Serial.println("DIFF SUM;SET TEMPERATURE;IS TEMPERATURE;DUTY CYCLE;VOLTAGE");
last_measured = getTemperature();
Timer1.initialize(1000);
Timer1.attachInterrupt(timer_isr);
heaterPID.SetMode(AUTOMATIC);
sendNext = millis();
}
void updateEEPROM() {
@@ -120,6 +133,7 @@ int getTemperature() {
uint16_t adcValue = analogRead(TEMP_SENSE); // read the input
if (adcValue >= 1015) { //Illegal value
analogWrite(HEATER_PWM, 0);
setOff(true);
return 999;
} else {
analogWrite(HEATER_PWM, pwm); //switch heater back to last value
@@ -292,11 +306,18 @@ void timer_disp_refresh() {
disp = true;
}
if (off) {
if (clr) {
display.fillRect(16, 0, 64, 24, WHITE);
display.setCursor(16,0);
display.setTextSize(3);
display.print("OFF");
if (clr || ((cur_t_old != cur_t) && ((cur_t_old == 999) || (cur_t == 999)))) {
if (cur_t == 999) {
display.fillRect(0,0,84,24, WHITE);
display.setCursor(6,4);
display.setTextSize(2);
display.print("NO TIP");
} else {
display.fillRect(0, 0, 84, 24, WHITE);
display.setCursor(16,0);
display.setTextSize(3);
display.print("OFF");
}
disp = true;
}
} else if (set_t_old != set_t || clr) {
@@ -324,6 +345,7 @@ void timer_disp_refresh() {
disp = true;
}
}
#ifdef HAS_BATTERY
if (battery_voltage > 1) {
display.fillRect(display.width()-11,0,10,6,BLACK);
display.drawRect(display.width()-10,1,8,4,WHITE);
@@ -334,6 +356,7 @@ void timer_disp_refresh() {
setOff(true);
}
}
#endif
if (disp)
display.display();
}
@@ -353,6 +376,7 @@ void timer_isr() {
void setError() {
error = true;
clear_display = true;
setOff(true);
}
@@ -368,67 +392,56 @@ void loop() {
} else {
target = set_t;
}
int16_t diff = target-cur_t;
cnt_temp_rise++;
//check the temperature rise is high enough
int16_t delta = cur_t-last_measured;
if (delta <= -20) {
if (!off && delta <= -20 && cur_td != 999) {
setError();
}
if (diff >= 15) {
if (delta < -5 && array_count > 2) {
setError();
} else {
last_diffs[array_index%NUM_DIFFS] = delta;
array_count = min(array_count+1, NUM_DIFFS);
if (array_count >= NUM_DIFFS) {
int16_t delta_t = 0;
for (uint8_t i = 0; i < array_count; i++) {
delta_t += last_diffs[(array_index+NUM_DIFFS-i)%NUM_DIFFS];
}
if (delta_t < NUM_DIFFS/8*2) {
setError();
}
}
array_index++;
}
} else {
array_count = 0;
}
set_td = target;
cur_td = cur_t;
int16_t diff = target-cur_t;
cnt_temp_rise++;
last_measured = cur_t;
/*
if (cnt_temp_rise >= TEMP_RISE_TIME/DELAY_MAIN_LOOP) {
//if risen less then 20deg then it's a sensor failure (tip not plugged in?)
if (diff_old-diff < TEMP_MIN_RISE) {
error = true;
stby = true;
clear_display = true;
} else {
diff_old = diff;
cnt_temp_rise = 0;
}
}
*/
if (error)
heaterPID.Compute();
if (error || off)
pwm = 0;
else
pwm = max(0, min(255, diff*CTRL_GAIN));
pwm = min(255,pid_val*255);
//pwm = max(0, min(255, diff*CTRL_GAIN));
//reset counter if not heating that much
if (pwm < min(TEMP_MIN_RISE*CTRL_GAIN, 200)) cnt_temp_rise = 0;
analogWrite(HEATER_PWM, pwm);
digitalWrite(HEAT_LED, cur_t <= target && (cur_t < target-5 || (millis()/1000)%2));
digitalWrite(HEAT_LED, cur_t+5 < target || (abs((int16_t)cur_t-(int16_t)target) <= 5 && (millis()/(stby?1000:500))%2));
battery_voltage = (analogRead(BATTERY_IN)*3*5/1024.0);
Serial.print(delta);
Serial.print(";");
Serial.print(set_t);
Serial.print(";");
Serial.print(cur_t);
Serial.print(";");
Serial.print(pwm);
Serial.print(";");
Serial.print(battery_voltage);
Serial.println("V");
Serial.flush();
if (sendNext <= millis()) {
sendNext += 100;
Serial.print(stored[0]);
Serial.print(";");
Serial.print(stored[1]);
Serial.print(";");
Serial.print(stored[2]);
Serial.print(";");
Serial.print(off?1:0);
Serial.print(";");
Serial.print(stby?1:0);
Serial.print(";");
Serial.print(stby_layoff?1:0);
Serial.print(";");
Serial.print(set_t);
Serial.print(";");
Serial.print(cur_t);
Serial.print(";");
Serial.print(pid_val);
Serial.print(";");
Serial.print(battery_voltage);
Serial.print(";");
Serial.print(battery_voltage);
Serial.print(";");
Serial.println(battery_voltage);
Serial.flush();
}
delay(DELAY_MAIN_LOOP);
}

View File

@@ -0,0 +1,505 @@
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <PID_v1.h>
#include <EEPROM.h>
#include "TimerOne.h"
#include "definitions.h"
volatile boolean off = true, stby = true, stby_layoff = true, sw_stby_old = false, sw_up_old = false, sw_down_old = false, clear_display = true, store_invalid = true, menu = false;
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] = {250, 300, 350}, set_t = 150, set_t_old, cur_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;
boolean blink;
uint16_t cnt_measure_voltage, cnt_compute, cnt_sw_poll, cnt_but_press, cnt_off_press, cnt_but_store;
float v_c1, v_c2, v_c3;
uint8_t array_index, array_count;
uint32_t sendNext;
uint32_t last_temperature_drop;
boolean wasOff = true;
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, 0);
#define ST7735_GRAY 0x94B2
PID heaterPID(&cur_td, &pid_val, &set_td, kp, ki, kd, DIRECT);
void setup() {
digitalWrite(HEATER_PWM, LOW);
pinMode(HEATER_PWM, OUTPUT);
pinMode(HEAT_LED, OUTPUT);
pinMode(TEMP_SENSE, INPUT);
pinMode(SW_T1, INPUT_PULLUP);
pinMode(SW_T2, INPUT_PULLUP);
pinMode(SW_T3, INPUT_PULLUP);
pinMode(SW_UP, INPUT_PULLUP);
pinMode(SW_DOWN, INPUT_PULLUP);
pinMode(STBY_NO, INPUT_PULLUP);
pinMode(SW_STBY, INPUT_PULLUP);
delay(100);
tft.initR(INITR_BLACKTAB);
tft.fillScreen(ST7735_BLACK);
tft.setRotation(1);
tft.drawBitmap(0, 20, maiskolben, 160, 64, ST7735_YELLOW);
tft.setCursor(20,86);
tft.setTextColor(ST7735_YELLOW);
tft.setTextSize(2);
tft.print("Maiskolben");
tft.setCursor(50,110);
tft.setTextSize(1);
tft.print("Version ");
tft.print(VERSION);
//PWM Prescaler = 1024
TCCR2B = TCCR2B & (0b11111000 | 7);
if (EEPROM.read(0) != EEPROM_CHECK) {
EEPROM.update(0, EEPROM_CHECK);
updateEEPROM();
}
stby = EEPROM.read(1);
for (uint8_t i = 0; i < 3; i++) {
stored[i] = EEPROM.read(2+i*2) << 8;
stored[i] |= EEPROM.read(3+i*2);
}
set_t = EEPROM.read(8) << 8;
set_t |= EEPROM.read(9);
for (uint8_t i = 0; i < 50; i++)
measureVoltage(); //measure average 50 times to get realistic results
Serial.begin(115200);
//Serial.println("DIFF SUM;SET TEMPERATURE;IS TEMPERATURE;DUTY CYCLE;VOLTAGE");
delay(3000);
tft.fillScreen(ST7735_BLACK);
last_measured = getTemperature();
Timer1.initialize(1000);
Timer1.attachInterrupt(timer_isr);
heaterPID.SetMode(AUTOMATIC);
sendNext = millis();
}
void updateEEPROM() {
EEPROM.update(1, stby);
for (uint8_t i = 0; i < 3; i++) {
EEPROM.update(2+i*2, stored[i] >> 8);
EEPROM.update(3+i*2, stored[i] & 0xFF);
}
EEPROM.update(8, set_t >> 8);
EEPROM.update(9, set_t & 0xFF);
}
int getTemperature() {
analogRead(TEMP_SENSE);//Switch ADC MUX
uint16_t adc = median(TEMP_SENSE);
if (adc >= 1020) { //Illegal value, tip not plugged in
analogWrite(HEATER_PWM, 0);
if (!off)
setError(NO_TIP);
return 999;
} else {
analogWrite(HEATER_PWM, pwm); //switch heater back to last value
}
return round(((float) adc)*ADC_TO_TEMP_GAIN+ADC_TO_TEMP_OFFSET); //apply linear conversion to actual temperature
}
void measureVoltage() {
analogRead(BAT_C1); //Switch analog MUX before measuring
v_c1 = v_c1*.9+(analogRead(BAT_C1)*5/1024.0)*.1;
analogRead(BAT_C2);
v_c2 = v_c2*.9+(analogRead(BAT_C2)*5/512.0)*.1;
analogRead(BAT_C3);
v_c3 = v_c3*.9+(analogRead(BAT_C3)*(5.0*1510.0)/(510.0*1024.0))*.1;
}
uint16_t median(uint8_t analogIn) {
uint16_t adcValue[3];
for (uint8_t i = 0; i < 3; i++) {
adcValue[i] = analogRead(analogIn); // read the input 3 times
}
uint16_t tmp;
if (adcValue[0] > adcValue[1]) {
tmp = adcValue[0];
adcValue[0] = adcValue[1];
adcValue[1] = tmp;
}
if (adcValue[1] > adcValue[2]) {
tmp = adcValue[1];
adcValue[1] = adcValue[2];
adcValue[2] = tmp;
}
if (adcValue[0] > adcValue[1]) {
tmp = adcValue[0];
adcValue[0] = adcValue[1];
adcValue[1] = tmp;
}
return adcValue[1];
}
void timer_sw_poll() {
stby_layoff = !digitalRead(STBY_NO);
if (!digitalRead(SW_STBY)) {
if (cnt_off_press == 100) {
setOff(!off);
}
cnt_off_press = min(101, cnt_off_press+1);
} else {
if (cnt_off_press > 0 && cnt_off_press != 101) {
setStandby(!stby);
}
cnt_off_press = 0;
}
boolean t1 = !digitalRead(SW_T1);
boolean t2 = !digitalRead(SW_T2);
boolean t3 = !digitalRead(SW_T3);
//simultanious push of multiple buttons
if (t1 + t2 + t3 > 1) {
store_to = 255;
store_invalid = true;
} else if (error != NO_ERROR) {
if (!(t1 | t2 | t3)) {
store_invalid = false;
} else if (!store_invalid && t3) {
error = NO_ERROR; //dismiss
set_t_old = 0; //refresh set_t display
store_invalid = true; //wait for release
}
} else {
//all buttons released
if (!(t1 | t2 | t3)) {
if (store_to != 255) {
if (cnt_but_store <= 100) {
set_t = stored[store_to];
setStandby(false);
updateEEPROM();
}
}
store_to = 255;
store_invalid = false;
cnt_but_store = 0;
} else
//one button pressed
if (!store_invalid) {
store_to = t2 + 2*t3;
if (cnt_but_store > 100) {
if (set_t != stored[store_to] && !stby) {
stored[store_to] = set_t;
cnt_but_store = 100;
updateEEPROM();
}
}
cnt_but_store++;
}
}
boolean sw_up = !digitalRead(SW_UP);
boolean sw_down = !digitalRead(SW_DOWN);
boolean sw_changed = (sw_up != sw_up_old) || (sw_down !=sw_down_old);
sw_up_old = sw_up;
sw_down_old = sw_down;
if((sw_up && sw_down) || !(sw_up || sw_down)) {
cnt_but_press = 0;
return;
}
if(sw_up || sw_down) {
cnt_but_press++;
if((cnt_but_press >= 100) || sw_changed) {
setStandby(false);
if(sw_up && set_t < MAX_TEMP) set_t++;
else if (sw_down && set_t > MIN_TEMP) set_t--;
if(!sw_changed) cnt_but_press = 97;
updateEEPROM();
}
}
}
void setStandby(boolean state) {
if (state == stby) return;
stby = state;
last_measured = cur_t;
last_temperature_drop = millis();
EEPROM.update(1, stby);
}
void setOff(boolean state) {
if (state == off) return;
if (!state)
analogWrite(HEATER_PWM, 0);
if (power_source == POWER_USB && !state) {
state = true; //don't switch on, if powered via USB
setError(USB_ONLY);
}
off = state;
wasOff = true;
last_measured = cur_t;
}
void display() {
int16_t temperature = cur_t; //buffer volatile value
boolean yell = stby || (stby_layoff && blink);
tft.drawCircle(20,63,8, off?ST7735_RED:yell?ST7735_YELLOW:ST7735_GREEN);
tft.drawCircle(20,63,7,off?ST7735_RED:yell?ST7735_YELLOW:ST7735_GREEN);
tft.fillRect(19,55,3,3,ST7735_BLACK);
tft.drawFastVLine(20,53,10, off?ST7735_RED:yell?ST7735_YELLOW:ST7735_GREEN);
if (error != NO_ERROR) {
if (error != error_old) {
error_old = error;
tft.setTextSize(1);
tft.setTextColor(ST7735_RED, ST7735_BLACK);
tft.setCursor(0,96);
switch (error) {
case EXCESSIVE_FALL:
tft.print("Error: Temperature dropped\nTip slipped out?");
break;
case NOT_HEATING:
tft.print("Error: Not heating\nWeak power source or short");
break;
case BATTERY_LOW:
tft.print("Error: Battery low\nReplace or charge");
break;
case USB_ONLY:
tft.print("Error: Power too low\nConnect power >5V");
break;
case NO_TIP:
tft.print("Error: No tip connected\nTip slipped out?");
break;
}
tft.setTextSize(2);
tft.setTextColor(ST7735_YELLOW, ST7735_BLACK);
tft.setCursor(10,112);
tft.print(" OK ");
tft.setTextColor(ST7735_RED, ST7735_BLACK);
tft.setCursor(54,26);
tft.setTextSize(3);
tft.print("ERR");
}
} else {
if (error != error_old) {
tft.fillRect(0, 96, 160, 16, ST7735_BLACK);
error_old = NO_ERROR;
}
tft.setTextSize(2);
tft.setCursor(15,112);
tft.setTextColor(ST7735_WHITE, ST7735_BLACK);
tft.print(stored[0]);
tft.write(' ');
tft.print(stored[1]);
tft.write(' ');
tft.print(stored[2]);
if (set_t_old != set_t) {
set_t_old = set_t;
tft.setTextColor(ST7735_WHITE, ST7735_BLACK);
tft.setCursor(54,26);
tft.setTextSize(3);
tft.print(set_t);
tft.fillTriangle(149, 50, 159, 50, 154, 38, (set_t < MAX_TEMP) ? ST7735_WHITE : ST7735_GRAY);
tft.fillTriangle(149, 77, 159, 77, 154, 90, (set_t > MIN_TEMP) ? ST7735_WHITE : ST7735_GRAY);
}
if (!off && !stby) {
uint16_t tout = min(max(0,(last_temperature_drop + STANDBY_TIMEOUT - (millis()+500)/1000)), STANDBY_TIMEOUT);
tft.setTextColor(ST7735_YELLOW, ST7735_BLACK);
tft.setTextSize(2);
tft.setCursor(58,78);
tft.print(tout/60);
tft.write(':');
if (tout%60 < 10) tft.write('0');
tft.print(tout%60);
} else if (temperature != 999) {
tft.fillRect(54, 78, 60, 20, ST7735_BLACK);
}
}
if (cur_t_old != temperature) {
tft.setCursor(54,52);
tft.setTextSize(3);
if (temperature == 999) {
tft.setTextColor(ST7735_RED, ST7735_BLACK);
tft.print("ERR");
tft.setCursor(44,76);
tft.setTextSize(2);
tft.print("NO TIP");
} else {
if (cur_t_old == 999) {
tft.fillRect(44,76,72,16,ST7735_BLACK);
}
tft.setTextColor(off ? temperature < 50 ? ST7735_GREEN : ST7735_RED : tft.Color565(min(10,abs(temperature-target_t))*25, 250 - min(10,max(0,(abs(temperature-target_t)-10)))*25, 0), ST7735_BLACK);
if (temperature < 100) tft.write(' ');
tft.print(temperature);
}
if (temperature < cur_t_old)
tft.drawFastHLine((int)(temperature/2.6), 0, 160-(int)(temperature/2.6), ST7735_BLACK);
else if (cur_t != 999) {
for (int16_t i = 0; i < temperature/2.6; i++) {
tft.drawPixel(i, 0, tft.Color565(min(255, max(0, i*5)), min(255, max(0, 400-i*2.5)), 0));
}
}
cur_t_old = temperature;
}
if (v_c3 > 1.0) {
if (v_c3 < 5.0) {
power_source = POWER_USB;
} else if (v_c2 < 1.0) {
power_source = POWER_CORD;
} else {
power_source = POWER_LIPO;
}
if (power_source != power_source_old) {
tft.fillRect(0, 5, 128, 20, ST7735_BLACK);
tft.fillRect(11, 25, 21, 20, ST7735_BLACK);
switch (power_source) {
case POWER_LIPO:
for (uint8_t i = 0; i < 3; i++) {
tft.fillRect(11, 5+i*14, 20, 12, ST7735_WHITE);
tft.fillRect(12, 6+i*14, 18, 10, ST7735_BLACK);
tft.drawFastVLine(31,8+i*14,6,ST7735_WHITE);
}
break;
case POWER_USB:
tft.setTextSize(1);
tft.setTextColor(ST7735_RED, ST7735_BLACK);
tft.setCursor(0,5);
tft.print("USB power only\nConnect power supply.");
if (!off) setError(USB_ONLY);
break;
}
power_source_old = power_source;
}
if (power_source == POWER_CORD) {
tft.setTextSize(2);
tft.setTextColor(ST7735_GREEN, ST7735_BLACK);
tft.setCursor(0,5);
tft.print(v_c3);
tft.print("V ");
} else if (power_source == POWER_LIPO) {
float volt[] = {v_c1, v_c2-v_c1, v_c3-v_c2};
for (uint8_t i = 0; i < 3; i++) {
if (volt[i] < 3.20) {
setError(BATTERY_LOW);
tft.fillRect(13, 7+14*i, max(1,min(16,(volt[i]-3.0)*14.2)), 8, blink?ST7735_RED:ST7735_BLACK);
} else {
tft.fillRect(13, 7+14*i, max(1,min(16,(volt[i]-3.0)*14.2)), 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+max(1,min(16,(volt[i]-3.0)*14.2)), 7+14*i, 17-max(1,min(16,(volt[i]-3.0)*14.2)), 8, ST7735_BLACK);
}
}
}
if (target_t-cur_t > 0.715*exp(0.0077*target_t)) {
//if (cur_t / (double)target_t < STANDBY_TEMPERATURE_DROP) {
if (stby && !wasOff) {
setStandby(false);
} else {
last_temperature_drop = millis()/1000;
}
} else if (wasOff) {
wasOff = false;
}
if (!off && !stby && millis()/1000 > (last_temperature_drop + STANDBY_TIMEOUT)) {
setStandby(true);
}
blink = !blink;
}
void compute() {
cur_t = getTemperature();
if (off) {
target_t = 0;
if (cur_t < TEMP_THRESHOLD) {
threshold_counter = TEMP_UNDER_THRESHOLD; //reset counter
}
} else {
if (stby_layoff || stby) {
target_t = STBY_TEMP;
} else {
target_t = set_t;
}
if (cur_t-last_measured <= -30 && last_measured != 999) {
setError(EXCESSIVE_FALL); //decrease of more than 30 degree is not realistic, short of ring and gnd is common.
}
if (cur_t < TEMP_THRESHOLD) {
if (threshold_counter == 0) {
setError(NOT_HEATING); //temperature is not reached in desired time, short of sensor and gnd too?
} else {
threshold_counter--;
}
} else {
threshold_counter = THRES_MAX_DECEED; //reset counter to a smaller value to allow small oscillation of temperature
}
}
set_td = target_t;
cur_td = cur_t;
last_measured = cur_t;
heaterPID.Compute();
if (error != NO_ERROR || off)
pwm = 0;
else
pwm = min(255,pid_val*255);
analogWrite(HEATER_PWM, pwm);
}
void timer_isr() {
if (cnt_compute >= TIME_COMPUTE_IN_MS+DELAY_BEFORE_MEASURE) {
compute();
cnt_compute=0;
} else if(cnt_compute >= TIME_COMPUTE_IN_MS) {
analogWrite(HEATER_PWM, 0); //switch off heater to let the low pass settle
}
cnt_compute++;
if(cnt_sw_poll >= TIME_SW_POLL_IN_MS){
timer_sw_poll();
cnt_sw_poll=0;
}
cnt_sw_poll++;
if(cnt_measure_voltage >= TIME_MEASURE_VOLTAGE_IN_MS) {
measureVoltage();
cnt_measure_voltage=0;
}
cnt_measure_voltage++;
}
void setError(error_type e) {
error = e;
setOff(true);
}
void loop() {
analogWrite(HEAT_LED, pwm);
//Switch to following if the oscillation of the led bothers you
//digitalWrite(HEAT_LED, cur_t+5 < target || (abs((int16_t)cur_t-(int16_t)target) <= 5 && (millis()/(stby?1000:500))%2));
if (sendNext <= millis()) {
sendNext += 100;
Serial.print(stored[0]);
Serial.print(";");
Serial.print(stored[1]);
Serial.print(";");
Serial.print(stored[2]);
Serial.print(";");
Serial.print(off?1:0);
Serial.print(";");
Serial.print(stby?1:0);
Serial.print(";");
Serial.print(stby_layoff?1:0);
Serial.print(";");
Serial.print(set_t);
Serial.print(";");
Serial.print(cur_t);
Serial.print(";");
Serial.print(pid_val);
Serial.print(";");
Serial.print(v_c2>1.0?v_c1:0.0);
Serial.print(";");
Serial.print(v_c2);
Serial.print(";");
Serial.println(v_c3);
Serial.flush();
display();
}
delay(DELAY_MAIN_LOOP);
}

View File

@@ -0,0 +1,149 @@
#define VERSION 103
#define EEPROM_CHECK 42
//#define HAS_BATTERY
#define STBY_TEMP 150
//SOFTWARE CAN'T MEASURE MORE THAN 422 DUE TO RESISTOR CONFIGURATION, IF SET TO >= 422 IT'S LIKELY TO KILL YOUR TIP!
//If read 1024 on Analog in, the tip is turned off
#define MAX_TEMP 400
#define MIN_TEMP 100
#define STANDBY_TIMEOUT 240 // seconds without any significant temperature drop, if exceeded it will standby
#define TEMP_THRESHOLD 50 //threshold voltage, that must be exceeded in given time:
#define TEMP_UNDER_THRESHOLD 150 //*10ms
#define THRES_MAX_DECEED 2 //max times the threshold temperature may be undercut by the current temperature
//Temperature in degree to rise at least in given time
#define TEMP_MIN_RISE 10
//Time in that the temperature must rise by the set temperature
#define TEMP_RISE_TIME 1000
#define SW_STBY 2
#define HEATER_PWM 3
#define SW_DOWN 4
#define HEAT_LED 5
#define SW_UP 6
#define SW_T3 7
#define SW_T2 8
#define SW_T1 9
#define TFT_CS 10
#define TEMP_SENSE A0
#define STBY_NO A1
#define BAT_C3 A2
#define BAT_C2 A3
#define BAT_C1 A4
#define TFT_DC A5
#define kp 0.03
#define ki 0.00001
#define kd 0.0
#define TIME_COMPUTE_IN_MS 10
#define TIME_MEASURE_VOLTAGE_IN_MS 200
#define TIME_SW_POLL_IN_MS 10
#define DELAY_BEFORE_MEASURE 10
#define DELAY_MAIN_LOOP 10
#define PID_SAMPLE_TIME 10
#define ADC_TO_TEMP_GAIN 0.39
#define ADC_TO_TEMP_OFFSET 23.9
#define CTRL_GAIN 10
typedef enum POWER_SOURCE {
NO_INIT,
POWER_USB,
POWER_CORD,
POWER_LIPO
} p_source;
typedef enum ERROR_TYPE {
NO_ERROR,
EXCESSIVE_FALL,
NOT_HEATING,
NO_TIP,
BATTERY_LOW,
USB_ONLY
} error_type;
const unsigned char maiskolben [] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, 0xe0, 0x60, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xcf, 0xff, 0xa0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb, 0xdc, 0xf7,
0x18, 0x47, 0x89, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8,
0x26, 0x78, 0xc6, 0x33, 0x9c, 0x7f, 0x0c, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0x08, 0x23, 0x49, 0xe6, 0x1b, 0xff, 0xf8, 0x39, 0xd0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0xc5, 0x31, 0xcf, 0xe7, 0x3f, 0xfe, 0x14, 0x21, 0x08,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x47, 0xf9, 0xff, 0xff, 0xcc,
0x40, 0x08, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x4e,
0x0f, 0xc1, 0x08, 0x04, 0x63, 0x00, 0x21, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x47, 0xe0, 0x0c, 0x01, 0x08, 0x07, 0xaf, 0x80, 0x60, 0x84, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x02, 0x0c, 0x01, 0x8c, 0xc7, 0xa4, 0x80, 0xe0, 0xf4,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0x0c, 0x60, 0x9f, 0xc7,
0xf0, 0xf3, 0x33, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x83,
0x0e, 0xe0, 0x77, 0x69, 0x9a, 0xf7, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0c, 0xfd, 0xb7, 0xb6, 0x67, 0x79, 0x9f, 0xfe, 0xee, 0x12, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0xfc, 0xf3, 0xbe, 0xff, 0xfb, 0xff, 0xf8, 0x46, 0x12,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xcd, 0xcf, 0xbc, 0x63, 0x18, 0x6e,
0x21, 0x80, 0x42, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x8c, 0xfe,
0x08, 0x42, 0x08, 0x44, 0x21, 0x48, 0x42, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x06, 0x84, 0xe2, 0x1e, 0x4a, 0x18, 0x44, 0xe1, 0xc0, 0x63, 0x0f, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x63, 0x3c, 0x7b, 0x18, 0x6e, 0x60, 0x80, 0x31, 0x07,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x65, 0xec, 0x71, 0x08, 0x22,
0x10, 0x00, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x39,
0x84, 0x21, 0x8c, 0x33, 0x10, 0x00, 0x20, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x08, 0x38, 0x84, 0x00, 0x08, 0x32, 0x10, 0x02, 0x20, 0x06, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x58, 0x10, 0x84, 0x00, 0x08, 0x20, 0x10, 0x03, 0xf1, 0x9f,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x70, 0x44, 0x60, 0x08, 0x23,
0x30, 0xc4, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0x00,
0xc7, 0x1f, 0xff, 0xff, 0x1f, 0x84, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x08, 0xc0, 0x7f, 0x82, 0x10, 0x8c, 0x03, 0x08, 0x84, 0x00, 0x87, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x78, 0x82, 0x10, 0x88, 0x01, 0x08, 0x84, 0x10, 0x87,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7c, 0x28, 0x82, 0x18, 0xc8, 0x21,
0x08, 0x82, 0x10, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfc, 0x28,
0x42, 0x08, 0xc0, 0x21, 0x8c, 0x42, 0x10, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf0, 0x00,
0x00, 0x3f, 0xfc, 0x18, 0x42, 0x08, 0xc4, 0x11, 0x8c, 0x42, 0x10, 0x83, 0x80, 0x00, 0x00, 0x00,
0x7f, 0xff, 0xfe, 0x89, 0xfc, 0xff, 0x94, 0x0c, 0x42, 0x08, 0x44, 0x30, 0x88, 0x42, 0x10, 0x03,
0x18, 0x00, 0x00, 0x00, 0x06, 0xff, 0xff, 0xff, 0xff, 0x00, 0x04, 0x0c, 0x62, 0x00, 0x04, 0x00,
0x88, 0x42, 0x10, 0x03, 0xf2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x04, 0x1c,
0x20, 0x00, 0x88, 0x00, 0x88, 0x42, 0x10, 0xc7, 0x06, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x07, 0x80, 0x1c, 0x23, 0x10, 0xc8, 0x00, 0x8c, 0x43, 0x30, 0xf9, 0x01, 0xf0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x37, 0x9d, 0xbc, 0x30, 0xfe, 0xe7, 0x6f, 0x11,
0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xce, 0x04, 0x4c, 0xe7, 0x9e, 0x2f,
0x31, 0x8e, 0x64, 0x31, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xce, 0x23,
0x8f, 0xe7, 0x1d, 0xc7, 0x31, 0x8c, 0x64, 0x11, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x02, 0x46, 0x30, 0xcf, 0xf6, 0x1c, 0xe6, 0x39, 0xcc, 0x64, 0x11, 0x00, 0x06, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x44, 0x38, 0xcc, 0x76, 0x18, 0xc2, 0x3d, 0x2c, 0x6c, 0x13,
0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x44, 0x38, 0xcc, 0x66, 0x10, 0x86,
0x31, 0x08, 0x48, 0x13, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x44, 0x38,
0xc0, 0x44, 0x10, 0x84, 0x21, 0x00, 0x48, 0x12, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x40, 0x30, 0x80, 0x44, 0x01, 0x04, 0x20, 0x00, 0x48, 0x1e, 0x00, 0x01, 0x80, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x84, 0x44, 0x21, 0x04, 0x20, 0x0c, 0xfc, 0x26,
0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x86, 0xcc, 0x21, 0x04,
0x23, 0x0f, 0x8e, 0xc2, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x00,
0x8e, 0xff, 0xff, 0x0f, 0xfb, 0xf9, 0x84, 0x46, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x71, 0xf0, 0x6c, 0x31, 0xc4, 0x40, 0x19, 0x00, 0x46, 0x00, 0x00, 0x30, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xb8, 0x30, 0x4c, 0x21, 0x04, 0x40, 0x11, 0x00, 0x46,
0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x20, 0x20, 0x48, 0x20, 0x00,
0x02, 0x11, 0x08, 0x44, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf2, 0x28,
0x20, 0x08, 0x02, 0x00, 0x80, 0x11, 0x08, 0x4c, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x22, 0x18, 0x20, 0x00, 0x02, 0x00, 0x80, 0x13, 0x1c, 0xd8, 0x00, 0x00, 0x03, 0x40,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x18, 0x20, 0x00, 0x42, 0x08, 0x84, 0x1d, 0xe6, 0x38,
0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x18, 0x21, 0x1c, 0x7e, 0x0e,
0x7f, 0x90, 0x44, 0x38, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x7c,
0x7c, 0xcc, 0x31, 0x88, 0x42, 0x10, 0x44, 0x38, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x1e, 0x42, 0x10, 0x08, 0x21, 0x08, 0x42, 0x10, 0x84, 0x78, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x40, 0x00, 0x08, 0x20, 0x08, 0x62, 0x10, 0x8c, 0x70,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x08, 0x00, 0x08,
0xc6, 0x21, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xd8,
0x60, 0x08, 0x44, 0x3f, 0xff, 0xe7, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

122
README.md
View File

@@ -1,2 +1,122 @@
# Maiskolben
SMD Solderingstation
## SMD Lötstation, kompatibel zu Weller-Lötspitzen
Weller baut seit Jahren *die* Lötstationen für den professionellen Bereich.
Die WMRP/WXRP-Stationen heben sich durch ihre aktiven Lötspitzen von anderen Produkten ab.
Mit einer außergewöhnlich kurzen Aufheizzeit im Sekundenbereich, einer direkt in der Spitze integrierten Temperaturmessung und dementsprechend schnelle Nachregulierung in Sekundenbruchteilen sind die Spitzen in der Industrie äußerst beliebt.
Die Spitzen selbst sind für 40€ im Handel erhältlich, die original Weller-Station kostet an die 600€.
Für den einfachen Maker oder Hobbyist auf keinen Fall ein Produkt, das beim Onlineshopping ohne weitere Überlegungen in den Warenkorb wandert.
Der *Maiskolben* hingegen ist eine erschwingliche Lösung für lediglich 40€, mit der die Spitzen ebenso angesteuert werden können.
* Betrieb mit 12V Netzteil oder 3s LiPo (Modellbau-Akku)
* Spannungsüberwachung (bei Akku für alle 3 Zellen und automatische Abschaltung bei zu geringer Ladung)
* Einfaches Wechseln von Spitzen mittels Klinken-Buchse/-Kabel
* 3 individuelle Temperatur-Presets
* Temperaturbereich von 100-400°C
* Gut ablesbares Farbdisplay mit allen nötigen Informationen
* Auto-Standby (entweder sensor- oder zeitgesteuert auf 150°C)
* Auto-Wakeup (entweder sensorgesteuert oder durch berühren von Lötschwamm, Messingwolle oder Lötstelle)
* USB-Schnittstelle zur Überwachung oder Steuerung mittels PC
* Potentialausgleich-/ESD-Buchse (über 1MΩ an GND)
* Optisches Feedback der Heizleistung durch LED
* Open-Source-Software auf Basis von Arduino
* Software-Update kann per USB eingespielt werden
## Stromzufuhr
Die Stromzufuhr wird wahlweise über die Hohlstecker-Buchse (10-18V) *ODER* die JST-XH4 (11,1V) Buchse angeschlossen.
Niemals beide gleichzeitig anschließen!
Der Lötkolben (Weller RT-XX, z.B. RT-1 für feine Lötarbeiten) wird über die Klinkenbuchse mit einer Verlängerung angesteckt.
## Display
Das Display zeigt mittig in weißer Schrift die Soll-Temperatur, darunter, sofern eine Spitze eingesteckt ist, die aktuelle Ist-Temperatur.
Bei eingeschaltetem Lötkolben erscheint zusätzlich noch die Zeit, nach der sich der Lötkolben automatisch in den Standby versetzt.
Der Balken am oberen Bildschirmrand spiegelt die Ist-Temperatur wieder.
Oben Links wird die Spannung bzw. die Ladezustände der einzelnen Zellen angezeigt.
Links wird durch das Power-Symbol der aktuelle Zustand (aus = rot, Standby = gelb, an = grün) signalisiert.
Am unteren Rand sind die drei Presets zu sehen.
## Bedienung
Nach dem Anstecken von Netzteil und Lötspitze kann die Station durch halten der Power-Taste (unten rechts) eingeschaltet werden.
Basierend auf der letzten Einstellung wird die angezeigte Soll- (Power-Symbol grün) oder die Standby-Temperatur (Symbol gelb) angefahren.
Mittels der Tasten rechts vom Display (Pfeile) lässt sich die Soll-Temperatur nach oben und unten korrigieren.
Sollte einer der Pfeile auf dem Display ausgegraut sein, so lässt sich über diese Temperatur hinaus keine weitere Änderung mehr vornehmen (100 ≥ T ≥ 400).
Mittels der Preset-Tasten unter dem Bildschirm lassen sich voreingestellte Temperaturen als Soll-Temperatur setzen und die Station gleichzeitig aus dem Standby aufwecken.
Durch gedrückthalten einer der drei Tasten wird der dort gespeicherte Wert durch die aktuelle Soll-Temperatur überschrieben.
Wird die Power-Taste nur kurz gedrückt, so wird der Standby-Zustand umgeschaltet.
## Fehlermeldungen
#### USB power only/Connect power supply; Power too low/Connect power >5V
Die Lötstation wird entweder nur über USB oder eine Spannungsquelle mit knapp 5V versorgt.
Zum Betrieb wird ein adäquates Netzteil oder Akku benötigt (≥40W)
#### NO TIP; No tip connected/Tip slipped out?
Es wurde keine Spitze eingesteckt oder nicht erkannt.
Es kann sein, dass die Spitze defekt ist oder nicht richtig in der Buchse steckt.
#### Temperature dropped/Tip slipped out?
Es wurde ein zu großer Temperaturabfall bemerkt und die Wahrscheinlichkeit besteht, dass die Spitze leicht aus der Buchse herausgerutscht ist.
Durch das Herausrutschen kann eine Verbindung zwischen Masse und dem Sensor hergestellt werden, sodass die erkannte Temperatur beim Offset von 24°C liegt.
#### Not heating/Weak power source or short
Entweder kann die Stromversorgung nicht genug Leistung bringen, um die Spitze in einem angemessenen Zeitraum aufzuheizen oder es liegt wie bei obigem Fehler ein Kurzschluss von Masse und Sensor vor.
#### Battery low/Replace or charge
Der Akku hat auf mindestens einer Zelle eine zu geringe Spannung und sollte ersetzt oder geladen werden.
Fehlermeldungen müssen durch OK bestätigt werden.
Die Fehlermeldungen können nicht unterdrückt werden, sollte allerdings mehrfach hintereinander eine Meldung bestätigt und die Station wieder eingeschaltet werden, kann dies zu übermäßigem Erwärmen der Lötspitze führen.
## Software-Update
Um die aktuellste Software auf den Maiskolbe zu übertragen, muss zunächst die [Arduino-IDE](https://www.arduino.cc/en/Main/Software) heruntergeladen werden.
In diesem Repository befinden sich zwei Programme, zum einen Maiskolben_LCD für die Hardware-Revision 1.0 und Maiskolben_TFT für Hardware-Revisionen ab 1.5.
Die .ino Datei kann mit Arduino geöffnet werden.
Der Serielle-Port muss nach Herstellen einer Verbindung zwischen Maiskolben und PC unter _Werkzeuge_ > _Port_ ausgewählt werden.
Unter umständen muss zuvor noch der [Treiber](http://www.wch.cn/download/CH341SER_ZIP.html) für den USB-Seriell-Wandler installiert werden.
Als _Board_ sollte _Arduino Nano_ mit _Prozessor_: _ATmega328_ ausgewählt werden.
Über _Sketch_ > _Hochladen_ bzw. den Pfeil nach Rechts in der oberen Leiste kann das Programm übertragen werden.
Unter umständen müssen vorher folgende Libraries noch über _Sketch_ > _Bibliothek einbinden_ > _.ZIP Bibliothek hinzufügen..._ eingebunden werden:
* [TimerOne](https://github.com/PaulStoffregen/TimerOne)
* [PID_v1](https://github.com/br3ttb/Arduino-PID-Library/)
* [Adafruit_ST7735](https://github.com/adafruit/Adafruit-ST7735-Library)
* [Adafruit_GFX](https://github.com/adafruit/Adafruit-GFX-Library)
### Changelog
* 2.0 Initiale Version für GitHub (TFT)
* Auto-Standby hinzugefügt
* Parameter angepasst
### Achtung!
Zur eigenen Sicherheit vor einem Software-Update die Lötspitze entfernen.
Zwar wird die Heizspannung beim Übertragen deaktiviert, dennoch kann es vereinzelt zu Fehlfunktionen und dadurch bedingtem unbeschränkten Aufheizen der Spitze kommen.
Modifikationen an der Software sind auf eigene Gefahr des Nutzers.
## Umbauten
Nachfolgend werden Modifikationen beschrieben, die die Funktionen des Maiskolben erweitern.
Für eigens durchgeführte Änderungen kann keine Haftung übernommen werden.
### Ablage-Sensor nachrüsten
Statt des automatischen Standbys nach einer bestimmten Zeit kann auch der Standby durch einen Ablage-Sensor erzwungen werden.
Auf der Platine befinden sich Kontakte mit STBY, 5V und GND.
Um den Standby zu aktivieren muss STBY mit GND verbunden werden, um wieder aufzuwachen muss die Verbindung getrennt werden.
Das Schalten von STBY nach GND kann entweder durch einen Schalter erfolgen, der beim auflegen der Spitze auslöst, alternativ mit einem Hall- oder Reed-Sensor.
Für letzteres muss an dem Ende des Klinken-Kabels, an dem die Spitze eingesteckt wird, ein Magnet angebracht werden, der den Sensor dann auslöst.
### Stromversorgungs-Schalter
Ab Hardware-Revision 1.5 gibt es auf der Platinenunterseite eine Lötbrücke mit der Beschriftung ON/OFF.
Bis einschließlich Revision 1.9 wird beim Durchtrennen dieser Brücke die gesamte Stromversorgung unterbrochen.
Ab Revision 1.11 wird hingegen nur noch die Zuleitung des LiPos getrennt.
An den beiden Lötpunkten können nach dem Durchtrennen der Leiterbahn Kabel zu einem Schalter gelötet werden, mit dem sich die Stromversorgung unterbrechen lässt.