※ ESP-WROOM-02 の Flashメモリサイズが 4MB のはずが、実は 2MB だったという情報がありました。
以下のページを参照して、Flashメモリサイズを確認しておくことを強くお勧めします。
●ESP-WROOM-02 ( ESP8266 ) チップ・メモリ・MACアドレス情報確認方法
(2017/10/2)
こんばんは。
ESP32工作に専念したいところですが、またESP8266 ネタでスイマセン。
前回記事では、ESP8266 ( ESP-WROOM-02 ) とI2C通信 OLED ( 有機EL ) SSD1306 の Yahoo! RSS ニュース電光掲示板の https ( SSL )対策を紹介しましたが、今回はmicro SDHC カードを使った ESP8266 と SPI通信の OLED SSD1351 の Yahoo! RSS ニュース電光掲示板の https ( SSL )対策です。
JR風電光掲示板の以下の記事と合わせてご覧ください。
https://www.mgo-tec.com/blog-entry-jr-train-message-board-01.html
Arduino core for ESP8266 用の私の自作ライブラリ、SD_EasyWebSocket を改良し、Beta ver 1.51 としました。
SPIFFS 用と同様に EWS_https_Web_Get 関数を追加しました。
このライブラリには、Arduino core for ESP8266 標準の WiFiClientSecure.h をインクルードしているので、ESP8266 のSRAMメモリを多く消費してしまいます。
前回記事でも説明しましたが、https ( SSL )の Web ページの場合は、httpと異なり、通信は暗号化されます。
そして、それを復号する際に多量の文字列処理を行うために、多量のメモリを消費します。
Webページのhttps ( SSL )化は Google でも推奨しているため、これから益々 https 化が進んでいきますから、IoT実現のためには https対策が必須です。
ESP8266 ( ESP-WROOM-02 )でこの対策をすると、やはりメモリがギリギリです。
ニュース記事を一気にグローバル変数や、ローカル変数に文字列を代入する(つまり SRAM へ取り込む)と、途端に例外エラーになってしまいます。
ですから、文字列の初期化で、配列数を出来るだけ少なくしておく必要があります。
その代わり、1文字スクロールする度にフォントを1文字分読み込むという処理になってしまうので、スクロール速度が遅くなってしまいます。
これは、今の私のプログラミング力では仕方ないかなと思っています。
そして、今回の Beta ver 1.51 では、WebSocket ハンドシェイク関数を大幅見直しをして、SRAM消費を劇的に減らしてみました。
というのは、ブラウザから GET リクエストがあった場合だけ、ローカル関数内のHTML文字列を読み込むという構成にしました。
そうすると、ローカル関数を抜けるとメモリを解放するので、消費を減らせます。
では、対策方法を説明します。
使用デバイス
ESPr Developer ( ESP8266, ESP-WROOM-02)
日本の電波法をクリアして技適取得した、Wi-Fi マイコンボード ESP-WROOM-02 をパッケージした、使いやすい超おすすめボードです。
OLED ( 有機EL ) SSD1351
スイッチサイエンスさんウェブショップにあります。
https://www.switch-science.com/catalog/1754/
かなり高価ですが、これだけ大きい画面の SPI通信OLED ( 有機EL )はなかなかありません。
しかも micro SD カードスロット付きという優れものです。
micro SDHC カード
以下のカードで動作確認済みです。
(他のカードでは動作しない場合も有り得ます。)
事前準備
以前の記事を参照して、micro SDHC カードに /EWS と /font いうフォルダを作成しておき、各ファイルのコピーまで済ませておいてください。
必要なファイルは以下の通りです。
【EWSフォルダに入れるもの】
- LIPhead1.txt (WebページHTMLヘッダの分割ファイル)
- LIPhead2.txt (WebページHTMLヘッダの分割ファイル)
- LIPhtml.htm (WebページHTMLのBody要素部分)
- dummy.txt 空のテキストファイル
【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コードへ変換するためのテーブルファイルです。
私の自作です。
micro SDHC カードの準備が済んだら、各デバイスを接続して、SD_EasyWebSocket ライブラリ以外はインストールしておいてください。
改良した SD_EasyWebSocketライブラリのインストール
新しいライブラリのインストール前に、必ず旧バージョンのライブラリをフォルダごと削除しておいてください。
その後、ESP8266_SD_EasyWebSocket Beta ver 1.51 ライブラリをインストールします。
GitHub の以下のページにアップしてありますので、ZIPファイルをダウンロードしてください。
https://github.com/mgo-tec/ESP8266_SD_EasyWebSocket
ZIPファイルのまま、Arduino IDE にインストールする方法は以下のページを参照してください。
GitHubにある ZIP形式ライブラリ のインストール方法 ( Arduino IDE )
改良したスケッチの入力
では、改良した以下のスケッチを Arduino IDE に入力してみてください。
以前の記事よりも大幅改良しました。
変更があったところのみ解説します。
【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)
/* 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 LGPL 2.1 License. * Copyright (c) 2016 mgo-tec * License reference URL --> https://opensource.org/licenses/mit-license.php * JPEGデコーダは「ないん」さん作成。http://yushakobo.jp/pluis9/2014/06/jpegdecoder/ */ #include <SD_EasyWebSocket.h> //beta ver1.50以上 #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_MisakiFNT_read.h> //beta ver 1.1 #include <JPEGDecoder.h> //ないんさん作成 #include <WiFiUdp.h> #include <SD.h> #include <SPI.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 = 350 , DispChar = 16}; //MaxTxt = スクロールする文字列の最大数(半角数) //DispChar = ディスプレイに表示できる半角文字数 uint8_t Sino_font_buf[16][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アドレス自動取得用 bool get_http_req_status = false; //ブラウザからGETリクエストがあったかどうかの判定変数 //-------NTPサーバー引数初期化----------------------------- IPAddress timeServer(52, 168, 138, 145); // time.windows.com 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; //-----時計表示引数初期化------------------------------ bool 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; bool 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 = 0; //文字列スクロールインターバル時間(8ms) uint8_t scl_cnt = 0; //フォントのスクロールカウント数 uint8_t cp = 0; uint16_t fnt_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 = "電車がまいります"; bool blink_1th_in = true; //起動時に点滅文字列を表示するかどうか bool blink_on = false; //点滅文字列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) { 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) { 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) { 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) { 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) { Serial.print("mgotec48 File not found"); return; }else{ Serial.println("mgotec48 File OK!"); } //-------------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路線色 Red = 31, Green = 63, Blue = 31; ShinonomeDisplayOut("山手線", jrX0, jrY0 + 5, Red, Green, Blue, jrbRed, jrbGreen, jrbBlue); Red = 31, Green = 63, Blue = 31; ShinonomeDisplayOut("渋谷方面", jrX0+63, jrY0 + 5, Red, Green, Blue, jrbRed, jrbGreen, jrbBlue); //最大半角12文字 MisakiDisplayOut("YamanoteLine", jrX0, jrY0+24, Red, Green, Blue, jrbRed, jrbGreen, jrbBlue); //最大半角16文字 MisakiDisplayOut(" for Shibuya", jrX0+63, jrY0+24, Red, Green, Blue, jrbRed, jrbGreen, jrbBlue); Red = 0, Green = 63, Blue = 0; BG_red = 0, BG_green = 0, BG_blue = 0; ShinonomeDisplayOut("--:--", jrX0+32, jrY0+36, Red, Green, Blue, BG_red, BG_green, BG_blue); Red = 31, Green = 30, Blue = 0; ShinonomeDisplayOut("普通", jrX0, jrY0+36, Red, Green, Blue, BG_red, BG_green, BG_blue); ShinonomeDisplayOut("渋谷", jrX0+72, jrY0+36, Red, Green, Blue, BG_red, BG_green, BG_blue); MisakiDisplayOut("方 ", jrX0+104, jrY0+36, Red, Green, Blue, BG_red, BG_green, BG_blue); MisakiDisplayOut("面 ", jrX0+104, jrY0+44, Red, Green, Blue, BG_red, BG_green, BG_blue); Red = 0, Green = 63, Blue = 0; MisakiDisplayOut("10", jrX0+112, jrY0+36, Red, Green, Blue, BG_red, BG_green, BG_blue); MisakiDisplayOut("両", jrX0+116, jrY0+44, Red, Green, Blue, BG_red, BG_green, BG_blue); //------------------------------------------------------------- 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_handshake(); 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(F("WebSocket Binary Receive Complete!\r\n")); jpegDraw_65k_color(PicFile); //SDカードに保存されたJPEG画像をOLEDに出力する Serial.println(F("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(F("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); cp = SFR.SjisToShinonome16FontRead(SinoZ, SinoH, 0, 0, sj_txt[sj_cnt], sj_txt[sj_cnt+1], Sino_font_buf[0], Sino_font_buf[1]); for(i=0; i<sj_length; i++){ Serial.write(txt[i]); //Arduino IDE 1.8.2以上 //Serial.write(sj_txt[i]); //Arduino IDE 1.8.1 以下 } 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(F("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 = "/rss/topics/top-picks.xml"; // Yahoo!ニューストピックストップ news_str += ews.EWS_https_Web_Get("news.yahoo.co.jp", news1_1_target_ip, '\n', "</rss>", "<title>", "</title>", "◆ "); break; } news_str += " "; Serial.print(F("\r\nWebGet str = ")); news_str.replace("&","&"); //XMLソースの場合、&が正しく表示されないので、全角に置き換える SPI.setFrequency(40000000); SPI.setDataMode(SPI_MODE0); u8ts.UTF8_to_SJIS_str_cnv(UtoS, news_str, sj_txt, &sj_length); cp = SFR.SjisToShinonome16FontRead(SinoZ, SinoH, 0, 0, sj_txt[0], sj_txt[1], Sino_font_buf[0], Sino_font_buf[1]); sj_cnt = cp; 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(news_str[iy]); //Arduino IDE 1.8.2以上の場合 //Serial.write(sj_txt[iy]); //Arduino IDE 1.8.1以下の場合 yield(); } Serial.println(); Serial.print(F("sj_length=")); Serial.println(sj_length); news_str =""; scl_cnt = 0; Web_first_get = false; blink_1th_in = true; WebGetTime = millis(); } } if(millis()-SCLtime > Scrolle_speed){ //文字スクロール if(scl_cnt == 8 ){ if(cp == 1 || fnt_cnt == 1){ cp = SFR.SjisToShinonome16FontRead(SinoZ, SinoH, 0, 0, sj_txt[sj_cnt], sj_txt[sj_cnt+1], Sino_font_buf[0], Sino_font_buf[1]); for(i=0; i<16; i++) dummy_Sino_font_buf[i] = Sino_font_buf[0][i]; //1文字目を予め入力しておく sj_cnt = sj_cnt + cp; fnt_cnt = 0; scl_cnt = 0; } } oled.Scroller_8x16Dot_Replace2(DispChar, Next_buf, SnnmDotOut, dummy_Sino_font_buf, Sino_font_buf, cp, &scl_cnt, &fnt_cnt); if(sj_cnt >= sj_length) sj_cnt = 0; SPI.setFrequency(20000000); SPI.setDataMode(SPI_MODE2); oled.SSD1351bv2_8x16_DisplayOut_1col_LtoR(jrX0, jrY0+52, msgRed, msgGreen, msgBlue, 16, SnnmDotOut); SCLtime = millis(); } if(blink_on == true){ //点滅文字列表示 if((sj_cnt == 0 && scl_cnt == 1 && fnt_cnt == 1) || 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()-LastNTP_Get > 3600000UL){ //1時間ごとに時刻補正 setSyncProvider(getNtpTime); LastNTP_Get = millis(); Serial.println(F("---------------NTP time acquisition completed")); } Clock_Display(); } //***************時計表示関数(時、分のみ表示)***************************** void Clock_Display(){ 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]; 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(F("Width :")); Serial.println(JpegDec.width); Serial.print(F("Height :")); Serial.println(JpegDec.height); Serial.print(F("Components:")); Serial.println(JpegDec.comps); Serial.print(F("MCU / row :")); Serial.println(JpegDec.MCUSPerRow); Serial.print(F("MCU / col :")); Serial.println(JpegDec.MCUSPerCol); Serial.print(F("Scan type :")); Serial.println(JpegDec.scanType); Serial.print(F("MCU width :")); Serial.println(JpegDec.MCUWidth); Serial.print(F("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((float)pImg[0]/(256/32)); G = round((float)pImg[0]/(256/64)); B = round((float)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((float)pImg[0]/(256/32)); G = round((float)pImg[1]/(256/64)); B = round((float)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(); } //******************** 8x16東雲フォント表示 ************************** void ShinonomeDisplayOut(String str, uint8_t x, uint8_t y, uint8_t font_R, uint8_t font_G, uint8_t font_B, uint8_t back_R, uint8_t back_G, uint8_t back_B){ SPI.setFrequency(40000000); SPI.setDataMode(SPI_MODE0); u8ts.UTF8_to_SJIS_str_cnv(UtoS, str, 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(x, y, font_R, font_G, font_B, back_R, back_G, back_B, sj_length, Sino_font_buf); } //******************** 8x8美咲フォント表示 ************************** void MisakiDisplayOut(String str, uint8_t x, uint8_t y, uint8_t font_R, uint8_t font_G, uint8_t font_B, uint8_t back_R, uint8_t back_G, uint8_t back_B){ uint8_t MSK_font_buf[32][8]; SPI.setFrequency(40000000); SPI.setDataMode(SPI_MODE0); u8ts.UTF8_to_SJIS_str_cnv(UtoS, str, sj_txt, &sj_length); 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(x, y, font_R, font_G, font_B, back_R, back_G, back_B, Misaki_Length, MSK_font_buf); } //******************** websocket ハンドシェイク ************************** void websocket_handshake(){ get_http_req_status = ews.Get_Http_Req_Status(); //ブラウザからGETリクエストがあったかどうかの判定 if(get_http_req_status == true){ String html_str1="", html_str2="", html_str3="", html_str4="", html_str5="", html_str6="", html_str7=""; //WebSocket ハンドシェイク関数 ews.EWS_HandShake_main(2, cs_SD, HTM_head_file1, HTM_head_file2, HTML_body_file, dummy_file, LIP, html_str1, html_str2, html_str3, html_str4, html_str5, html_str6, html_str7); } }
【解説】
●8行目:
ESP8266_SD_EasyWebSocket ライブラリ Beta ver 1.51 をインクルードしてください。
●48行目:
天気予報は550 くらいのバイト数が必要ですが、トップニューストピックスならば 350 バイトで良いと思います。
これを多くしてしまうと、たちまち SRAM のオーバーロードを引き起こしますので要注意です。
●50行目:
領域確保を以前の記事よりも劇的に減らしました。
本当は、フォントを SRAM に取り込みたいところですが、メモリがギリギリなので仕方ありません。
●64行:
新しく追加した変数です。
ブラウザからGET リクエストがあった場合の判定に使います。
●174-199行:
重複するところは、新たにローカル関数を作ってまとめました。
637-656行に示しています。
●222行:
658-666行にローカル関数化して、ブラウザからGETリクエストがあった時だけ WebSocket ハンドシェイク(コネクション確立)するようにしてます。
●416行:
EasyWebSocket ライブラリ Beta ver 1.50 から新たに追加した、https ( SSL )化した記事取得関数です。
EWS_https_Web_Get
使用方法は、EWS_Web_Get と同じです。
●429-430行:
Arduino IDE 1.8.2 からシリアルモニターで UTF-8 日本語漢字表示ができるようになりました。
(Windows 10 の場合)
1.8.1以下の場合は従来どおり、Shift_JISコードで出力してください。
●442-460行:
SRAM削減の為、1文字スクロールしたらフォントを1文字読み込む方式にしました。
●658-666行:
SRAM消費削減のため、ブラウザに吐き出す HTML文字列を全てローカル関数内に置いています。
ただ、この電光掲示板プログラムの場合、HTML文字列は micro SDHC カード内にあるため、元々それほどメモリを消費していません。
ですが、こういう形式にすれば、Loop関数を繰り返す度に HandShake 関数を読み込む必要が無いため、より良いと思います。
659行目の関数が、Beta ver 1.50 から追加になった関数です。
Get_Http_Req_Status()
これは、ブラウザから GET リクエストがあった場合のみ、true を返します。
EWS_HandShake_main(セレクト番号, cs_SD, HTM_head_file1, HTM_head_file2, HTML_body_file1, HTML_body_file1, ローカルIPアドレス, html_str1, html_str2, html_str3, html_str4, html_str5, html_str6, html_str7);
今回追加になった関数です。
セレクト番号は今回は 2 としておいてください。
これは、ハンドシェイク関数を分割したものです。
ローカルIP自動取得する場合と固定にする場合や、HTMLヘッダファイルをSDカードから読み込む場合とスケッチに書き込む場合など、いろいろと使い分けられるようにしたものです。
コンパイル実行
では、コンパイル書き込み実行してみてください。
ブラウザの使い方は以前の記事と同じです。
ブラウザは Google Chrome を推奨します。
Microsoft Edge では動作しませんのでご注意ください。
動作した動画はこんな感じです。
いかがでしょうか。
I2C通信の OLED SSD1306 よりは格段にスムースですよね。
でも、これが最高速です。
http記事取得よりは遅くなっています。
ESP32 ( ESP-WROOM-32 )ならばSRAM も5倍ありますので、もっと高速にできそうです。
いずれ、それにも挑戦してみようと思います。
今回は以上です。
そろそろ ESP32 工作に専念しなければ・・・
ではまた・・・。
Amazon.co.jp 当ブログのおすすめ
コメント
KKHMF 1.5インチカラー OLED モニタモジュール Arduino対応
には、SDカードスロットはないですよー。
tsukaps さん
ご連絡ありがとうございます。
大変失礼しました。
以前の文章が残っておりました。
早速修正いたします。
m(_ _)m
追記です。
早速修正いたしました。
もし、買ってしまった方は、別途、microSDカードスロット基板を購入していただき、接続していただくしかありません。
SCK , MISO , MOSI には必ずプルアップ抵抗 10kΩ以上を入れてください。
全く気付きませんで、大変申し訳ございませんでした。
m(_ _)m
SparkFun マイクロSDカードスロット・ピッチ変換基板
以前、mgo-tecさんのページでパクらしてもらい3個制作。すべて友人に譲りました。ntpつながらなくなりのサーバー変更しましたが、いまだ好評です。リクエストは受け付けないのは存じておりますが、M5Stack版で是非お願いします。
tsukaps さん
お久しぶりです。
再びコメント下さり、ホントにありがとうございます。
昔の記事でこういうコメントを頂けるのはとっても嬉しいです。
作った甲斐がありました。
ですが、他の方からもそういう要望頂いたのですが、やっぱり今は全く余裕がありません。
本業や家庭の事情でメチャメチャ多忙になってしまいました。
新規記事を上げることすらできていません。
もうちょっとしたら落ち着いて新規記事の編集に取り組むつもりですが、過去のソースを改良するところまでは手が回るかどうか分からない状態です。
ご要望にお応えできず、ホントにホントに申し訳ございません。
m(_ _)m