├── .gitattributes ├── .gitignore ├── Bill of Materials ├── Electrical Design └── SolderReflowOven │ ├── .theia │ └── launch.json │ └── SolderReflowOven.ino └── Mechanical Design └── 3D Mesh Files ├── enclosure.3mf └── lid.3mf /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | Electrical Design/tft screen.gsheet 3 | Mechanical Design/3D Mesh Files/connector test.3mf 4 | Mechanical Design/3D Mesh Files/screen test.3mf 5 | Mechanical Design/DXF Files 6 | Electrical Design/gridTouchscreenTest 7 | -------------------------------------------------------------------------------- /Bill of Materials: -------------------------------------------------------------------------------- 1 | Description Link 2 | Arduino Board https://www.digikey.com/en/products/detail/arduino/A000067/2639006?s=N4IgTCBcDaIIwAYCsCC0i4A5UDkAiIAugL5A 3 | Thermocouple Amp https://www.digikey.com/en/products/detail/adafruit-industries-llc/3263/6227009 4 | Thermocouple https://www.digikey.com/en/products/detail/digilent-inc/240-080/5418219 5 | Solid State Relay https://www.digikey.com/en/products/detail/sensata-crydom/D2440/221764 6 | TFT Display With Touchscreen https://www.digikey.com/en/products/detail/adafruit-industries-llc/2050/5761252 7 | NEMA 5-15R Power Receptacle https://www.digikey.com/en/products/detail/qualtek/738W-X2-03/245568 8 | IEC 320-C14 Power Receptacle https://www.digikey.com/en/products/detail/adam-tech/IEC-GS-1-100/9831135 9 | Power Cord https://www.digikey.com/en/products/detail/phihong-usa/AC30UNA/2384475 10 | -------------------------------------------------------------------------------- /Electrical Design/SolderReflowOven/.theia/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | "version": "0.2.0", 5 | "configurations": [ 6 | 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /Electrical Design/SolderReflowOven/SolderReflowOven.ino: -------------------------------------------------------------------------------- 1 | #define THERMO_CS 8 2 | #define SSR_PIN 2 3 | #define TFT_CS 10 4 | #define TFT_DC 9 5 | #define TFT_RST -1 // RST can be set to -1 if you tie it to Arduino's reset 6 | // Note the X and Y pin numbers are opposite from what is printed on the TFT display. This was done to align with the screen rotation. 7 | #define YP A0 // must be an analog pin, use "An" notation! 8 | #define XM A1 // must be an analog pin, use "An" notation! 9 | #define YM 7 // can be a digital pin 10 | #define XP 6 // can be a digital pin 11 | // This is calibration data for the raw touch data to the screen coordinates 12 | #define TS_MINX 190 13 | #define TS_MINY 400 14 | #define TS_MAXX 890 15 | #define TS_MAXY 820 16 | 17 | #include 18 | #include 19 | #include 20 | #include "Adafruit_GFX.h" 21 | #include 22 | #include 23 | #include "Adafruit_HX8357.h" 24 | #include 25 | #include "TouchScreen.h" 26 | 27 | // Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC 28 | Adafruit_HX8357 tft = Adafruit_HX8357(TFT_CS, TFT_DC, TFT_RST); 29 | // SoftSPI - note that on some processors this might be *faster* than hardware SPI! 30 | //Adafruit_HX8357 tft = Adafruit_HX8357(TFT_CS, TFT_DC, SOFT_MOSI, SOFT_CLK, TFT_RST, SOFT_MISO); 31 | const int displayWidth = 480, displayHeight = 320; 32 | const int gridSize = 80; 33 | // For better pressure precision, we need to know the resistance 34 | // between X+ and X- Use any multimeter to read it 35 | // For the one we're using, its 300 ohms across the X plate 36 | TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300); 37 | TSPoint touchpoint = ts.getPoint(); 38 | bool setupMenu = false, editMenu = false, reflowMenu = false; 39 | const int touchHoldLimit = 500; 40 | 41 | // use hardware SPI, just pass in the CS pin 42 | Adafruit_MAX31856 maxthermo = Adafruit_MAX31856(THERMO_CS); 43 | // Use software SPI: CS, DI, DO, CLK 44 | //Adafruit_MAX31856 maxthermo = Adafruit_MAX31856(THERMO_CS, SOFT_MOSI, SOFT_MISO, SOFT_CLK); 45 | 46 | unsigned long timeSinceReflowStarted; 47 | unsigned long timeTempCheck = 1000; 48 | unsigned long lastTimeTempCheck = 0; 49 | double preheatTemp = 180, soakTemp = 150, reflowTemp = 230, cooldownTemp = 25; 50 | unsigned long preheatTime = 120000, soakTime = 60000, reflowTime = 120000, cooldownTime = 120000, totalTime = preheatTime + soakTime + reflowTime + cooldownTime; 51 | bool preheating = false, soaking = false, reflowing = false, coolingDown = false, newState = false; 52 | uint16_t gridColor = 0x7BEF; 53 | uint16_t preheatColor = HX8357_RED, soakColor = 0xFBE0, reflowColor = 0xDEE0, cooldownColor = HX8357_BLUE; // colors for plotting 54 | uint16_t preheatColor_d = 0xC000, soakColor_d = 0xC2E0, reflowColor_d = 0xC600, cooldownColor_d = 0x0018; // desaturated colors 55 | 56 | // Define Variables we'll be connecting to 57 | double Setpoint, Input, Output; 58 | 59 | 60 | // Specify the links and initial tuning parameters 61 | double Kp=2, Ki=5, Kd=1; 62 | PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT); 63 | 64 | 65 | void setup() { 66 | Serial.begin(115200); 67 | while (!Serial) 68 | delay(10); 69 | Serial.println("Solder Reflow Oven"); 70 | delay(100); 71 | tft.begin(); 72 | tft.setRotation(1); 73 | tft.fillScreen(HX8357_BLACK); 74 | tft.setCursor(0,0); 75 | tft.setTextSize(1); 76 | 77 | 78 | if (!maxthermo.begin()) { 79 | Serial.println("Could not initialize thermocouple."); 80 | while (1) delay(10); 81 | } 82 | 83 | maxthermo.setThermocoupleType(MAX31856_TCTYPE_K); 84 | 85 | /* 86 | Serial.print("Thermocouple type: "); 87 | switch (maxthermo.getThermocoupleType() ) { 88 | case MAX31856_TCTYPE_B: Serial.println("B Type"); break; 89 | case MAX31856_TCTYPE_E: Serial.println("E Type"); break; 90 | case MAX31856_TCTYPE_J: Serial.println("J Type"); break; 91 | case MAX31856_TCTYPE_K: Serial.println("K Type"); break; 92 | case MAX31856_TCTYPE_N: Serial.println("N Type"); break; 93 | case MAX31856_TCTYPE_R: Serial.println("R Type"); break; 94 | case MAX31856_TCTYPE_S: Serial.println("S Type"); break; 95 | case MAX31856_TCTYPE_T: Serial.println("T Type"); break; 96 | case MAX31856_VMODE_G8: Serial.println("Voltage x8 Gain mode"); break; 97 | case MAX31856_VMODE_G32: Serial.println("Voltage x8 Gain mode"); break; 98 | default: Serial.println("Unknown"); break; 99 | } 100 | */ 101 | 102 | maxthermo.setConversionMode(MAX31856_ONESHOT_NOWAIT); 103 | 104 | Setpoint = cooldownTemp; 105 | // tell the PID to range between 0 and the full window size 106 | myPID.SetOutputLimits(0, 1); 107 | 108 | // turn the PID on 109 | myPID.SetMode(AUTOMATIC); 110 | 111 | pinMode(SSR_PIN, OUTPUT); 112 | digitalWrite(SSR_PIN,LOW); 113 | 114 | 115 | } 116 | 117 | void loop() { 118 | digitalWrite(SSR_PIN,LOW); 119 | ///* Setup Menu */// 120 | tft.fillScreen(HX8357_BLACK); 121 | drawSetupMenu(); 122 | setupMenu = true; 123 | Serial.println("Setup Menu"); 124 | while(setupMenu){ 125 | touchpoint = ts.getPoint(); 126 | if(touchpoint.z > ts.pressureThreshhold){ 127 | int setupMenuXPos = getGridCellX(), setupMenuYPos = getGridCellY(); 128 | Serial.print("Setup menu touch: ("); Serial.print(setupMenuXPos); Serial.print(","); Serial.print(setupMenuYPos); Serial.print(") -> "); 129 | if(setupMenuYPos < 3){ // Somewhere other than the start button 130 | editMenu = true; 131 | bool editingPreheat = false, editingSoak = false, editingReflow = false; 132 | if(setupMenuXPos < 2 ){ // Somwhere within the preheat zone 133 | editingPreheat = true; 134 | tft.fillScreen(preheatColor); 135 | Serial.println("Edit Preheat Menu"); 136 | drawEditMenu("Preheat"); 137 | centerText(2,0,1,1,HX8357_WHITE,String(int(preheatTemp))); 138 | centerText(5,0,1,1,HX8357_WHITE, formatTime(preheatTime)); 139 | } 140 | else if(setupMenuXPos > 3 ){// Somwhere within the reflow zone 141 | editingReflow = true; 142 | tft.fillScreen(reflowColor); 143 | Serial.println("Edit Reflow Menu"); 144 | drawEditMenu("Reflow"); 145 | centerText(2,0,1,1,HX8357_WHITE,String(int(reflowTemp))); 146 | centerText(5,0,1,1,HX8357_WHITE, formatTime(reflowTime)); 147 | } 148 | else{ // Somwhere within the soak zone 149 | editingSoak = true; 150 | tft.fillScreen(soakColor); 151 | Serial.println("Edit Soak Menu"); 152 | drawEditMenu("Soak"); 153 | centerText(2,0,1,1,HX8357_WHITE,String(int(soakTemp))); 154 | centerText(5,0,1,1,HX8357_WHITE, formatTime(soakTime)); 155 | } 156 | while(editMenu){// Stay in this loop until the save button is pressed 157 | touchpoint = ts.getPoint(); 158 | if(touchpoint.z > ts.pressureThreshhold){ 159 | int editMenuXPos = getGridCellX(), editMenuYPos = getGridCellY(); 160 | Serial.print("Edit menu touch at ("); Serial.print(editMenuXPos); Serial.print(","); Serial.print(editMenuYPos); Serial.print(") -> "); 161 | if(editMenuYPos == 1){ // One of the up arrows was pressed 162 | if(editMenuXPos < 3){ // The Temp up arrow was pressed 163 | Serial.println("Temp up arrow"); 164 | tft.fillRoundRect(2*gridSize+2, 0*gridSize+2, gridSize-4, gridSize-4, 10, HX8357_BLACK); 165 | if(editingPreheat){ 166 | if(preheatTemp < 300); 167 | preheatTemp += 10; 168 | centerText(2,0,1,1,HX8357_WHITE,String(int(preheatTemp))); 169 | } 170 | if(editingSoak){ 171 | if(soakTemp < 300); 172 | soakTemp += 10; 173 | centerText(2,0,1,1,HX8357_WHITE,String(int(soakTemp))); 174 | } 175 | if(editingReflow){ 176 | if(reflowTemp < 300); 177 | reflowTemp += 10; 178 | centerText(2,0,1,1,HX8357_WHITE,String(int(reflowTemp))); 179 | } 180 | } 181 | else{// The Time up arrow was pressed 182 | Serial.println("Time up arrow"); 183 | tft.fillRoundRect(5*gridSize+2, 0*gridSize+2, gridSize-4, gridSize-4, 10, HX8357_BLACK); 184 | if(editingPreheat){ 185 | if(preheatTime < 300000) 186 | preheatTime += 10000; 187 | centerText(5,0,1,1,HX8357_WHITE, formatTime(preheatTime)); 188 | } 189 | if(editingSoak){ 190 | if(soakTime < 300000) 191 | soakTime += 10000; 192 | centerText(5,0,1,1,HX8357_WHITE, formatTime(soakTime)); 193 | } 194 | if(editingReflow){ 195 | if(reflowTime < 300000) 196 | reflowTime += 10000; 197 | centerText(5,0,1,1,HX8357_WHITE, formatTime(reflowTime)); 198 | } 199 | } 200 | } 201 | else if(editMenuYPos == 2){// One of the down arrows was pressed 202 | if(editMenuXPos < 3){ // The Temp down arrow was pressed 203 | Serial.println("Temp down arrow"); 204 | tft.fillRoundRect(2*gridSize+2, 0*gridSize+2, gridSize-4, gridSize-4, 10, HX8357_BLACK); 205 | if(editingPreheat){ 206 | if(preheatTemp > 100) 207 | preheatTemp -= 10; 208 | centerText(2,0,1,1,HX8357_WHITE,String(int(preheatTemp))); 209 | } 210 | if(editingSoak){ 211 | if(soakTemp > 100) 212 | soakTemp -= 10; 213 | centerText(2,0,1,1,HX8357_WHITE,String(int(soakTemp))); 214 | } 215 | if(editingReflow){ 216 | if(reflowTemp > 100) 217 | reflowTemp -= 10; 218 | centerText(2,0,1,1,HX8357_WHITE,String(int(reflowTemp))); 219 | } 220 | } 221 | else{// The Time down arrow was pressed 222 | Serial.println("Time down arrow"); 223 | tft.fillRoundRect(5*gridSize+2, 0*gridSize+2, gridSize-4, gridSize-4, 10, HX8357_BLACK); 224 | if(editingPreheat){ 225 | if(preheatTime > 30000) 226 | preheatTime -= 10000; 227 | centerText(5,0,1,1,HX8357_WHITE, formatTime(preheatTime)); 228 | } 229 | else if(editingSoak){ 230 | if(soakTime > 30000) 231 | soakTime -= 10000; 232 | centerText(5,0,1,1,HX8357_WHITE, formatTime(soakTime)); 233 | } 234 | else if(editingReflow){ 235 | if(reflowTime > 30000) 236 | reflowTime -= 10000; 237 | centerText(5,0,1,1,HX8357_WHITE, formatTime(reflowTime)); 238 | } 239 | } 240 | } 241 | else if(editMenuYPos == 3){ // Save button was pressed 242 | Serial.println("Save button"); 243 | tft.fillScreen(HX8357_BLACK); 244 | drawSetupMenu(); 245 | editMenu = false; 246 | } 247 | delay(touchHoldLimit); // so holding the button down doesn't read multiple presses 248 | } 249 | } 250 | } 251 | else{// Start button was pressed 252 | Serial.println("Start button"); 253 | setupMenu = false; 254 | } 255 | delay(touchHoldLimit); // so holding the button down doesn't read multiple presses 256 | } 257 | } 258 | ///* Reflow Menu */// 259 | tft.fillScreen(HX8357_BLACK); 260 | drawReflowMenu(); 261 | drawButton(0,3,2,1, HX8357_GREEN, HX8357_WHITE, "Start"); 262 | bool start = false; 263 | while(!start){ 264 | touchpoint = ts.getPoint(); 265 | if(touchpoint.z > ts.pressureThreshhold){ 266 | if(getGridCellX() <2 && getGridCellY() == 3){ 267 | start = true; 268 | } 269 | delay(touchHoldLimit); // so holding the button down doesn't read multiple presses 270 | } 271 | } 272 | drawButton(0,3,2,1, HX8357_RED, HX8357_WHITE, "Stop"); 273 | Serial.println("Reflow Menu"); 274 | unsigned long reflowStarted = millis(); 275 | reflowMenu = true; 276 | while(reflowMenu){ 277 | timeSinceReflowStarted = millis() - reflowStarted; 278 | if(timeSinceReflowStarted - lastTimeTempCheck > timeTempCheck){ 279 | lastTimeTempCheck = timeSinceReflowStarted; 280 | printState(); 281 | // check for conversion complete and read temperature 282 | if (maxthermo.conversionComplete()) { 283 | Serial.print("\tSetpoint:"); Serial.print(Setpoint); 284 | Input = maxthermo.readThermocoupleTemperature(); 285 | Serial.print("\tInput:"); Serial.print(Input); 286 | myPID.Compute(); 287 | if(Output < 0.5){ 288 | digitalWrite(SSR_PIN,LOW); 289 | } 290 | if(Output > 0.5){ 291 | digitalWrite(SSR_PIN,HIGH); 292 | } 293 | Serial.print("\tOutput:"); Serial.println(Output); 294 | plotDataPoint(); 295 | } 296 | else { 297 | Serial.println("\tConversion not complete!"); 298 | } 299 | // trigger a conversion, returns immediately 300 | maxthermo.triggerOneShot(); 301 | } 302 | if(timeSinceReflowStarted > totalTime){ 303 | reflowMenu = false; 304 | } 305 | else if(timeSinceReflowStarted > (preheatTime + soakTime + reflowTime)){ // preheat and soak and reflow are complete. Start cooldown 306 | if(!coolingDown){ 307 | newState = true; 308 | preheating = false, soaking = false, reflowing = false, coolingDown = true; 309 | } 310 | Setpoint = cooldownTemp; 311 | } 312 | else if(timeSinceReflowStarted > (preheatTime + soakTime)){ // preheat and soak are complete. Start reflow 313 | if(!reflowing){ 314 | newState = true; 315 | preheating = false, soaking = false, reflowing = true, coolingDown = false; 316 | } 317 | Setpoint = reflowTemp; 318 | } 319 | else if(timeSinceReflowStarted > preheatTime){ // preheat is complete. Start soak 320 | if(!soaking){ 321 | newState = true; 322 | preheating = false, soaking = true, reflowing = false, coolingDown = false; 323 | } 324 | Setpoint = soakTemp; 325 | } 326 | else{ // cycle is starting. Start preheat 327 | if(!preheating){ 328 | newState = true; 329 | preheating = true, soaking = false, reflowing = false, coolingDown = false; 330 | } 331 | Setpoint = preheatTemp; 332 | } 333 | touchpoint = ts.getPoint(); 334 | if(touchpoint.z > ts.pressureThreshhold){ 335 | if(getGridCellX() < 2 && getGridCellY() == 3){ 336 | reflowMenu = false; 337 | } 338 | delay(touchHoldLimit); // so holding the button down doesn't read multiple presses 339 | } 340 | } 341 | drawButton(0,3,2,1, HX8357_GREEN, HX8357_WHITE, "Done"); 342 | bool done = false; 343 | while(!done){ 344 | touchpoint = ts.getPoint(); 345 | if(touchpoint.z > ts.pressureThreshhold){ 346 | if(getGridCellX() < 2 && getGridCellY() == 3){ 347 | done = true; 348 | } 349 | delay(touchHoldLimit); // so holding the button down doesn't read multiple presses 350 | } 351 | } 352 | } 353 | 354 | void printState(){ 355 | String time = formatTime(timeSinceReflowStarted); 356 | Serial.print("Current time: "); Serial.print(time); Serial.print("\t"); 357 | tft.fillRoundRect(5*gridSize+2, 3*gridSize+2, gridSize-4, gridSize-4, 10, HX8357_BLACK); 358 | centerText(5,3,1,1,0,HX8357_WHITE,time); 359 | centerText(5,3,1,1,2,HX8357_WHITE,String(Input)); 360 | String currentState; 361 | if(preheating){ 362 | currentState = "Preheating"; 363 | } 364 | if(soaking){ 365 | currentState = "Soaking"; 366 | } 367 | if(reflowing){ 368 | currentState = "Reflowing"; 369 | } 370 | if(coolingDown){ 371 | currentState = "Cool Down"; 372 | } 373 | Serial.print(currentState); 374 | if(newState){ 375 | newState = false; 376 | tft.fillRoundRect(2*gridSize+2, 3*gridSize+2, 2*gridSize-4, gridSize-4, 10, HX8357_BLACK); 377 | centerText(2,3,2,1,HX8357_WHITE,currentState); 378 | } 379 | } 380 | 381 | void drawGrid(){ 382 | tft.setFont(); 383 | tft.setTextColor(HX8357_WHITE); 384 | tft.drawRect(0,0,displayWidth,displayHeight-gridSize,gridColor); 385 | for(int i=1; i<6; i++){ 386 | tft.drawFastVLine(i*gridSize,0,displayHeight-gridSize,gridColor); 387 | } 388 | for(int j=1; j<4; j++){ 389 | tft.drawFastHLine(0,j*gridSize,displayWidth,gridColor); 390 | } 391 | tft.setCursor(4,4); tft.print("300"); 392 | tft.setCursor(4,1*gridSize+4); tft.print("200"); 393 | tft.setCursor(4,2*gridSize+4); tft.print("100"); 394 | 395 | tft.setCursor(1*gridSize+4,3*gridSize-7-4); tft.print(formatTime(totalTime/6)); 396 | tft.setCursor(2*gridSize+4,3*gridSize-7-4); tft.print(formatTime(2*totalTime/6)); 397 | tft.setCursor(3*gridSize+4,3*gridSize-7-4); tft.print(formatTime(3*totalTime/6)); 398 | tft.setCursor(4*gridSize+4,3*gridSize-7-4); tft.print(formatTime(4*totalTime/6)); 399 | tft.setCursor(5*gridSize+4,3*gridSize-7-4); tft.print(formatTime(5*totalTime/6)); 400 | 401 | plotReflowProfile(); 402 | } 403 | 404 | void drawButton(int x, int y, int w, int h, uint16_t backgroundColor, uint16_t textColor, String text){ 405 | tft.setFont(&FreeMonoBold12pt7b); 406 | if(backgroundColor == HX8357_BLACK){ 407 | tft.drawRoundRect(x*gridSize+2, y*gridSize+2, w*gridSize-4, h*gridSize-4, 10, HX8357_WHITE); 408 | } 409 | else{ 410 | tft.fillRoundRect(x*gridSize+2, y*gridSize+2, w*gridSize-4, h*gridSize-4, 10, backgroundColor); 411 | } 412 | if(text == "UP_ARROW"){ 413 | tft.fillTriangle(x*gridSize+(w*gridSize-60)/2, y*gridSize+(h*gridSize-52)/2+52, x*gridSize+(w*gridSize-60)/2+60, y*gridSize+(h*gridSize-52)/2+52, x*gridSize+w*gridSize/2, y*gridSize+(h*gridSize-52)/2, textColor); 414 | } 415 | else if(text == "DOWN_ARROW"){ 416 | tft.fillTriangle(x*gridSize+(w*gridSize-60)/2, y*gridSize+(h*gridSize-52)/2, x*gridSize+(w*gridSize-60)/2+60, y*gridSize+(h*gridSize-52)/2, x*gridSize+w*gridSize/2, y*gridSize+(h*gridSize-52)/2+52, textColor); 417 | } 418 | else{ 419 | int16_t textBoundX, textBoundY; 420 | uint16_t textBoundWidth, textBoundHeight; 421 | tft.getTextBounds(text,0,0,&textBoundX, &textBoundY, &textBoundWidth, &textBoundHeight); 422 | tft.setCursor(x*gridSize+(w*gridSize-textBoundWidth)/2, y*gridSize+(h*gridSize+textBoundHeight)/2); tft.setTextColor(textColor); tft.print(text); 423 | } 424 | } 425 | 426 | void centerText(int x, int y, int w, int h, uint16_t textColor, String text){ 427 | tft.setFont(&FreeMonoBold12pt7b); 428 | int16_t textBoundX, textBoundY; 429 | uint16_t textBoundWidth, textBoundHeight; 430 | tft.getTextBounds(text,0,0,&textBoundX, &textBoundY, &textBoundWidth, &textBoundHeight); 431 | tft.setCursor(x*gridSize+(w*gridSize-textBoundWidth)/2, y*gridSize+(h*gridSize+textBoundHeight)/2); 432 | tft.setTextColor(textColor); tft.print(text); 433 | } 434 | 435 | void centerText(int x, int y, int w, int h, int justification, uint16_t textColor, String text){ 436 | tft.setFont(&FreeMonoBold12pt7b); 437 | int16_t textBoundX, textBoundY; 438 | uint16_t textBoundWidth, textBoundHeight; 439 | tft.getTextBounds(text,0,0,&textBoundX, &textBoundY, &textBoundWidth, &textBoundHeight); 440 | switch(justification){ 441 | case 0: //top justified 442 | tft.setCursor(x*gridSize+(w*gridSize-textBoundWidth)/2, y*gridSize+(h*gridSize/2-textBoundHeight)/2+textBoundHeight); 443 | break; 444 | case 1: //center justified 445 | tft.setCursor(x*gridSize+(w*gridSize-textBoundWidth)/2, y*gridSize+(h*gridSize+textBoundHeight)/2); 446 | break; 447 | case 2: //bottom justified 448 | tft.setCursor(x*gridSize+(w*gridSize-textBoundWidth)/2, y*gridSize+gridSize-(h*gridSize/2-textBoundHeight)/2); 449 | break; 450 | } 451 | tft.setTextColor(textColor); tft.print(text); 452 | } 453 | 454 | void drawSetupMenu(){ 455 | tft.setFont(&FreeMonoBold12pt7b); 456 | drawButton(0,0,2,3, preheatColor, HX8357_WHITE, ""); drawButton(2,0,2,3, soakColor, HX8357_WHITE, ""); drawButton(4,0,2,3, reflowColor, HX8357_WHITE, ""); 457 | centerText(0,0,2,1, HX8357_WHITE, "Preheat"); centerText(2,0,2,1, HX8357_WHITE, "Soak"); centerText(4,0,2,1, HX8357_WHITE, "Reflow"); 458 | centerText(0,1,2,1,0, HX8357_WHITE, String(int(preheatTemp)) + " C"); centerText(2,1,2,1,0, HX8357_WHITE, String(int(soakTemp)) + " C"); centerText(4,1,2,1,0, HX8357_WHITE, String(int(reflowTemp)) + " C"); 459 | centerText(0,1,2,1,2, HX8357_WHITE, String(formatTime(preheatTime)) + " min."); centerText(2,1,2,1,2, HX8357_WHITE, String(formatTime(soakTime)) + " min.");centerText(4,1,2,1,2, HX8357_WHITE, String(formatTime(reflowTime)) + " min."); 460 | drawButton(0,3,6,1, HX8357_GREEN, HX8357_WHITE, "Confirm"); 461 | tft.drawCircle(90,95,3,HX8357_WHITE); tft.drawCircle(250,95,3,HX8357_WHITE); tft.drawCircle(410,95,3,HX8357_WHITE); 462 | } 463 | 464 | void drawReflowMenu(){ 465 | tft.setFont(&FreeMonoBold12pt7b); 466 | drawGrid(); 467 | centerText(4,3,1,1,0, HX8357_WHITE, "Time: "); 468 | centerText(4,3,1,1,2, HX8357_WHITE, "Temp: "); 469 | //drawButton(0,3,2,1, HX8357_RED, HX8357_WHITE, "Stop"); drawButton(0,3,2,1, HX8357_RED, HX8357_WHITE, "Start"); 470 | } 471 | 472 | void drawEditMenu(String stage){ 473 | tft.setFont(&FreeMonoBold12pt7b); 474 | centerText(0,0,2,1,0, HX8357_WHITE, stage); centerText(0,0,2,1, HX8357_WHITE, " Temp: "); drawButton(0,1,3,1, HX8357_WHITE, HX8357_BLACK, "UP_ARROW"); drawButton(0,2,3,1, HX8357_WHITE, HX8357_BLACK, "DOWN_ARROW"); 475 | centerText(3,0,2,1,0, HX8357_WHITE, stage); centerText(3,0,2,1, HX8357_WHITE, " Time: "); drawButton(3,1,3,1, HX8357_WHITE, HX8357_BLACK, "UP_ARROW"); drawButton(3,2,3,1, HX8357_WHITE, HX8357_BLACK, "DOWN_ARROW"); 476 | //centerText(0,1,2,1,0, HX8357_WHITE, String(int(preheatTemp))); //centerText(2,1,2,1,0, HX8357_WHITE, String(int(soakTemp))); centerText(4,1,2,1,0, HX8357_WHITE, String(int(reflowTemp))); 477 | //centerText(0,1,2,1,2, HX8357_WHITE, String(formatTime(preheatTime))); //centerText(2,1,2,1,2, HX8357_WHITE, String(formatTime(soakTime)));centerText(4,1,2,1,2, HX8357_WHITE, String(formatTime(reflowTime))); 478 | //drawButton(0,0,2,2, HX8357_BLACK, HX8357_WHITE, ""); drawButton(2,0,2,2, HX8357_BLACK, HX8357_WHITE, ""); drawButton(4,0,2,2, HX8357_BLACK, HX8357_WHITE, ""); 479 | 480 | //drawButton(0,2,1,1, HX8357_WHITE, HX8357_BLACK, "UP");drawButton(1,2,1,1, HX8357_WHITE, HX8357_BLACK, "DOWN"); 481 | //drawButton(2,2,1,1, HX8357_WHITE, HX8357_BLACK, "UP");drawButton(3,2,1,1, HX8357_WHITE, HX8357_BLACK, "DOWN"); 482 | //drawButton(4,2,1,1, HX8357_WHITE, HX8357_BLACK, "UP");drawButton(5,2,1,1, HX8357_WHITE, HX8357_BLACK, "DOWN"); 483 | tft.drawCircle(90,95,3,HX8357_WHITE); tft.drawCircle(250,95,3,HX8357_WHITE); tft.drawCircle(410,95,3,HX8357_WHITE); 484 | drawButton(0,3,6,1, HX8357_GREEN, HX8357_WHITE, "Save"); 485 | } 486 | 487 | int getGridCellX(){ 488 | int xpoint = touchpoint.x; 489 | Serial.print("x resistance: ");Serial.print(xpoint); Serial.print(" "); 490 | //xpoint = map(xpoint,TS_MINX,TS_MAXX,displayWidth-1,0); 491 | if(xpoint > 824) 492 | return 0; 493 | else if(xpoint > 689) 494 | return 1; 495 | else if(xpoint > 546) 496 | return 2; 497 | else if(xpoint > 399) 498 | return 3; 499 | else if(xpoint > 259) 500 | return 4; 501 | else 502 | return 5; 503 | } 504 | 505 | int getGridCellY(){ 506 | int ypoint = touchpoint.y; 507 | Serial.print("y resistance: ");Serial.print(ypoint); Serial.print(" "); 508 | //ypoint = map(ypoint,TS_MINY,TS_MAXY,0,displayHeight-1); 509 | if(ypoint > 800) 510 | return 3; 511 | else if(ypoint > 690) 512 | return 2; 513 | else if(ypoint > 500) 514 | return 1; 515 | else 516 | return 0; 517 | } 518 | 519 | String formatTime(unsigned long milliseconds) { 520 | // Calculate the number of minutes and seconds 521 | unsigned long totalSeconds = milliseconds / 1000; 522 | unsigned int minutes = totalSeconds / 60; 523 | unsigned int seconds = totalSeconds % 60; 524 | 525 | // Format the time as a string with a leading zero if necessary 526 | String formattedTime = (minutes < 10 ? "0" : "") + String(minutes) + ":" + (seconds < 10 ? "0" : "") + String(seconds); 527 | 528 | return formattedTime; 529 | } 530 | 531 | /*int mapTime(int time){ 532 | return map(time,0,totalTime,0,displayWidth); 533 | }*/ 534 | 535 | /*int mapTemp(int temp){ 536 | return map(temp,0,300,3*gridSize,0); 537 | }*/ 538 | 539 | void plotDataPoint(){ 540 | uint16_t color; 541 | if(preheating){ 542 | color = preheatColor; 543 | } 544 | if(soaking){ 545 | color = soakColor; 546 | } 547 | if(reflowing){ 548 | color = reflowColor; 549 | } 550 | if(coolingDown){ 551 | color = cooldownColor; 552 | } 553 | tft.fillCircle(map(timeSinceReflowStarted,0,totalTime,0,displayWidth),map(Input,0,300,3*gridSize,0),2, color); 554 | //tft.fillCircle(mapTime(timeSinceReflowStarted), mapTemp(Input), 2, color); 555 | } 556 | 557 | void plotReflowProfile(){ 558 | int xMin, xMax, amp; 559 | xMin = 0; 560 | xMax = map(preheatTime,0,totalTime,0,displayWidth); 561 | amp = map(preheatTemp,0,300,3*gridSize,0) - map(cooldownTemp,0,300,3*gridSize,0); 562 | for(int i = 0; i <= (xMax-xMin); i++){ 563 | tft.fillCircle(xMin+i,-amp/2*cos(PI*i/(xMax-xMin))+map(cooldownTemp,0,300,3*gridSize,0)+amp/2,2,preheatColor_d); 564 | } 565 | 566 | xMin = map(preheatTime,0,totalTime,0,displayWidth); 567 | xMax = map(preheatTime+soakTime,0,totalTime,0,displayWidth); 568 | amp = map(soakTemp,0,300,3*gridSize,0) - map(preheatTemp,0,300,3*gridSize,0); 569 | //amp = 80; 570 | for(int i = 0; i <= (xMax-xMin); i++){ 571 | tft.fillCircle(xMin+i,-amp/2*cos(PI*i/(xMax-xMin))+map(preheatTemp,0,300,3*gridSize,0)+amp/2,2, soakColor_d); 572 | } 573 | 574 | xMin = map(preheatTime+soakTime,0,totalTime,0,displayWidth); 575 | xMax = map(preheatTime+soakTime+reflowTime,0,totalTime,0,displayWidth); 576 | amp = map(reflowTemp,0,300,3*gridSize,0) - map(soakTemp,0,300,3*gridSize,0); 577 | //amp = 80; 578 | for(int i = 0; i <= (xMax-xMin); i++){ 579 | tft.fillCircle(xMin+i,-amp/2*cos(PI*i/(xMax-xMin))+map(soakTemp,0,300,3*gridSize,0)+amp/2,2,reflowColor_d); 580 | } 581 | 582 | xMin = map(preheatTime+soakTime+reflowTime,0,totalTime,0,displayWidth); 583 | xMax = map(totalTime,0,totalTime,0,displayWidth); 584 | amp = map(cooldownTemp,0,300,3*gridSize,0) - map(reflowTemp,0,300,3*gridSize,0); 585 | //amp = 80; 586 | for(int i = 0; i <= (xMax-xMin); i++){ 587 | tft.fillCircle(xMin+i,-amp/2*cos(PI*i/(xMax-xMin))+map(reflowTemp,0,300,3*gridSize,0)+amp/2,2, cooldownColor_d); 588 | } 589 | } -------------------------------------------------------------------------------- /Mechanical Design/3D Mesh Files/enclosure.3mf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytesizedengineering/Solder-Reflow-Oven/a9f96df5d62be56e380504e2f7175b774f568bb4/Mechanical Design/3D Mesh Files/enclosure.3mf -------------------------------------------------------------------------------- /Mechanical Design/3D Mesh Files/lid.3mf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytesizedengineering/Solder-Reflow-Oven/a9f96df5d62be56e380504e2f7175b774f568bb4/Mechanical Design/3D Mesh Files/lid.3mf --------------------------------------------------------------------------------