= 自作リフロー = とりあえず、いきなりスケッチを置いておきます。 回路図とかはそのうち。 == 材料 == * [http://www.switch-science.com/products/detail.php?product_id=3 Arduino Duemilanove] * [http://www.switch-science.com/products/detail.php?product_id=176 バニラシールド] * [http://www.switch-science.com/products/detail.php?product_id=146 K型熱電対温度センサモジュールキット(SPI接続)MAX6675使用] * よくあるLCD(16x2文字) * ピンヘッダとか。 * LED、LEDの電流制限用に抵抗 * タクトスイッチ * 半導体リレー(ゼロクロス型) * 半導体リレー用の放熱板 * 耐熱電線 * ファストン端子(平型端子)、圧着工具必須 * コンベクションオーブン(テスト中→[http://www.amazon.co.jp/dp/B000ARSQWM http://www.amazon.co.jp/dp/B000ARSQWM]) * カプトンテープ(またはその他耐熱性の粘着テープ)少々 * いらない基板 案外お金がかかるなあ。 キットにしたら幾らになるだろう? 買う人少ないだろうし。 == 残り作業 == * ブザーが欲しい。開始・終了で鳴らす。 * 冷めてきて動かしても大丈夫になったことを知らせたい。 * オーブンの筐体の隙間にグラスウールまたはロックウールを詰め込んで試してみる。 * それでだめなら、もう少し強力なオーブンを探す。 * PCをコンソールにするアプリが欲しい。 == 外部ライブラリ == * [http://www.arduino.cc/playground/Code/PIDLibrary PIDライブラリ] == `ReflowOven.pde` == {{{ #!c #include #include "PID_Beta6.h" /* * * Config * */ #define BUTTON 3 #define HEATER 9 #define SENSOR 10 LiquidCrystal lcd(2, 8, 4, 5, 6, 7); /* * * Temperature sensor * */ #include "SPI.h" const static double TEMP_ERROR = 10000; void sensorSetup() { SPI_Master.begin(SENSOR); } double sensorValue() { SPI_Master.enable(SENSOR); int value; value = SPI_Master.read() << 8; value |= SPI_Master.read(); SPI_Master.disable(); if ((value & 0x0004) != 0) return TEMP_ERROR; return (value >> 3) * 0.25; } /* * * Main * */ /* PID */ double temperature, output, target; PID pid(&temperature, &output, &target, 75, 50, 0); /* Profile */ const double Rstart = 3.0; // max ramp-up rate to Ts_min const double Rup = 3.0; // max ramp-up rate from Ts_max to Tpeak const double Rdown = 6.0; // max ramp-down rate from Tpeak to Ts_max const double Ts_min = 150.0; const double Ts_max = 190.0; const int ts = 120; // pre-heat duration const double Tpeak = 232.0; const double TL = 220.0; const int tL = 50; // keep above TL for tL const double Tend = 80.0; /* State Machine */ int state; unsigned long nextOff, nextCheck, meltCount; double slope, destination; void setup() { digitalWrite(HEATER, false); pinMode(HEATER, OUTPUT); digitalWrite(BUTTON, true); // pull-up pinMode(BUTTON, INPUT); Serial.begin(9600); sensorSetup(); pid.SetOutputLimits(0, 1000); // 1000 milliseconds pid.SetMode(AUTO); lcd.begin(16, 2); // cols, rows lcd.clear(); lcd.print("Reflow Oven"); delay(2000); state = nextOff = nextCheck = 0; } /* * 0: waiting for button press. * 1: ramp-up to 150, slope rate between 1.0/sec and 3.0/sec. * 2: preheat to 190, for 60 and 120 seconds. * 3: heat to 232 and keep 232, over 220 for 30 to 60 seconds. * 5: cool down to under 50 * 6: fail */ void loop() { unsigned long now; now = millis(); /* state 0: wait for button presss. */ if (digitalRead(BUTTON) == LOW) { while (digitalRead(BUTTON) == LOW) delay(100); delay(100); if (state == 0) state = 9; else state = 0; nextOff = nextCheck = now; } /* Heater */ if (now < nextCheck) { /* PWM on 1Hz */ if (now < nextOff) digitalWrite(HEATER, true); else digitalWrite(HEATER, false); } else { /* * Check */ nextCheck += 1000; // 1 second temperature = sensorValue(); if (temperature == TEMP_ERROR) state = 6; /* Check if the state changes. */ switch (state) { case 9: state = 1; pid.Reset(); target = temperature; slope = Rstart; destination = Ts_min; break; case 1: if (temperature >= Ts_min) { state = 2; slope = (Ts_max - Ts_min) / ts; destination = Ts_max; } break; case 2: if (temperature >= Ts_max) { state = 3; slope = Rup; destination = Tpeak; meltCount = 0; } break; case 3: if (temperature >= TL) { state = 4; meltCount = 0; } break; case 4: if (++meltCount > tL) { state = 5; slope = Rdown; destination = -273.0; } break; case 5: if (temperature <= Tend) state = 0; break; } /* Next target */ switch (state) { case 1: case 2: case 3: target += slope; if (target > destination) target = destination; break; case 5: target -= slope; if (target < destination) target = destination; break; } /* Heater control value */ switch (state) { case 1: case 2: case 3: case 4: case 5: pid.Compute(); nextOff = now + output; break; case 0: case 6: default: nextOff = 0; } /* LCD display */ lcd.clear(); switch (state) { case 0: lcd.print("Press to start"); break; case 1: lcd.print("Ramp up"); break; case 2: lcd.print("Pre-heat"); break; case 3: lcd.print("Heat up"); break; case 4: lcd.print("Melted"); break; case 5: lcd.print("Cool down"); break; case 6: lcd.print("Fail"); break; } lcd.setCursor(0, 1); lcd.print(output); lcd.print(' '); if (temperature != TEMP_ERROR) lcd.print(temperature); SerialReceive(); SerialSend(); } } /******************************************** * Serial Communication functions / helpers ********************************************/ union { // This Data structure lets byte asBytes[24]; // us take the byte array float asFloat[6]; // sent from processing and } // easily convert it to a foo; // float array // getting float values from processing into the arduino // was no small task. the way this program does it is // as follows: // * a float takes up 4 bytes. in processing, convert // the array of floats we want to send, into an array // of bytes. // * send the bytes to the arduino // * use a data structure known as a union to convert // the array of bytes back into an array of floats // the bytes coming from the arduino follow the following // format: // 0: 0=Manual, 1=Auto, else = ? error ? // 1-4: float setpoint // 5-8: float input // 9-12: float output // 13-16: float P_Param // 17-20: float I_Param // 21-24: float D_Param void SerialReceive() { // read the bytes sent from Processing int index = 0; byte Auto_Man = -1; while (Serial.available() && index < 25) { if (index == 0) Auto_Man = Serial.read(); else foo.asBytes[index-1] = Serial.read(); index++; } // if the information we got was in the correct format, // read it into the system if (index == 25 && (Auto_Man == 0 || Auto_Man == 1)) { target = double(foo.asFloat[0]); if (Auto_Man == 0) // * only change the output if we are in { // manual mode. otherwise we'll get an output = double(foo.asFloat[2]); // output blip, then the controller will } // overwrite. double p, i, d; // * read in and set the controller tunings p = double(foo.asFloat[3]); // i = double(foo.asFloat[4]); // d = double(foo.asFloat[5]); // pid.SetTunings(p, i, d); // if(Auto_Man==0) pid.SetMode(MANUAL);// * set the controller mode else pid.SetMode(AUTO); // } Serial.flush(); // * clear any random data from the serial buffer } // unlike our tiny microprocessor, the processing ap // has no problem converting strings into floats, so // we can just send strings. much easier than getting // floats from processing to here no? void SerialSend() { Serial.print("PID "); Serial.print(target); Serial.print(" "); Serial.print(temperature); Serial.print(" "); Serial.print(output); Serial.print(" "); Serial.print(pid.GetP_Param()); Serial.print(" "); Serial.print(pid.GetI_Param()); Serial.print(" "); Serial.print(pid.GetD_Param()); Serial.print(" "); if (pid.GetMode() == AUTO) Serial.println("Automatic"); else Serial.println("Manual"); } }}} == `SPI.h` == {{{ #!c #ifndef __SPI_H__ #define __SPI_H__ #include "WProgram.h" class SPI_Master_Class { public: static void begin(int slaveselecter); void enable(int slaveselecter); void disable(); byte write_and_read(byte data) const; void write(byte data) const; byte read() const; private: static boolean initialized_; static const int SS = 10; static const int MOSI = 11; static const int MISO = 12; static const int SCK = 13; static int enabled_; }; extern SPI_Master_Class SPI_Master; #endif //__SPI_H__ }}} == `SPI.cpp` == {{{ #!c #include "SPI.h" boolean SPI_Master_Class::initialized_ = false; int SPI_Master_Class::enabled_ = -1; void SPI_Master_Class::begin(int slaveselecter) { if (!initialized_) { initialized_ = true; enabled_ = -1; pinMode(SS, OUTPUT); // Must be set as OUTPUT before SPE is asserted. pinMode(MOSI, OUTPUT); pinMode(MISO, INPUT); digitalWrite(MISO, HIGH); // Pull-up pinMode(SCK, OUTPUT); SPCR = (1<= 0) { digitalWrite(enabled_, HIGH); enabled_ = -1; } } byte SPI_Master_Class::write_and_read(byte data) const { SPDR = data; while (!(SPSR & (1<