昨日試した扇風機遊びを少し進め、温度制御を付けてみたい。
おあつらえ向きに、最初に買った「Seeedstudio SIDEKICK BASIC KIT」には、サーミスタが入っている。これで温度を検知して、温度ごとに扇風機の回転を制御するわけだ。値の変更などはETHERNET SHIELD 2をつなぎ、ウェブインターフェイスで行うと面白いだろう。
以前にも試したが、しかし、このキット付属のサーミスタは情報が少なくて困る。そこで、ちゃんと計算してそれなりの温度測定をしてみたい。以前は測りたい温度近傍の特性値をテーブルから拾って2次式で近似しただけであった。
スペックシートによれば、-55℃で10583.3Ω、+125℃で1.277Ωになる、とある。常温付近では0℃で190.07Ω、40℃で24.87Ωだ。もしサーミスタ直付けでArduinoから5V給電すれば、-55℃のときは0.47mAだが、0℃で26.3mA、40℃で201mAと、Arduinoに流せる電流の20mAを大きく逸脱してしまう。
40度付近でも大丈夫なように、抵抗を付加しなければならない。40℃の時に流したい電流を5mAと仮定すれば、
……というような計算で975Ωくらいつなげておけばよいということになる。
しかし、手持ちの抵抗は、キットに入っていた330Ω・1kΩ・10kΩの3種類しかないから、これで賄うしかない。
一番大きい抵抗は10kΩだから、これで計算しなおすと、40℃の時で
5V / (24.87Ω + 10kΩ) = 0.5mA
2番目は1kΩ、同じく40℃の時で
5V / (24.87Ω + 1kΩ) = 4.88mA
同様に3番目の330Ωだと
5V / (24.87Ω + 330Ω) = 14mA
…ということになる。間をとって、1kΩで回路を作ってみよう。
次に、サーミスタは抵抗から温度を知る。一方、Arduinoは電圧からデジタル値を知る。したがって、電圧からまず抵抗値を知らなければその先の温度測定に進めない。
先の電流調整用の1kΩ抵抗をR0として含め、現在のサーミスタの抵抗値を知るには、一般に式は次のようになろう。
これをソースコードに表せば、次のようになる。
const float v0 = 5.0, r0 = 1000.0; // Arduino +5Vと電流調整抵抗1kΩ
const int resolution = 1024; // アナログ入力の分解能
int srcVal = 0;
float vt = 0.0, rt = 0.0;
srcVal = analogRead(THERMISTOR);
vt = srcVal * (v0 / (resolution - 1));
rt = (v0 * r0 - vt * r0) / vt;
次に、知った抵抗値から温度を知る計算である。
サーミスタの抵抗は対数特性を持っているので、関数は対数モデルになる。また、サーミスタの特性を表す重要な値は「B値」と呼ばれるものと、Ta(温度)、R0(抵抗)の3つで、それらはスペックシートに書いてある。
TaとR0については、「温度がTaのとき、抵抗はR0になる」というものだ。私の手元のサーミスタは、スペックシートによると、
Ta = 25℃
R0 = 50kΩ
……とある。
B値は「抵抗の対数と、温度の比」である。これもスペックシートに関係式が書いてある。
これらのスペックシートの値と、知った抵抗値から温度を求める式は、次のようになる。
これを、先の抵抗値を知る部分と併せて、Arduinoで使える関数にすれば、このようになろうか。
float tempMesure(){
const float B = 4350.0, Ta = 25.0, Rt0 = 50000.0; // MF11-503Kスペックシート記載
const float K = 273.15; // 熱力学温度の定数
const float v0 = 5.0, r0 = 1000.0; // Arduino +5Vと電流調整抵抗1kΩ
const int resolution = 1024; // アナログ入力の分解能
int srcVal = 0;
float vt = 0.0, rt = 0.0;
srcVal = analogRead(THERMISTOR);
vt = srcVal * (v0 / (resolution - 1));
rt = (v0 * r0 - vt * r0) / vt;
return(1.0 / (log(rt / Rt0) / B + 1.0 / (Ta + K)) - K);
}
これを用いて、少し真面目っぽい温度測定スケッチを書けば、次のようになる。
//
// thermistor2temp.ino
// サーミスタでわりと真面目に温度を測る。
// 27.7.5(日)
// 佐藤俊夫
//
const int THERMISTOR = 1;
void setup()
{
Serial.begin(9600);
}
void loop()
{
float t = 0.0;
t = tempMesure();
Serial.print("Temp = ");
Serial.print(t);
Serial.println("C");
delay(500);
}
float tempMesure(){
const float B = 4350.0, Ta = 25.0, Rt0 = 50000.0; // MF11-503Kスペックシート記載
const float K = 273.15; // 熱力学温度の定数
const float v0 = 5.0, r0 = 1000.0; // Arduino +5Vと電流調整抵抗1kΩ
const int resolution = 1024; // アナログ入力の分解能
int srcVal = 0;
float vt = 0.0, rt = 0.0;
srcVal = analogRead(THERMISTOR);
vt = srcVal * (v0 / (resolution - 1));
rt = (v0 * r0 - vt * r0) / vt;
return(1.0 / (log(rt / Rt0) / B + 1.0 / (Ta + K)) - K);
}
次に、温度に応じて扇風機の回転数が変わるようにしよう。先日作った「ソリッドステート・リレーモジュール」を遺憾なく使用する。
ソリッドステート・リレーモジュール
このモジュールの内部には、秋月電子の「ソリッド・ステート・リレー(SSR)キット 25A(20A)タイプ」が組み付けてある。
27℃で1/fゆらぎエフェクト、28℃で弱風、29℃で強風、とでもしてみようか。1/fゆらぎエフェクトには、先週書いた関数をそのままコピペする。
//
// thermistor2windFan.ino
// サーミスタで温度を測り、扇風機を制御する。
// 27.7.5(日)
// 佐藤俊夫
//
const int THERMISTOR = 1, WINDFAN = 9;
void setup()
{
Serial.begin(9600);
pinMode(WINDFAN, OUTPUT);
}
void loop()
{
const int half = 128, full = 255;
const float lowTemp = 27.0, midTemp = 28.0, highTemp = 29.0;
float t = 0.0, f = 0.0;
t = tempMesure();
Serial.print("temp=");
Serial.println(t);
if(t > highTemp){
analogWrite(WINDFAN, full);
}else
if(t > midTemp){
analogWrite(WINDFAN, half);
}else
if(t > lowTemp){
f = f1Fluctuation();
analogWrite(WINDFAN, f);
}else{
analogWrite(WINDFAN, 0);
}
delay(20);
}
float tempMesure(){
const float B = 4350.0, Ta = 25.0, Rt0 = 50000.0; // MF11-503Kスペックシート記載
const float K = 273.15; // 熱力学温度の定数
const float v0 = 5.0, r0 = 1000.0; // Arduino +5Vと電流調整抵抗1kΩ
const int resolution = 1024; // アナログ入力の分解能
int srcVal = 0;
float vt = 0.0, rt = 0.0;
srcVal = analogRead(THERMISTOR);
vt = srcVal * (v0 / (resolution - 1));
rt = (v0 * r0 - vt * r0) / vt;
return(1.0 / (log(rt / Rt0) / B + 1.0 / (Ta + K)) - K);
}
int f1Fluctuation(){
static float x = 0.1;
if(x < 0.5){
x = x + 2 * x * x;
}
else {
x = x - 2 * (1.0 - x) * (1.0 - x);
}
if(x < 0.05 || x > 0.995){
x = random(10, 90) / 100.0;
}
return((int)(x * 255));
}
回路図など、描くも愚かというか、こんな簡単なものである。
さて次に、ETHERNET SHIELD 2をつなぎ、この扇風機をWeb化する。昨日買っておいた「クリアランス確保用ピンソケット」が役に立つ。
組み付けるとこんな感じである。回路はネットにつながない場合と同じでよい。
こんなインターフェイスで動かす。
設定した温度になると扇風機が回る。
スケッチはこのようになる。
//
// web2thermistor_windFan.ino
// サーミスタで温度を測り、扇風機を制御する。
// 27.7.5(日)
// 佐藤俊夫
//
#include <SPI.h>
#include <Ethernet2.h>
//
byte mac[] = {
0x90, 0xA2, 0xDA, 0x0F, 0xF6, 0x74
};
IPAddress ip(192, 168, 1, 129);
EthernetServer SERVER(80);
EthernetClient CLIENT;
const int THERMISTOR = 1, WINDFAN = 9;
//
void setup()
{
Ethernet.begin(mac, ip);
SERVER.begin();
Serial.begin(9600);
pinMode(WINDFAN, OUTPUT);
}
void loop()
{
const int half = 128, full = 255;
static float lowTemp = 27.0, midTemp = 28.0, highTemp = 29.0;
float t = 0.0, f = 0.0;
// Webサーバの動作
char c;
String rstr = "";
CLIENT = SERVER.available();
if (CLIENT) {
while (CLIENT.connected()) {
if (CLIENT.available()) {
c = CLIENT.read();
rstr += c;
if(rstr.endsWith("\r\n")){
break;
}
}
}
if(rstr.indexOf("low=") >= 0){
lowTemp = rstr.substring(rstr.indexOf("low=") + 4, rstr.indexOf("&green=")).toInt();
midTemp = rstr.substring(rstr.indexOf("mid=") + 4, rstr.indexOf("&blue=")).toInt();
highTemp = rstr.substring(rstr.indexOf("high=") + 5, rstr.indexOf("&end")).toInt();
}
rstr = "";
sendform(lowTemp, midTemp, highTemp);
delay(1);
// close the connection:
CLIENT.stop();
}
// 温度測定
t = tempMesure();
Serial.print("temp=");
Serial.println(t);
if(t > highTemp){
analogWrite(WINDFAN, full);
Serial.println("high");
}else
if(t > midTemp){
analogWrite(WINDFAN, half);
}else
if(t > lowTemp){
f = f1Fluctuation();
analogWrite(WINDFAN, f);
}else{
analogWrite(WINDFAN, 0);
}
delay(20);
}
float tempMesure(){
// Seeedstudio SIDEKICK BASIC KIT付属のサーミスタ「MF11-503K」で
// 温度を測る。
const float B = 4350.0, Ta = 25.0, Rt0 = 50000.0; // MF11-503Kスペックシート記載
const float K = 273.15; // 熱力学温度の定数
const float v0 = 5.0, r0 = 1000.0; // Arduino +5Vと電流調整抵抗1kΩ
const int resolution = 1024; // アナログ入力の分解能
int srcVal = 0;
float vt = 0.0, rt = 0.0;
srcVal = analogRead(THERMISTOR);
vt = srcVal * (v0 / (resolution - 1));
rt = (v0 * r0 - vt * r0) / vt;
return(1.0 / (log(rt / Rt0) / B + 1.0 / (Ta + K)) - K);
}
int f1Fluctuation(){
// 間欠カオス法により0~255の間で1/fゆらぎを生成して返す。
static float x = 0.1;
if(x < 0.5){
x = x + 2 * x * x;
}
else {
x = x - 2 * (1.0 - x) * (1.0 - x);
}
if(x < 0.05 || x > 0.995){
x = random(10, 90) / 100.0;
}
return((int)(x * 255));
}
void sendform(float lowTemp, float midTemp, float highTemp){
// フォームを送る。
char* formFirstHalf[] = {
"<html>",
" <head>",
" <meta charset=\"utf-8\">",
" </head>",
" <body bgcolor='#ddddff'>",
" <center>",
" <h1>Arduino ネット扇風機</h1>",
" <form method='GET'>",
" <table>",
" <tr>",
" <th>ゆらぎ送風温度</th>",
" <th>弱風温度</th>",
" <th>強風温度</th>",
" </tr>"
}; // 14 num.
char* formSecondHalf[] = {
" </table>",
" <input type='hidden' name='end'>",
" <input type='submit' value='セット'>",
" </form>",
" </center>",
" </body>",
"</html>"
}; // 7 num.
int i = 0;
CLIENT.println("HTTP/1.1 200 OK");
CLIENT.println("Content-Type: text/html");
CLIENT.println("Connection: close");
CLIENT.println();
CLIENT.println("<!DOCTYPE HTML>");
for(i = 0; i < 14; i++){
CLIENT.println(formFirstHalf[i]);
}
CLIENT.println(" <tr>");
CLIENT.println(" <td>");
CLIENT.println(" <input type='text' name='low' size='6em' value =");
CLIENT.print(lowTemp);
CLIENT.println(">");
CLIENT.println(" </td>");
CLIENT.println(" <td>");
CLIENT.println(" <input type='text' name='mid' size='6em' value =");
CLIENT.print(midTemp);
CLIENT.println(">");
CLIENT.println(" </td>");
CLIENT.println(" <td>");
CLIENT.println(" <input type='text' name='high' size='6em' value =");
CLIENT.print(highTemp);
CLIENT.println(">");
CLIENT.println(" </td>");
CLIENT.println(" </tr>");
for(i = 0; i < 7; i++){
CLIENT.println(formSecondHalf[i]);
}
}