こんばんは。
今回は ESP32 – DevKitC ( ESP-WROOM-32 開発ボード )とフルカラー 有機EL ( OLED ) SSD1331を使って、天気予報をエクセルで自作したフォントを表示させてみました。
電子工作的に文字列を抽出するのが比較的やりやすい Yahoo! Japan RSS サイトから天気予報を取得してみました。
因みに、Yahoo! RSS サイトの利用は個人使用の場合は基本的に無料だそうですが、それ以外の利用は Yahoo! Japan さんにお問い合わせしてくださいね。
よって、この古いプログラムは動作しません。
でも、気象庁のホームページから天気予報データを取得する方式を、この記事の最後の方に追記しましたので参照してみてください。
(2022/08/27時点)
ESP8266 の以前の記事や、こちらの記事では、天気予報を取得しても、ディスプレイには日本語テキスト表示しかしていませんでしたが、今回はそれを自作ロゴでフォント化して表示させてみました。
ロゴの方が文字よりも格段に読み取りやすくなりますね。
ところで、最近、Yahoo! Japan RSS サイトは全て SSL 化 ( https )へ移行し始めていて、つい数日前まで天気予報ページだけまだ http ページだったのですが、つい昨日( 2017/5/28 )に確認したら、ここも https ページに移行されていました。
これで、私が取得したいページは全てSSL 化されてしまったので、 Arduino core for the ESP32 のライブラリも、WiFi.h のインクルードでは取得できません。
WiFiClientSecure.h をインクルードしなければなりませんので、スケッチもそれに変更しました。
ただ、Yahoo! RSS の天気予報では降水確率は取得できません。
週間の予報になっているので、更に情報を絞って、今日と明日の天気だけ表示させてシンプル化しました。
これを応用すれば、天気予報でLED の色を変えたり、モーターを回したりすることも将来的には可能です。
ということで、以下解説してみます。
準備するもの
ESP32 – DevKitC ( ESP-WROOM-32 開発ボード )
日本で使える技適を取得した ESP-WROOM-32 を搭載した Wi-Fi & Bluetooth マイコンボードです。
私は秋月電子通商さんで購入しました。
http://akizukidenshi.com/catalog/g/gM-11819/
Amazon.co.jp ではちょっと高いですが、以下の販売店で売っていました。
カラー OLED SSD1331 モジュール
最近このフルカラー 有機EL ( OLED )の記事ばかり書いていますが、フルカラーOLED としては安価なのにとても優れた小型ディスプレイだと思います。
日本の販売店では殆ど売っていないのが寂しいところです。
Amazon.co.jp では以下で売っていますが、中国販売店です。
確実なことが言えなくて申し訳ないのですが、品質や到着時間は全く分かりません。よく吟味して選んでください。
あと、Amazonプライム対応のものもありましたね。
また、Amazon 以外ですが、秋月電子通商さんにもありました。
http://akizukidenshi.com/catalog/g/gM-11560/
Pmod というのは良く分からなく、これは使ったことが無いのですが、同じ SSD1331 で SPI 通信なので使えると思われます。
SparkFun マイクロSDカードスロット・ピッチ変換基板
いつも私が使っているものです。
これには2.54mmピンヘッダは付属しておりませんので、別途ハンダ付けする必要があります。
SparkFun マイクロSDカードスロット・ピッチ変換基板
micro SDHC カード
私が使って動作確認が取れている micro SDHC カードは以下のものです。
ブレッドボード SAD-101 ( サンハヤト )
このブレッドボードは ESP32 – DevKitC を挿し込んでも、片側1列、もう片側2列の空きがある、私のお勧めブレッドボードです。
他のメーカーの場合、ブレッドボードに空きが無いので注意してください。
1/4W 10kΩ程度の固定抵抗 2つ
これは、SPI通信のMOSI ( Master Output Slave Input ) 端子と MISO ( Master Input Slave Output )端子をプルアップするためのものです。
2.54mmピッチ ピンヘッダ
SparkFun micro SD カードスロットにはピンヘッダが付属していないので、ハンダ付けしておいてください。
Microsoft Excel
自作フォントを編集するためのWindows ソフトです。
ジャンパーワイヤー
パソコン、USBケーブル等
Wi-Fi環境
インターネットにつながっている Wi-Fi 環境が必要です。
事前に ESP32 – DevKitC が接続できるようにルーターのセキュリティ等の設定を済ませておいてください。
接続する
以前の記事と同様で、micro SDHC カードスロットは VSPI 接続。
OLED SSD1331 は HSPI 接続です。
MOSI ( Master Output Slave Input )や、MISO ( Master Input Slave Output )には必ずプルアップ抵抗を接続しておいてください。
Arduino core for the ESP32 のインストール
Arduino IDE のバージョン推奨は 1.8.2 以上です。
予めインストールしておいてください。
Arduno core for the ESP32 は、つい最近 Wi-Fi が繋がりにくい不具合がありましたが、今は完全に解消されていますので、最新版をインストールしてください。
インストール方法は以下のページを参照してください。
Arduino core for the ESP32 のインストール方法
ライブラリのインストール
前回の記事で紹介した、Arduino core for the ESP32 用の私の自作 ESP32_SSD1331ライブラリをインストールしてください。
ライブラリの掲載先は GitHub の以下のページにありますので、ZIPファイルをダウンロードしてください。
https://github.com/mgo-tec/ESP32_SSD1331
ZIPファイルのままArduino IDE にインストールする方法は以下のページを参照してください。
GitHubにある ZIP形式ライブラリ のインストール方法 ( Arduino IDE )
フォントファイルのダウンロードおよび作成
フォントファイルは GitHub の以下のページにありますので、ZIPファイルをダウンロードして解凍して使用してください。
https://github.com/mgo-tec/Excel_Make_Font_CSV
font フォルダの中に MyFont.fnt というファイルで、既にバイナリ化したフォントファイルがあります。
今回は自分でフォントを編集することが面倒な人の為に予めバイナリ化しておきました。
自分流にフォントを変更したい場合は、
MyFont_ver2.0.xlsx
というエクセルファイルを下図の赤枠囲いの中を編集してください。
1 を入力すればフォントを変えられます。
(以前の記事と編集方法が変わっています)
ご自分の好きな形に編集してみてください。
赤い文字のフォント番号は連番になっていて、後で紹介する Arduino IDE スケッチで使用します。
その後、下図の様にCSV_fileOUTput というシートを開いて、CSV 形式で保存してください。
以前の記事と異なるのは、16バイト毎に横に表示させたことです。
Stirling などのバイナリエディタの表示形式と揃えるためにこうしました。
その後は、micro SDHC カードにArduino IDE で以下のスケッチでコンパイル書き込みして、バイナリ形式フォントファイルを出力します。
CSV文字出力が横に16文字になったので、スケッチも変更しました。
MyFont_CSV_Binary_Convert_ver2.0.ino
これの詳しい方法は以下のページを参照してください。
方法はちょっと異なりますが、ほぼ同じようなものです。
エクセル で 16 x 16 ドットフォントを自作して、フルカラー 有機EL ( OLED ) 時計を作ってみた
micro SDHC カードにフォントファイルをコピーする
先ほどダウンロードしたフォントファイルを micro SDHC カードに保存します。
micro SDHC カードのルートに font というフォルダを作成して、そこに MyFont.fnt ファイルをコピーしてください。
micro SDHC カードは通常は FAT32 でフォーマットされていますが、そうでない場合は以下のページを参照してフォーマットしておいてください。
micro SD 、micro SDHC カードの初期化(フォーマット)方法
Yahoo! Japan RSS 天気予報ページアドレスを調べる
では、SSL ( https )化された Yahoo! Japan RSS サイトのご自分の地域のアドレスを調べます。
まず、パソコンのブラウザ等で以下のトップページにアクセスしてください。
https://weather.yahoo.co.jp/weather/rss/
ここでご自分の天気を知りたい地方をクリックして、URL覧からアドレスを調べます。
例えば、「東京」ならば、
https://rss-weather.yahoo.co.jp/rss/days/4410.xml
というアドレスになります。
パソコンやスマホのブラウザのURL入力欄にアドレスが表記されています。
これでサーバー等のアドレスが以下のように判明できます。
HOSTサーバー: rss-weather.yahoo.co.jp
ターゲットページ: /rss/days/4410.xml
これを次のスケッチに入力していきます。
Arduino IDE スケッチの入力
では、Arduino IDE を起動して以下のスケッチを入力してみてください。
よって、この古いプログラムは動作しません。
でも、気象庁のホームページから天気予報データを取得する方式を、この記事の最後の方に追記しましたので参照してみてください。
【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)
/* * Copyright (c) 2017 Mgo-tec * Released under the MIT license * https://opensource.org/licenses/mit-license.php */ #include <WiFiClientSecure.h> #include <SD.h> #include "ESP32_SSD1331.h" const char* ssid = "xxxx"; //ご自分のルーターのSSIDに書き換えてください const char* password = "xxxx"; //ご自分のルーターのパスワードに書き換えてください const char* MyFont_file = "/font/MyFont.fnt"; //自作フォントファイル名を定義 const uint8_t CS_SD = 5; //SD card CS ( Chip Select ) const uint8_t SCLK_OLED = 14; //SCLK const uint8_t MOSI_OLED = 13; //MOSI (Master Output Slave Input) const uint8_t MISO_OLED = 12; //これは実際は使っていない。MISO (Master Input Slave Output) const uint8_t CS_OLED = 15; const uint8_t DC_OLED = 16; //OLED DC(Data/Command) const uint8_t RST_OLED = 4; //OLED Reset ESP32_SSD1331 ssd1331(SCLK_OLED, MISO_OLED, MOSI_OLED, CS_OLED, DC_OLED, RST_OLED); uint8_t MyFont_buf[34][2][16]; //自作フォントを格納する //-----Yahoo記事取得引数初期化------------------------- uint64_t WebGetTime = 0; bool Web_first_get = true; //**********セットアップ******************** void setup() { Serial.begin(115200); delay(10); Serial.println(); Serial.print(F("Connecting to ")); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(); Serial.println(F("WiFi connected")); delay(1000); Serial.println(WiFi.localIP()); delay(10); SD.begin(CS_SD, SPI, 24000000, "/sd"); File MyF = SD.open(MyFont_file, FILE_READ); if (!MyF) { Serial.print(MyFont_file); Serial.println(" File not found"); return; }else{ Serial.print(MyFont_file); Serial.println(" File read OK!"); } //自作フォントを一気にグローバル変数(SRAM)に読み込む for(int i=0; i<34; i++){ MyFont_SD_Read(MyF, 2, i, MyFont_buf[i]); } MyF.close(); ssd1331.SSD1331_Init(); //「今日」表示 ssd1331.SSD1331_8x16_Font_DisplayOut(2, 0, 32, 0, 7, 0, MyFont_buf[17]); ssd1331.SSD1331_8x16_Font_DisplayOut(2, 16, 32, 0, 7, 0, MyFont_buf[10]); //「明日」表示 ssd1331.SSD1331_8x16_Font_DisplayOut(2, 0, 48, 7, 1, 0, MyFont_buf[19]); ssd1331.SSD1331_8x16_Font_DisplayOut(2, 16, 48, 7, 1, 0, MyFont_buf[10]); } //**************メインループ******************* void loop() { //300秒(5分)毎に Yahoo!Japan RSS 天気予報取得 if(Web_first_get == true || (millis() - WebGetTime) > 300000UL){ String weather_str; uint8_t from1, from2, to1; String w_str1, dummy1, w_str2; weather_str = Web_Get("rss-weather.yahoo.co.jp", "/rss/days/4410.xml", '>', "</rss", "】 ", " - ", "|"); Serial.println(weather_str); from1 = weather_str.indexOf('|', 0); from2 = weather_str.indexOf('|', 2); to1 = weather_str.indexOf('|', 2); w_str1 = weather_str.substring(from1, to1); dummy1 = weather_str.substring(from2); w_str2 = dummy1.substring(0, dummy1.indexOf('|', 2)); Serial.print("本日 = "); Serial.println(w_str1); Serial.print("明日 = "); Serial.println(w_str2); YahooRSS_Weather_Display(w_str1, 0, 48, 32, MyFont_buf); YahooRSS_Weather_Display(w_str2, 1, 48, 48, MyFont_buf); WebGetTime = millis(); Web_first_get = false; } } //****************** Webページから http GET ****************** String Web_Get(const char* host, String target_ip, char char_tag, String Final_tag, String Begin_tag, String End_tag, String Paragraph) { String ret_str = ""; WiFiClientSecure Sec_client; if (Sec_client.connect(host, 443)) { Serial.print(host); Serial.print(F("-------------")); Serial.println(F("connected")); Serial.println(F("--------------------WEB HTTP GET Request")); String str1 = "GET " + target_ip + " HTTP/1.1\r\n"; str1 += "Host: " + String( host ) + "\r\n"; str1 += "User-Agent: BuildFailureDetectorESP32\r\n"; str1 += "Connection: close\r\n\r\n\0"; //http1.1では、closeを使うと、サーバーの応答後に切断される。最後に空行必要 Sec_client.print( str1 ); Serial.println( str1 ); }else { // if you didn't get a connection to the server: Serial.println(F("connection failed")); } if(Sec_client){ String dummy_str; uint16_t from, to; Serial.println(F("--------------------WEB HTTP Response")); while(Sec_client.connected()){ while (Sec_client.available()) { if(dummy_str.indexOf(Final_tag) == -1){ dummy_str = Sec_client.readStringUntil(char_tag); if(dummy_str.indexOf(Begin_tag) >= 0){ from = dummy_str.indexOf(Begin_tag) + Begin_tag.length(); to = dummy_str.indexOf(End_tag); ret_str += Paragraph; ret_str += dummy_str.substring(from,to); ret_str += " "; } }else{ while(Sec_client.available()){ Sec_client.read(); //サーバーから送られてくるデータを余すことなく受信することが重要 yield(); } delay(10); Sec_client.stop(); Serial.println(F("--------------------Sec_client Stop")); break; } yield(); } yield(); } } ret_str += "\0"; if(Sec_client){ delay(5); Sec_client.stop(); delay(5); Sec_client.flush(); Serial.println(F("--------------------Sec_client Stop")); } return ret_str; } //*********** 天気予報表示 ********************** void YahooRSS_Weather_Display(String str, uint8_t wDay, uint8_t x0, uint8_t y0, uint8_t fntBuf[][2][16]){ uint8_t Sunny_red = 7, Sunny_green = 3, Sunny_blue = 0; //256bit color 晴れ uint8_t Cloudy_red = 3, Cloudy_green = 3, Cloudy_blue = 1; //256bit color 曇り uint8_t Rain_red = 0, Rain_green = 0, Rain_blue = 3; //256bit color 雨、大雨、暴風雨 uint8_t Snow_red = 7, Snow_green = 7, Snow_blue = 3; //256bit color 雪 uint8_t Thunder_red = 7, Thunder_green = 7, Thunder_blue = 0; //256bit color 雷 uint8_t L_red = 7, L_green = 7, L_blue = 3; //256bit color 線 uint8_t red = 7, green = 7, blue = 3; uint8_t fnt_num = 0; //自作フォント番号 bool Single = true; ssd1331.Display_Clear(x0, y0, x0+16*3-1, y0+16-1); if((str.indexOf("時々") >= 0) || (str.indexOf("一時") >= 0)){ Single = false; fnt_num = 27; ssd1331.SSD1331_8x16_Font_DisplayOut(2, x0+16, y0, L_red, L_green, L_blue, fntBuf[fnt_num]); }else if(str.indexOf("後") >= 0){ Single = false; fnt_num = 28; ssd1331.SSD1331_8x16_Font_DisplayOut(2, x0+16, y0, L_red, L_green, L_blue, fntBuf[fnt_num]); }else if(str.indexOf("時々") < 0 && str.indexOf("後") < 0){ Single = true; } if(str.indexOf("晴") == 1){ red = Sunny_red; green = Sunny_green; blue = Sunny_blue; fnt_num = 20; }else if(str.indexOf("曇") == 1){ red = Cloudy_red; green = Cloudy_green; blue = Cloudy_blue; fnt_num = 21; }else if(str.indexOf("雨") == 1){ red = Rain_red; green = Rain_green; blue = Rain_blue; fnt_num = 22; }else if(str.indexOf("雪") == 1){ red = Snow_red; green = Snow_green; blue = Snow_blue; fnt_num = 24; }else if(str.indexOf("雷") == 1){ red = Thunder_red; green = Thunder_green; blue = Thunder_blue; fnt_num = 25; }else if((str.indexOf("暴風雨") == 1) || (str.indexOf("大雨") == 1)){ red = Rain_red; green = Rain_green; blue = Rain_blue; fnt_num = 23; } if(Single == false){ ssd1331.SSD1331_8x16_Font_DisplayOut(2, x0, y0, red, green, blue, fntBuf[fnt_num]); if(str.indexOf("晴") > 1){ red = Sunny_red; green = Sunny_green; blue = Sunny_blue; fnt_num = 20; }else if(str.indexOf("曇") > 1){ red = Cloudy_red; green = Cloudy_green; blue = Cloudy_blue; fnt_num = 21; }else if(str.indexOf("雨") > 1){ red = Rain_red; green = Rain_green; blue = Rain_blue; fnt_num = 22; }else if(str.indexOf("雪") > 1){ red = Snow_red; green = Snow_green; blue = Snow_blue; fnt_num = 24; }else if(str.indexOf("雷") == 1){ red = Thunder_red; green = Thunder_green; blue = Thunder_blue; fnt_num = 25; }else if((str.indexOf("暴風雨") == 1) || (str.indexOf("大雨") == 1)){ red = Rain_red; green = Rain_green; blue = Rain_blue; fnt_num = 23; } ssd1331.SSD1331_8x16_Font_DisplayOut(2, x0+16*2, y0, red, green, blue, fntBuf[fnt_num]); }else{ ssd1331.SSD1331_8x16_Font_DisplayOut(2, x0+16, y0, red, green, blue, fntBuf[fnt_num]); } } //*************SDカード読み込み************** void MyFont_SD_Read(File F, uint8_t ZorH, uint8_t num, uint8_t buf[2][16]){ F.seek(num * (16 * ZorH)); F.read(buf[0], 16); F.read(buf[1], 16); }
【解説】
●6行目:
Yahoo! RSS 天気予報が SSL 化されたことにより、WiFiClientSecure.h をインクルードしなければなりません。これは WiFi.h よりも SRAM を多く消費します。
●8行目:
有機EL ( OLED ) SSD1331 の自作ライブラリのインクルードです。
●10-11行目:
ご自分のルーター環境の SSID やパスワードに書き換えてください。
●13行目:
micro SDHC カードに保存してあるフォントファイルを定義します。
●15-22行目:
ESP32 – DevKitC の GPIO 設定です。
OLED への MISO ( Master Input Slave Output ) は使いません。
●24行目:
自作ライブラリ ESP32_SSD1331 のクラス初期化です。
●26行目:
自作フォントを格納する3次元配列です。
全角は 16×16 ピクセルですが、8×16ピクセルを2つ組み合わせます。
とりあえず 34 文字のフォントを格納します。
●29-30行:
Yahoo! Japan RSS 天気予報を取得するための変数初期化です。
●34-53行:
ルーター(アクセスポイント)と接続します。
これはサンプルスケッチをそのまま利用しています。
●55-71行:
micro SDHC カードからフォントを読み込んで、MyFont_buf へ格納します。
68行では 252行以降の関数を呼んでいます。
●73行:
自作ライブラリ、ESP32_SSD1331 を初期化する関数です。
●76-80行:
「今日」と「明日」という自作フォントを OLED に表示させています。
ESP32_SSD1331 の関数の使い方については以下のページを参照してください。
有機EL ( OLED ) SSD1331 ライブラリを作成しました
●85行:
起動初回と300秒(5分)毎に Yahoo! RSS 天気予報を取得するためのif文です。
●90行:
ここで、SSL化したYahoo! Japan RSS のサイトから各地方の天気予報文字列を取得して、String文字列 weather_str へ格納しています。
ここでは週間予報が取得できます。
ホストサーバーを指定し、天気予報を取得する各地域のターゲットページを指定しています。
HOSTサーバーやターゲットページは先に説明しましたので、ここで入力します。
Web_Get関数は 110-175行にあるので、詳細は後で述べます。
●93-100行:
weather_str 文字列から Ardunio 標準 String クラス関数の substring やindexOf を使って、週間天気予報文字列から「今日」と 「明日」の天気予報をそれぞれ抽出しています。
Ardunio 標準 String クラス関数については以下のページを参照してください。
●102-103行:
177-250行の関数を呼んで、有機EL ( OLED )ディスプレイ SSD1331 に自作フォントを表示させています。
●110-175行:
SSL 化された Yahoo! Japan RSS サイトから文字列を抽出する関数です。
116行目で SSL ポート443 を開いて Yahoo! RSS ホストサーバーにアクセスして、ターゲットページに GETリクエストを送ります。
138-139行のSec_client.connected(); や Sec_client.available(); でサーバーからのレスポンスを待って文字列を取得します。
141-148行で、欲しい文字列を抽出します。
150-157行でサーバーから送られてくる文字列を余すことなく全て受け取ります。
1文字でも受け取り漏らすと、後でエラーの可能性が出てくるので、ここは重要です。
受け取って、サーバーからの有効な文字列が無くなったら切断します。
そして、取得した文字列 ret_str を返します。
●177-250行:
抽出した「今日」と「明日」の天気予報文字列からフォント番号を割り当ててディスプレイに表示させる関数です。
178-183行で天気ロゴフォントの色を256色カラーで指定します。
191-201行では、「時々」や「一時」、「のち」などの文字列を表示させる条件分岐です。
フォント番号は先に述べたエクセルファイルからフォント番号を指定します。
Single がfalse ならばフォントを2つ表示させ、 true ならばフォントを1つ表示させます。
203-221行では天気予報文字列のうち最初に現れる文字からフォント番号を割り当てます。
223-244行では「時々」や「一時」などがあった場合にはフォントを2つ表示させるので、2番目に現れる天気文字を抽出してフォントを割り当てます。
●252-256行:
ここで micro SDHC カードからフォントファイルを読み込み、配列に格納します。
16×16 ピクセルのフォントなので、32バイト読み込みますが、8×16ピクセル毎に配列を分けます。
将来的に半角にも対応するためでもあります。
コンパイル書き込み実行
ブラウザからスケッチをコピーしたら、必ず一旦保存してください。そうしないと正しく表示されませんので要注意!!
では、ご自分のWi-Fiルーターを起動し、インターネットに接続できる状態にしておいて、上記のスケッチをコンパイル書き込み実行してみてください。
そして、間髪入れずにシリアルモニターを115200bps で起動してください。
すると、こんな感じで表示されていればOKです。
※ Arduino IDE 1.8.1 以下では文字化けします。
1.8.2 以降を使用してください。
そして、有機EL ( OLED ) SSD1331 ではこんな感じに表示されればOKです。
写真ではうまく表示されていませんが、実際は色はもっと鮮やかです。
追記(Yahoo天気から気象庁天気予報に変更 2022/08/27)
このブログのこちらの記事でお伝えしたように、2022/03/31でYahoo! Japan天気予報の配信が終了してしまいました。
そこで、読者の方々からプログラムが動かないというコメント投稿があり、新たに変更したArduinoスケッチを紹介します。
気象庁のルートCA証明書公開鍵や、ご自分の地域の office_code と area_code は以下の記事を参照して書き換えてください。
気象庁天気予報JSONを取得して天気予報を表示させてみた
【動作確認済みバージョン】
Arduino core for the ESP32 ver 2.0.4
ESP32_SSD1331 beta ver 1.71
/* * Copyright (c) 2017 Mgo-tec * Released under the MIT license * https://opensource.org/licenses/mit-license.php * Use Arduino core for the ESP32 ver 2.0.4. * Use ESP32_SSD1331 beta ver 1.71 library. */ #include <WiFiClientSecure.h> #include <SD.h> #include <ESP32_SSD1331.h> static const char* ssid = "xxxxxxxxx"; //ご自分のルーターのSSIDに書き換えてください static const char* password = "xxxxxxxxx"; //ご自分のルーターのパスワードに書き換えてください const char* MyFont_file = "/font/MyFont.fnt"; //自作フォントファイル名を定義 const uint8_t CS_SD = 5; //SD card CS ( Chip Select ) const uint8_t SCLK_OLED = 14; //SCLK const uint8_t MOSI_OLED = 13; //MOSI (Master Output Slave Input) const uint8_t MISO_OLED = 12; //これは実際は使っていない。MISO (Master Input Slave Output) const uint8_t CS_OLED = 15; const uint8_t DC_OLED = 16; //OLED DC(Data/Command) const uint8_t RST_OLED = 4; //OLED Reset ESP32_SSD1331 ssd1331(SCLK_OLED, MISO_OLED, MOSI_OLED, CS_OLED, DC_OLED, RST_OLED); uint8_t MyFont_buf[34][2][16]; //自作フォントを格納する uint64_t WebGetTime = 0; bool Web_first_get = true; //------気象庁ホームページ天気予報用初期化----------------------- const char* jp_weather_host = "www.jma.go.jp"; String office_code_str = "130000"; //東京都 //String office_code_str = "011000"; //宗谷 //String office_code_str = "050000"; //秋田 String area_code_str = "130010"; //東京地方 //String area_code_str = "011000"; //宗谷 //String area_code_str = "050010"; //秋田沿岸 String jp_weather_target_url = "/bosai/forecast/data/forecast/" + office_code_str + ".json"; //ルートCA公開鍵 const char* jp_weather_root_ca= \ "-----BEGIN CERTIFICATE-----\n" \ "MIIDxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n" \ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n" \ .................................... ~省略~ .................................... "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n" \ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n" \ "-----END CERTIFICATE-----\n"; //**********セットアップ******************** void setup() { Serial.begin(115200); delay(10); Serial.println(); Serial.print(F("Connecting to ")); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(); Serial.println(F("WiFi connected")); delay(1000); Serial.println(WiFi.localIP()); delay(10); SD.begin(CS_SD, SPI, 24000000, "/sd"); File MyF = SD.open(MyFont_file, FILE_READ); if (!MyF) { Serial.print(MyFont_file); Serial.println(" File not found"); return; }else{ Serial.print(MyFont_file); Serial.println(" File read OK!"); } //自作フォントを一気にグローバル変数(SRAM)に読み込む for(int i=0; i<34; i++){ MyFont_SD_Read(MyF, 2, i, MyFont_buf[i]); } MyF.close(); ssd1331.SSD1331_Init(); //「今日」表示 ssd1331.SSD1331_8x16_Font_DisplayOut(2, 0, 32, 0, 7, 0, MyFont_buf[17]); ssd1331.SSD1331_8x16_Font_DisplayOut(2, 16, 32, 0, 7, 0, MyFont_buf[10]); //「明日」表示 ssd1331.SSD1331_8x16_Font_DisplayOut(2, 0, 48, 7, 1, 0, MyFont_buf[19]); ssd1331.SSD1331_8x16_Font_DisplayOut(2, 16, 48, 7, 1, 0, MyFont_buf[10]); } //**************メインループ******************* void loop() { //300秒(5分)毎に Yahoo!Japan RSS 天気予報取得 if(Web_first_get == true || (millis() - WebGetTime) > 300000UL){ String weather_str; weather_str = Web_Get( jp_weather_root_ca, jp_weather_host, jp_weather_target_url, area_code_str); Serial.println(weather_str); uint8_t x0 = 48, y0 = 32; dispJapanWeatherMyFont(x0, y0, weather_str, MyFont_buf); WebGetTime = millis(); Web_first_get = false; } } //*********** 気象庁天気予報JSONゲット *********** String Web_Get(const char* root_ca, const char* host, String target_url, String area_code_str) { int16_t wifi_state = WiFi.status(); if( wifi_state != WL_CONNECTED ){ return "※WiFi APに接続できません"; } String ret_str1; WiFiClientSecure client; client.setCACert(root_ca); char separation_tag = ']'; String search_key = "code\":\"" + area_code_str + "\"},\"weatherCodes\":[\""; String paragraph = "|"; uint32_t time_out = millis(); while(true){ /*インターネットが不意に切断されたときや、長時間接続している時には再接続できなくなる。 再接続時、client.connect が true になるまで時間がかかる場合があるので、数回トライする必要がある。*/ if ( client.connect( host, 443 ) ){ Serial.print( host ); Serial.print( F("-------------") ); Serial.println( F("connected") ); Serial.println( F("-------Send HTTPS GET Request") ); String str1 = String( F("GET ") ); str1 += target_url + F(" HTTP/1.1\r\n"); str1 += F("host: "); str1 += String( host ) + F("\r\n"); str1 += F("User-Agent: BuildFailureDetectorESP32\r\n"); str1 += F("Accept: text/html,application/xhtml+xml,application/xml\r\n"); str1 += F("Connection: keep-alive\r\n\r\n"); //closeを使うと、サーバーの応答後に切断される。最後に空行必要 str1 += "\0"; client.print( str1 ); //client.println にしないこと。最後に改行コードをプラスして送ってしまう為 client.flush(); //client出力が終わるまで待つ log_v( "%s", str1.c_str() ); //Serial.flush(); //シリアル出力が終わるまで待つ指令は、余分なdelayがかかってしまうので基本的に使わない break; } if( ( millis() - time_out ) > 20000 ){ Serial.println( F("time out!") ); Serial.println( F("host connection failed.") ); return "※Host に接続できません。"; } delay(1); } time_out = millis(); if( client ){ String tmp_str; Serial.println( F("-------Receive HTTPS Response") ); if( client.connected() ){ while(true) { if( ( millis() - time_out ) > 60000 ){ Serial.println( F("time out!")); Serial.println( F("Host HTTPS response failed.") ); break; } tmp_str = client.readStringUntil( separation_tag ); //Serial.println(tmp_str); if( tmp_str.indexOf( search_key ) >= 0 ){ ret_str1 += paragraph; ret_str1 += tmp_str; ret_str1 += "] "; break; } delay(1); } while(client.available()){ if( ( millis() - time_out ) > 60000 ) break; //60seconds Time Out client.read(); delay(1); } delay(10); client.stop(); delay(10); Serial.println( F("-------Client Stop") ); } } ret_str1 += "\0"; if( ret_str1.length() < 20 ) ret_str1 = "※WEB GETできませんでした"; if( client ){ delay(10); client.stop(); delay(10); Serial.println( F("-------Client Stop") ); } return ret_str1; } //*************SDカード読み込み************** void MyFont_SD_Read(File F, uint8_t ZorH, uint8_t num, uint8_t buf[2][16]){ F.seek(num * (16 * ZorH)); F.read(buf[0], 16); F.read(buf[1], 16); } //*************気象庁天気予報用**************************** void dispJapanWeatherMyFont(uint8_t x0, uint8_t y0, String &weather_str, uint8_t fntBuf[][2][16]) { ssd1331.Display_Clear(x0, y0, x0+16*3-1, y0+16-1); uint8_t today_fnum[3] = {}, tomorrow_fnum[3] = {}; uint8_t today_col[3][3] ={}, tomorrow_col[3][3] = {}; String weather_code_from_key = "weatherCodes\":[\""; uint8_t from1 = weather_str.indexOf(weather_code_from_key, 0) + weather_code_from_key.length(); uint8_t to1 = from1 + 3; uint8_t from2 = to1 + 3; uint8_t to2 = from2 + 3; String today_w_code_str = weather_str.substring(from1, to1); String tomorrow_w_code_str = weather_str.substring(from2, to2); uint16_t today_weather_code = atoi(today_w_code_str.c_str()); uint16_t tomorrow_weather_code = atoi(tomorrow_w_code_str.c_str()); Serial.print(F("Today Weather Coode = ")); Serial.println(today_weather_code); Serial.print(F("Tomorrow Weather Code = ")); Serial.println(tomorrow_weather_code); JapanWeatherFontNum(today_weather_code, 0, today_fnum, today_col); JapanWeatherFontNum(tomorrow_weather_code, 1, tomorrow_fnum, tomorrow_col); for(int i=0; i<3; i++){ ssd1331.SSD1331_8x16_Font_DisplayOut(2, x0 + 16 * i, y0, today_col[i][0], today_col[i][1], today_col[i][2], fntBuf[today_fnum[i]]); delay(1); } for(int i=0; i<3; i++){ ssd1331.SSD1331_8x16_Font_DisplayOut(2, x0 + 16 * i, y0 + 16, tomorrow_col[i][0], tomorrow_col[i][1], tomorrow_col[i][2], fntBuf[tomorrow_fnum[i]]); delay(1); } } //***************** 気象庁 Weather MyFont Number get ************************************************* void JapanWeatherFontNum(uint16_t weather_code, uint8_t wDay, uint8_t (&Fnum)[3], uint8_t (&col)[3][3]) { uint8_t Sunny_red = 31, Sunny_green = 40, Sunny_blue = 0; uint8_t Cloudy_red = 29, Cloudy_green = 60, Cloudy_blue = 29; uint8_t Rain_red = 0, Rain_green = 0, Rain_blue = 31; uint8_t Snow_red = 31, Snow_green = 63, Snow_blue = 31; col[1][0] = 31; col[1][1] = 63; col[1][2] = 31; //矢印 uint8_t sunny_fnt_num = 20; uint8_t cloudy_fnt_num = 21; uint8_t rain_fnt_num = 22; uint8_t snow_fnt_num = 24; uint8_t storm_fnt_num = 23; //uint8_t thunder_fnt_num = 25; switch(weather_code){ //--------Clear(晴れ)----------------- case 100: case 123: case 124: case 130: case 131: //Serial.println("晴れ"); Fnum[0] = 0; col[1][0] = Sunny_red; col[1][1] = Sunny_green; col[1][2] = Sunny_blue; Fnum[1] = sunny_fnt_num; //縦棒、または矢印 Fnum[2] = 0; break; //--------晴れ時々(一時)曇り---------------- case 101: case 132: //Serial.println("晴れ時々曇り"); col[0][0] = Sunny_red; col[0][1] = Sunny_green; col[0][2] = Sunny_blue; Fnum[0] = sunny_fnt_num; Fnum[1] = 27; //[時々(一時)]縦棒 col[2][0] = Cloudy_red; col[2][1] = Cloudy_green; col[2][2] = Cloudy_blue; Fnum[2] = cloudy_fnt_num; break; //--------晴れ時々(一時)雨---------------- case 102: case 103: case 106: case 107: case 108: case 120: case 121: case 140: //Serial.println("晴れ時々雨"); col[0][0] = Sunny_red; col[0][1] = Sunny_green; col[0][2] = Sunny_blue; Fnum[0] = sunny_fnt_num; Fnum[1] = 27; //[時々(一時)]縦棒 col[2][0] = Rain_red; col[2][1] = Rain_green; col[2][2] = Rain_blue; Fnum[2] = rain_fnt_num; break; //--------晴れ時々(一時)雪---------------- case 104: case 105: case 160: case 170: //Serial.println("晴れ時々雪"); col[0][0] = Sunny_red; col[0][1] = Sunny_green; col[0][2] = Sunny_blue; Fnum[0] = sunny_fnt_num; Fnum[1] = 27; //[時々(一時)]縦棒 col[2][0] = Snow_red; col[2][1] = Snow_green; col[2][2] = Snow_blue; Fnum[2] = snow_fnt_num; break; //--------晴れ後曇り---------------- case 110: case 111: //Serial.println("晴れ後曇り"); col[0][0] = Sunny_red; col[0][1] = Sunny_green; col[0][2] = Sunny_blue; Fnum[0] = sunny_fnt_num; Fnum[1] = 28; //[のち]矢印 col[2][0] = Cloudy_red; col[2][1] = Cloudy_green; col[2][2] = Cloudy_blue; Fnum[2] = cloudy_fnt_num; break; //--------晴れ後雨---------------- case 112: case 113: case 114: case 118: case 119: case 122: case 125: case 126: case 127: case 128: //Serial.println("晴れ後雨"); col[0][0] = Sunny_red; col[0][1] = Sunny_green; col[0][2] = Sunny_blue; Fnum[0] = sunny_fnt_num; Fnum[1] = 28; //[のち]矢印 col[2][0] = Rain_red; col[2][1] = Rain_green; col[2][2] = Rain_blue; Fnum[2] = rain_fnt_num; break; //--------晴れ後雪---------------- case 115: case 116: case 117: case 181: //Serial.println("晴れ後雪"); col[0][0] = Sunny_red; col[0][1] = Sunny_green; col[0][2] = Sunny_blue; Fnum[0] = sunny_fnt_num; Fnum[1] = 28; //[のち]矢印 col[2][0] = Snow_red; col[2][1] = Snow_green; col[2][2] = Snow_blue; Fnum[2] = snow_fnt_num; break; //--------曇り----------------- case 200: case 209: case 231: //Serial.println("曇り"); Fnum[0] = 0; col[1][0] = Cloudy_red; col[1][1] = Cloudy_green; col[1][2] = Cloudy_blue; Fnum[1] = cloudy_fnt_num; //縦棒、または矢印 Fnum[2] = 0; break; //--------曇り時々晴れ----------------- case 201: case 223: //Serial.println("曇り時々晴れ"); col[0][0] = Cloudy_red; col[0][1] = Cloudy_green; col[0][2] = Cloudy_blue; Fnum[0] = cloudy_fnt_num; Fnum[1] = 27; //[時々(一時)]縦棒 col[2][0] = Sunny_red; col[2][1] = Sunny_green; col[2][2] = Sunny_blue; Fnum[2] = sunny_fnt_num; break; //--------曇り時々雨----------------- case 202: case 203: case 206: case 207: case 208: case 220: case 221: case 240: //Serial.println("曇り時々雨"); col[0][0] = Cloudy_red; col[0][1] = Cloudy_green; col[0][2] = Cloudy_blue; Fnum[0] = cloudy_fnt_num; Fnum[1] = 27; //[時々(一時)]縦棒 col[2][0] = Rain_red; col[2][1] = Rain_green; col[2][2] = Rain_blue; Fnum[2] = rain_fnt_num; break; //--------曇り一時雪----------------- case 204: case 205: case 250: case 260: case 270: //Serial.println("曇り一時雪"); col[0][0] = Cloudy_red; col[0][1] = Cloudy_green; col[0][2] = Cloudy_blue; Fnum[0] = cloudy_fnt_num; Fnum[1] = 27; //[時々(一時)]縦棒 col[2][0] = Snow_red; col[2][1] = Snow_green; col[2][2] = Snow_blue; Fnum[2] = snow_fnt_num; break; //--------曇り後晴れ----------------- case 210: case 211: //Serial.println("曇り後晴れ"); col[0][0] = Cloudy_red; col[0][1] = Cloudy_green; col[0][2] = Cloudy_blue; Fnum[0] = cloudy_fnt_num; Fnum[1] = 28; //[のち]矢印 col[2][0] = Sunny_red; col[2][1] = Sunny_green; col[2][2] = Sunny_blue; Fnum[2] = sunny_fnt_num; break; //--------曇り後雨----------------- case 212: case 213: case 214: case 218: case 219: case 222: case 224: case 225: case 226: //Serial.println("曇り後雨"); col[0][0] = Cloudy_red; col[0][1] = Cloudy_green; col[0][2] = Cloudy_blue; Fnum[0] = cloudy_fnt_num; Fnum[1] = 28; //[のち]矢印 col[2][0] = Rain_red; col[2][1] = Rain_green; col[2][2] = Rain_blue; Fnum[2] = rain_fnt_num; break; //--------曇り後雪----------------- case 215: case 216: case 217: case 228: case 229: case 230: case 281: //Serial.println("曇り後雪"); col[0][0] = Cloudy_red; col[0][1] = Cloudy_green; col[0][2] = Cloudy_blue; Fnum[0] = cloudy_fnt_num; Fnum[1] = 28; //[のち]矢印 col[2][0] = Snow_red; col[2][1] = Snow_green; col[2][2] = Snow_blue; Fnum[2] = snow_fnt_num; break; //--------雨----------------- case 300: case 304: case 306: case 328: case 329: case 350: //Serial.println("雨"); Fnum[0] = 0; col[1][0] = Rain_red; col[1][1] = Rain_green; col[1][2] = Rain_blue; Fnum[1] = rain_fnt_num; //縦棒、または矢印 Fnum[2] = 0; break; //--------雨時々晴れ----------------- case 301: //Serial.println("雨時々晴れ"); col[0][0] = Rain_red; col[0][1] = Rain_green; col[0][2] = Rain_blue; Fnum[0] = rain_fnt_num; Fnum[1] = 27; //[時々(一時)]縦棒 col[2][0] = Sunny_red; col[2][1] = Sunny_green; col[2][2] = Sunny_blue; Fnum[2] = sunny_fnt_num; break; //--------雨時々曇り----------------- case 302: //Serial.println("雨時々曇り"); col[0][0] = Rain_red; col[0][1] = Rain_green; col[0][2] = Rain_blue; Fnum[0] = rain_fnt_num; Fnum[1] = 27; //[時々(一時)]縦棒 col[2][0] = Cloudy_red; col[2][1] = Cloudy_green; col[2][2] = Cloudy_blue; Fnum[2] = cloudy_fnt_num; break; //--------雨時々雪----------------- case 303: case 309: case 322: //Serial.println("雨時々雪"); col[0][0] = Rain_red; col[0][1] = Rain_green; col[0][2] = Rain_blue; Fnum[0] = rain_fnt_num; Fnum[1] = 27; //[時々(一時)]縦棒 col[2][0] = Snow_red; col[2][1] = Snow_green; col[2][2] = Snow_blue; Fnum[2] = snow_fnt_num; break; //--------暴風雨----------------- case 308: //Serial.println("暴風雨"); Fnum[0] = 0; col[1][0] = Rain_red; col[1][1] = Rain_green; col[1][2] = Rain_blue; Fnum[1] = storm_fnt_num; //縦棒、または矢印 Fnum[2] = 0; break; //--------雨後晴れ----------------- case 311: case 316: case 320: case 323: case 324: case 325: //Serial.println("雨後晴れ"); col[0][0] = Rain_red; col[0][1] = Rain_green; col[0][2] = Rain_blue; Fnum[0] = rain_fnt_num; Fnum[1] = 28; //[のち]矢印 col[2][0] = Sunny_red; col[2][1] = Sunny_green; col[2][2] = Sunny_blue; Fnum[2] = sunny_fnt_num; break; //--------雨後曇り----------------- case 313: case 317: case 321: //Serial.println("雨後曇り"); col[0][0] = Rain_red; col[0][1] = Rain_green; col[0][2] = Rain_blue; Fnum[0] = rain_fnt_num; Fnum[1] = 28; //[のち]矢印 col[2][0] = Cloudy_red; col[2][1] = Cloudy_green; col[2][2] = Cloudy_blue; Fnum[2] = cloudy_fnt_num; break; //--------雨後雪----------------- case 314: case 315: case 326: case 327: //Serial.println("雨後雪"); col[0][0] = Rain_red; col[0][1] = Rain_green; col[0][2] = Rain_blue; Fnum[0] = rain_fnt_num; Fnum[1] = 28; //[のち]矢印 col[2][0] = Snow_red; col[2][1] = Snow_green; col[2][2] = Snow_blue; Fnum[2] = snow_fnt_num; break; //--------雪----------------- case 340: case 400: case 405: case 425: case 426: case 427: case 450: //Serial.println("雪"); Fnum[0] = 0; col[1][0] = Snow_red; col[1][1] = Snow_green; col[1][2] = Snow_blue; Fnum[1] = storm_fnt_num; //縦棒、または矢印 Fnum[2] = 0; break; //--------雪時々晴れ----------------- case 401: //Serial.println("雪時々晴れ"); col[0][0] = Snow_red; col[0][1] = Snow_green; col[0][2] = Snow_blue; Fnum[0] = snow_fnt_num; Fnum[1] = 27; //[時々(一時)]縦棒 col[2][0] = Sunny_red; col[2][1] = Sunny_green; col[2][2] = Sunny_blue; Fnum[2] = sunny_fnt_num; break; //--------雪時々曇り----------------- case 402: //Serial.println("雪時々曇り"); col[0][0] = Snow_red; col[0][1] = Snow_green; col[0][2] = Snow_blue; Fnum[0] = snow_fnt_num; Fnum[1] = 27; //[時々(一時)]縦棒 col[2][0] = Cloudy_red; col[2][1] = Cloudy_green; col[2][2] = Cloudy_blue; Fnum[2] = sunny_fnt_num; break; //--------雪時々雨----------------- case 403: case 409: //Serial.println("雪時々雨"); col[0][0] = Snow_red; col[0][1] = Snow_green; col[0][2] = Snow_blue; Fnum[0] = snow_fnt_num; Fnum[1] = 27; //[時々(一時)]縦棒 col[2][0] = Rain_red; col[2][1] = Rain_green; col[2][2] = Rain_blue; Fnum[2] = rain_fnt_num; break; //--------暴風雪----------------- //暴風雪アイコンはまだ無い case 406: case 407: //Serial.println("暴風雪"); Fnum[0] = 0; col[1][0] = Snow_red; col[1][1] = Snow_green; col[1][2] = Snow_blue; Fnum[1] = storm_fnt_num; //縦棒、または矢印 Fnum[2] = 0; break; //--------雪後晴れ----------------- case 361: case 411: case 420: //Serial.println("雪後晴れ"); col[0][0] = Snow_red; col[0][1] = Snow_green; col[0][2] = Snow_blue; Fnum[0] = snow_fnt_num; Fnum[1] = 28; //[のち]矢印 col[2][0] = Sunny_red; col[2][1] = Sunny_green; col[2][2] = Sunny_blue; Fnum[2] = sunny_fnt_num; break; //--------雪後曇り----------------- case 371: case 413: case 421: //Serial.println("雪後曇り"); col[0][0] = Snow_red; col[0][1] = Snow_green; col[0][2] = Snow_blue; Fnum[0] = snow_fnt_num; Fnum[1] = 28; //[のち]矢印 col[2][0] = Cloudy_red; col[2][1] = Cloudy_green; col[2][2] = Cloudy_blue; Fnum[2] = cloudy_fnt_num; break; //--------雪後雨----------------- case 414: case 422: case 423: //Serial.println("雪後雨"); col[0][0] = Snow_red; col[0][1] = Snow_green; col[0][2] = Snow_blue; Fnum[0] = snow_fnt_num; Fnum[1] = 28; //[のち]矢印 col[2][0] = Rain_red; col[2][1] = Rain_green; col[2][2] = Rain_blue; Fnum[2] = rain_fnt_num; break; default: break; } }
まとめ
どうでしょうか?
うまく表示できたでしょうか?
実はお天気表示としてはロゴフォントがまだ足りないかもしれません。
霧や吹雪などは作っていません。
その場合はご自分でフォントを追加してみてください。
将来的には降水確率まで表示させたいと思っています。
これができれば、ニュース記事電光掲示板とNTP時計と組み合わせると、この小さい画面で社会人に必要な情報が得られるガジェットっぽいものができますね。
実はそういうものが既に私の手元では完成しています。
近々記事にしたいと思っています。
以上、もし何か不具合等ありましたらコメント等でご連絡いただけるとありがたいです。
ではまた・・・。
Amazon.co.jp 当ブログのおすすめ
コメント
mgo-tec 様
電子工作初心者です。こちらのサイト(天気予報を自作フォントで表示してみた)を参考にさせていただいているのですが、ESP32への書き込みがどうしてもうまくいきません。プログラムのコンパイルは成功するのですが、ESP32への書き込みで「A fatal error occurred: Timed out waiting for packet content」というエラーが出てしまいます。古いバージョンのArduino core for the ESP32をいくつか試してみても、何度書き込みをしてみても、ダメで…。
何か対策かあれば教えていただきたいです。
よろしくお願いします。
kirahijiさん
記事をご覧いただき、ありがとうございます。
まず、お聞きしたいのは、デバイスは何を使っていますでしょうか?
M5Stack ですか?
ESP32-DevKitC ですか?
ESPr Developer 32 ですか?
また、以下の記事は参照されましたでしょうか?
ESP32 ( ESP-WROOM-32 , M5Stack )自分的 トラブルシューティング まとめ
ご返信、ありがとうございます。
デバイスはESP32-DevKitCを使っています。
トラブルシューティングのまとめサイトには一通り目を通し、色々試してはみたのですが、なかなかうまくいかず、、、。
kirahijiさん
ESP32-DevKitC は最新のものですか?
最近、秋月電子さんから ESP-WROOM-32D のボードが発売されていて、パーツも一新されています。
http://akizukidenshi.com/catalog/g/gM-13628/
その場合、もしかしたら、USBシリアルのドライバが認識しないかも知れません。
Facebookの ESP8266/ESP32環境向上委員会でそういう情報があったかもしれませんので、見てみて下さい。
また、パソコンが MAC の場合はUSBシリアルドライバのインストールが必要かも知れません。
私はWindowsしか持っていないので、正直分かりませんが・・・。
あと、パソコンに接続する USBケーブルが粗悪だったり、1m以上長かったり、USBハブを使っていたりするとダメです。
良質で太く短いケーブルに替えて、USBハブを使わず、パソコン直に挿してください。
または、Arduino IDE の「ツール」の Upload Speed を下げてみて下さい。
あとは、試しに、リセットボタンとは別の ENボタンを押し続けながらコンパイル書き込みして、終わったらボタンを放してみて下さい。
ENボタンがダメならリセットボタンを押し続けながらコンパイル書き込みしてみて下さい。
以上をそれぞれ試してみて下さい。