鉄道 JR風 有機EL 電光掲示板を作ってみた。(スマホで編集できて、Yahoo! ニュースも表示できる)

記事公開日:2016年11月14日
最終修正日:2017年4月13日

スポンサーリンク

Yahoo!ニュース RSS サイトが https ( SSL )化されてしまいました。
その対策方法は以下のページにあります。

JR 風 Yahoo! ニュース 電光掲示板 の https ( SSL )対策

合わせてご参照ください。
(2017/4/11時点)

こんばんは。

今回はちょっとしたお遊びで、今まで作ってきた有機EL ( OLED ) Yahoo! ニュース電光掲示板を JR電光掲示板風にアレンジしてみました。

しかも、スマホのブラウザで文字や路線色を変えられて、メッセージも変えられます。
時計としての機能もあり、NTPサーバーから時刻を自動補正します。
そして、スマホにあるJPEG画像も表示できて、リアルタイムに変更できるというものです。

今まで取り組んできた、ESP-WROOM-02 ( ESP8266 ) とスマホとのWi-Fi 連携、Web記事取得、SPI高速化、文字コード変換、フリーフォント表示、電光掲示板スクロールなどの総まとめ、という感じです。

では、以下の動画をご覧ください。

いかがでしょうか。
時刻は現在時刻を表示していて、NTPサーバーで1時間毎に補正してます。

今回は8×8ドットの美咲フォントも使って、小さい文字のOLED表示も挑戦してみました。
以前の記事で、極小有機EL ( OLED ) に美咲フォントを表示させたときには、小さすぎて老眼には辛かったので、2倍角にして表示させてました。
しかし、このJR風電光掲示板では8×8ドットフォントがとても良い効果を発揮してくれました。

拡大した写真はこんな感じです。

jr_denko_03

小さいディスプレイでは、美咲フォントそのままではあまりにも小さすぎて使い道が無いと思っていたのですが、こうするとなかなかイイもんですね。

実は、固定の文字を変更するときに、文字数が多すぎると、他の表示を潰してしまうので画面が崩れてしまいます。これは趣味程度のものなので、お許しください。
予め指定された文字数を送信してください。
もし、崩れた場合は、リセットすれば良いです。

また、動画では分からないのですが、ESPr Developer ( ESP-WROOM-02, ESP8266 ) のローカルIPアドレスを自動取得するようにしました。
今までは、HTMLヘッダファイルをアドレスが変わる度に書き換えていたのですが、その煩わしさから解放されます。
その代わりに、ヘッダファイルを分割することになります。
まぁ、ヘッダファイルはそんなに編集することが無いので良いと思います。

では、前置きはこれくらいにして、作り方を解説していきます。

1.準備するもの

過去の記事と同様で、使うデバイスは2つのみのシンプルなものです。
初めて当ブログをご覧になる方のために、説明しておきます。
分かっている方は読み飛ばしてください。

●ESPr Developer ( ESP-WROOM-02 開発ボード) スイッチサイエンス製
Amazon.co.jp
当ブログで何度も紹介しているお勧めボードです。
日本の電波法をクリアした技適認証済みの ESP-WROOM-02 ( ESP8266 )に電源レギュレーターやUSBシリアルインターフェースなどをパッケージにしたボードです。
Arduino IDE で開発ができて、Wi-Fi が使用できて、メモリはArduino MEGA以上あるという優れものボードです。しかも小さい!!

これと同じピン配列の以下のボードも使用できます。

●Adafruit OLED Breakout Board – 16-bit Color 1.5″ w/microSD holder
Amazon.co.jp
ちょっと高価なデバイスですが、最大262000色のフルカラーの表示ができて、しかもmicroSDカードスロットが付属しているというものです。
有機EL なので、バックライト無しで自ら発光するので、薄くてクリアな画面です。

ピンヘッダはご自分でハンダ付けする必要があります。
組み立て方や使い方は以下のページを参照してください。

Adafruit 16-bit フルカラー OLED ( SSD1351 ) を ESPr Developer ( ESP-WROOM-02 ) で動かしてみた

●micro SD カード
当方で動作確認したものはこれです。
Amazon.co.jp

アクセスが速い Class 10 のものを使用しましたが、Class 4 でもあまり変わりませんでした。
TOSHIBAの方は標準サイズのSDカードアダプターは付属していないので、パソコンのカードスロットに挿入する場合は別途購入が必要です
他のメーカーのものでも、おそらく動作するとの思われますが、稀に動作しないものもありますのでご注意ください。

●ブレッドボード
このブレッドボードがお勧めです。

ESPr Developer を挿しても、両サイドに2列の空きがあって重宝してます。

●ジャンパーワイヤー、パソコン、USBケーブル等

できるだけ最新のスマートフォンと最新ブラウザ
Android の Google Chrome が推奨ですが、iOS Safari でも動作します。
OS や ブラウザは最新版にアップデートしておいてください。
古いスマホだと途中でフリーズする場合があります。

●2.4GHz Wi-Fi (無線LAN)ルーター および、インターネット接続環境
できるだけ高性能の物が通信が速いです。
予め、スマホのWi-Fiでインターネットサーフィンができるように設定しておいてください。

2.接続する

過去の記事と接続が異なっているところがありますのでご注意ください

後で紹介する OLED_SSD1351 ライブラリをバージョンアップした関係上、16番ピンをリセット以外の信号ピンとして使えなくなりました

ということで、以下の図を参照して接続してみてください。

JR_denko_02

3.Arduino IDE 設定、ESP8266ボードのインストール

当方ではWindows環境で作っております。
予め、Arduino IDE をインストールしておいてください。
動作確認が取れているのは、Arduino.ccページの ver 1.6.12 です。

また、Arduino IDE に Arduino core for ESP8266 Wi-Fi Chip ボードをインストールしておいてください。
これの方法やボード設定は以下のページを参照してください。

Arduino IDE に Staging(Stable)版ESP8266 ボードをインストールする方法

4.ライブラリのインストール

Yahoo!ニュース RSS サイトが https ( SSL )化されてしまいました。
その対策方法は以下のページにあります。

JR 風 Yahoo! ニュース 電光掲示板 の https ( SSL )対策

合わせてご参照ください。
その場合、SD_EasyWebSocket は Beta ver 1.50 をインストールする必要があり、スケッチも大幅変更する必要があります。
(2017/4/11時点)

Arduino IDE に予めインストールして置いて欲しいライブラリは以下の7つです。
※新しいバージョンのライブラリをインストールする場合には、必ず古いバージョンのライブラリをフォルダごと削除してください

GitHub にある ZIPファイルをIDEにインストールする方法は以下のページを参照してください。

GitHubにある ZIP形式ライブラリ のインストール方法

OLED_SSD1351bv2 ( 当方自作 )  Beta ver 2.0

ダウンロード先( GitHub ) → OLED_SSD1351bv2

これは、Adafruit OLED SSD1351 をSPI通信で使うライブラリです。
Adafruit標準のライブラリよりも高速で処理してくれると思います。
これの使い方は以前とあまり変わらないのですが、SPI高速化をした時にレジスタダイレクトアクセスを使用した関係上、過去のバージョンと同じようには使えなくなってしまったので、競合を避けるためにライブラリの名称を変更しました。
ですから、このライブラリに関しては今回初登場なので、古いライブラリを削除せずにインストールするだけです。
使い方は過去の記事を参照していただければと思います。
ただ、後で述べる電光掲示板スクロール用関数をちょっと変えました。

また、後で述べてますが、GPIO#16 はリセット信号以外を割り当てないようにしてください
16番ピンはGPIOレジスタダイレクトアクセスで制御できないためです。

SD_UTF8toSJIS ( 当方自作 ) beta ver 1.0.1

ダウンロード先( GitHub ) → SD_UTF8toSJIS

これは、文字コードのUTF-8 形式を Shift_JIS に変換するライブラリです。
予めSDカードに保存してある変換テーブルファイル Utf8Sjis.tbl を使います。
使い方は過去記事と同じです。
SPIFFSよりも、SDカードの方が読み取りは格段に速いです。

SD_ShinonomeFONTreadbeta ( 当方自作 ) ver 1.0.1

ダウンロード先( GitHub ) → SD_ShinonomeFONTreadbeta

これは、フリーの16×16ドット日本語漢字フォントを使うライブラリです。
東雲(しののめ)フォントを使います。
以前にも述べましたが、とても素晴らしいフォントで、半角英語、カタカナまであります。
これの使い方は以下のページを参照してください。

OLED ( 有機EL ) SSD1306 に16×16ドットのフリーの日本語漢字、東雲フォントを表示させてみました

その他、過去記事に所々使い方がありますので、参照してみてください。

SD_EasyWebSocket ( 当方自作 ) beta ver1.47

ダウンロード先( GitHub ) → SD_EasyWebSocket

これは、スマホのブラウザと双方向Wi-Fiリアルタイム通信するための自作ライブラリです。
ブラウザに表示させるWebページのHTMLファイルをSDカードから読み取ります。
今回では1.45→1.47 へバージョンアップしました。
後で述べますが、ESPr Developer ( ESP8266 ) のローカルIPアドレスを自動取得する関数を追加しました。
そのため、今までのHTMLヘッダファイルを分割することになりました。

SD_MisakiFNT_read( 当方自作 ) beta ver1.1

ダウンロード先( GitHub ) → SD_MisakiFNT_read

これは、過去の記事で使用した、8×8ドットの日本語漢字(美咲)フォントをSDカードから読み取るライブラリです。
今まではSPIFFSで読み取る物しかなかったのですが、今回からSDカード用も作りました。
新たに追加した関数では、スケッチ上でforループを使わずに文字列全てを変換できるようにしました。
そこでは、以前のライブラリでは全角半角混在の文字列はうまく判別できないことがありましたが、今回はかなり判別できるようになったと思います。その関数については後述します。

JPEGDecoder ( 「ないん」さん作成 )

ないん」さん作成による、フリーのJPEG画像表示ライブラリです。
BMP画像表示ライブラリはネットに結構あるのですが、JPEG画像については意外と無いもので、このライブラリはとても貴重です。
ライセンスはパブリックドメインなので、フリーです。
「ないん」さんに感謝・感謝・感謝・・・。

このライブラリはそのまま使用するとJPEG画像は1度しか表示できないので、以下のリンクで ライブラリにinit()関数を追加する必要があります。

インストール方法→ スマホのJPEG写真や画像をWi-Fiで飛ばして OLED ( 有機EL )に表示させてみた

Timeライブラリ ( Arduino標準 )  ver 1.4

インストール方法 → こちらを参照

Arduino 標準のライブラリで、時計表示には欠かせないものです。

5.microSDカードにファイルを保存しておく

販売している最近の大手メーカー製のmicroSDカードはフォーマットされているものが殆どですが、フォーマットされていない場合は、以下のページを参照してください。
(Windows の場合で説明してます)

micro SD 、micro SDHC カードの初期化(フォーマット)方法

microSDカードに保存するファイルはGitHubの以下のページにあります。

https://github.com/mgo-tec/SDcard_sample_filses

ダウンロードしたZIPファイルを解凍して、EWS フォルダと font フォルダを、それぞれフォルダごとmicro SD カードのルートに コピーしておいてください。

EWSフォルダには、スマホのブラウザに表示させるWebページ用の分割HTMLファイルやヘッダ部分が入っています。
その中で今回使うファイルは以下の3つです。

  • LIPhead1.txt (WebページHTMLヘッダの分割ファイル)
  • LIPhead2.txt (WebページHTMLヘッダの分割ファイル)
  • LIPhtml.htm (WebページHTMLのBody要素部分)

fontフォルダには、フリーの日本語漢字フォントが入っています。
今回はすべてのファイルを使います。

  • 4X8.FNT
    (4×8ドット美咲フォント 半角英数字カナ用 門真なむ さん作
  • mgotec48.FNT
    (4×8ドット 半角数字用 mgo-tec作)
  • MSKG13KU.FNT
    (8×8ドット JIS13区入り美咲フォント 日本語かな漢字用 門真なむ さん作
  • shnm8x16.bdf
    (8×16ドット東雲フォント 半角英数字カナ用 保守・開発 /efont/さん
    オリジナルのファイルネームは
    shnm8x16r.bdf ですが、ESP8266 SDカードライブラリでは英数字8文字+拡張子3文字なので、’r’ をカットしました
  • shnmk16.bdf
    (16×16ドット東雲フォント 日本語かな漢字用 保守・開発 /efont/さん
  • Utf8Sjis.tbl
    (UTF-8→Shift_JIS 変換テーブル mgo-tec作)

6.スマホ画像をリサイズ(サイズ縮小)しておく

スマートフォンに保存してあるJPEG画像や写真を予め縮小(リサイズ)しておきます。
以下の記事を参照していただき、画像サイズを
128×53 ピクセル
にしておいてください。
これより大きいサイズの場合、画面が崩れますので注意してください。

Androidスマホの写真 ( 画像 )サイズ変更 ( リサイズ )するアプリ Image Shrink の使い方

iPhone iPad の写真リサイズ( 画像サイズ変更 )するアプリ、バッチリサイズ の使い方

7.スケッチの入力

Yahoo!ニュース RSS サイトが https ( SSL )化されてしまいました。
その対策方法は以下のページにあります。

JR 風 Yahoo! ニュース 電光掲示板 の https ( SSL )対策

合わせてご参照ください。
その場合、SD_EasyWebSocket は Beta ver 1.50 をインストールする必要があり、スケッチも大幅変更する必要があります。
(2017/4/11時点)

では、Arduino IDE で以下のスケッチを入力してください。

ライセンスは MIT としました。
Web上でソースコードを公開する場合にはライセンスを明記しないと自由に配布できないそうなので、Arduino core for ESP8266 ライブラリのLGPL v2 と親和性のあるMITにしました。

/* ESPr Developer or ESP-WROOM-02 or ESP8266
 * Adafruit OLED SSD1351 Breakout Board – 16-bit Color 1.5″ w/microSD holder
 * This Sketch is The MIT License (MIT)
 * Copyright (c) 2016 mgo-tec 
 * License reference URL --> https://opensource.org/licenses/mit-license.php
 * JPEGデコーダは「ないん」さん作成。http://yushakobo.jp/pluis9/2014/06/jpegdecoder/
 */
#include <OLED_SSD1351bv2.h> //beta ver 2.0
#include <SD_UTF8toSJIS.h> //beta ver 1.0.1
#include <SD_ShinonomeFONTread.h>  //beta ver 1.0.1
#include <SD_EasyWebSocket.h> //beta ver1.47
#include <SD_MisakiFNT_read.h> //beta ver 1.1
#include <JPEGDecoder.h> //ないんさん作成
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <SD.h>
#include <SPI.h>
#include <Hash.h>
#include <TimeLib.h> //timeライブラリver1.4の場合
 
SD_UTF8toSJIS u8ts;
SD_ShinonomeFONTread SFR;
OLED_SSD1351bv2 oled;
SD_MisakiFNT_read MFR;
SD_EasyWebSocket ews;
 
const uint8_t sclk = 14; //SPI clock
const uint8_t mosi =13; //Master Output Slave Input ESP8266=Master,BME280=slave 
const uint8_t miso =12; //Master Input Slave Output
const uint8_t cs_SD = 15; //SD card CS
const uint8_t cs_OLED = 0;
const uint8_t DCpin =  5; //OLED DC(Data/Command)
const uint8_t RSTpin =  4; //OLED Reset

const char* ssid = "xxxx"; //ご自分のルーターのSSIDを入力
const char* password = "xxxx"; //ご自分のルーターのパスワードを入力
 
const char* UTF8SJIS_file = "font/Utf8Sjis.tbl"; //UTF8 Shift_JIS 変換テーブルファイル名
const char* Shino_Zen_Font_file = "font/shnmk16.bdf"; //全角東雲フォントファイル名を定義
const char* Shino_Half_Font_file = "font/shnm8x16.bdf"; //半角東雲フォントファイル名を定義
const char* Misaki_Zen_Font_file = "font/MSKG13KU.FNT"; //全角美咲フォントファイル名を定義
const char* Misaki_Half_Font_file = "font/mgotec48.FNT"; //半角美咲フォントファイル名を定義
const char* HTM_head_file1 = "EWS/LIPhead1.txt"; //HTMLヘッダファイル1
const char* HTM_head_file2 = "EWS/LIPhead2.txt"; //HTMLヘッダファイル2
const char* HTML_body_file = "EWS/LIPhtml.htm"; //HTML body要素ファイル
const char* dummy_file = "EWS/dummy.txt"; //HTMLファイル連結のためのダミーファイル
 
char PicFile[14] = "/PICtmp.jpg"; //スマホから送信された画像ファイルをSDカードのルートに保存するファイル名を指定。filename Max 8文字, 拡張子3文字
 
enum { MaxTxt = 800 , DispChar = 16}; //MaxTxt = スクロールする文字列の最大数(半角数)
                                      //DispChar = ディスプレイに表示できる半角文字数
uint8_t Sino_font_buf[MaxTxt][16]; //東雲(しののめ)フォントバッファ
uint8_t SnnmDotOut[DispChar][16];
uint8_t Next_buf[DispChar][16];
uint8_t dummy_Sino_font_buf[16];

File UtoS; //ファイルハンドル名定義
File SinoZ;
File SinoH;
File MisakiZ;
File MisakiH;

int PingSendTime = 30000; //スマホとのWebSocket通信接続確認の為のPing送信間隔設定(30秒)

IPAddress LIP; //ESP-WROOM-02(ESP8266) ローカルIPアドレス自動取得用
String html_str1 = "";
String html_str2 = "";
String html_str3 = "";

//-------NTPサーバー引数初期化-----------------------------
IPAddress timeServer(132, 163, 4, 101); // time-a.timefreq.bldrdoc.gov; //(129, 6, 15, 28)time.nist.gov NTP server
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
const int timeZone = 9;     // Tokyo
WiFiUDP Udp;
unsigned int localPort = 8888;  // local port to listen for UDP packets
time_t prevDisplay = 0; // when the digital clock was displayed
uint32_t LastNTP_Get = 0;
//-----時計表示引数初期化------------------------------ 
boolean clock_Disp_Set = true, clock_Disp_OUT = true;
uint8_t clock_Select = 0;
uint32_t sec_colon_time = 0;
uint8_t h_t = 25, min_t = 61; //初回時刻表示させるために、時刻の有り得ない数値で予め初期化しておく
//-----Yahoo記事取得引数初期化-------------------------
uint32_t WebGetTime = 0;
boolean Web_first_get = true, Text_Box_On = false;
uint8_t Yahoo_get = 1;
//-----電光掲示板文字列およびスクロール引数初期化-------
uint8_t sj_txt[MaxTxt]; //Shift_JISコード収納配列
uint16_t sj_cnt = 0; //Shift_JISコード配列カウント数
uint16_t sj_length; //Shift_JISコード長
uint32_t SCLtime; //文字列スクロールスピードでmillisを取得して代入する引数
uint16_t Scrolle_speed = 8; //文字列スクロールインターバル時間(8ms)
uint8_t scl_cnt = 0; //フォントのスクロールカウント数
//------JR風電光掲示板引数初期化-----------------------
uint8_t Red, Green, Blue;  //OLED SSD1351 65kカラーの場合、赤max=31, 緑max=63, 青max=31
uint8_t jrbRed = 7, jrbGreen = 14, jrbBlue = 7; //JR風電光掲示板のベース色
uint8_t BG_red = 0, BG_green = 0, BG_blue = 0; //OLED全体の背景色
uint8_t jrX0 = 0, jrY0 = 0; //JR風電光掲示板位置の左上座標(看板描画原点)
uint8_t picX0 = 16, picY0 = 74; //JPEG画像表示位置の左上座標(描画原点)
uint8_t msgRed = 0, msgGreen = 63, msgBlue = 0; //OLED SSD1351 65kカラーの場合、赤max=31, 緑max=63, 青max=31
String blink_msg = "電車がまいります";
boolean blink_1th_in = true; //起動時に点滅文字列を表示するかどうか
boolean blink_on = true; //点滅文字列ON/OFF判定

//**************セットアップ************************************
void setup() {
  Serial.begin(115200);
  uint16_t i, j;

  delay(1000);
  ews.AP_Connect(ssid, password); //ルーター(アクセスポイント)へ接続
  delay(1000);
  LIP = WiFi.localIP(); //ESP8266のローカルIPアドレスを自動取得
 
  Serial.println();
  Serial.println(F("closing connection"));
  delay(1000);

  SPI.begin();  
  SPI.setFrequency(20000000);
  SPI.setDataMode(SPI_MODE2); //これは実はMODE3

  oled.SSD1351bv2_Init(sclk, mosi, cs_OLED, DCpin, RSTpin); //OLED初期化

  jpegDraw_65k_color(PicFile); //SDカードに保存された画像をOLEDに出力する

  SD.begin(cs_SD,40000000);
   
  Serial.println("card initialized.");
  UtoS = SD.open(UTF8SJIS_file, FILE_READ);
  if (UtoS == NULL) {
    Serial.print("**Utf8Sjis.tbl File not found");
    return;
  }else{
    Serial.println("Utf8Sjis.tbl File OK!");
  }
  SinoH = SD.open(Shino_Half_Font_file, FILE_READ);
  if (SinoH == NULL) {
    Serial.print("shnm8x16.bdf File not found");
    return;
  }else{
    Serial.println("shnm8x16.bdf File OK!");
  }
  SinoZ = SD.open(Shino_Zen_Font_file, FILE_READ);
  if (SinoZ == NULL) {
    Serial.print("shnmk16.bdf File not found");
    return;
  }else{
    Serial.println("shnmk16.bdf File OK!");
  }
  MisakiZ = SD.open(Misaki_Zen_Font_file, FILE_READ);
  if (MisakiZ == NULL) {
    Serial.print("MSKG13KU.FNT File not found");
    return;
  }else{
    Serial.println("MSKG13KU.FNT File OK!");
  }
  MisakiH = SD.open(Misaki_Half_Font_file, FILE_READ);
  if (MisakiH == NULL) {
    Serial.print("mgotec48 File not found");
    return;
  }else{
    Serial.println("mgotec48 File OK!");
  }

  uint8_t MSK_font_buf[32][8];
  //-------------JR風看板設定-------------------------------
    SPI.setFrequency(20000000);
    SPI.setDataMode(SPI_MODE2);
    oled.SSD1351bv2_RectFill(jrX0, jrY0, jrX0 + 127, jrY0 + 73, jrbRed, jrbGreen, jrbBlue); //看板ベース
    Red = 0, Green = 0, Blue = 0;
    oled.SSD1351bv2_RectFill(jrX0, jrY0+34, jrX0 + 127, jrY0 + 70, Red, Green, Blue); //電光掲示板部
    Red = 0, Green = 63, Blue = 0;
    oled.SSD1351bv2_RectFill(jrX0+50, jrY0+5, jrX0+60, jrY0+33, Red, Green, Blue); //JR路線色
  
    SPI.setFrequency(40000000);
    SPI.setDataMode(SPI_MODE0);
    u8ts.UTF8_to_SJIS_str_cnv(UtoS, "山手線", sj_txt, &sj_length);
    SFR.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, sj_txt, sj_length, Sino_font_buf);
    SPI.setFrequency(20000000);
    SPI.setDataMode(SPI_MODE2);
    Red = 31, Green = 63, Blue = 31;
    oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR_BGcolor(jrX0, jrY0 + 5, Red, Green, Blue, jrbRed, jrbGreen, jrbBlue, 6, Sino_font_buf);  
   
    SPI.setFrequency(40000000);
    SPI.setDataMode(SPI_MODE0);
    u8ts.UTF8_to_SJIS_str_cnv(UtoS, "渋谷方面", sj_txt, &sj_length);
    SFR.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, sj_txt, sj_length, Sino_font_buf);
    SPI.setFrequency(20000000);
    SPI.setDataMode(SPI_MODE2);
    Red = 31, Green = 63, Blue = 31;
    oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR_BGcolor(jrX0+63, jrY0 + 5, Red, Green, Blue, jrbRed, jrbGreen, jrbBlue, 8, Sino_font_buf);  
    
    SPI.setFrequency(40000000);
    SPI.setDataMode(SPI_MODE0);
    u8ts.UTF8_to_SJIS_str_cnv(UtoS, "YamanoteLine", sj_txt, &sj_length); //最大半角12文字
    uint16_t Misaki_Length = MFR.Sjis_To_MisakiFNT_Read_ALL(MisakiZ, MisakiH, 0, 0, sj_txt, sj_length, MSK_font_buf);
    SPI.setFrequency(20000000);
    SPI.setDataMode(SPI_MODE2);
    oled.SSD1351bv2_8x8_DisplayOut_1col_LtoR_BGcolor(jrX0, jrY0+24, Red, Green, Blue, jrbRed, jrbGreen, jrbBlue, Misaki_Length, MSK_font_buf);
  
    SPI.setFrequency(40000000);
    SPI.setDataMode(SPI_MODE0);
    u8ts.UTF8_to_SJIS_str_cnv(UtoS, "  for Shibuya", sj_txt, &sj_length); //最大半角16文字
    Misaki_Length = MFR.Sjis_To_MisakiFNT_Read_ALL(MisakiZ, MisakiH, 0, 0, sj_txt, sj_length, MSK_font_buf);
    SPI.setFrequency(20000000);
    SPI.setDataMode(SPI_MODE2);
    oled.SSD1351bv2_8x8_DisplayOut_1col_LtoR_BGcolor(jrX0+63, jrY0+24, Red, Green, Blue,  jrbRed, jrbGreen, jrbBlue, Misaki_Length, MSK_font_buf);   
    
    SPI.setFrequency(40000000);
    SPI.setDataMode(SPI_MODE0);
    u8ts.UTF8_to_SJIS_str_cnv(UtoS, "--:--", sj_txt, &sj_length);
    SFR.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, sj_txt, sj_length, Sino_font_buf);
    SPI.setFrequency(20000000);
    SPI.setDataMode(SPI_MODE2);
    Red = 0, Green = 63, Blue = 0;
    BG_red = 0, BG_green = 0, BG_blue = 0;
    oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR_BGcolor(jrX0+32, jrY0+36, Red, Green, Blue, BG_red, BG_green, BG_blue, sj_length, Sino_font_buf);
    
    SPI.setFrequency(40000000);
    SPI.setDataMode(SPI_MODE0);
    u8ts.UTF8_to_SJIS_str_cnv(UtoS, "普通", sj_txt, &sj_length);
    SFR.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, sj_txt, sj_length, Sino_font_buf);
    SPI.setFrequency(20000000);
    SPI.setDataMode(SPI_MODE2);
    Red = 31, Green = 30, Blue = 0;
    oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR_BGcolor(jrX0, jrY0+36, Red, Green, Blue, BG_red, BG_green, BG_blue, sj_length, Sino_font_buf);
    
    SPI.setFrequency(40000000);
    SPI.setDataMode(SPI_MODE0);
    u8ts.UTF8_to_SJIS_str_cnv(UtoS, "渋谷", sj_txt, &sj_length);
    SFR.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, sj_txt, sj_length, Sino_font_buf);
    SPI.setFrequency(20000000);
    SPI.setDataMode(SPI_MODE2);
    oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR_BGcolor(jrX0+72, jrY0+36, Red, Green, Blue, BG_red, BG_green, BG_blue, sj_length, Sino_font_buf);
    
    SPI.setFrequency(40000000);
    SPI.setDataMode(SPI_MODE0);
    u8ts.UTF8_to_SJIS_str_cnv(UtoS, "方 ", sj_txt, &sj_length);
    Misaki_Length = MFR.Sjis_To_MisakiFNT_Read_ALL(MisakiZ, MisakiH, 0, 0, sj_txt, sj_length, MSK_font_buf);
    SPI.setFrequency(20000000);
    SPI.setDataMode(SPI_MODE2);
    oled.SSD1351bv2_8x8_DisplayOut_1col_LtoR_BGcolor(jrX0+104, jrY0+36, Red, Green, Blue, BG_red, BG_green, BG_blue, Misaki_Length, MSK_font_buf);
  
    SPI.setFrequency(40000000);
    SPI.setDataMode(SPI_MODE0);
    u8ts.UTF8_to_SJIS_str_cnv(UtoS, "面 ", sj_txt, &sj_length);
    Misaki_Length = MFR.Sjis_To_MisakiFNT_Read_ALL(MisakiZ, MisakiH, 0, 0, sj_txt, sj_length, MSK_font_buf);
    SPI.setFrequency(20000000);
    SPI.setDataMode(SPI_MODE2);
    oled.SSD1351bv2_8x8_DisplayOut_1col_LtoR_BGcolor(jrX0+104, jrY0+44, Red, Green, Blue, BG_red, BG_green, BG_blue, Misaki_Length, MSK_font_buf);
    
    SPI.setFrequency(40000000);
    SPI.setDataMode(SPI_MODE0);
    u8ts.UTF8_to_SJIS_str_cnv(UtoS, "10", sj_txt, &sj_length);
    Misaki_Length = MFR.Sjis_To_MisakiFNT_Read_ALL(MisakiZ, MisakiH, 0, 0, sj_txt, sj_length, MSK_font_buf);
    SPI.setFrequency(20000000);
    SPI.setDataMode(SPI_MODE2);
    Red = 0, Green = 63, Blue = 0;
    oled.SSD1351bv2_8x8_DisplayOut_1col_LtoR_BGcolor(jrX0+112, jrY0+36, Red, Green, Blue, BG_red, BG_green, BG_blue, Misaki_Length, MSK_font_buf);
  
    SPI.setFrequency(40000000);
    SPI.setDataMode(SPI_MODE0);
    u8ts.UTF8_to_SJIS_str_cnv(UtoS, "両", sj_txt, &sj_length);
    Misaki_Length = MFR.Sjis_To_MisakiFNT_Read_ALL(MisakiZ, MisakiH, 0, 0, sj_txt, sj_length, MSK_font_buf);
    SPI.setFrequency(20000000);
    SPI.setDataMode(SPI_MODE2);
    oled.SSD1351bv2_8x8_DisplayOut_1col_LtoR_BGcolor(jrX0+116, jrY0+44, Red, Green, Blue, BG_red, BG_green, BG_blue, Misaki_Length, MSK_font_buf);
  //-------------------------------------------------------------    
  for(i=0; i<16; i++) { //初期化しておく
    for(j=0; j<16; j++){
      Sino_font_buf[i][j] = 0;
      SnnmDotOut[i][j] = 0;
      Next_buf[i][j] = 0;
    }
  }
  //NTPサーバーから時刻を取得---------------------------
  Udp.begin(localPort);
  setSyncProvider(getNtpTime);
  delay(1000);
  
  scl_cnt = 0;
  sj_cnt = 16;
  SCLtime = millis();
  prevDisplay = now();
  LastNTP_Get = millis();  
}
//********************メインループ************************************** 
void loop() {
  //WebSocket ローカルIPアドレスを自動取得して、ハンドシェイク(コネクション確立)
  ews.EWS_Dev_AutoLIP_HandShake(cs_SD, HTM_head_file1, LIP, HTM_head_file2, HTML_body_file, html_str1, html_str2, html_str3, dummy_file);
  
  int16_t i, j;
  String ret_str; //スマホからWebSocket通信で送られてくるテキストデータを格納

  ret_str = ews.EWS_ESP8266DataReceive_SD_write(PingSendTime, cs_SD, PicFile); //スマホからバイナリ形式ファイルが送られて来たらSDカードへ格納
  if(ret_str == "_Binary"){ //スマホからのバイナリデータを受信した場合
    Serial.println("WebSocket Binary Receive Complete!\r\n");
    jpegDraw_65k_color(PicFile); //SDカードに保存されたJPEG画像をOLEDに出力する
    Serial.println("JPEG display complete----------");
    ret_str="";
  }

  if(ret_str != "_close"){ //スマホブラウザから受信した文字列を判別して動作を決める
    if(ret_str != "\0"){
      if(ret_str != "Ping"){
        Serial.println(ret_str);
        if(ret_str[0] != 't'){
          switch(ret_str[4]){
            case 'Y': //Yahoo記事GET
              Web_first_get = true; Text_Box_On = false; Yahoo_get = 1;
              break;
            case 'b': //点滅文字列OFF
              blink_on = false;
              break;
            case 'B': //点滅文字列ON
              blink_on = true;
              blink_1th_in = true;
              break;
            case 'M': //スクロール文字色選択
              switch(ret_str[7]){
                case 'G':
                  msgRed = 0, msgGreen = 63, msgBlue = 0; //OLED SSD1351 65kカラーの場合、赤max=31, 緑max=63, 青max=31
                  break;
                case 'O':
                  msgRed = 31, msgGreen = 30, msgBlue = 0;
                  break;
                case 'W':
                  msgRed = 31, msgGreen = 63, msgBlue = 31;
                  break;
                case 'B':
                  msgRed = 0, msgGreen = 0, msgBlue = 31;
                  break;
                case 'R':
                  msgRed = 31, msgGreen = 0, msgBlue = 0;
                  break;
              }
              break;
            case 'R': //電車路線色選択
              switch(ret_str[9]){
                case 'G':
                  Red = 0, Green = 63, Blue = 0;
                  break;
                case 'O':
                  Red = 31, Green = 30, Blue = 0;
                  break;
                case 'Y':
                  Red = 31, Green = 63, Blue = 0;
                  break;
                case 'S':
                  Red = 0, Green = 40, Blue = 31;
                  break;
                case 'B':
                  Red = 0, Green = 0, Blue = 31;
                  break;
                case 'D':
                  Red = 0, Green = 30, Blue = 0;
                  break;
              }
              SPI.setFrequency(20000000);
              SPI.setDataMode(SPI_MODE2);
              oled.SSD1351bv2_RectFill(jrX0+50, jrY0+5, jrX0+60, jrY0+33, Red, Green, Blue); //JR路線色
              break;
            case 'S': //スクロールスピード
              uint16_t ws_data = (ret_str[0]-0x30)*100 + (ret_str[1]-0x30)*10 + (ret_str[2]-0x30);
              Scrolle_speed = floor((200-ws_data)/(200/40)); //スマホスライダー値(0-200)で、値を(40-0)に変更したい場合
              Serial.print("Scrolle_speed = "); Serial.println(Scrolle_speed);
              break;
          }
        }else if(ret_str[0] == 't'){
          uint8_t JR_Sino_SJtxt[32], JR_Msk_SJtxt[32];
          uint16_t JR_Sino_SJlength, JR_Msk_SJlength, msk_fnt_length;
          uint8_t JR_Sino_font_buf[16][16];
          uint8_t JR_Msk_font_buf[16][8];
          String txt = ret_str.substring(ret_str.indexOf('|')+1, ret_str.length()-1);
          uint16_t t_len = txt.length();
          for(i=0; i<16; i++){ //初期化しておく
            for(j=0; j<16; j++){
              JR_Sino_font_buf[i][j] = 0;
              if(j<8) JR_Msk_font_buf[i][j] = 0;
            }
          }

          switch(ret_str[1]){
            case '7': //点滅メッセージ変換
              blink_msg = txt;
              break;
            case '8': //スクロールメッセージ変換
              if(t_len < 20){
                for(i=0; i<(20-t_len); i++){ //文字数が少ない場合にスペースを入れる
                  txt += ' ';
                }
              }
              txt = "  " + txt + "                ";
              SPI.setFrequency(40000000);
              SPI.setDataMode(SPI_MODE0);
              u8ts.UTF8_to_SJIS_str_cnv(UtoS, txt, sj_txt, &sj_length);
              SFR.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, sj_txt, sj_length, Sino_font_buf);
              for(i=0; i<sj_length; i++) Serial.write(sj_txt[i]);
              Serial.println();
              for(i=0; i<16; i++) dummy_Sino_font_buf[i] = Sino_font_buf[0][i];
              Text_Box_On = true;
              scl_cnt = 0; sj_cnt = 0;
              blink_1th_in = true;
              break;
            default: //JR風看板、固定文字変換
              switch(ret_str[2]){
                case 'S': //東雲フォント変換
                  SPI.setFrequency(40000000);
                  SPI.setDataMode(SPI_MODE0);
                  u8ts.UTF8_to_SJIS_str_cnv(UtoS, txt, JR_Sino_SJtxt, &JR_Sino_SJlength);
                  SFR.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, JR_Sino_SJtxt, JR_Sino_SJlength, JR_Sino_font_buf);
                  for(i=0; i<JR_Sino_SJlength; i++) Serial.write(JR_Sino_SJtxt[i]);
                  Serial.println();
                  SPI.setFrequency(20000000);
                  SPI.setDataMode(SPI_MODE2);
                  switch(ret_str[1]){
                    case '1': //路線文字変更
                      Red = 31, Green = 63, Blue = 31; //白色
                      oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR_BGcolor(jrX0, jrY0 + 5, Red, Green, Blue, jrbRed, jrbGreen, jrbBlue, JR_Sino_SJlength, JR_Sino_font_buf); 
                      break;
                    case '3': //方向文字変更
                      Red = 31, Green = 63, Blue = 31; //白色
                      oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR_BGcolor(jrX0+63, jrY0 + 5, Red, Green, Blue, jrbRed, jrbGreen, jrbBlue, JR_Sino_SJlength, JR_Sino_font_buf);
                      break;
                    case '5': //電車種別変更
                      Red = 31, Green = 30, Blue = 0; //オレンジ
                      oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR_BGcolor(jrX0, jrY0+36, Red, Green, Blue, BG_red, BG_green, BG_blue, JR_Sino_SJlength, JR_Sino_font_buf);
                      break;
                    case '6': //行き先変更
                      Red = 31, Green = 30, Blue = 0; //オレンジ
                      oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR_BGcolor(jrX0+72, jrY0+36, Red, Green, Blue, BG_red, BG_green, BG_blue, JR_Sino_SJlength, JR_Sino_font_buf);
                      break;
                  }
                  break;
                case 'M': //美咲フォント変換
                  SPI.setFrequency(40000000);
                  SPI.setDataMode(SPI_MODE0);
                  u8ts.UTF8_to_SJIS_str_cnv(UtoS, txt, JR_Msk_SJtxt, &JR_Msk_SJlength);
                  Serial.print("JR_Msk_SJlength=");Serial.println(JR_Msk_SJlength);
                  msk_fnt_length = MFR.Sjis_To_MisakiFNT_Read_ALL(MisakiZ, MisakiH, 0, 0, JR_Msk_SJtxt, JR_Msk_SJlength, JR_Msk_font_buf);

                  SPI.setFrequency(20000000);
                  SPI.setDataMode(SPI_MODE2);
                  switch(ret_str[1]){
                    case '2': //路線小文字変更
                      oled.SSD1351bv2_RectFill(jrX0, jrY0+21, jrX0+49, jrY0+33, jrbRed, jrbGreen, jrbBlue);
                      Red = 31, Green = 63, Blue = 31; //白色
                      oled.SSD1351bv2_8x8_DisplayOut_1col_LtoR_BGcolor(jrX0, jrY0+24, Red, Green, Blue, jrbRed, jrbGreen, jrbBlue, msk_fnt_length, JR_Msk_font_buf);
                      break;
                    case '4': //方向小文字変更
                      oled.SSD1351bv2_RectFill(jrX0+61, jrY0+21, jrX0+127, jrY0+33, jrbRed, jrbGreen, jrbBlue);
                      Red = 31, Green = 63, Blue = 31; //白色
                      oled.SSD1351bv2_8x8_DisplayOut_1col_LtoR_BGcolor(jrX0+63, jrY0+24, Red, Green, Blue, jrbRed, jrbGreen, jrbBlue, msk_fnt_length, JR_Msk_font_buf);
                      break;
                  }
                  break;
              }
              break;
          }
          txt = "";
        }
        ret_str = "";
      }
    }
  }else if(ret_str == "_close"){
    ret_str = "";
  }

  if(Text_Box_On == false){ //Yahoo!ニュースGET
    if(Web_first_get == true || millis()-WebGetTime > 600000UL){ //Web記事を10分毎に取得
      String news_str;
      String news1_1_target_ip;
      char Web_h[3], Web_m[3];
      sprintf(Web_h, "%02d", hour());//ゼロを空白で埋める場合は%2dとする
      sprintf(Web_m, "%02d", minute());
      news_str = "◆ " + String(Web_h) + ":" + String(Web_m) + " ";

      switch(Yahoo_get){
        case 1:          
          news1_1_target_ip = "/pickup/rss.xml"; // Yahoo!ニューストピックストップ
          news_str += ews.EWS_Web_Get("news.yahoo.co.jp", news1_1_target_ip, '\n', "</rss>", "<title>", "</title>", "◆ ");
          break;
      }
      news_str += "                ";
      Serial.print("\r\nWebGet str = ");
      news_str.replace("&amp;","&"); //XMLソースの場合、&が正しく表示されないので、全角に置き換える
      SPI.setFrequency(40000000);
      SPI.setDataMode(SPI_MODE0);
      u8ts.UTF8_to_SJIS_str_cnv(UtoS, news_str, sj_txt, &sj_length);
      SFR.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, sj_txt, sj_length, Sino_font_buf);
      for(i=0; i<16; i++) dummy_Sino_font_buf[i] = Sino_font_buf[0][i]; //1文字目を予め入力しておく
      for(uint16_t iy=0; iy<sj_length; iy++){
        Serial.write(sj_txt[iy]);
        yield();
      }
      Serial.println(); Serial.print("sj_length="); Serial.println(sj_length);
      news_str ="";
      scl_cnt = 0; sj_cnt = 0;
      Web_first_get = false;
      blink_1th_in = true;
      WebGetTime = millis();
    }
  }

  if(blink_on == true){ //点滅文字列表示
    if((sj_cnt == sj_length-1 && scl_cnt == 8) || blink_1th_in == true){
      uint16_t blink_sj_length;
      uint8_t blink_sj_txt[16];
      uint8_t blink_font_buf[16][16];
      SPI.setFrequency(40000000);
      SPI.setDataMode(SPI_MODE0);
      u8ts.UTF8_to_SJIS_str_cnv(UtoS, blink_msg, blink_sj_txt, &blink_sj_length);
      SFR.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, blink_sj_txt, blink_sj_length, blink_font_buf);
      Red = 31; Green = 0; Blue = 0;
      SPI.setFrequency(20000000);
      SPI.setDataMode(SPI_MODE2);
      for(i=0; i<3; i++){ //3回点滅
        oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR(jrX0, jrY0+52, Red, Green, Blue, 16, blink_font_buf);
        delay(500);
        oled.SSD1351bv2_RectFill(jrX0, jrY0+52, 127, jrY0+68, BG_red, BG_green, BG_blue);
        delay(500);
      }
      blink_1th_in = false;
    }
  }
  
  if(millis()-SCLtime > Scrolle_speed){ //文字スクロール
    oled.Scroller_8x16Dot_Replace2(DispChar, Next_buf, SnnmDotOut, dummy_Sino_font_buf, Sino_font_buf, sj_length, &scl_cnt, &sj_cnt);
    SPI.setFrequency(20000000);
    SPI.setDataMode(SPI_MODE2);
    oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR(jrX0, jrY0+52, msgRed, msgGreen, msgBlue, 16, SnnmDotOut);
    SCLtime = millis();
  }

  if(millis()-LastNTP_Get > 3600000UL){ //1時間ごとに時刻補正
    setSyncProvider(getNtpTime);
    LastNTP_Get = millis();
    Serial.println("---------------NTP time acquisition completed");
  }

  Clock_Display(clock_Select);
}
//***************時計表示関数(時、分のみ表示)*****************************
void Clock_Display(uint8_t sec_or_ymd){
  String time_str;
  uint8_t time_sj_txt[25];
  uint16_t time_sj_length;
  char h_chr[3], m_chr[3];
  uint8_t time_dot[32][16];
  int i;
  
  Red = 0, Green = 63, Blue = 0;

  if(h_t != hour()){ //時間が変わったら時間表示
    sprintf(h_chr, "%2d", hour());//ゼロを空白で埋める場合は%2dとすれば良い
    SPI.setFrequency(40000000);
    SPI.setDataMode(SPI_MODE0);
    u8ts.UTF8_to_SJIS_str_cnv(UtoS, String(h_chr), time_sj_txt, &time_sj_length);
    SFR.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, time_sj_txt, time_sj_length, time_dot);
    SPI.setFrequency(20000000);
    SPI.setDataMode(SPI_MODE2);
    oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR_BGcolor(jrX0+32, jrY0+36, Red, Green, Blue, BG_red, BG_green, BG_blue, time_sj_length, time_dot);
    h_t = hour();
  }
  if(min_t != minute()){ //分が変わったら分表示
    sprintf(m_chr, "%02d", minute());//ゼロを空白で埋める場合は%2dとすれば良い
    SPI.setFrequency(40000000);
    SPI.setDataMode(SPI_MODE0);
    u8ts.UTF8_to_SJIS_str_cnv(UtoS, String(m_chr), time_sj_txt, &time_sj_length);
    SFR.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, time_sj_txt, time_sj_length, time_dot);
    SPI.setFrequency(20000000);
    SPI.setDataMode(SPI_MODE2);
    oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR_BGcolor(jrX0+56, jrY0+36, Red, Green, Blue, BG_red, BG_green, BG_blue, time_sj_length, time_dot);
    min_t = minute();
  }
  if(now() != prevDisplay){ //ここからコロン点滅表示。(図形表示)
    SPI.setFrequency(20000000);
    SPI.setDataMode(SPI_MODE2);
    oled.SSD1351bv2_RectFill(jrX0+51, jrY0+40, jrX0+52, jrY0+41, Red, Green, Blue);
    oled.SSD1351bv2_RectFill(jrX0+51, jrY0+47, jrX0+52, jrY0+48, Red, Green, Blue);
    prevDisplay = now();
    sec_colon_time = millis();
  }else if(millis()-sec_colon_time >= 500){
    SPI.setFrequency(20000000);
    SPI.setDataMode(SPI_MODE2);
    oled.SSD1351bv2_RectFill(jrX0+51, jrY0+40, jrX0+52, jrY0+41, BG_red, BG_green, BG_blue);
    oled.SSD1351bv2_RectFill(jrX0+51, jrY0+47, jrX0+52, jrY0+48, BG_red, BG_green, BG_blue);
  }
}
//************JPEG画像65kカラー表示関数************************************************
void jpegDraw_65k_color(char* filename){
  char str[100];
  uint8 *pImg;
  int x,y,bx,by;
  uint8_t R, G, B;
 
  SD.begin(cs_SD,40000000);
  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);
 
  SPI.setFrequency(20000000);
  SPI.setDataMode(SPI_MODE2);
  oled.SSD1351bv2_RectFill(jrX0, picY0, jrX0+127, picY0+53, 0, 0, 0); //OLEDに黒画面出力
  SPI.setFrequency(40000000);
  SPI.setDataMode(SPI_MODE0);
  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/32));
              G = round(pImg[0]/(256/64));
              B = round(pImg[0]/(256/32));
              SPI.setFrequency(20000000);
              SPI.setDataMode(SPI_MODE2);                
              oled.SSD1351bv2_1pixel_DisplayOut(picX0+x, picY0+y, R, G, B); //OLEDに1pixelづつ65kカラーで出力
            }else{ // RGB
              if(x < 128 && y < 128){
                R = round(pImg[0]/(256/32));
                G = round(pImg[1]/(256/64));
                B = round(pImg[2]/(256/32));
                SPI.setFrequency(20000000);
                SPI.setDataMode(SPI_MODE2);
                oled.SSD1351bv2_1pixel_DisplayOut(picX0+x, picY0+y, R, G, B); //OLEDに1pixelづつ65kカラーで出力
              }
            }
        }
        pImg += JpegDec.comps ;
      }
    }
  }
}
//*************************NTP Time**************************************
time_t getNtpTime(){
  while (Udp.parsePacket() > 0) ; // discard any previously received packets
  Serial.println("Transmit NTP Request");
  sendNTPpacket(timeServer);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) {
    int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      Serial.println("Receive NTP Response");
      Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
    }
  }
  Serial.println("No NTP Response :-(");
  return 0; // return 0 if unable to get the time
}
//*************************NTP Time**************************************
void sendNTPpacket(IPAddress &address){
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;         
  Udp.beginPacket(address, 123); //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}

【解説】
過去の記事と重複しているところは省略していますのでご了承ください。

●8行目:
今回でバージョンアップした、OLED SSD1351 beta ver 2.0 のライブラリインクルードです。
過去のライブラリとの競合を避けるために、ライブラリの名前を変えました。bv2 という文字が追加されています。
以前の記事で紹介したSPI通信高速化で、ESP-WROOM-02 ( ESP8266 ) のGPIO ダイレクトアクセスを使っていますので、以前のスケッチはそのままでは使えませんのでご注意ください
そして、ver2.0 では、GPIOダイレクトアクセスが他のピンに影響を与えないように PIN_OUT_SET などを指定するようにしました。

●11行目:
当方自作のSDカード用EasyWebSocketライブラリです。
スマホのブラウザと ESP-WROOM-02 ( ESP8266 ) をリアルタイムで双方向通信( WebSocket )をするためのものです。
今回でbeta ver 1.47 になっていますので注意してください。
新しいバージョンのライブラリを使う時には、古いライブラリのフォルダごと削除して、再インストールしてください。
その時に必ずIDEを再起動してください。

●12行目:
今回新たに追加した、SDカード読み取り用 8×8 ドット美咲フォントライブラリです。
以前のSPIFFS用ライブラリは使えませんのでご注意ください。

●13行目:
「ないん」さん作成のJPEGデコーダのインクルードです。

●14-18行:
Arduino core for ESP8266 Wi-Fi chip 標準ライブラリです。

●19行:
Arduino 標準のTimeライブラリです。

●21-25行:
当方自作ライブラリのクラス名を定義します。
自由な名前をつけることができます。

●27-33行:
ESPr Developer ( ESP-WROOM-02, ESP8266 )のGPIOピン設定です。
SDカードライブラリでSPI通信のピン番号が決まっていますので、それに合わせて他の番号も割り振っています。
ただし、注意していただきたいのは、16番ピンはリセット以外に使わないようにしてください
なぜかというと、SPI高速化によるGPIOダイレクトアクセスでは16番ピンは割り当てられません。
OLED_SSD1351bv2ライブラリでは高速化に影響しないリセットピンだけはdigitalWriteを使っているので、16番ピンを割り当てることができます。

●35-36行:
ご自分のルーター(Wi-Fiアクセスポイント)のSSIDとパスワードに、xxxx部分を書き換えてください。

●38-45行:
micro SD カードにあるフォントファイルやHTMLファイルを定義してます。

●46行:
空のdummyファイルを定義してます。
今回はブラウザに表示するWebページのHTMLのbody要素部分を全てmicro SDカードに保存していて、ESP8266からは送信してません。ですから、連結するファイルは不要なのですが、ファイルが存在しないとエラーになるので、空のダミーファイルの存在が必要です。

●48行:
ブラウザから送信されたJPEG画像ファイルをmicro SDカードのルートに保存しておく一時ファイルを指定してます。
このファイル名配列はポインタではなく、固定長の配列である必要があります。
理由は、画像が送信されると、297行目の関数で前の画像ファイルを削除して新たに保存し直します。その時に、SDカードライブラリのremove関数を使ってますが、それはポインタのファイル名ではエラーになってしまう為です。

●50行:
スクロールする文字数を半角で800にしてますが、あまり多くしてしまうとメモリエラーになります。
このプログラムではSRAMをかなり消費していますので、ご注意ください。

●65行:
SD_EasyWebSocketライブラリで、beta ver 1.47 から追加した ESP8266 のローカルIPアドレス自動取得のためのものです。
114行で自動取得して、292行で使用してます。

●70-78行:
NTPサーバー時刻取得設定です。
ESP8266ボードライブラリのサンプルスケッチをそのまま流用してます。

●114行:
ここでESPr Developer ( ESP-WROOM-02, ESP8266 ) のローカルIPアドレスを自動取得しています。

●120-122行:
以前の記事で書きましたが、OLED_SSD1351 のSPI通信はMODE3です。
しかし、ロジックアナライザーで測定してみると、ESP8266標準のSPIライブラリではSPI_MODE2 がMODE3 でした。おそらくライブラリの名称誤りだと思います。
そして、最大周波数は20MHzでした。
SDカードはESP8266の安定周波数の最高40MHzで読み取るので、OLEDを表示する指令を出す度に周波数やモードの指定替えが必要になります。

●126行:
593-650行で作ったJPEG画像の表示関数を実行しています。
SSD1351 のカラーは最大262000色を表示できますが、電光掲示板の文字列スクロールの高速化を図るために、65000色で表示するようにしてます。

●128行:
ESP-WROOM-02 ( ESP8266 ) は、内部周波数を最大の160MHz にしてますが、SPI通信は理論上80MHz まで指定できます。
ただ、安定動作のためには、
160/4 = 40MHz
が望ましいのです。

●131-165行:
SDカードのファイル読み取りハンドル取得

●168-269行:
セットアップ内のこの部分で、OLED SSD1351 ディスプレイにJR風 電光掲示板 のベースを表示させてます。
Arduino IDE に日本語文字列を入力した場合、文字コードはUTF-8 になるので、Shift_JISコードに変換してます。
その時にはSDカードから読み取るのでSPI周波数を40MHz にして、OLED に表示するときにはSPI周波数を20MHz にしてます。
198行では、今回新たに作ったSDカード読み取り用、美咲フォントの文字列一括変換関数です。
以前の記事の関数ではforループを使って1文字ずつ取得していましたが、今回はちょっと進化しました。
美咲フォントの半角は4×8ドットですが、マイコンの扱える最少ビットは8bitなので、半角と全角を判別して、ビットを詰めるというところの工夫が必要でした。
今回はうまく変換できるようになったと思います。

半角、全角混在の美咲フォントの場合、4bitのものをそれぞれビットを詰めて空白を埋める作業をするので、Shift_JISバイト長と美咲フォントのfont_bufバイト長は異なります。
ですから、Misaki_Length という引数を用意して、美咲フォントバイト長を新たに取得する必要があります。

●292行:
SD_EasyWebSocketライブラリ、beta ver 1.47 で新たに追加した関数です。
HTMLヘッダファイルのローカルIPアドレス部分をESP8266で自動取得するために、ヘッダファイルを分割してIPアドレスをマージしてます。
今回のスケッチでは、ESP8266 のSRAMメモリ節約のために、HTMLタグを全てSDカードに保存しています。
body要素がまとまっているので、パソコンで編集するとリアルタイムでWebページの表示確認ができるので良いと思います。

この関数はメインループ内で常にスマホからのアクセスがあるかどうかチェックしています。
アクセスがあったら、HTMLタグをブラウザへ吐き出します。
アクセスが無い場合はスルーします。

●297行:
スマホブラウザからボタンが押されたり、文字列の送信があった場合に、WebSocket通信でテキストメッセージを受信する関数です。
バイナリデータの場合は指定したファイル名でSDカードに上書き保存します。
先ほど述べたように、ファイル名はポインタはダメで、必ず固定長配列です。

●298-303行:
バイナリデータを受信したら、OLED SSD1351 ディスプレイにJPEG画像を表示させます。
この辺は以下の記事で説明したことと同じです。

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

●305-469行:
ここで、スマホのブラウザからWebSocket通信で受信したテキスト文字列を判別して、有機EL ( OLED ) ディスプレイの動作を決めています。
ブラウザのボタンが押されたときには
100|Yahoo
というテキストを受信します。
スクロールスピードがタッチされたら、最初の3文字の数値が変化したテキストが送られてきます。

ブラウザのテキストボックスから文字列が送信されてきた場合には、
t1S|山手線
というテキストを受信します。
これは文字配列になっているので、switch文で分類できるわけです。
この辺の詳しい解説は過去のEasyWebSocketライブラリの解説を参考にしていただければと思いますので、ここでは省略します。

385-461行で、ブラウザから送られてきたテキスト文字列から、固定文字列、点滅文字列、スクロール文字列へそれぞれ分類し、さらに東雲フォント、美咲フォントかを判別してそれぞれ変換してます。
先にも述べましたが、8×8ドットの美咲フォントは半角が4×8ドットなので、8ビット単位のfont_buf にする場合、半角を詰めて空白を埋めて変換するので、Shift_JISのバイト長と、美咲フォントのバイト長が異なります。
よって、新たにmsk_fnt_lengthという引数を用意して、美咲フォントバイト長を取得する必要があります。

●471-505行:
ここでは、10分毎に Yahoo! RSSサイトのトップニューストピックスを取得しています。
472行で記事取得間隔を極端に短くしてしまうと、Yahoo! RSSサーバーに負担をかけてしまい、Dos攻撃と判断されてしまうので十分注意してください。
免責事項でも記述しておりますが、当ブログのスケッチやライブラリを使って起こったいかなるトラブルも当方では責任を負いませんので、予めご了承ください。一切無保証です。
マイコンの誤作動や、高速リセット繰り返しなどのエラーも考えられるので、ここの部分は十分注意してください。

483行で記事を取得してターゲットテキストを抽出してます。

●507-527行:
ここでは、有機EL ( OLED )ディスプレイに表示される、「電車がまいります」などの点滅文字列を変換してます。
点滅している間はdelay関数を使っているので、全ての動作が止まります。

●529-535行:
530行を毎度通る度に文字列のドット(pixel) を1ずつずらしてスクロールしてます。

●537-541行:
1時間ごとにNTPサーバーから時刻を取得して、ESPr Developer ( ESP-WROOM-02, ESP8266 ) の内部時計を補正してます。

●546-591行:
ここでは、時計表示をしています。
時間と分のみです。
コロンは文字列ではなく、小さい四角形を描いて表示させてます。

●593-650行:
「ないん」さん作成のJPEGデコーダライブラリを使って、SDカードから読み込んだJPEG画像ファイルを有機EL ( OLED ) に表示させてます。
ここでは、65000色カラーで表示させてます。
以前の記事では262000カラーで表示させてますが、文字列高速スクロールのために画質を落としています。

先ほど述べたように、600行の JpegDec.init(); 関数は自分でライブラリに新たに追加しなければなりません。
これが無いと、スマホから画像の送信は1回切りとなってしまいます。

●652-688行:
これは、Arduino core for ESP8266 のNTPサーバーサンプルスケッチそのままの流用です。

8.コンパイルおよび実行

では、いよいよArduino IDE でコンパイル実行してみてください。
予め、ルーター(アクセスポイント)は起動しておき、ESPr Developer と接続できるようにしておいてください。

先で紹介した動画のようにYahoo! 記事が表示されれば成功です。

その後、スマホブラウザのURL入力欄にESPr Developer ( ESP-WROOM-02, ESP8266 ) のローカルIPアドレスを入力してください。
分からなければ、Arduino IDE のシリアルモニタを115200bpsで起動して、ESPr Developer のリセットボタンを押してください。
するとこんな感じで表示されると思います。

WiFi connected
Server started
192.168.0.14

この数値の部分がESP8266のローカルIPアドレスなので、これをブラウザのURL入力すればOKです。

初期のブラウザコネクションでは、WebSocket ハンドシェイク(コネクション確立)まで少々時間がかかります。

ブラウザには下図の様に表示されて、WebSocket Status が
WebSocket.CONNECTED
と表示されればOKです。

JR_denko_05
JR_denko_06
JR_denko_07

注意していただきたいのは、Yahoo! ニュース取得ボタンが押されると、WebSocketプロトコルと異なるHTTP通信となるために、WebSocket通信が切断されます。
その後に操作したい場合は、WS-Recconectボタンを押してください。

また、指定された文字数より大きかったり、画像が大きかったりすると画面は崩れますのでご注意ください。
画像を電車にすると雰囲気が出てイイですね。

9.まとめ

いかがでしょうか。
ちゃんと表示できたでしょうか。

改めて記事にすると、もの凄く多くの工程があるもんです。
初めてこのページから見た方は、挫折すると思いました。
何といっても、記事を書いている自分でさえ、途中で挫折しそうになりました。

今回のJR風 Yahoo! ニュース電光掲示板では、いままでやってきたことの総まとめみたいな感じで全部盛り込んでみました。
今回はいろいろと自作ライブラリを更新しましたが、昔作ったものは将来のことをあまり考えずに思い付きで作ったものばかりだったので、今の記事との整合性を取るのが難しく、とても大変な作業になってしまいました。
ライブラリを公開するときには、ある程度将来的に拡張することを考慮に入れてないとエライ目に遭いますね。
独学なのでご容赦ください。m(_ _)m

作った後で思ったのですが、8×8ドットの美咲フォントを小さい有機EL ( OLED ) に表示させる場合、グラフの座標数値などにも生かせると思いました。
いつか試してみたいと思います。

今回はここまでにします。

ではまた・・・。

Yahoo!ニュース RSS サイトが https ( SSL )化されてしまいました。
その対策方法は以下のページにあります。

JR 風 Yahoo! ニュース 電光掲示板 の https ( SSL )対策

合わせてご参照ください。
その場合、SD_EasyWebSocket は Beta ver 1.50 をインストールする必要があり、スケッチも大幅変更する必要があります。
(2017/4/11時点)

スポンサーリンク

広告 と mgo-tec電子工作 関連コンテンツ

Amazon.co.jp広告






投稿者:

mgo-tec

Arduino, ESP8266, ESP-WROOM-02 等を使って、主にスマホと連携した電子工作やプログラミング記事を書いてます。ライブラリも作ったりしてます。趣味、独学でやってますので、動作保証はしません。 電子回路やプログラミングの専門家ではありません。 畑違いの仕事をしてます。 でも、少しだけ電気の知識が必要な仕事なので、電気工事士や工事担任者等の資格は持ってます。

「鉄道 JR風 有機EL 電光掲示板を作ってみた。(スマホで編集できて、Yahoo! ニュースも表示できる)」への4件のフィードバック

  1. 初めまして、いつも楽しく拝見しています。
    今回のスケッチをコンパイルしようとしたところ、530行の”Scroller_8x16Dot_Replace2″の所で止まってしまいました。ライブラリーの方には”Scroller_8x16Dot_Replace”しか無い様で、スケッチとgithubにある”OLED_SSD1351bv2″ライブラリーのバージョンが違うようです、いかがでしょうか。

    1. masaさん

      大変失礼いたしました。
      たまたま再アップできる環境に居たので、早速再アップしました。
      こちらのチェックが足りませんでした。
      多くの方にご迷惑をおかけしたかもしれません。
      個人運営ブログだと、ところどころ抜けてしまいますね。
      こうやって、コメントでご指摘いただけると、とても助かります。
      ありがとうございました。

      以後、十分に気を付けてアップしたいと思います。m(_ _)m

      1. mgo-tecさん

        早速対応して頂きありがとうございました。
        やってみたところうまく行きました。まだArduinoはやり始めたばかりの初心者ですが、丁寧に説明してくださっているのでとても分かりやすいです。
        mgo-tecさんが開発された技術で、なにか自分でも出来ないかと思っています。
        これからも応援していますので、頑張って下さい。

        1. こちらこそ、今回はとても助けていただきました。
          このご指摘が無かったら、より多くの方にご迷惑をかけてしまったかもしれません。
          ほんとに感謝しています。
          m(_ _)m
          個人的にはまだまだ雑な記事だと思っておりますが、こういうお声をいただくと、辛い記事アップでも何とか乗り越えられそうです。
          ガンバります!!
          ありがとうございました。

コメントを残す

メールアドレスが公開されることはありません。

*