プログラミングを何読んで覚えたんだったか

投稿日:

 美しいお姉さんが電車の中で「たのしいプログラミング Pythonではじめよう」なんつー本を読み耽っているのを見た。そのお姉さんはどうもプログラマなどには見えない。カジュアルな感じのお姉さんが、カジュアル~、に、そういう本を読んでいる。ああ、時代だなあ、と思う。

 そういえば、私自身はプログラミングの入門本というものを読んだことがないことに気づいた。BASICで8ビットプログラミングをしていた頃は入門本などなくても、マシンのリファレンス・マニュアルで十分だったし、機械語やアセンブラなどは命令表を見てプログラムを作ったものだし、それ以降も、例えばCだったらカーニハンとリッチーの「プログラミング言語C」、C++だったらストラストラップの「プログラミング言語C++」、Javaだったらゴスリングの「プログラミング言語Java」など、それらの言語の開発者が書いたものを参考にして覚えてきた。

 今はほとんどネット上のリファレンスで間に合ってしまう。だからPHPや、あるいはCSSとかHTMLの参考書など1冊も持っていない。

 そうなってしまった理由は、多分古い時代からプログラミングに親しんできたからだと思う。世の中の進歩に合わせて、自分も少しづつ進歩してきたので、入門本を必要としなかったのだ。

 だから、私のやり方はこれから入門する若い人には全く参考にならないということになる。

 ということは、さしずめ私など、「参考にならない、何の役にも立たぬ先輩」ということになるわけだ。後輩の指導には向かぬ。ハァ。

Raspberry Piで温度を測ろう

投稿日:

 以前、Arduinoのアナログ入力にサーミスタをつなぎ、温度計にして遊んだ最近はLCDディスプレイに温度を表示してみたり、なかなか面白い。

 一方、Raspberry PiはWebによくなじむ。ApacheもPHPも走る。

IMG_3317 それで、先週宮城大学小島研究室のサイトで公開しておられるADCアクセスのためのソースコードを拝借して遊んだが、これを使ってWeb温度計にしてみた。

 まず、ブレッドボードにAD変換ICのMCP3208と、サーミスタ、分圧用の抵抗1kΩを置き、適切に配線する。これには、以前Arduinoで遊ぶために買った「Seeedstudio Arduino Sidekick Basic Kit」に入っていたMF11-503Kというサーミスタをそのまま使う。

 それから、C++でこんなプログラムを書く。先週も書いたが、宮城大学小島研究室のサイトで公開しておられるコードは、Raspberry Pi 1では何の支障もなく動くが、Raspberry Pi 2 Model Bではそのままでは動かない。一部の型キャストなどを変更する必要がある。それについては先週のエントリで記してある。

//
//    tempMesure.cpp
//      佐藤俊夫
//      Sun Sep  6 13:57:53 JST 2015
//      サーミスタMF11-503Kと1kΩ抵抗を直列につなぎ、3.3Vを印加して、
//      その間から分圧をとって温度を測る。
//      宮城大学小島研究室でウェブ公開しておられるソースコードを拝借した。
//      コンパイル
//        cc -lm tempMesure.cpp raspSPI.o raspADC.o -o tempMesure
//
#include <stdio.h>
#include <math.h>
#include "raspADC.h"

#define ADC_CHIP  ADC_3208
float tempMesure(int srcVal);

int main() {
  ADC adc;

  adc.init("/dev/spidev0.1", ADC_CHIP);
  printf("%2.2f\n", tempMesure(adc.get(0)));
}

float tempMesure(int srcVal){
  const float B = 4350.0, Ta = 25.0, Rt0 = 50000.0;  //  MF11-503Kスペックシート記載
  const float K = 273.15;  //  熱力学温度の定数
  const float v0 = 3.3, r0 = 1000.0;  //  Arduino +5Vと電流調整抵抗1kΩ
  const int resolution = 4096;  //  アナログ入力の分解能
  float vt = 0.0, rt = 0.0;

  vt = srcVal * (v0 / (resolution - 1));
  rt = (v0 * r0 - vt * r0) / vt;
  return(1.0 / (log(rt / Rt0) / B + 1.0 / (Ta + K)) - K);
}

 これは単に、サーミスタで計った現在の温度を標準出力に書くだけだ。

 これを「popen();」で開けば、PHPで読める。簡単である。

<html>
 <head>
  <meta name="Editor" content="vim">
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
  <link rel="icon" href="favicon.ico" type="image/x-icon" />
  <link rel="Shortcut Icon" type="image/x-icon" href="favicon.ico" />
  <title>PHPで温度計</title>
  <meta http-equiv="Keyword" content="佐藤俊夫, 佐藤, 俊夫, SATOTOSHIO, SatoToshio, sato, toshio, Raspberry Pi, Raspberry Pi 2 Model B">
 </head>
 <body bgcolor="#888888">
<body>
  <center>
  <h1><img src="raspberry_pi.png" width="50px" align="middle"/>Raspberry Pi + PHPで温度計</h1>
  <hr>
  </center>
<?
ini_set( 'display_errors', 1 );
$process = popen("sudo /home/toshio/SPI_ADC/tempMesure", "r");
$temp = fgets($process);
?>
<center>
<table border=1 width="120px">
<tr> <th>0</th><th>10</th> <th>20</th> <th>30</th> <th>40</th> <th>50</th> <th>60</th></tr>
<tr><td colspan = 7>
<table>
<tr>
<td bgcolor="red" width="<?print $temp * 2?>px"><?print $temp?>℃</td&t;</tr></table>
</tr>
</table>
</td></tr></tr></table>
</center>
</body>
</html>

raspberry_tempMesure
 テーブルの幅を温度計のアルコール柱に見立てて、赤い帯で表すようにしてみた。


 Arduinoでも同じようなことはできるが、Arduinoで難しいのは、こういうふうに、無駄に画像を配置したり、少し分量の多いHTMLを書いたりすることだ。だが、こんなことはRaspberry Piには造作もなくできてしまう。

 反面、Arduinoはアナログ入力が直接可能だが、Raspberry Piで同じことをしようとすると、アナログ入力にAD変換ICを準備し、これとの通信にもいろいろと研究の上で取り組む必要がある。簡単で刺激的な目的のために少しプロトタイピングをしてみようと考えている多くのクリエイターがなしうるところではない。

 入門書籍「Raspberry Piをはじめよう」にもそうしたことが少し書かれてある。

暑熱をばひとつ

投稿日:

 黙々と呟き続ける我が作品、「ネット便器」である。いや、便器そのものを作ったのはINAX社であるから、私の作品と言うこともあるまいが、まあ、こんな下品かつ無意味なものを作ることができるのもDIY、というかMAKERS精神なればこそ、である。

ツイートする私の作品「ネット便器」(笑)

 ちなみに、こういう手作りは、数年前からDIYとは言わず、Makersムーブメントと言うようになったのだそうな。DIYとの違いは、ざっくり言えばネットのあるなしである。

 それにしても、暑い。暑熱である。秋とは暦ばかり、なんて暑いんだ。

 私の家には温度計がないのだが、今日のような折ふし、たまさかには室温が知りたくもなる。知ったところで「うわっ32度だってよ余計に暑くなったゾなんてこったいッ!」などとうわ言のようにうそぶきつつ興奮する以外にないのだが、それでもやっぱり知りたいのである。

 エアコンのリモコンに設定温度とは別に温度計がついており、一応それで用は足りているのだが、1℃単位のザックリした温度計なので、不満である。

 こんなこともあろうかと、私こと佐藤は常々周到怠りない。我が作品「ネット便器」は、気温をツイートできるのだ!!トイレに行き、便器のフタを開け閉めしてから部屋に戻り、ツイッターを見ると、自宅の気温がだいたいわかるわけである。おお、なんとスンバラシイ。Arduino万歳。とっとと内紛やめて楽しくやろうぜベイベー!!

 ……。

 めんどくさい。

 だいたい、気温ぐらいその場でわかるようにすべきではなかったのか。便器のフタを開け閉めしてツイッター見なきゃ気温が分からん家なんて、どうなっとるんだ一体ッ!?。ネット便器の本体に気温を表示すべきだ!!っていうか、なんで便所で気温を測らねばならんのだ!!

 というわけで、発作的に自宅を飛び出し、向かったのは八潮の秋月電子である。

 「どうして近所の100円ショップで温度計を買わんのだ?」という愚問は禁止の方向でお願いしたい。

 秋葉原に行けばよいのだが、自宅からは八潮の秋月電子のほうが近いのである。それに、秋葉原の秋月電子は、人でごった返して足の踏み場がなく、店頭で品定めをする余裕が全くない。八潮の秋月電子は空いているので、店内でのんべんだらりとデータブックを読みながら部品を選ぶことも可能である。

 目当ては、日立「HD44780」という液晶ドライバの、互換ICを搭載した液晶ディスプレイである。大概の液晶ディスプレイは、この30年も前に開発された名作IC互換になっているのである。

 他に、7セグメントLEDで気温を表示させることも考えたが、実は思いのほか、Arduinoでの表示に限っては液晶ディスプレイのほうがラクなのだ。7セグメントLEDは簡素なだけに意外に奥が深く、多くの桁を表示させるためのダイナミック点灯やその明るさ補償、足りない電流を他の電源から持って来るなど、やることが多い。

 さて、秋月電子八潮店である。

 店内にはズラリと液晶ディスプレイが並んでいる。手ごろなところで、バックライト付きの液晶ディスプレイ、「SD1602 HUOB-XA」という型番のものを購入した。900円。

 他に、後で遊ぶためにアノードコモンの7セグメントLEDを買う。これは例の「TLC5940NT」に接続して遊ぶのである。ひとつ60円。

 それから、切らしてしまったQIコネクタのピン端子も買う。シースが見当たらないので、店員さんに「これのシースありませんか」と聞くと、ハウジングのことですか?この端子にはハウジングみたいなものはありませんよ、と答えるではないか。うーん。秋葉原の千石電商なら、左奥の抽斗にザクザク入っているのだが、どうも、八潮の秋月電子にはないらしい。というか、実は八潮の秋月の店員さん、QIコネクタにはシースがあるってことを知らんのではなかろうか。

 それはそれとして……。

今日買ったもの
IMG_3220

 帰宅して早速とりかかる。

 製品はこういうものがビニール袋に封入されているので、付属のピンヘッダを半田付けする必要がある。ちょいちょいちょい、と素早い仕事だ。

ピンヘッダをつける
IMG_3224
IMG_3225

ちょいちょいっとな、……っと。
IMG_3230

 ネットで情報を漁る。

 あるサイトによると、基盤ウラの「J3」というプリントをショートし、「R9」というプリントに100Ωの抵抗を付けると、基盤の電源でバックライトが光らせられる、とあるので、早速真似をする。

 ところが、他の回路とともに作動させてみると、どうも不安定である。バックライトの電流は、データシートによると40mAとある。電流を実回路で測定してみたところ、データシートに記載の値よりは少ないものの、37~38mAくらい流れていることが分かった。Arduinoで安心して流せるのは20mAまでなので、これはどうも過大かもしれない。Arduinoは50mAくらいまで流すことができるが、余裕は十分にあったほうがよいだろう、ということで、R9に取り付けた100Ωのジャンプ抵抗は取り外した。

 液晶ディスプレイのみ単体ならば余裕はあるものの、他の回路を接続するのであれば外部電源で点灯した方が良いように思われる。

 で、データシートと、Arduino IDEの「サンプルスケッチ」の中にある「LiquidCrystal」のコメントを参考にブレッドボードを結線する。ブレッドボードには「Seeedstudio SIDEKICK BASIC KIT」に入っていたサーミスタを、1kΩの抵抗とともに取り付けてアナログ1番ピンに入れる。

ブレッドボードの様子
IMG_3233

回路図
「暑熱をばひとつ」の回路図

 スケッチはこんなふうにゴリゴリと書いて、動けとばかりArduinoに注入し、荒い息を吐く。

//
//  thermistor2LCD.ino
//    サーミスタで気温を測り、LCDに表示する。
//    佐藤俊夫
//    27.08.09(日) 1900~
//
#include <stdio.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
const int THERMISTOR = 1;

void setup() {
  lcd.begin(16, 2);
  pinMode(THERMISTOR, INPUT);
}

void loop() {
  char tempStr[16], dispStr[32];
  lcd.setCursor(0, 0);
  dtostrf(tempMesure(), 5, 2, tempStr);
  sprintf(dispStr, "Temp. %s C", tempStr);
  lcd.print(dispStr);
  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);
}

 こうして、液晶ディスプレイに気温が表示できるようになった。

……ていうか、暑ッ!(笑)
IMG_3232



ネット便器

投稿日:

 時代はIoTである。モノをネットに接続することは、もはや正義を通り越して神の啓示であるとすら言えるほどだ(笑)。

 やろうやろうと思ってやっていない一事はこのことだ。すなわち、「Arduino」を使用して、直接モノにツイートさせること、これである。モノがツイートするのはきっと面白いに違いない。

 「Twitter Arduino」あたりで検索すると、ライブラリが出てくる。今回はこれ(Tweet Library 1.3)をありがたく使わせていただく。

 さて、そうと決まれば何をネットに接続するか、である。ここはやはり、自宅の便器をネットに接続するという、これを一度やってみるべきであろう。

 やはり、IoT時代であるから、便器もネットにつないでやらなければ面白くないだろう。便器だって平等に扱ってやらねば、他の物品との差別感を覚えてひがむようになり、性格が曲がってしまうかもしれない。このように性格の歪んだ便器は、人間様が用を足すときに局部に噛みついてくるようなことが万に一つはないとも限らない。便器にも時々はインターネットと会話をさせてやるべきだ。

 とりあえず、便所のフタの開閉を検知するため、「チルトスイッチ」を使用する。

チルトスイッチ
IMG_3218

 これは、傾けるとスイッチが入るというもので、Arduinoを買ったときに一緒に買った「Seeedstudio SIDEKICK BASIC KIT」というセットに入っていたものだ。

 回路は簡単である。下図のように、行儀よく10kΩほどプルダウンしておけば誤動作は少ない。

チルトスイッチ回路図

 スケッチのほうは、ライブラリの導入に多少手間取った。使用させていただいた「Tweet Library」は、「ETHERNET SHIELD」のほうに対応しており、私が持っている「ETHERNET SHIELD 2」にはそのままでは対応していない。

 基本的にヘッダファイルのインクルードを「#include <Ethernet.h>」から「#include <Ethernet2.h>」に書き換えるだけでいいのでは、と思ったのだが、どうもうまくいかない。さっぱりお手上げだったのだが、いろいろといじくりまわしているうち、エラーメッセージをよく見てみると、「クラスの2重定義」という意味のエラーが出ていることがわかった。なぜか、「libraries\Ethernet2\srcの下にあるやつと重なっている」みたいなメッセージが出ている。ハテ、とライブラリのあるディレクトリを見てみると、「libraries\Ethernet2\src」の下に、なぜか「Twitter.h」と「Twitter.cpp」がある。

 なんだかわかんないけどいいや、消しちゃえ!と、それをぞんざい適当に消したら、うまくコンパイルできるようになった。しかし、そのことで5~6時間ほどハマッてしまった。

 ETHERNET SHIELD 2とプロトタイプシールドを、先日買った「継ぎ足しピンヘッダ」を介して積み重ね、次のようにしていつもの100円ショップのアクリル枠にねじ止めする。

IMG_3213

 手前に緑色のチルトスイッチが取り付けられていることがわかるだろう。

 Twitterに専用アカウントをとり、Tweet Libraryの説明にしたがってトークンを取得する。専用アカウントは、その名も「佐藤家の物」である(笑)。

 それから、スケッチを次のように書く。

//
//  つぶやき便所 tweetToilet.ino
//    27.08.08(土) 1000~
//    佐藤俊夫
//    チルトスイッチで便所のふたの動きを検出し、呟かせる。
//
#include <SPI.h>
#include <Ethernet2.h>
#include <Twitter.h>
#include <stdio.h>
//
byte MAC[] = { 0x90, 0xA2, 0xDA, 0x0F, 0xF6, 0x74 };
IPAddress IP(192, 168, 1, 129);
Twitter TWITTER("hogehogehoge-hagehagehagehage......");  //  トークン
const int TILTSW = 9;
//
void setup()
{
  pinMode(TILTSW, INPUT);
  delay(1000);
  Ethernet.begin(MAC, IP);
  delay(1000);
}

void loop()
{
  static int tiltSwStatus = LOW, prevStatus = LOW;
  int i = 0;
  tiltSwStatus = tiltSw();
  if(tiltSwStatus != prevStatus){
    prevStatus = tiltSwStatus;
    tweetMsg(tiltSwStatus);
    delay(1000);
  }else{
    ;
  }
}
//
int tiltSw(){
  //  チルトスイッチの読み取りを安定させるため、100回連続して同じ値が返るまで読む。
  int i = 0, prevStatus = LOW, nowStatus = LOW;
  prevStatus = digitalRead(TILTSW);
  do{
    nowStatus = digitalRead(TILTSW);
    if(nowStatus == prevStatus){
      i ++;
    }else{
      i = 0;
    }
    prevStatus = nowStatus;
  }while(i < 100);
  return(nowStatus);
}
//
void tweetMsg(int tiltStatus){
  const char
    openMsg[] = "%e3%82%84%e3%81%82%e3%80%82%e4%bf%ba%e3%81%af%e4%bd%90%e8%97%a4%e5%ae%85%e3%81%ae%e4%be%bf%e5%99%a8%e3%81%a0%e3%80%82%e4%bb%8a%e3%83%95%e3%82%bf%e3%81%8c%e9%96%8b%e3%81%84%e3%81%a6%e3%81%84%e3%82%8b%e3%80%82",
    //  'やあ。俺は佐藤宅の便器だ。今フタが開いている。'    
    closeMsg[] = "%e4%bf%ba%e3%81%af%e4%bd%90%e8%97%a4%e5%ae%85%e3%81%ae%e4%be%bf%e5%99%a8%e3%81%a0%e3%80%82%e4%bb%8a%e3%83%95%e3%82%bf%e3%81%8c%e9%96%89%e3%81%be%e3%81%a3%e3%81%9f%e3%80%82";
    //  '俺は佐藤宅の便器だ。今フタが閉まった。'
  char tweetStr[256];
  if(tiltStatus == HIGH){
    sprintf(tweetStr, "%s  \r\n %ld", openMsg, millis());
    //  Twitterは同じ文字列を繰り返し書き続けられないので、起動時間を付けて書き、重複を防ぐ。
  }else{
    sprintf(tweetStr, "%s  \r\n %ld", closeMsg,  millis());
  }
  TWITTER.post(tweetStr);
  TWITTER.wait();
}

 うまく動くようになったら、ホット・グルーでプロトタイプを便器のフタに取り付ける。

IMG_3216IMG_3215

 便所にLAN工事を施しておしまいだ。

IMG_3214
 

 上記動画のように便器のふたを開け閉めすると、やおら便器が次のように呟きだすのである。



耽るLチカ

投稿日:

 LEDは半導体であるから、電球と違って、光り始める前は抵抗は無限大であり、電流は流れない。ところが電圧がVfを超えて光り始めたら最後、抵抗がなくなって電源をショートさせた状態でぶっ飛び、自分自身も壊れる。従って必ず抵抗を一緒にくっつけてやるのだ。

 その抵抗を選ぶのは簡単で、基本的に E = I\cdot R という、この式のみでよい。LEDを買ってくると、流すべき電流値(If)、光り始める電圧(Vf)がどこかに書いてあるから、それに従って計算する。すなわち、

R = \frac{V - Vf}{If}

ここに、

V 自分が用意する電源の電圧
Vf 買ってきたLEDのVf(順方向電圧)、つまり光り始める電圧
If 買ってきたLEDのIf(順方向電流)、つまり光らせるために必要な電流

 ただ、抵抗は入手可能な数値が決まっており、そのものピタリという抵抗値のものは売っていない。なので、計算した値と一番近い抵抗を選び、その抵抗値で再び電流を計算して、買ってきたLEDのIfを超えていないかどうかを確かめる。

 これが、簡単な計算ではあるけどいちいち面倒くさい。

 それでまあ、抵抗を選ぶのにこういうスプレッドシートを作る。

 それから、これを使ってみよう。

IMG_3181

 これは、「武蔵野電波のプロトタイパーズ第15回『TLC5940で16個のLEDを遊ぶ』」で取り上げられている「TLC5940NT」というICだ。千石電商本店2階、入って左側の、一番奥のほうの抽斗で売られている。武蔵野電波のページでは400~700円とされているが、千石電商の店頭売りでは390円である。

 Arduinoで使うには、GitHubにあるライブラリをダウンロードし、zipを展開して出てくる「tlc5940」というディレクトリをArduino IDEのインストールディレクトリの下にある「libraries」の中にコピーすればよい。

 スペックシートはコレだが、スペックシートを見るより、Arduinoにライブラリを入れると出てくるようになるサンプルスケッチの「BasicUse」を見た方が分かり易いと思う。こんな風にサンプルは書かれている。

/*
    Basic Pin setup:
    ------------                                  ---u----
    ARDUINO   13|-> SCLK (pin 25)           OUT1 |1     28| OUT channel 0
              12|                           OUT2 |2     27|-> GND (VPRG)
              11|-> SIN (pin 26)            OUT3 |3     26|-> SIN (pin 11)
              10|-> BLANK (pin 23)          OUT4 |4     25|-> SCLK (pin 13)
               9|-> XLAT (pin 24)             .  |5     24|-> XLAT (pin 9)
               8|                             .  |6     23|-> BLANK (pin 10)
               7|                             .  |7     22|-> GND
               6|                             .  |8     21|-> VCC (+5V)
               5|                             .  |9     20|-> 2K Resistor -> GND
               4|                             .  |10    19|-> +5V (DCPRG)
               3|-> GSCLK (pin 18)            .  |11    18|-> GSCLK (pin 3)
               2|                             .  |12    17|-> SOUT
               1|                             .  |13    16|-> XERR
               0|                           OUT14|14    15| OUT channel 15
    ------------                                  --------

    -  Put the longer leg (anode) of the LEDs in the +5V and the shorter leg
         (cathode) in OUT(0-15).
    -  +5V from Arduino -> TLC pin 21 and 19     (VCC and DCPRG)
    -  GND from Arduino -> TLC pin 22 and 27     (GND and VPRG)
    -  digital 3        -> TLC pin 18            (GSCLK)
    -  digital 9        -> TLC pin 24            (XLAT)
    -  digital 10       -> TLC pin 23            (BLANK)
    -  digital 11       -> TLC pin 26            (SIN)
    -  digital 13       -> TLC pin 25            (SCLK)
    -  The 2K resistor between TLC pin 20 and GND will let ~20mA through each
       LED.  To be precise, it's I = 39.06 / R (in ohms).  This doesn't depend
       on the LED driving voltage.
    - (Optional): put a pull-up resistor (~10k) between +5V and BLANK so that
                  all the LEDs will turn off when the Arduino is reset.

    If you are daisy-chaining more than one TLC, connect the SOUT of the first
    TLC to the SIN of the next.  All the other pins should just be connected
    together:
        BLANK on Arduino -> BLANK of TLC1 -> BLANK of TLC2 -> ...
        XLAT on Arduino  -> XLAT of TLC1  -> XLAT of TLC2  -> ...
    The one exception is that each TLC needs it's own resistor between pin 20
    and GND.

    This library uses the PWM output ability of digital pins 3, 9, 10, and 11.
    Do not use analogWrite(...) on these pins.

    This sketch does the Knight Rider strobe across a line of LEDs.

    Alex Leone <acleone ~AT~ gmail.com>, 2009-02-03 */

#include "Tlc5940.h"

void setup()
{
  /* Call Tlc.init() to setup the tlc.
     You can optionally pass an initial PWM value (0 - 4095) for all channels.*/
  Tlc.init();
}

/* This loop will create a Knight Rider-like effect if you have LEDs plugged
   into all the TLC outputs.  NUM_TLCS is defined in "tlc_config.h" in the
   library folder.  After editing tlc_config.h for your setup, delete the
   Tlc5940.o file to save the changes. */

void loop()
{
  int direction = 1;
  for (int channel = 0; channel < NUM_TLCS * 16; channel += direction) {

    /* Tlc.clear() sets all the grayscale values to zero, but does not send
       them to the TLCs.  To actually send the data, call Tlc.update() */
    Tlc.clear();

    /* Tlc.set(channel (0-15), value (0-4095)) sets the grayscale value for
       one channel (15 is OUT15 on the first TLC, if multiple TLCs are daisy-
       chained, then channel = 16 would be OUT0 of the second TLC, etc.).

       value goes from off (0) to always on (4095).

       Like Tlc.clear(), this function only sets up the data, Tlc.update()
       will send the data. */
    if (channel == 0) {
      direction = 1;
    } else {
      Tlc.set(channel - 1, 1000);
    }
    Tlc.set(channel, 4095);
    if (channel != NUM_TLCS * 16 - 1) {
      Tlc.set(channel + 1, 1000);
    } else {
      direction = -1;
    }

    /* Tlc.update() sends the data to the TLCs.  This is when the LEDs will
       actually change. */
    Tlc.update();

    delay(75);
  }

}

 この最初のほうのコメントにアスキー・アートで書かれている図を見て結線するとよい。こんな感じだ。

IMG_3185

 LEDは秋葉原・千石電商の隣の店、「akiba LEDピカリ館」で売っていた10個入り300円の白色LEDで、Vfが3.0V~3.4V、Ifが20mAとある。電源が5Vならば100Ωばかり抵抗を付けてやればいい理屈だが、全部点灯させるとArduinoがダメになってしまうから、さらに絞って10KΩつけてやる。

 コンパイルして動かすとこうなる。

 10kΩでもこれくらい明るい。

 このICを使うと、パルス幅変調の幅も、Arduinoが256段階であるのに比べ、4096段階と格段に細かくなる。

 アレンジを加えてみよう。昨日買ってきたポテンショメータを使う。アナログの4番ピンと5番ピンに50kΩのポテンショメータと10kΩの抵抗をつなぎ、それぞれを強さと速さにして、「尾を引いたみたいに」明るさ制御をする。

 ポテンショメータの回路はこうする。

IMG_3191

 図の「E1」をアナログ入力で読めばよい。ポテンショメータのつまみの位置は、次の計算でR2を求めれば明らかになる。

E_{0} = I_{0}\cdot(R_{1} + R_{2})
I_{0} = \frac{E_{0}}{R_{1} + R_{2}}…①
I_{0} = \frac{E_{1}}{R_{2}}…②
① = ②
\frac{E_{0}}{R_{1} + R_{2}} = \frac{E_{1}}{R_{2}}
E_{0}\cdot R_{2} = E_{1}\cdot R_{1} + E_{1}\cdot R_{2}
R_{2}(E_{0} - E_{1}) = E_{1}\cdot R_{1}
R_{2} = \frac{E_{1}\cdot R_{1}}{E_{0}-E_{1}}

 組み付けるとこうなる。

IMG_3187

 動かすとこんな感じ。

 スケッチはこんな感じ。

//
//  wPotentio2tlc5940.ino
//    ポテンショメータとTLC5940でLチカ
//    27.08.02(日)0900~
//    佐藤俊夫
//
#include "Tlc5940.h"
//
const float
  R1 = 10000.0,     //  ポテンショメータ前の抵抗10kΩ, 
  E0 = 5.0,         //  電源電圧5V, 
  MAXVR = 50000.0;  //  ポテンショメータの最大抵抗
const unsigned int   VR1 = 4, VR2 = 5;  //  ポテンショメータはアナログピンのA4・A5
const unsigned int MAX_LED = 15;  //  LEDは0~15の16個
//
void setup()
{
  Tlc.init();
  pinMode(VR1, INPUT);
  pinMode(VR2, INPUT);
}

void loop()
{
  float vr1 = 0.0, vr2 = 0.0, e11 = 0.0, e21 = 0.0;
  static unsigned int topLed = 0, tailLen = 10;
  e11 = analogRead(VR1) * (5.0 / 1024);
  e21 = analogRead(VR2) * (5.0 / 1024);
  vr1 = (e11 * R1) / (E0 - e11);  //  明るさ
  vr2 = (e21 * R1) / (E0 - e21);  //  速さ
  if(++topLed > MAX_LED + tailLen)  topLed = 0;
  Tlc.clear();
  int bright = constrain(fmap(vr1, 0.0, MAXVR, 0, 4095), 0, 4095);
  Tlc.set(topLed, bright);
  for(int i = topLed - 1; i >= 0; i--){
    bright -= (4096 / tailLen);
    if(bright < 0) bright = 0;
    Tlc.set(i, bright);
  }
  Tlc.update();
  unsigned int delayTime = constrain(fmap(vr2, 0.0, MAXVR, 100, 10), 10, 100);
  delay(delayTime);
}
//
float fmap(float x, float in_min, float in_max, float out_min, float out_max) {
  //  もともとの「map()」がlong int型でこの用途に合わないので、float型を定義
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

ただのLチカがこれまた

投稿日:

 釣りは「へら鮒に始まりへら鮒に終わる」と言うそうだが、「ArduinoはLチカに始まりLチカに終わる」などと誰かが言っていそうで多分誰も言っていない(笑)。

 さておき、けっこう楽しいんだよな、Lチカ。

 で、なんっか、手持ちのものをいろいろとくっつけたくなるのだ。今日目に入ったのはSeeedstudio SIDEKICK BASIC KITに入っていた青いポテンショメータ。私が少年の頃は「バリオーム」「ボリューム」「可変抵抗」と言ったものだが、今はポテンショメータと言うそうな。

 ポテンショメータでLチカのスピードを調整してみよう。

 まず、ポテンショメータのスペックシートなどがなくてはっきりしないから、テスターを当てて抵抗を測る。0Ωから10.5kΩまで変化できることがわかった。

 このまま直列に+5Vを印加してアナログピンに入力してしまいそうだが、0Ωの時に過電流になってしまうから、10kΩの別の抵抗と直列に入れた方がいいだろう。そうするとポテンショメータを最小に回し切っても0.5mAくらいに抑えられる。

 計算はどうなるかというと、こんなようなことになる。

IMG_3161

 式に代入すれば、ポテンショメータを最大抵抗にしたとき2.44V、最小抵抗にしたとき0Vになることがわかる。

 プロトタイピングシールドのブレッドボードは小さくて全部の部品が載らないから、普段使っている普通のブレッドボードにつける。

IMG_3157

 動かすとこんな感じだ。

 スケッチはこんな感じ。

//
//  vr2speed.ino
//    ポテンショメータでLチカ制御
//    27.07.26(日)1300~
//    佐藤俊夫
//
const unsigned int
  STARTPIN  = 2,
  ENDPIN    = 8,
  INPUTPIN  = 9,
  VR        = 0,
  DELAYMIN  = 5,
  DELAYMAX  = 100;
const float
  V0 = 5.0,         //  +5V
  R1 = 10000.0,     //  アナログピンに入れるための電流制限抵抗10kΩ
  VRMIN = 0.0,      //  ポテンショメータの最小抵抗実測値 0Ω
  VRMAX = 10500.0;  //  ポテンショメータの最大抵抗実測値 10.5kΩ
//
void setup() {
  //  330Ωをカソードにそれぞれ入れてデジタル2~8番にLEDを繋いである。
  for(int i = STARTPIN; i <= ENDPIN; i++){
    pinMode(i, OUTPUT);
  }
  //  タクトスイッチは9番に繋いでアースし、内蔵プルアップ抵抗を使っている。
  pinMode(INPUTPIN, INPUT_PULLUP);
  //  ポテンショメータは10kΩ抵抗と直列に繋ぎ、間から出力を取っている。
  pinMode(VR, INPUT);
}

void loop() {
  static int i = STARTPIN, RLdirection = 1, delaytime = DELAYMIN;
  static unsigned long int prevtime = 0;
  if(prevtime + delaytime < millis()){
    digitalWrite(i, LOW);
    i += RLdirection;
    if(i > ENDPIN) i = STARTPIN;
    if(i < STARTPIN) i = ENDPIN;
    digitalWrite(i, HIGH);
    prevtime = millis();
  }
  if(digitalRead(INPUTPIN) == LOW){
    delay(500);
    RLdirection *= -1;
  }
  float v1 = (V0 * R1) / (R1 + VRMAX);
  float vr = analogRead(VR) * (V0 / 1024);
  delaytime = constrain(fmap(vr, v1, 0.0, DELAYMAX, DELAYMIN), DELAYMIN, DELAYMAX);
}

float fmap(float x, float in_min, float in_max, float out_min, float out_max) {
  //  もともとの「map()」がlong int型でこの用途に合わないので、float型を定義
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}




カメラで遊んでみる

投稿日:

 秋月電子のサイトを見ていると、小さなカメラがあり、Arduinoに付きそうな感じだ。

 3850円。Arduino自体が2800円かそこらなので、それに比べるとちょっと高いが、早速行って購入。

 だが、あまり情報は多くない。まず、メーカーのサイトを見ていくと、チュートリアルがあり、「とりあえずテストするには、電源をくれてやって、一番端のピンをテレビにつなぎゃあ絵が出る」みたいなザックリ感満載の解説が。それで、テレビにつなぐためのRCAジャックなども買う。

 チュートリアルはこれを読んでおけばだいたいいいようだ。

 ほどいてみるとこんな感じで、かなり小さい。

IMG_3112

 ピンのピッチが2mmで、ブレッドボードで扱いにくい。それで、普通の2.54mmのピンヘッダを出して、その根元をこんなふうにムリヤリ(笑)2mmピッチにせばめる。

IMG_3116

 こいつをカメラの基盤にえいやっ、とねじ込み、半田付けする。

IMG_3118

 なかなか小さいので、ルーペと老眼鏡を併用しつつ、ICなんか壊しちゃってもナンだから、20Wのぬるくて細い半田鏝でさっさとつける。

IMG_3117

 我ながらなかなかスピーディな仕事だなあ(笑)。

 で、メーカーのサイトには「5V」と書いてあるが、これは互換品の別の製品のためのチュートリアルのようで、買ってきたものの基盤をよく見ると「3.3V」と印刷されている。壊してはもったいないから、3.3Vで試す。3.3Vの電源代わりにArduinoの3.3Vピンを使う。

 基盤の印刷通り、3.3V、GND、それから右端のピンをRCAジャックのセンターに、RCAジャックのアースを同じくGNDに入れて、テレビの前に持っていく。

IMG_3119

 テレビにつなぐと、おお、確かに、値段なりのフザけた画質(笑)で、自分の顔が映る。

IMG_3121

 上下が逆だが、まあ、いいや。

 で、今度はArduinoで画像を撮影してみよう。

 チュートリアルにしたがってArduino用のライブラリをダウンロードし、これをArduinoのインストールフォルダの「libraries」に配置する。

 そうしておいてArduinoのIDEを起動すると、「ファイル」→「スケッチの例」の中に「Adafruit VC0706 Serial Camera Library」が現れるから、この中から「Snapshot」を選ぶ。これは静止画をjpegで撮影するスケッチのサンプルだ。

// This is a basic snapshot sketch using the VC0706 library.
// On start, the Arduino will find the camera and SD card and
// then snap a photo, saving it to the SD card.
// Public domain.

// If using an Arduino Mega (1280, 2560 or ADK) in conjunction
// with an SD card shield designed for conventional Arduinos
// (Uno, etc.), it's necessary to edit the library file:
//   libraries/SD/utility/Sd2Card.h
// Look for this line:
//   #define MEGA_SOFT_SPI 0
// change to:
//   #define MEGA_SOFT_SPI 1
// This is NOT required if using an SD card breakout interfaced
// directly to the SPI bus of the Mega (pins 50-53), or if using
// a non-Mega, Uno-style board.

#include <Adafruit_VC0706.h>
#include <SPI.h>
#include <SD.h>

// comment out this line if using Arduino V23 or earlier
#include <SoftwareSerial.h>         

// uncomment this line if using Arduino V23 or earlier
// #include <NewSoftSerial.h>       

// SD card chip select line varies among boards/shields:
// Adafruit SD shields and modules: pin 10
// Arduino Ethernet shield: pin 4
// Sparkfun SD shield: pin 8
// Arduino Mega w/hardware SPI: pin 53
// Teensy 2.0: pin 0
// Teensy++ 2.0: pin 20
#define chipSelect 10

// Pins for camera connection are configurable.
// With the Arduino Uno, etc., most pins can be used, except for
// those already in use for the SD card (10 through 13 plus
// chipSelect, if other than pin 10).
// With the Arduino Mega, the choices are a bit more involved:
// 1) You can still use SoftwareSerial and connect the camera to
//    a variety of pins...BUT the selection is limited.  The TX
//    pin from the camera (RX on the Arduino, and the first
//    argument to SoftwareSerial()) MUST be one of: 62, 63, 64,
//    65, 66, 67, 68, or 69.  If MEGA_SOFT_SPI is set (and using
//    a conventional Arduino SD shield), pins 50, 51, 52 and 53
//    are also available.  The RX pin from the camera (TX on
//    Arduino, second argument to SoftwareSerial()) can be any
//    pin, again excepting those used by the SD card.
// 2) You can use any of the additional three hardware UARTs on
//    the Mega board (labeled as RX1/TX1, RX2/TX2, RX3,TX3),
//    but must specifically use the two pins defined by that
//    UART; they are not configurable.  In this case, pass the
//    desired Serial object (rather than a SoftwareSerial
//    object) to the VC0706 constructor.

// Using SoftwareSerial (Arduino 1.0+) or NewSoftSerial (Arduino 0023 & prior):
#if ARDUINO >= 100
// On Uno: camera TX connected to pin 2, camera RX to pin 3:
SoftwareSerial cameraconnection = SoftwareSerial(2, 3);
// On Mega: camera TX connected to pin 69 (A15), camera RX to pin 3:
//SoftwareSerial cameraconnection = SoftwareSerial(69, 3);
#else
NewSoftSerial cameraconnection = NewSoftSerial(2, 3);
#endif

Adafruit_VC0706 cam = Adafruit_VC0706(&cameraconnection);

// Using hardware serial on Mega: camera TX conn. to RX1,
// camera RX to TX1, no SoftwareSerial object is required:
//Adafruit_VC0706 cam = Adafruit_VC0706(&Serial1);

void setup() {

  // When using hardware SPI, the SS pin MUST be set to an
  // output (even if not connected or used).  If left as a
  // floating input w/SPI on, this can cause lockuppage.
#if !defined(SOFTWARE_SPI)
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
  if(chipSelect != 53) pinMode(53, OUTPUT); // SS on Mega
#else
  if(chipSelect != 10) pinMode(10, OUTPUT); // SS on Uno, etc.
#endif
#endif

  Serial.begin(9600);
  Serial.println("VC0706 Camera snapshot test");
  
  // see if the card is present and can be initialized:
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    // don't do anything more:
    return;
  }  
  
  // Try to locate the camera
  if (cam.begin()) {
    Serial.println("Camera Found:");
  } else {
    Serial.println("No camera found?");
    return;
  }
  // Print out the camera version information (optional)
  char *reply = cam.getVersion();
  if (reply == 0) {
    Serial.print("Failed to get version");
  } else {
    Serial.println("-----------------");
    Serial.print(reply);
    Serial.println("-----------------");
  }

  // Set the picture size - you can choose one of 640x480, 320x240 or 160x120 
  // Remember that bigger pictures take longer to transmit!
  
  cam.setImageSize(VC0706_640x480);        // biggest
  //cam.setImageSize(VC0706_320x240);        // medium
  //cam.setImageSize(VC0706_160x120);          // small

  // You can read the size back from the camera (optional, but maybe useful?)
  uint8_t imgsize = cam.getImageSize();
  Serial.print("Image size: ");
  if (imgsize == VC0706_640x480) Serial.println("640x480");
  if (imgsize == VC0706_320x240) Serial.println("320x240");
  if (imgsize == VC0706_160x120) Serial.println("160x120");

  Serial.println("Snap in 3 secs...");
  delay(3000);

  if (! cam.takePicture()) 
    Serial.println("Failed to snap!");
  else 
    Serial.println("Picture taken!");
  
  // Create an image with the name IMAGExx.JPG
  char filename[13];
  strcpy(filename, "IMAGE00.JPG");
  for (int i = 0; i < 100; i++) {
    filename[5] = '0' + i/10;
    filename[6] = '0' + i%10;
    // create if does not exist, do not open existing, write, sync after write
    if (! SD.exists(filename)) {
      break;
    }
  }
  
  // Open the file for writing
  File imgFile = SD.open(filename, FILE_WRITE);

  // Get the size of the image (frame) taken  
  uint16_t jpglen = cam.frameLength();
  Serial.print("Storing ");
  Serial.print(jpglen, DEC);
  Serial.print(" byte image.");

  int32_t time = millis();
  pinMode(8, OUTPUT);
  // Read all the data up to # bytes!
  byte wCount = 0; // For counting # of writes
  while (jpglen > 0) {
    // read 32 bytes at a time;
    uint8_t *buffer;
    uint8_t bytesToRead = min(32, jpglen); // change 32 to 64 for a speedup but may not work with all setups!
    buffer = cam.readPicture(bytesToRead);
    imgFile.write(buffer, bytesToRead);
    if(++wCount >= 64) { // Every 2K, give a little feedback so it doesn't appear locked up
      Serial.print('.');
      wCount = 0;
    }
    //Serial.print("Read ");  Serial.print(bytesToRead, DEC); Serial.println(" bytes");
    jpglen -= bytesToRead;
  }
  imgFile.close();

  time = millis() - time;
  Serial.println("done!");
  Serial.print(time); Serial.println(" ms elapsed");
}

void loop() {
}

 で、これはSDカードに書き込むようになっている。

 私の手持ちの、ArduinoにSDカードをつなぐ手段は、先日から愛用中の「ETHERNET SHIELD 2」に搭載されているSDカードスロットだけだから、とりあえずこれを使う。

 手持ちのSDカードをETHERNET SHIELD 2に挿し、Adafruitのサイトのチュートリアルを参考に回路をブレッドボードに組む。

IMG_3122

 注意する点は2つだ。

  1.  サンプルスケッチをよく読むと、普通のSDカードは10番ピンを使うが、ETHERNET SHIELD 2を使う場合は4番ピンにつながる。なので、サンプルスケッチの中の「#define chipSelect 10」というところを「#define chipSelect 4」に書き換えなければならない。
  2.  メーカーサイトのチュートリアルでは、カメラに添付の10kΩの抵抗をTXの次に直列に二つ入れて、1本目と2本目の間からTXをとり、それをアースしているが、どうもこれだとうまく行かなかった。多分、このチュートリアルは給電が5Vだからだと思う。そこで、アースはそのままに、1本目の手前でTXをとるとうまくいった。
    IMG_3127

 そうやってArduinoをスタートさせると、写真が1枚だけ撮れる。

 下は、そうやって撮った私の顔である。

IMAGE02

 ……むっちゃむさくるしいなあw。

ウェブ扇風機

投稿日:

 昨日試した扇風機遊びを少し進め、温度制御を付けてみたい。

 おあつらえ向きに、最初に買った「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と仮定すれば、

IMG_3075

……というような計算で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として含め、現在のサーミスタの抵抗値を知るには、一般に式は次のようになろう。

IMG_3079

 これをソースコードに表せば、次のようになる。

  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値は「抵抗の対数と、温度の比」である。これもスペックシートに関係式が書いてある。

 これらのスペックシートの値と、知った抵抗値から温度を求める式は、次のようになる。

IMG_3080

 これを、先の抵抗値を知る部分と併せて、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);
}

 次に、温度に応じて扇風機の回転数が変わるようにしよう。先日作った「ソリッドステート・リレーモジュール」を遺憾なく使用する。

ソリッドステート・リレーモジュール
IMG_3081

 このモジュールの内部には、秋月電子の「ソリッド・ステート・リレー(SSR)キット 25A(20A)タイプ」が組み付けてある。

 27℃で1/fゆらぎエフェクト、28℃で弱風、29℃で強風、とでもしてみようか。1/fゆらぎエフェクトには、先週書いた関数をそのままコピペする。

IMG_3083

//
//  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));
}

 回路図など、描くも愚かというか、こんな簡単なものである。

IMG_3084

 さて次に、ETHERNET SHIELD 2をつなぎ、この扇風機をWeb化する。昨日買っておいた「クリアランス確保用ピンソケット」が役に立つ。

IMG_3067

 組み付けるとこんな感じである。回路はネットにつながない場合と同じでよい。

IMG_3087

 こんなインターフェイスで動かす。

ネット扇風機インターフェイス

 設定した温度になると扇風機が回る。

IMG_3088

 スケッチはこのようになる。

//
//  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]);
  }

}






LEDをネットにつないでコントロール

投稿日:

 先週少しやりかけて途中だった、Arduino UNO に ETHERNET SHIELD 2 をとりつけ、ウェブインターフェイスを作ってこれでLEDをコントロールするという遊びの続きをやる。

 こんなふうなインターフェイスでコントロールすることにする。

名称未設定 1

 で、ブレッドボードとArduinoはこんな感じ。「SeeedStudio SIDEKICK BASIC KIT」に入っていた3色LEDを使う。

IMG_3004

 スケッチはこんな感じで。ウェブ上で同じようなことを公開している方々は、ブラウザの出力を受けるのに、たいてい普通のポインタ文字列を使っておられるのだが、せっかくStringクラスがあるので、それを活用することにした。Stringクラスを使う欠点は多少メモリを浪費することだが、反面、余裕は十分あり、今回は別にメモリが足りないというわけでもないので、そうした。

//
//  Web2TriColorLED.ino
//    27.06.07(日) 1800~
//    佐藤俊夫
//    3色LEDをウェブインターフェイスで制御
//    ETHERNET SHIELD 2 使用
//
#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;
int R = 0, G = 0, B = 0;
const int LEDR = 3, LEDG = 5, LEDB = 6;
//
void setup() {
  Ethernet.begin(mac, ip);
  SERVER.begin();
  pinMode(LEDR, OUTPUT);
  pinMode(LEDG, OUTPUT);
  pinMode(LEDB, OUTPUT);
}


void loop() {
  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("red=") >= 0){
      R = rstr.substring(rstr.indexOf("red=") + 4, rstr.indexOf("&green=")).toInt();
      G = rstr.substring(rstr.indexOf("green=") + 6, rstr.indexOf("&blue=")).toInt();
      B = rstr.substring(rstr.indexOf("blue=") + 5, rstr.indexOf("&end")).toInt();
    }
    analogWrite(LEDR, R);
    analogWrite(LEDG, G);
    analogWrite(LEDB, B);
    delay(10);
    rstr = "";
    sendform();
    // give the web browser time to receive the data
    delay(1);
    // close the connection:
    CLIENT.stop();
  }
}
//
void sendform(){
  char* formFirstHalf[] = {
    "<html>",
    "  <head>",
    "    <meta charset=\"utf-8\">",
    "  </head>",
    "  <body>",
    "    <center>",
    "      <h1>Arduino 3色LEDをウェブインターフェイスで</h1>",
    "      <form method='GET'>",
    "        <table>",
    "          <tr>",
    "  	    <th style='background-color:red;  color:white;'>赤</th>",
    "  	    <th style='background-color:green;color:white;'>緑</th>",
    "  	    <th style='background-color:blue; color:white;'>青</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("              <select name='red'>");
  for(i = 0; i <= 255; i++){
    CLIENT.print("                <option value='");
    CLIENT.print(i, DEC);
    if(i == R){
      CLIENT.print("' selected>");
    }else{
      CLIENT.print("'>");
    }
    CLIENT.print(255 - i, DEC);
    CLIENT.println("</option>");
  }
  CLIENT.println("              </select>");
  CLIENT.println("            </td>");
  CLIENT.println("            <td>");
  CLIENT.println("              <select name='green'>");
  for(i = 0; i <= 255; i++){
    CLIENT.print("                <option value='");
    CLIENT.print(i, DEC);
    if(i == G){
      CLIENT.print("' selected>");
    }else{
      CLIENT.print("'>");
    }
    CLIENT.print(255 - i, DEC);
    CLIENT.println("</option>");
  }
  CLIENT.println("              </select>");
  CLIENT.println("            </td>");
  CLIENT.println("            <td>");
  CLIENT.println("              <select name='blue'>");
  for(i = 0; i <= 255; i++){
    CLIENT.print("                <option value='");
    CLIENT.print(i, DEC);
    if(i == B){
      CLIENT.print("' selected>");
    }else{
      CLIENT.print("'>");
    }
    CLIENT.print(255 - i, DEC);
    CLIENT.println("</option>");
  }
  CLIENT.println("              </select>");
  CLIENT.println("            </td>");
  CLIENT.println("          </tr>");
  for(i = 0; i < 7; i++){
    CLIENT.println(formSecondHalf[i]);
  }

}

 気を付けるところは、私が買った「SeeedStudio SIDEKICK BASIC KIT」というキットに入っている3色LEDは「アノードコモン(共通陽極)」というタイプで、プラスに5V、各色のコントロールはマイナスになる、という点だ。ここを間違えると思ったように動作しない。一番長い脚が共通陽極だ。封入の切り欠きを左にして、左から赤、アノード、緑、青の順に脚が並ぶ。

 また、フォームから与える値も、アノードコモンであるゆえに、「0」が最も明るくなり、「255」が最も暗くなるので、インターフェイスを逆にするなど、多少工夫が必要だ。

 それから、PWM(パルス幅変調)の機能を大いに活用し、いろいろな色が出せるようにした。この場合の注意点は、ETHERNET SHIELD 2を取り付けると、10番・11番・12番のピンが使えなくなるので、他のピンで出力しなければならないことだ。今回は3番・5番・6番を使った。

 で、ウェブインターフェイスをタブレットにロードする。

IMG_3005

 動かしてみるとこんな感じで、いろんな色になって面白い。


Arduino入門キットのサーミスタ

投稿日:

 先日、Arduino UNOと一緒に買った入門キットは、「seeedstudio SIDEKICK BASIC KIT」と言うもので、千石電商の2階に置いてあったから無造作にヒョイと選んだものだ。

 ブレッドボードにジャンパ各色、小さいサーボ、可変抵抗、抵抗、チルトスイッチ、ダイオード、ボタンやスイッチ、ブザー、LED各種、コンデンサ、Cdsセル、サーミスタなどが詰め合わせになっており、オライリーの「Arduinoをはじめよう第2版」の題材を一通り試せるようになっている。

 まず過不足のないところなのだが、seeedstudioというのは何分中国企業で、日本語のドキュメントが少ない。

 と言っても、部品は簡単なものばかりなので、特にドキュメントなどなくても自分でテスターで計りながら使えばそれでよいのだが、この中の、サーミスタに関する情報が非常に少なく、難渋する。

 サーミスタには写真のように「503」と刻印があるのみで、なんの情報もない。

 ネット上で探すと、一応、seeedstudioのFAQページに、情報があることはある。

  •  そのページ
  •  その中に、中国語のPDFで、こんなシートがある。

     要するに「MF11-503K」という型番で、「503」というのはどうやら、「25℃で50kΩ」とでも言う意味らしい。

     で、電圧発生抵抗を一本入れて、えーっと、どういう計算になるんだっけな、……と初心に帰る。

     抵抗2本、直列に入れた時の一本目の抵抗の電圧はこうだから……


    Scan10001

     自分の指でつまんで温めたりして試したいんで、ターゲット温度を体温前後にする。なので、データシートどおりに抵抗が変化するんなら、こういう感じに電圧は変化する。


    Scan10002

    ……で、Arduinoのアナログ入力は、0V~5Vを0~1024にマッピングするんで、1024/5をこれらにかけて、近似させればいいよね。

     2次式モデルでこうなった。

    v = -9.663E-5 * v * v + 0.164 * v + 0.197;

     結局、サーミスタに直列に10kΩ、サーミスタの下からアナログへ出して、サーボにつなぐ。サーボの針が体温で上がったり下がったりするわけだ。

     コードはこんな感じ。

    //
    //  サーミスタでサーボを制御
    //    佐藤俊夫
    //    27.05.05(火)
    //
    #include <Servo.h>
    //
    const int SERVO = 9;  //  D9(Servo)
    const int THERMISTOR = 0;  //  A0
    const float INMIN = 30;
    const float INMAX  = 40;
    const float OUTMIN = 5.0;
    const float OUTMAX = 175.0;
    //
    Servo meter;
    
    void setup() {
      meter.attach(SERVO);
    }
    
    void loop() {
      int v = 0;
      //
      v = analogRead(THERMISTOR);
      v = -9.663E-5 * v * v + 0.164 * v + 0.197;
      v = map(v, INMIN, INMAX, OUTMIN, OUTMAX);
      v = constrain(v, OUTMIN, OUTMAX);
      meter.write(v);
    }