スマホのJPEG写真や画像をWi-Fiで飛ばして OLED ( 有機EL )に表示させてみた。ESP-WROOM-02 ( ESP8266 )使用

ESP8266 ( ESP-WROOM-02 )

5.Arduino IDE にスケッチを入力

では、Arduino IDE にスケッチを入力していきます。

【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)

/* Please use SD_EasyWebSocket beta ver 1.45
 * Please use OLED_SSD1351 ver 1.53
 */ 
#include <ESP8266WiFi.h>
#include <SD.h>
#include <Hash.h>
#include <SD_EasyWebSocket.h>
#include <OLED_SSD1351.h>
#include <JPEGDecoder.h>

const uint8_t sclk = 14; //OLED & SDcard & BME280 SCLK
const uint8_t mosi =13; //Master Output Slave Input ESP8266=Master, BME280 & OLED & SD = slave 
const uint8_t miso =12; //Master Input Slave Output
const uint8_t cs_OLED = 16; //OLED CS(ChipSelect)
const uint8_t cs_SD = 15; //SDcard CS(ChipSelect)
const uint8_t DCpin =  5; //OLED D/C (Data/Command)
const uint8_t RSTpin =  4; //OLED Reset

const char* ssid = "xxxx";
const char* password = "xxxx";

const char* HTM_head_file = "EWS/EwsHead2.txt"; //HTMLヘッダファイル
const char* HTML_file1 = "EWS/html01.txt"; //HTML body要素ファイルの一部
const char* HTML_file2 = "EWS/html02.txt"; //HTML body要素ファイルの一部
const char* SD_PicDir = "picture/"; //スマホへ送信するSDカード内の画像ファイルフォルダを指定
char PicFile[14] = "/test.jpg"; //スマホから送信された画像ファイルをSDカードのルートに保存するファイル名を指定

uint32_t ESP_send_Time, LastTime;
uint8_t cnt = 0;

SD_EasyWebSocket ews; //自作ライブラリのクラス名定義
OLED_SSD1351 ssd1351; //自作ライブラリのクラス名定義

File dir;

String html_str1 = "";
String html_str2 = "";
String html_str3 = "";

String ret_str; //スマホからWebSocket通信で送られてくるテキストデータを格納
bool IMG_get = false; //スマホから画像をGETするボタンが押されたかどうかのフラグ

int PingSendTime = 60000; //ESP-WOOM-02からスマホへPing送信する間隔 60秒

void setup() 
{
  pinMode(sclk, OUTPUT);
  pinMode(mosi, OUTPUT);
  pinMode(miso, INPUT); //ここはINPUTなので注意
  pinMode(cs_OLED, OUTPUT);
  pinMode(cs_SD, OUTPUT);
  pinMode(DCpin, OUTPUT); //OLED SSD1351 のDCピン
  pinMode(RSTpin, OUTPUT); //OLED SSD1351 のリセットピン
  
  pinMode(2, OUTPUT); //使わないピンはOUTPUTにしてHIGHレベルにしておくと誤作動少ない
  pinMode(0, OUTPUT); //使わないピンはOUTPUTにしてHIGHレベルにしておくと誤作動少ない

  ssd1351.SSD1351_Init262(sclk, mosi, cs_OLED, DCpin, RSTpin, 1); //OLEDを256kカラーで初期化
  delay(50);
  ssd1351.SSD1351_BlackOut262(); //OLEDを256kカラーで黒画面出力

  ews.AP_Connect(ssid, password);

  Serial.println(); Serial.println("Initializing SD card...");
  if (!SD.begin(cs_SD,SPI_FULL_SPEED)) {
    Serial.println("Card failed, or not present");
    return;
  }
  Serial.println("card initialized. OK!");

  if(SD.remove(PicFile)){ //※remove関数の引数はポインタにするとエラーになるので、必ず固定長配列宣言すること。
    Serial.println("Removed test.jpg");
  }else{
    Serial.println("Not removed test.jpg");
  }

  //ここからHTMLタグの動的selectボックス生成
  dir = SD.open(SD_PicDir);

  String pic_files;
  uint8_t i;
  File entry;

  Serial.println("-----------SD card picture files");
  i=0;
  html_str1 = "\r\n";
  while(true){
    entry =  dir.openNextFile();
    if (! entry) {
       break;  // no more files
    }
    pic_files = entry.name();
    Serial.print(i); Serial.print(" : "); Serial.println(pic_files);
    entry.close();
    i++;
    html_str1 += "    <option value=\"";
    html_str1 += pic_files;
    html_str1 += "\">";
    html_str1 += pic_files;
    html_str1 += "</option>\r\n";
  }
  Serial.println("--------------------------------");  
  ESP_send_Time = millis();
  LastTime = millis();
}

void loop() {
  ews.EWS_Dev_HandShake(cs_SD, HTM_head_file, HTML_file1, html_str1, html_str2, html_str3, HTML_file2); 
  
  if(IMG_get){ //スマホからSDカード内画像GETボタンが押された場合
    SD.begin(cs_SD,SPI_FULL_SPEED);
    LastTime = millis();
    Serial.println("----------------GO! SDcard Picture Get!");

    while(!ews.HTTP_SD_Pic_Send("http://192.168.0.14", SD_PicDir)){
      if(millis()-LastTime>5000){ //5s Time Out
        Serial.println("Time Out ! Cannot Get Picture");
        break;
      }
    }
    IMG_get = false;
  }else{
    ret_str = ews.EWS_ESP8266DataReceive_SD_write(PingSendTime, cs_SD, PicFile); //スマホからバイナリ形式ファイルが送られて来たらSDカードへ格納
    if(ret_str == "_Binary"){
      Serial.println("WebSocket Binary Receive Complete!\r\n");
      delay(10); 
      SD.begin(cs_SD,SPI_FULL_SPEED);
      jpegDraw(PicFile); //SDカードに保存された画像をOLEDに出力する
      Serial.println("JPEG display complete----------");
      ret_str="";
    }
  }
  String str;
  
  if(ret_str != "_close" && ret_str != "_Binary"){
    if(millis()-ESP_send_Time > 500){//Data transmission from WROOM (ESP8266) every 500ms
      if(cnt > 3){
        cnt = 0;
      }
      switch(cnt){
        case 0:
          str = "Connected";
          break;
        case 1:
          str = "WebSockets";
          break;
        case 2:
          str = "Hello!!";
          break;
        case 3:
          str = "World!!";
          break;
      }
      ews.EWS_ESP8266_Str_SEND(str, "wroomTXT");
      ESP_send_Time = millis();
      cnt++;
    }
    if(ret_str != "\0"){
      Serial.println(ret_str);
      if(ret_str != "Ping"){
        if(ret_str[0] != 't'){
          int ws_data = (ret_str[0]-0x30)*100 + (ret_str[1]-0x30)*10 + (ret_str[2]-0x30);
          switch(ret_str[4]){
            case 'I':
              Serial.println("Picture GET Request Receive!!!!!!!");
              IMG_get = true;
              ret_str = "";
              break;
          }
        }
      }
    }
  }else if(ret_str == "_close"){
    ESP_send_Time = millis();
    ret_str = "";
  }
}

void jpegDraw(char* filename){
  char str[100];
  uint8 *pImg;
  int x,y,bx,by;
  uint8_t R, G, B;

  SD.begin(cs_SD,SPI_FULL_SPEED);
  
  JpegDec.init(); //この関数だけライブラリに独自に追加して作らなければならない
  
  JpegDec.decode(filename,0);

  Serial.print("Width     :");
  Serial.println(JpegDec.width);
  Serial.print("Height    :");
  Serial.println(JpegDec.height);
  Serial.print("Components:");
  Serial.println(JpegDec.comps);
  Serial.print("MCU / row :");
  Serial.println(JpegDec.MCUSPerRow);
  Serial.print("MCU / col :");
  Serial.println(JpegDec.MCUSPerCol);
  Serial.print("Scan type :");
  Serial.println(JpegDec.scanType);
  Serial.print("MCU width :");
  Serial.println(JpegDec.MCUWidth);
  Serial.print("MCU height:");
  Serial.println(JpegDec.MCUHeight);
  Serial.println("");

  sprintf(str,"#SIZE,%d,%d",JpegDec.width,JpegDec.height);
  Serial.println(str);

  //pinMode(sclk, OUTPUT); //OLED_SSD1351ライブラリver1.40の場合はこのコメントを外す
  //pinMode(mosi, OUTPUT); //OLED_SSD1351ライブラリver1.40の場合はこのコメントを外す
  ssd1351.SSD1351_BlackOut262(); //OLEDを256kカラーで黒画面出力
  SD.begin(cs_SD,SPI_FULL_SPEED);

  while(JpegDec.read()){
    pImg = JpegDec.pImage ;

    for(by=0; by<JpegDec.MCUHeight; by++){
      for(bx=0; bx<JpegDec.MCUWidth; bx++){
        x = JpegDec.MCUx * JpegDec.MCUWidth + bx;
        y = JpegDec.MCUy * JpegDec.MCUHeight + by;
        
        if(x<JpegDec.width && y<JpegDec.height){
            if(JpegDec.comps == 1){ // Grayscale
              R = round(pImg[0]/(256/64));
              G = round(pImg[0]/(256/64));
              B = round(pImg[0]/(256/64));                  
              //pinMode(sclk, OUTPUT); //OLED_SSD1351ライブラリver1.40の場合はこのコメントを外す
              //pinMode(mosi, OUTPUT); //OLED_SSD1351ライブラリver1.40の場合はこのコメントを外す
              ssd1351.SSD1351_1pixel_DisplayOut262(x, y, R, G, B); //OLEDに1pixelづつ256kカラーで出力
              pinMode(sclk, SPECIAL); //OLED動作後にSDカードを動作させるために必要
              pinMode(mosi, SPECIAL); //OLED動作後にSDカードを動作させるために必要
              pinMode(miso, SPECIAL); //OLED動作後にSDカードを動作させるために必要
            }else{ // RGB
              if(x < 128 && y < 128){
                R = round(pImg[0]/(256/64));
                G = round(pImg[1]/(256/64));
                B = round(pImg[2]/(256/64));
                //pinMode(sclk, OUTPUT); //OLED_SSD1351ライブラリver1.40の場合はこのコメントを外す
                //pinMode(mosi, OUTPUT); //OLED_SSD1351ライブラリver1.40の場合はこのコメントを外す
                ssd1351.SSD1351_1pixel_DisplayOut262(x, y, R, G, B); //OLEDに1pixelづつ256kカラーで出力
              }
              pinMode(sclk, SPECIAL); //OLED動作後にSDカードを動作させるために必要
              pinMode(mosi, SPECIAL); //OLED動作後にSDカードを動作させるために必要
              pinMode(miso, SPECIAL); //OLED動作後にSDカードを動作させるために必要
            }
        }
        pImg += JpegDec.comps ;
      }
    }
  }
}
※OLED_SSD1351ライブラリbeta ver1.53では以下の行をコメントアウトしてください。
212行
213行
230行
231行
241行
242行
Beta ver 1.40ではコメントを外してください。
バージョンアップしたときにここを修正しておりませんでした。
puw2さんからコメントでご指摘いただきました。
大変感謝いたしますm(_ _)m
(2016/10/21現在)

【解説】
●4-6行目:
Arduino core for ESP8266 Wi-Fi Chip の標準ライブラリです。
SD.h はESP8266のライブラリです。Arduino ではありません。

●11-17行:
ESP-WROOM-02 のGPIOピンを宣言します。
これはデータシートを見て、標準のピン配置に設定しました。

●19-20行:
ここで、ご自分のルーターのSSID とパスワードに書き換えてください。

●22-24行:
スマホに送信するHTMLファイルの分割ファイルを定義しています。予めmicro SD カードに保存してあるファイルです。

●25行:
micro SD カードのpicture フォルダにある画像ファイルがスマホからGET要求されたときに送るものです。
そのフォルダを指定します。

●26行:
スマホから送信されてくるバイナリ形式の画像ファイルを一時保存しておくファイル名をしてします。
ここでは必ずスラッシュ “/” を入れておいてください。
そして、毎回電源投入するとsetup関数内で SD.remove クラスで削除されます。
ファイル名はポインタではエラーになるので、必ず固定長の配列にしなければいけません
これが今回発見したことです。

●31-32行:
自作ライブラリのクラス名を指定します。
好きな名前にすることができます。

●58行、60行:
OLED_SSD1351ライブラリのver1.40からの新しいクラスです。262000色カラー用です。

●105行まで
ここまでは以前の記事と全く同じですので、そちらを参照してください。
やはり、HTMLのselectボックスの動的生成はもう一度自慢したいところです。

●108行:
前回と同じく、先に紹介した micro SD カードのEWSフォルダに保存した、分割HTMLファイルをマージして出力する関数です。
ここではWebSocket再接続されたかどうかの判断もしています。

●110-121行:
こちらは以前の記事を参照してください。

●123-131行:
123行では SD_EasyWebSocket beta ver 1.45 からのライブラリです。
スマホからWebSocket通信でバイナリ形式のデータを受信したら、そのままSDカードにファイルを書き込みます。
前に書き込んであった test.jpg ファイルは強制削除されます。
現在のところ、別のファイル名で保存するようにはしておりませんのでご了承ください。
ここで、ライブラリのソースコードを見ていただけると分かると思うのですが、スマホからバイナリ形式で送信すると、バイト長によって頭に送られてくるフラグのバイト長も変化します。
その可変長フラグを認識できるようにするのが苦労しました。
また、スマホ側のJavaScript もバイナリを送る場合には、ある特別な手法がありました。
ここではとても紹介しきれないのですが、ソースコードを見て理解していただければと思います。

●113-177行:
こちらは以前の記事を参照してください。

●171-254行:
JPEG Decoder のサンプルスケッチを参考にし、シリアルでCSV出力するところをOLEDの1pixel に出力するところに変えました。
それは、232行と243行です。
今回からOLED_SSD1351ライブラリで追加した関数です。
262000色カラーで指定できます。
Red, Green, Blue それぞれ6bit で表現するので、0~63の値を入れられます。
JPEGデコーダで256段階の数値を読み取るので、それを64段階変換しているのが、238-240行です。

ここで、とても重要なことがあります。
今回とても苦労して発見したところなのですが、ESP8266 のSPI通信に使うGPIOピンがなぜかOLEDとSDカードの通信の競合が起こるんです。
CSピンのHIGH、LOW をちゃんと切り替えてもダメでした。
そこで、SDカード通信をする前に SD.begin を実行させ、OLED を表示させる前に pinMode(sclk, OUTPUT); と
pinMode(mosi, OUTPUT) を実行させるとうまくいきました。

※OLED_SSD1351 Beta 1.53ではこのpinMode設定は不要になりました。
でも、SD.begin をドット単位で毎回呼び出すととても表示速度が遅くなります。
そこで、SD.beginクラスのライブラリソースコードを解読して、いろいろと実験してみると、

pinMode(sclk, SPECIAL);

というところが目につき、それを mosi miso のそれぞれのピンにも実行させてみました。
そうしたら、バッチリ、高速でSDカードを読み取ることができました。
よく調べてないのですが、この SPECIAL という変数を割り当てると、どうやらSDカード用のGPIOピンに自動的に割り当てられるようです。
でも、ESP-WROOM-02 のデータシートを見てデフォルトのSPIピンにしたんですけどね・・・。
ということで、これのおかげで素早い画像の切り替えが実現できたわけです。

5.コンパイル書き込み、実行させる

では、スケッチをコンパイル書き込みして、即シリアルモニタを起動してみてください。
すると下図の様に表示されます。シリアルモニタの右下隅は115200 bps にしておいてください。
ローカルIPアドレスが分からなかった方はシリアルモニターに表示されます。

では、スマホのブラウザのURL入力欄にIPアドレスを入力してみてください。
たとえば、192.168.0.14 というふうに入力します。

すると、シリアルモニタに下図の様に表示されたらWebSocketコネクション(ハンドシェイク)完了です。

すると、スマホのブラウザでは下図の様に表示されます。
ここでは Google Chrome を使用しております。

次に、「ファイルを選択」ボタンをタッチして、スマホの中の好きな画像を選択してください。
必ず、先に述べたように画像を128×128 pixel にリサイズしておいてください。
画像を選ぶと下図の様に表示されます。
ファイルパスの fakepath というのは、セキュリティ上の理由で自動的にそうなってしまうようです。

スマホ側の「Send Image」ボタンを押すとシリアルモニタには下図の様に表示されます。

すると、有機EL ( OLED )ディスプレイに画像が表示されていると思います。

あと、Get Image ボタンの動作は以前の記事と同じですので、そちらを参照してください。

以上です。
いかがでしたでしょうか。ちゃんど動作しましたでしょうか。
今回はなかなか面白いコーディングでした。
ブラウザの挙動やJavaScript がだんだんわかってきましたし、ESP8266のSPIライブラリの挙動も分かってきましたし・・・。

いやぁ、それにしても、このブログ記事を書くのが一番重労働です。
文章の誤字脱字が多いかも知れませんのでご容赦くださいませ。
しかし、これを何とかしないと時間が足りなくて困り果ててしまいます。

ではまた・・・。

コメント

  1. ないん より:

    初めまして。ないんと申します。JPEGデコーダを使って頂きありがとうございます!
    動画見ましたがサクサクですね〜スゴい。
    しかもとても丁寧な解説記事で頭が下がります。私にはムリですね(笑)
    ライブラリの方は確かに2回目のことは考えてなかったかも知れません…
    他にも色々参考になることがありますのでまた寄らせていただきますね!

    • mgo-tec mgo-tec より:

      ないん さん

      コメントありがとうございます。
      JPEGDecoderは素晴らしいライブラリですね。
      これからもジャンジャン使わせていただきます。
      こちらこそ、ブログによらせていただきますので、よろしくお願いします。

  2. puw2 より:

    こんにちは
    ESP8266の使い方で大変参考にさせて頂いています
    プログラムどうりに入力して動作確認していたのですが
    どうしてもOLEDの表示がノイズらしき物は出るのですが
    表示は出来ませんでした4,5日悩んだ後思い余って下記の
    出力部分をコメントアウトして試したら表示出来ました
    理由は良く解りませんが参考にして下さい
    いつも丁寧な解説有難う御座います
    R = round(pImg[0]/(256/64));
    G = round(pImg[1]/(256/64));
    B = round(pImg[2]/(256/64));
    // pinMode(sclk, OUTPUT); //SDカード動作後にOLEDを動作させるために必要
    // pinMode(mosi, OUTPUT); //SDカード動作後にOLEDを動作させるために必要
    ssd1351.SSD1351_1pixel_DisplayOut262(x, y, R, G, B); //OLEDに1pixelづつ256kカラーで出力
    }
    今後ともよろしくお願いします

    • mgo-tec mgo-tec より:

      puw2さん

      コメントありがとうございます。
      大変失礼しました。
      OLED_SSD1351ライブラリをバージョンアップしたときに、pinMode(sclk,OUTPUT)とpinMode(mosi,OUTPUT)が不要になったので、そのアナウンスをすることを忘れておりました。
      バージョンアップするときには以後気を付けたいと思います。
      動かなかった皆さま、ゴメンナサイ。m(_ _)m

タイトルとURLをコピーしました