wiki:HomeReflow

Version 3 (modified by sgk, 12 years ago) ( diff )

--

自作リフロー

とりあえず、いきなりスケッチを置いておきます。 回路図とかはそのうち。

材料

案外お金がかかるなあ。 キットにしたら幾らになるだろう? 買う人少ないだろうし。

課題

  • 温度上昇が遅い。
  • ボタンひとつで操作性が悪い。

残り作業

  • ブザーが欲しい。開始・終了で鳴らす。
  • 冷めてきて動かしても大丈夫になったことを知らせたい。
  • オーブンの筐体の隙間にグラスウールまたはロックウールを詰め込んで試してみる。
  • それでだめなら、もう少し強力なオーブンを探す。
  • PCをコンソールにするアプリが欲しい。

外部ライブラリ

ReflowOven.pde

パラメータは、鉛フリーのプロファイルに合わせてあります。

#include <LiquidCrystal.h>
#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

#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

#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<<SPE)|(1<<MSTR);  // SPE: SPI Enable; MSTR: Master
    byte garbage;
    garbage = SPSR;
    garbage = SPDR;
  }

  if (slaveselecter != SS)
    pinMode(slaveselecter, OUTPUT);
  digitalWrite(slaveselecter, HIGH);  // Disable
}

void
SPI_Master_Class::enable(int slaveselecter) {
  disable();
  digitalWrite(slaveselecter, LOW);
  enabled_ = slaveselecter;
}

void
SPI_Master_Class::disable() {
  if (enabled_ >= 0) {
    digitalWrite(enabled_, HIGH);
    enabled_ = -1;
  }
}

byte
SPI_Master_Class::write_and_read(byte data) const {
  SPDR = data;
  while (!(SPSR & (1<<SPIF)))
    ;
  return SPDR;
}

void
SPI_Master_Class::write(byte data) const {
  write_and_read(data);
}

byte
SPI_Master_Class::read() const {
  return write_and_read(0x00);
}

SPI_Master_Class SPI_Master;
Note: See TracWiki for help on using the wiki.