Arduinoで温度調節器を作った話
追記(2019/10/12):I制御の理解が間違ってることに気づきました。一定時間τ内でのerrorを足し合わせるべきだった。修正する気が起きないのでプログラムは参考にしないでください。
ちょっと前の話ですが、鳥人間してた頃にフレーム班という班でCFRPの成型・加工を主にやってました。
130度で硬化するカーボンシート、通称プリプレグという高価な材料を触らせてもらっていました。
そこで熱硬化性のエポキシを固めるのに電気炉で加熱するわけですが、温度を急激に変えると強度が落ちてしまうので3時間くらい温度を測りながら電圧を上げ下げするわけです、しかも140度を超えると逆に溶けてしまって使い物にならなくなるという結構シビアな作業でした。長時間だし単価も高いので精神的にも肉体的にもキツい。
そこで温度管理を自動化しようと案が挙がるわけです。
丁度ANOVAを買うか悩んでた時期だったのでついでにこれを利用して自動でローストビーフや温泉卵を作れたらいいなと思ってました。
そうしてこのブログに出会いました。
で、この通りにやってうまくいったので温度計を130度まで耐えれるやつに変えてカーボン焼きをやってみたんですが、流れる電流がでかすぎたため発熱しまくって、コードが焼けたり、抵抗が破裂したり、プラネジが溶けたりしました。発熱しない導線を探す必要があります。ヒートシンクだけでは放熱しきれないです。
↓ドライヤーでテストする様子
これに懲りてANOVAを購入することを決意しました。
下に完成したコードを自己満で載せます、一応後輩に引き継ぎしたけど他にやることいっぱいあるから使ってくれなさそう。
ソリッドステートリレーを使って電気的にオンにしたりオフにしたりすることで温度調節してる。
元コードはPID制御だったけど環境的にD要らなそうだったから排除した。
1秒あたりに何ミリ秒電流を流すかを決める式は
Output = Kp * error + Ki * integral
Kp,Ki :係数(変位量を調整できる)
Output :出力値
error :目標値に対する現在温度との差
integral:errorの和
モニタに出力するのは(現在温度, 目標温度, 電流を流す時間(ms))
*Arduinoコード
Arduinoはarduino-1.7.10.org-windowsを使いました。
以下コード
/*
* 温度変化の流れ +2℃/1分のペースで上昇→90℃で30分キープ→+2℃/1分のペースで上昇→130℃で1時間キープ→停止
* シリアルモニタに表示される3つの値(現在温度, 目標温度, 1秒あたり何ミリ秒オンにしたか)
* 温度計はMAX31855
*/
#define RelayPin 2 //SSRに接続するピンがpin2であると指定する
//max6675
#include "SPI.h"
#define VCC 8
#define GND 9
#define SLAVE 10
int value ;
int stage, time_1 = 0, time_2 = 0, time_3 = 0, time_4 = 0;
float a_in, temp_c ;
double Setpoint, Input, Output, error = 0, integral = 0;
const double Kp = 500.0, Ki = 5.0;//PI制御の係数。Kpを大きくすると温度変化が大きくなる。
int WindowSize = 1000; //時間枠(ミリ秒)。この時間枠の中で1回ON/OFFを行う。
void setup()
{
Serial.begin(9600) ; // パソコン(ArduinoIDE)とシリアル通信の準備を行う
pinMode(RelayPin, OUTPUT); //Relay PINを出力に
stage = 1 ;
//max6675
#ifdef GND
pinMode(GND, OUTPUT);
digitalWrite(GND, LOW);
#endif
#ifdef VCC
pinMode(VCC, OUTPUT);
digitalWrite(VCC, HIGH);
#endif
pinMode(SLAVE, OUTPUT);
digitalWrite(SLAVE, HIGH);
Serial.begin(9600);
SPI.begin();
SPI.setBitOrder(MSBFIRST);
SPI.setClockDivider(SPI_CLOCK_DIV4);
SPI.setDataMode(SPI_MODE0);
}
void loop()
{
if(Serial.available()>0){
Serial.read();
}
digitalWrite(SLAVE, LOW); // Enable a chip
value = SPI.transfer(0x00) << 8; // read High byte
value |= SPI.transfer(0x00); // read Low byte
digitalWrite(SLAVE, HIGH); // Desavle a chip
temp_c = (value >> 3) * 0.25;
switch (stage) {
//---------------------------------------------------------------------
case 1:// stageが1(90℃まで加熱)のとき実行される
time_1 += 1 ;
Setpoint = 25 + time_1/30 ;//30秒に+1℃の間隔で目標温度を変更。
//90℃になるとstage2に移行。
if( temp_c > 90){
stage = 2;
}
break;
//----------------------------------------------------------------------
case 2:// stageが2(90℃でキープ)のとき実行される
time_2 += 1 ;
Setpoint = 90 ;
// stage2になってから30分経つとstage3に移行。
if(time_2 == 1800){
stage = 3;
}
break;
//------------------------------------------------------------------------
case 3: // stageが3(130℃まで加熱)のとき実行される
time_3 += 1 ;
Setpoint = 90 + time_3/30;//30秒に+1℃の間隔で目標温度を変更。
if( temp_c > 130){//130℃になるとstage4に移行。
stage = 4;
}
break;
//---------------------------------------------------------------------------
case 4:// stageが4(130℃でキープ)のとき実行される
time_4 += 1 ;
Setpoint = 130 ;
// stage4になってから60分経つとstage5に移行。
if(time_4 == 3600){
stage = 5;
}
break;
//------------------------------------------------------------------------
case 5:// stageが5のとき実行される
Setpoint = 25 ;//25℃(外気温度)に戻す。
break;
//------------------------------------------------------------------------
}
//PIの計算
Input = temp_c;//入力値
error = Setpoint - Input;//偏差
integral += error;//積分差
Output = Kp * error + Ki * integral ;// 出力値
Output = min(max(0,Output), WindowSize);//出力値を1000mm秒の範囲に直す。
//SSRの制御
digitalWrite(RelayPin, HIGH);
delay(Output);//オン
digitalWrite(RelayPin, LOW);
delay(WindowSize-Output);//オフ
char t1[6]; // 温度表示の文字列の内容は 0.0 ~ 100.0 の可能性があるため、
// 最低でも 5 文字+文字列終端記号の 1 文字分を確保しておく
dtostrf( temp_c, 3, 1, t1 ); // 温度の数値を文字列に変換します。
Serial.print( t1 );
Serial.print(",");
char t2[6];
dtostrf( Setpoint, 3, 1, t2 ); // 温度の数値を文字列に変換します。
Serial.print( t2 );
Serial.print(",");
char t3[6];
dtostrf( Output, 3, 1, t3 ); // Outputの数値を文字列に変換します。
Serial.println( t3 );
}
*自動グラフ描画(processingコード)
Processingと連動させて使うときはarduino側のシリアルモニタは消しとかないと使えないので注意。
ピクセル数的にグラフの横軸が全然取れなくて最新30分しか表示されないので改善したい。
↓こんな感じに描画されます
(青:1秒あたりのオンにする時間、赤:目標温度、白:現在温度)
以下コード
import processing.serial.*;
Serial arduino;
int size = 1800;
float d1 = new float[size];
float d2 = new float[size];
float d3 = new float[size];
int top = 25;
int bottom = 725;
int left = 5;
int right = 1805;
float highest = 140.0;
float lowest = 0.0;
void setup(){
size( 1850, 730 );
String arduinoPort = Serial.list()[0];
arduino = new Serial( this, arduinoPort, 9600 );
arduino.write( "r" );
arduino.bufferUntil( ' ' );
}
void draw(){
int i, j;
float drawData = new float[2];
background( 0 );
noFill();
stroke( 0, 255, 0 );
strokeWeight( 1 );
rect( left, top, right-left, bottom-top );
for( i=60; i<right-left; i+=60 ){
line( i+left, top, i+left, bottom );
}
for( i=50; i<bottom-top; i+=50 ){
line( left, i+top, right, i+top );
}
stroke( 255, 255, 255 );
for( i=0; i<size-1; i++ ){
for( j=0; j<2; j++ ){
drawData[j] = d1[i+j];
if( drawData[j] > highest ){
drawData[j] = highest;
}else if( drawData[j] < lowest ){
drawData[j] = lowest;
}
}
line( right-i, bottom-(drawData[0]-lowest)/(highest-lowest)*(bottom-top),
right-(i+1), bottom-(drawData[1]-lowest)/(highest-lowest)*(bottom-top) );
}
stroke( 255, 0, 0 );
for( i=0; i<size-1; i++ ){
for( j=0; j<2; j++ ){
drawData[j] = d2[i+j];
if( drawData[j] > highest ){
drawData[j] = highest;
}else if( drawData[j] < lowest ){
drawData[j] = lowest;
}
}
line( right-i, bottom-(drawData[0]-lowest)/(highest-lowest)*(bottom-top),
right-(i+1), bottom-(drawData[1]-lowest)/(highest-lowest)*(bottom-top) );
}
stroke( 0, 0, 255 );
for( i=0; i<size-1; i++ ){
for( j=0; j<2; j++ ){
drawData[j] = d3[i+j];
if( drawData[j] > highest ){
drawData[j] = highest;
}else if( drawData[j] < lowest ){
drawData[j] = lowest;
}
}
line( right-i, bottom-(drawData[0]-lowest)/(highest-lowest)*(bottom-top),
right-(i+1), bottom-(drawData[1]-lowest)/(highest-lowest)*(bottom-top) );
}
text( nf(highest,2,1), right+5, top+8 );
text( nf(lowest,2,1), right+5, bottom );
text( "temperture: "+nf(d1[0],2,1)+"°C ", left, top-5 );
}
void serialEvent( Serial arduino ){
String s = arduino.readStringUntil( ' ' );
arduino.write( "r" );
println(s);
for( int i=size-1; i>0; i-- ){
d1[i] = d1[i-1];
d2[i] = d2[i-1];
d3[i] = d3[i-1];
}
float temp[] = float(split(s, ','));
d1[0] = temp[0];
d2[0] = temp[1];
d3[0] = temp[2]/10;
}