main.cpp 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. #include <Arduino.h>
  2. #include <HardwareSerial.h>
  3. #include <SPI.h>
  4. // #include <Wire.h>
  5. #include <U8g2lib.h>
  6. #include <PID_v1.h>
  7. #include <MAX6675.h>
  8. #include <EasyButton.h>
  9. #define DISPLAY_RESET_PIN 10
  10. #define DISPLAY_DC_PIN 9
  11. #define DISPLAY_CS_PIN 8
  12. #define THERMOCOUPLE_CS_PIN 7
  13. #define DONE_LED_PIN 5
  14. #define SSR_PIN 4
  15. #define BUTTON_PIN 3
  16. #define FAN_PIN 6
  17. const char* lcdMessagesReflowStatus[] = {
  18. "Ready",
  19. "Heat",
  20. "Hold",
  21. "Cool",
  22. "Done",
  23. "Wait",
  24. "Err"
  25. };
  26. typedef enum REFLOW_STATE
  27. {
  28. REFLOW_STATE_IDLE,
  29. REFLOW_STATE_PREHEAT,
  30. REFLOW_STATE_SOAK,
  31. REFLOW_STATE_COOL,
  32. REFLOW_STATE_COMPLETE,
  33. REFLOW_STATE_TOO_HOT,
  34. REFLOW_STATE_ERROR
  35. } reflowState_t;
  36. typedef enum REFLOW_STATUS
  37. {
  38. REFLOW_STATUS_OFF,
  39. REFLOW_STATUS_ON
  40. } reflowStatus_t;
  41. struct reflowProfile{
  42. const char* profileName;
  43. int soakTemp;
  44. unsigned long soakPeriodMS;
  45. const char* timeInSeconds;
  46. };
  47. #define NUM_REFLOW_PROFILES 3
  48. reflowProfile profiles[NUM_REFLOW_PROFILES] = {
  49. {"Dry PLA", 50, 1800000*8, "14400"}, // 4 hours
  50. {"Dry PETG", 70, 1800000*4, "7200"}, // 2 hours
  51. {"Dry Dessicant", 93, 1800000*6, "108000"} // 3 hours
  52. };
  53. // ***** CONSTANTS *****
  54. #define TEMPERATURE_ROOM 45
  55. #define TEMPERATURE_COOL_MIN 50
  56. // ***** PID PARAMETERS *****
  57. // ***** PRE-HEAT STAGE *****
  58. #define PID_KP_PREHEAT 64
  59. #define PID_KI_PREHEAT 0.5
  60. #define PID_KD_PREHEAT 0
  61. // ***** SOAKING STAGE *****
  62. // #define PID_KP_SOAK 300 // default 300
  63. // #define PID_KI_SOAK 0.05 // default 0.05
  64. // #define PID_KD_SOAK 250 // default 250
  65. #define PID_SAMPLE_TIME 1000 //default 1000
  66. // ***** PID CONTROL VARIABLES *****
  67. double setpoint;
  68. double inputTemp;
  69. double temporaryInputVar;
  70. double output;
  71. double kp = PID_KP_PREHEAT;
  72. double ki = PID_KI_PREHEAT;
  73. double kd = PID_KD_PREHEAT;
  74. unsigned int windowSize;
  75. unsigned long now;
  76. unsigned long windowStartTime;
  77. unsigned long nextRead;
  78. unsigned long soakStartTime;
  79. unsigned long timerSoak;
  80. unsigned long completePeriod;
  81. // Reflow oven controller state machine state variable
  82. reflowState_t reflowState = REFLOW_STATE_IDLE;
  83. // Reflow oven controller status
  84. reflowStatus_t reflowStatus = REFLOW_STATUS_OFF;
  85. // did encounter a thermocouple error?
  86. int tcErrorCount = 0;
  87. bool TCError = false;
  88. bool startReflow = false;
  89. int activeReflowProfile = 0;
  90. MAX6675 tcouple(THERMOCOUPLE_CS_PIN);
  91. U8G2_SSD1305_128X64_ADAFRUIT_F_4W_HW_SPI u8g2(U8G2_R0, DISPLAY_CS_PIN, DISPLAY_DC_PIN, DISPLAY_RESET_PIN);
  92. // PID reflowOvenPID(&inputTemp, &output, &setpoint, kp, ki, kd, DIRECT);
  93. PID reflowOvenPID(&inputTemp, &output, &setpoint, kp, ki, kd, P_ON_M, DIRECT);
  94. EasyButton button(BUTTON_PIN);
  95. // Called once in setup, sets global display attributes.
  96. inline void u8g2_prepare(void) {
  97. u8g2.setFont(u8g2_font_8x13_tr);
  98. u8g2.setFontRefHeightExtendedText();
  99. u8g2.setDrawColor(1);
  100. u8g2.setFontPosTop();
  101. u8g2.setFontDirection(0);
  102. }
  103. // Handle reading temperature from max6675.
  104. // Also contains logic to protect against single/double read errors.
  105. // Requires three read errors in a row to go into error state.
  106. void readTemp(void) {
  107. temporaryInputVar = tcouple.readTempC();
  108. // inputTemp = tcouple.readTempC();
  109. if(temporaryInputVar == 0.00 || temporaryInputVar == -1.00 ) {
  110. if(tcErrorCount >= 3) {
  111. TCError = true;
  112. reflowState = REFLOW_STATE_ERROR;
  113. reflowStatus = REFLOW_STATUS_OFF;
  114. } else {
  115. tcErrorCount++;
  116. }
  117. } else {
  118. inputTemp = temporaryInputVar;
  119. tcErrorCount = 0;
  120. TCError = false;
  121. }
  122. }
  123. void buttonPressed(void) {
  124. // If currently reflow process is on going
  125. if (reflowStatus == REFLOW_STATUS_ON)
  126. {
  127. // Button press is for cancelling
  128. // Turn off reflow process
  129. reflowStatus = REFLOW_STATUS_OFF;
  130. // Reinitialize state machine
  131. reflowState = REFLOW_STATE_IDLE;
  132. // Turn off PID calculations
  133. reflowOvenPID.SetMode(MANUAL);
  134. } else {
  135. startReflow = true;
  136. }
  137. }
  138. void buttonHeld(void) {
  139. // If currently reflow process is on going
  140. if (reflowStatus == REFLOW_STATUS_ON)
  141. {// Button press is for cancelling
  142. // Turn off reflow process
  143. reflowStatus = REFLOW_STATUS_OFF;
  144. // Reinitialize state machine
  145. reflowState = REFLOW_STATE_IDLE;
  146. } else {
  147. activeReflowProfile++;
  148. if (activeReflowProfile >= NUM_REFLOW_PROFILES) {
  149. activeReflowProfile = 0;
  150. }
  151. }
  152. }
  153. // Reflow oven controller state machine
  154. inline void handleReflowState(void) {
  155. switch (reflowState)
  156. {
  157. case REFLOW_STATE_IDLE:
  158. // If oven temperature is still above room temperature
  159. if (inputTemp >= TEMPERATURE_ROOM)
  160. {
  161. reflowState = REFLOW_STATE_TOO_HOT;
  162. }
  163. // If switch is pressed, start reflow process
  164. else if (startReflow)
  165. {
  166. // Turn off done LED if it was on from a previous cycle.
  167. digitalWrite(DONE_LED_PIN, LOW);
  168. // Reset switch state to prevent triggering later code erroneously.
  169. startReflow = false;
  170. // Initialize PID control window starting time
  171. windowStartTime = millis();
  172. // Ramp up to minimum soaking temperature
  173. setpoint = profiles[activeReflowProfile].soakTemp;
  174. // Tell the PID to range between 0 and the full window size
  175. reflowOvenPID.SetOutputLimits(0, windowSize);
  176. reflowOvenPID.SetSampleTime(PID_SAMPLE_TIME);
  177. // Turn the PID on
  178. reflowOvenPID.SetMode(AUTOMATIC);
  179. // Proceed to preheat stage
  180. reflowState = REFLOW_STATE_PREHEAT;
  181. }
  182. break;
  183. case REFLOW_STATE_PREHEAT:
  184. reflowStatus = REFLOW_STATUS_ON;
  185. // If minimum soak temperature is achieved.
  186. if (inputTemp >= profiles[activeReflowProfile].soakTemp)
  187. {
  188. soakStartTime = millis();
  189. // Chop soaking period into smaller sub-period
  190. timerSoak = soakStartTime + profiles[activeReflowProfile].soakPeriodMS;
  191. // Set less agressive PID parameters for soaking ramp
  192. // reflowOvenPID.SetTunings(PID_KP_SOAK, PID_KI_SOAK, PID_KD_SOAK);
  193. // Proceed to soaking state
  194. reflowState = REFLOW_STATE_SOAK;
  195. }
  196. break;
  197. case REFLOW_STATE_SOAK:
  198. // If micro soak temperature is achieved
  199. if (millis() > timerSoak)
  200. {
  201. reflowStatus = REFLOW_STATUS_OFF;
  202. reflowState = REFLOW_STATE_COOL;
  203. reflowOvenPID.SetMode(MANUAL);
  204. }
  205. break;
  206. case REFLOW_STATE_COOL:
  207. // If minimum cool temperature is achieved
  208. if (inputTemp <= TEMPERATURE_COOL_MIN)
  209. {
  210. digitalWrite(DONE_LED_PIN, HIGH);
  211. completePeriod = millis() + 5000;
  212. // Turn off reflow process
  213. reflowStatus = REFLOW_STATUS_OFF;
  214. // Proceed to reflow Completion state
  215. reflowState = REFLOW_STATE_COMPLETE;
  216. }
  217. break;
  218. case REFLOW_STATE_COMPLETE:
  219. if (millis() > completePeriod)
  220. {
  221. // Reflow process ended
  222. reflowState = REFLOW_STATE_IDLE;
  223. }
  224. break;
  225. case REFLOW_STATE_TOO_HOT:
  226. // If oven temperature drops below room temperature
  227. if (inputTemp < TEMPERATURE_ROOM)
  228. {
  229. // Ready to reflow
  230. reflowState = REFLOW_STATE_IDLE;
  231. }
  232. break;
  233. case REFLOW_STATE_ERROR:
  234. // If thermocouple problem is still present
  235. if (isnan(inputTemp))
  236. {
  237. // Wait until thermocouple wire is connected
  238. reflowState = REFLOW_STATE_ERROR;
  239. }
  240. else
  241. {
  242. // Clear to perform reflow process
  243. reflowState = REFLOW_STATE_IDLE;
  244. }
  245. break;
  246. }
  247. }
  248. // PID computation and SSR control
  249. inline void handleSSR(void) {
  250. if (reflowStatus == REFLOW_STATUS_ON)
  251. {
  252. now = millis();
  253. reflowOvenPID.Compute();
  254. if((now - windowStartTime) > windowSize)
  255. {
  256. // Time to shift the Relay Window
  257. windowStartTime += windowSize;
  258. }
  259. if(output > (now - windowStartTime)) {
  260. digitalWrite(SSR_PIN, HIGH);
  261. } else {
  262. digitalWrite(SSR_PIN, LOW);
  263. }
  264. } else {
  265. // Reflow oven process is off, ensure oven is off
  266. digitalWrite(SSR_PIN, LOW);
  267. }
  268. }
  269. void drawScreen(void) {
  270. u8g2.clearBuffer();
  271. short rowOffset = 0;
  272. const short rowSize = 12;
  273. // Temperature Row
  274. char temperatureStr[8];
  275. dtostrf(inputTemp, 4, 2, temperatureStr);
  276. u8g2.drawStr( 0, rowOffset, "Temp: ");
  277. u8g2.drawStr( 60, rowOffset, temperatureStr);
  278. u8g2.drawStr( 100, rowOffset, "C");
  279. rowOffset += rowSize;
  280. // Current Profile
  281. u8g2.drawStr( 0, rowOffset, profiles[activeReflowProfile].profileName);
  282. rowOffset += rowSize;
  283. // General status row
  284. u8g2.drawStr( 0, rowOffset, lcdMessagesReflowStatus[reflowState]);
  285. rowOffset += rowSize;
  286. // Timer for Heat cycle row
  287. if(reflowState == REFLOW_STATE_SOAK) {
  288. char soakSecondsStr[8];
  289. itoa((millis() - soakStartTime)/1000, soakSecondsStr, 10);
  290. u8g2.drawStr( 0, rowOffset, "Time: ");
  291. u8g2.drawStr( 40, rowOffset, soakSecondsStr);
  292. u8g2.drawStr( 80, rowOffset, profiles[activeReflowProfile].timeInSeconds);
  293. }
  294. rowOffset += rowSize;
  295. char *targetTempStr = "Target Temp: ";
  296. itoa(profiles[activeReflowProfile].soakTemp, &targetTempStr[13], 10);
  297. u8g2.drawStr( 0, rowOffset, targetTempStr);
  298. rowOffset += rowSize;
  299. u8g2.sendBuffer();
  300. }
  301. inline void handleFan() {
  302. if(reflowStatus == REFLOW_STATUS_ON || reflowState != REFLOW_STATE_IDLE) {
  303. digitalWrite(FAN_PIN, HIGH);
  304. } else {
  305. digitalWrite(FAN_PIN, LOW);
  306. }
  307. }
  308. void setup(void) {
  309. // Turn off SSR.
  310. digitalWrite(SSR_PIN, LOW);
  311. pinMode(SSR_PIN, OUTPUT);
  312. digitalWrite(FAN_PIN, LOW);
  313. pinMode(FAN_PIN, OUTPUT);
  314. pinMode(BUTTON_PIN, INPUT_PULLUP);
  315. button.begin();
  316. button.onPressed(buttonPressed);
  317. button.onPressedFor(1000, buttonHeld);
  318. digitalWrite(DONE_LED_PIN, LOW);
  319. pinMode(DONE_LED_PIN, OUTPUT);
  320. u8g2.begin();
  321. u8g2.clearBuffer();
  322. u8g2_prepare();
  323. // Set window size
  324. windowSize = 2000;
  325. // Initialize thermocouple reading variable
  326. nextRead = millis();
  327. // Serial.begin(115200);
  328. }
  329. void loop(void) {
  330. now = millis();
  331. if (now > nextRead) {
  332. readTemp();
  333. // Serial.print(profiles[activeReflowProfile].profileName);
  334. // Serial.print(" ");
  335. // Serial.print(lcdMessagesReflowStatus[reflowState]);
  336. // Serial.print(": ");
  337. // Serial.print(setpoint);
  338. // Serial.print(",");
  339. // Serial.println(inputTemp);
  340. nextRead += 500;
  341. }
  342. handleReflowState();
  343. button.read();
  344. handleSSR();
  345. handleFan();
  346. drawScreen();
  347. }