2015年12月29日火曜日

ESPにつないだRGB LEDをブラウザから制御する


■WiFi制御Lチカ■

ESPからサーバへデータを送信するのは何度かやったのですが、そういえばブラウザなどからESPを制御する、というのはやっていなかったので試してみました。

上の動画では、

  1. http://server/?w=4
  2. http://server/?r=1023&g=1023&b=1023
  3. http://server/?r=1023&g=0&b=0
  4. http://server/?r=0&g=1023
  5. http://server/?w
  6. http://server/?r=0&g=0&b=1023

の順番で操作しています。1.明滅指定、2.全色全点灯、3.青のみ明滅、4.緑のみ明滅、5. 明滅をストップ、6.青のみ点灯、という制御です。

こういうのをゼロから作るのはシンドいので、まず土台になりそうなプログラムをESP8266サンプルの中から探すと「HelloServer」というのが見つかりました。

これは、ブラウザから「/」または「/inline」でアクセスするとそれぞれ異なるメッセージが返ってきて、それ以外だとnot foundとともにargが表示される、というものです。必要なのはパスとargなので、これを使います。

■仕様と解説■

単にLEDを点滅させるだけじゃつまらないので、

  • RGB LEDを接続する
  • 各色ごとに明るさを変える r=100&g=200&b=300 って感じで
  • w=<n>と指定するとn mSec×256周期で滑らかに明滅する。ただしその時に設定されている各色の値がピークの明るさになる。

って仕様にしてみました。明るさを調節するにはanalogWriteでPWMを使い、それを滑らかに変化させるためにTickerを使います。

analogWriteは指定したGPIOに対して約500hzのPWM出力をします。値として0-1023を指定することができますが、値/1023の比率でonになっている時間が長くなります。0を指定すればずっとoff、512なら約50%、1023ならonのまま、ってことです。

Tickerは指定した秒数ごとに指定した関数を呼び出してくれます。attachで有効になり、detachで無効になります。今回のプログラムではsetupであらかじめサインカーブを描くテーブルisinを作っておき、設定したミリ秒数毎にテーブルの値を出力することで滑らかに明るさを変化しています。実際にその処理をおこなっているのはwaveHandlerです。waveHandlerではwaveCountで示されるisinの値を各色ごとの直近の明るさ比率(gRGB)に補正して、RGBごとに位相を120度ずつずらして(i*MaxSinTable/3)出力しています。一個出力したらwaveCountに1を足して次の値を準備します。

ブラウザからのコマンドを受け付けるのはhandleRootです。最初の例で示したように引数は同時に複数指定することができます。何個引数があるかはserver.args()で知ることができるので、これでforループを回します。そして、「引数名=値」の形式の引数名に相当するargName(i)の最初の1文字だけを取り出し、値をarg(i)から取り出します。そして引数名として指定されたのがr, g, bならばそれぞれの明るさをセットし、wならば値をミリ秒単位の割り込み周期としてattachかdetachを実行します。wはトグル動作つまり呼ばれるごとにon/offを切り替えるので、tickerAttachedというフラグを用意して切り替えます。

サーバのルートが呼ばれたらhandleRootを呼び出すということは、setup()の中で
server.on("/", handleRoot);
と指定しています。

…楽だわー。Play! Framework並にラクだわー(すいません、仕事でstruts2などという化石の干物を腐らせたようなライブラリを相手にしているので、つい愚痴が)

なお、handleNotFoundはサンプルのままです。ブラウザから例えば
http:<server>/foo?tare=panda
と呼び出すと、画面上には
File Not Found

URI: /foo
Method: GET
Arguments: 1
 tare: panda

という表示が返ってきてソースと見比べながら引数などをどういう具合いに扱われるかがよくわかります。

また、setup()では
  server.on("/inline", [](){
    server.send(200, "text/plain", "this works as well");
  });
という記述もあります。この [ ]( ) { } は無名の関数で、別のところにhandler関数を書くかわりにここで簡単な処理をチャッチャと記述してしまうことができます。サンプルのように単純なテキストメッセージを返すならこれで十分ですね。

■ハードウェア■

素のESP-WROOM-02は配線が面倒くさいので、今回は自家製のESP基板を使いました。FTDIとRGB LEDを接続するだけなので素晴らしく簡単です。電源にも良い部品を使っているので動作も安定しています(だれも褒めてくれないので自分だけは自分に優しくしよう)。



ESPでのお約束なプルダウン / プルアップ抵抗などの他には、RGB LEDを接続するだけです。

RGB LEDは秋月の「RGBフルカラーLED 5mm4本足 OSTA5131」です。BlueとGreenのLEDは3.6v以上の電圧かけないと光らないのが建前なんですが、実際には3.3vでも結構明るく点灯しちゃうんですよね。なお、GPIOと直結するのはまずいので、220オームの抵抗を直列に入れてます。また、このRGB LEDはカソードコモンといってマイナスにつなぐ側が共通になっています。GNDと接続してください。その辺、ブレッドボードでつくると妙にでかくなってしまうので、aitendoのユニバーサル基板で作りました。
ああハンダ付けは楽しいw


■はまりどころ■

今回はなかった、と言いたかったんですが、実は解決できていない問題が1つ。このプログラムを起動して r=200&g=200&b=200&w=8 と暗め and ゆっくり目で動かしていると時々「パッ」と明るくなります。WiFiに関する機能を取り除いても起こりますが、明滅させないでいると起こりません。

明滅時にはanalogWrite命令で頻繁にPWMの出力幅を変えているのですが、実行のタイミングが悪いとHIGHがしばらく続いてしまうのではないかと思います。同じような現象はmbed / LPC1114FN28でも経験していて、この時はPWMの周波数を高くすることで回避できたのですが、Arduino IDE環境ではPWMの周波数を簡単には変更できないのでさてどーしたもんかしら。

■ソース■

というわけで、ソースです。

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include "Ticker.h"
const char* ssid = "arduino";
const char* password = "1234567890qaz";
MDNSResponder mdns;
ESP8266WebServer server(80);
// LED table
//
const int pins[3] = {12, 13, 14};
enum {ColorRed, ColorGreen, ColorBlue};
float gRGB[3];
Ticker ticker;
// sin curve table
//
#define MaxSinTable 256
#define MaxAnalogWrite 1023
int isin[MaxSinTable];
// handle timer interrupt
//
int waveCount = 0;
boolean tickerAttached = false;
void waveHandler() {
if (tickerAttached) {
for (int i = 0; i < 3; i++) {
analogWrite(pins[i], isin[(waveCount + i*MaxSinTable/3) % MaxSinTable]*gRGB[i]);
}
waveCount++;
if (waveCount >= MaxSinTable) waveCount = 0;
}
}
// handle root '/'
void handleRoot() {
server.send(200, "text/plain", "hello from esp8266!");
for (int i = 0; i < server.args(); i++) {
char *cStr = (char *)server.argName(i).c_str();
char c = cStr[0];
int value = atoi(server.arg(i).c_str());
if (value < 0) value = 0;
if (value >= MaxAnalogWrite) value = MaxAnalogWrite;
if (c == 'r') {
analogWrite(pins[ColorRed], value);
Serial.print(" red = ");
Serial.println(gRGB[ColorRed] = (float)value / 1023.0);
} else if (c == 'g') {
analogWrite(pins[ColorGreen], value);
Serial.print("green = ");
Serial.println(gRGB[ColorGreen] = (float)value / 1023.0);
} else if (c == 'b') {
analogWrite(pins[ColorBlue], value);
Serial.print(" blue = ");
Serial.println(gRGB[ColorBlue] = (float)value / 1023.0);
} else if (c == 'w') {
if (tickerAttached) {
Serial.println("detach");
ticker.detach();
tickerAttached = false;
} else {
Serial.print("attach = ");
Serial.println(value);
ticker.attach((float)value/1000.0, waveHandler);
tickerAttached = true;
}
}
}
}
// handle not found (original)
//
void handleNotFound(){
String message = "File Not Found\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += (server.method() == HTTP_GET)?"GET":"POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
for (uint8_t i=0; i<server.args(); i++){
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
}
server.send(404, "text/plain", message);
}
// setup
//
void setup(void){
Serial.begin(115200);
// fill sin curve
for (int i = 0; i < MaxSinTable; i++) {
isin[i] = sin(3.14159265*2.0*(float)i/(float)MaxSinTable) * MaxAnalogWrite/2 + MaxAnalogWrite/2;
}
// rgb default
gRGB[ColorRed] = gRGB[ColorGreen] = gRGB[ColorBlue] = 0.0;
// connect wifi
WiFi.begin(ssid, password);
Serial.println("");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
if (mdns.begin("esp8266", WiFi.localIP())) {
Serial.println("MDNS responder started");
}
server.on("/", handleRoot);
server.on("/inline", [](){
server.send(200, "text/plain", "this works as well");
});
server.onNotFound(handleNotFound);
server.begin();
Serial.println("HTTP server started");
}
void loop(void){
server.handleClient();
}

2 件のコメント:

  1. >だれも褒めてくれないので自分だけは自分に優しくしよう
    褒めてますって。

    返信削除
  2. なはははは、ありがとうございます。

    返信削除

注: コメントを投稿できるのは、このブログのメンバーだけです。