Arduino – ESP32 stable 1.0.1 のインストール
つい最近、Arduino core for the ESP32 の stable版が 1.0.1 にバージョンアップしました。
それをボードマネージャーでインストールしてください。
今回は stable 1.0.1から新たに追加された関数を使っていますので、古いバージョンは使えません。
ご注意ください。
インストール方法は以下の記事を参照してください。
Arduino core for the ESP32 のインストール方法
M5Stack にYahooニュース天気予報時計が表示できるように、ライブラリやフォントを設定しておく
以前の以下の記事を参照して、micro SDHC カードに日本語フォントファイルをコピーしておき、また、その他のライブラリ群をインストールしておいて、M5Stack に Yahooニュース天気予報時計が表示できる状態にしておいてください。
M5Stack ( ESP32 ) のボタンで記事を選択できる Yahoo News 電光掲示板 天気予報 Watch
スケッチ(プログラム)の入力
WDT ( ウォッチドッグタイマ )を停止させるプログラム
では、電波時計用 JJY 発信プログラムと合わせたスケッチは以下の通りです。
これは ESP32 をマルチタスクで動かします。
M5Stack の LCD ディスプレイの文字列スクロールを止めずに、電波時計用 JJY 信号を同時に発信するので、いろいろ工夫するところがあります。
※Yahoo RSS 天気 は2022/03/31 で配信終了してしまいました。残念、、、。
このコードでは天気予報は表示されません。
よって、気象庁ホームページから天気予報を取得する方法に更新しました。
詳しくは以下の記事を参照してください。
【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)
//use Arduino core for the ESP32 stable 1.0.1 #define MGO_TEC_BV1_M5STACK_SD_SKETCH #include <mgo_tec_bv1_m5stack_sd_yahoo.h> const char* ssid = "xxxxxxxx"; //ご自分のルーターのSSIDに書き換えてください const char* password = "xxxxxxxx"; //ご自分のルーターのパスワードに書き換えてください const char* utf8sjis_file = "/font/Utf8Sjis.tbl"; //UTF8 Shift_JIS 変換テーブルファイル名を記載しておく //const char* shino_full_font_file = "/font/shnmk16.bdf"; //オリジナル東雲全角フォントファイル const char* shino_half_font_file = "/font/shnm8x16.bdf"; //半角フォントファイル名を定義 const char* shino_full_font_file = "/font/MYshnmk16.bdf"; //自作改変全角東雲フォントファイル const char* my_font_file = "/font/MyFont.fnt"; //自作フォントファイル名を定義(天気予報用) //-----Yahooニュース切替select box関連初期化----- const uint8_t max_select2 = 9; mgo_tec_esp32_bv1::SelectUrl sel_news[ max_select2 ] = { { "主要", "/rss/topics/top-picks.xml" }, { "スポーツ", "/rss/topics/sports.xml" }, { "エンタメ", "/rss/topics/entertainment.xml" }, { "IT", "/rss/topics/it.xml" }, { "経済", "/rss/topics/business.xml" }, { "科学", "/rss/topics/science.xml" }, { "国際", "/rss/topics/world.xml" }, { "地域", "/rss/topics/local.xml" }, { "国内", "/rss/topics/domestic.xml" } }; //------Yahooニュース表示関連初期化---------------- const char* yahoo_news_host = "news.yahoo.co.jp"; const char* yahoo_weather_host = "rss-weather.yahoo.co.jp"; const char* yahoo_weather_target_url = "/rss/days/4410.xml"; //地域:東京 uint8_t url0_num = 0; uint8_t url1_num = 1; boolean isChange_news = false; //------ NTP時刻文字表示系 引数初期化----- int timezone = 9; //Tokyo const char *ntp_server_name = "time.windows.com"; //-----Webデータ取得変数------------- const uint32_t interval_web_get_time = 1200000; //20分間隔でNTP時刻およびYahoo記事取得 uint32_t wifi_connect_last_time = 0; boolean isWifi_connect_first = true; boolean isClear_disp = false; //-----電波時計合わせ用定義---------- const uint8_t gpio_high = 17; const uint8_t gpio_low = 16; uint8_t min_now, hour_now, year_now, weekday_now; uint16_t day_total; uint8_t parity1, parity2; //パリティ定義 uint32_t last_time_jjy = 0; boolean isReset_jjy = true; //NTP時刻取得したり、Web記事取得した場合 true; boolean isJJY_through = false; //***********セットアップ**************************** void setup() { delay(1000); Serial.begin(115200); mM5.init( utf8sjis_file, shino_half_font_file, shino_full_font_file ); //天気予報用自作フォント初期化 mM5.yahoo[0].initWeather( my_font_file ); LCD.brightness( 50 ); //LCD LED Full brightness setupWatchFont(); //時計表示フォントセットアップ setupYmdFont(); //年月日表示フォントセットアップ setupWeatherFont(); //天気予報フォントセットアップ setupNewsFont(); //ニュース記事文字列のセットアップ setupMessageWindow(); //メッセージウィンドウ表示セットアップ //起動時のみのメッセージウィンドウ設定 mM5.msg.m_padding = 4; mM5.msg.dispMsgWindow( 0, "WiFi Connecting..." ); getWeb(); mM5.watch.watchDispReset(); //時計表示が崩れる為に一旦リセット //---------------------------------- setTime( now() + 1 ); //標準時とキッチリ合わせるため、1秒早める。 TaskHandle_t th; //マルチタスクハンドル定義 xTaskCreatePinnedToCore(Task1, "Task1", 8192, NULL, 5, &th, 0); //マルチタスク起動 } //***********メインループ**************************** void loop(){ //Statusメッセージ表示 mM5.wifi_msg.dispWifiStatusMsgShort(); //WiFiステータス表示 mM5.watch.ntp_msg.dispWebGetStatusMsgShort( mM5.watch.ntp_msg_status, "NTP" ); mM5.yahoo[0].weather_msg.dispWebGetStatusMsgShort( mM5.yahoo[0].weather_msg_status, "天" ); mM5.yahoo[0].news_msg.dispWebGetStatusMsgShort( mM5.yahoo[0].news_msg_status, sel_news[url0_num].name ); mM5.yahoo[1].news_msg.dispWebGetStatusMsgShort( mM5.yahoo[1].news_msg_status, sel_news[url1_num].name ); if( isChange_news == true ){ LCD.displayClear( 0, 24, 319, 239 ); mM5.watch.watchDispReset(); mM5.yahoo[0].m_isWeather_get = true; mM5.watch.m_changeYMD = true; isChange_news = false; } mM5.watch.scrolleWatch(); //時計スクロール mM5.watch.displayColon2(); //時計コロン表示 mM5.watch.displayYMDW(); //年月日、曜日表示 //Yahoo! Japan RSS 天気予報表示 mM5.yahoo[0].dispYahooJweatherMyFont( mM5.yahoo[0].weather_fnt ); //Yahoo! Japan RSS ニューススクロール mM5.yahoo[0].scrolleYahooJnews2( mM5.yahoo[0].news_font, mM5.yahoo[0].news_scl_set ); mM5.yahoo[1].scrolleYahooJnews2( mM5.yahoo[1].news_font, mM5.yahoo[1].news_scl_set ); button_action(); //ボタン操作 } //************ マルチタスクループ ****************** void Task1( void *pvParameters ) { pinMode(gpio_high, OUTPUT); pinMode(gpio_low, OUTPUT); disableCore0WDT(); //Arduino-ESP32 stable 1.0.1 WDT動作をストップ for(;;){ last_time_jjy = millis(); getWeb(); //Web記事およびNTP時刻取得 sendJJY(); //Web記事およびNTP時刻取得 } } //*******NTPサーバー、Yahoo! Japan RSS 記事取得******* void getWeb(){ if( isWifi_connect_first || isChange_news || isJJY_through || (millis() - wifi_connect_last_time > interval_web_get_time) ){ WiFi_AP_Connect(); //WiFi起動、アクセスポイント接続 if( isChange_news == false ){ mM5.watch.getNTPserverSel( timezone, ntp_server_name ); } mM5.yahoo[0].getYahooJweather( yahoo_weather_host, yahoo_weather_target_url ); mM5.yahoo[0].getYahooJnews( yahoo_news_host, sel_news[url0_num].url ); mM5.yahoo[1].getYahooJnews( yahoo_news_host, sel_news[url1_num].url ); delay(1000); WiFi.disconnect( true,true ); //WiFi OFF, eraseAP=true これを使う場合、Core Debug Level "なし" に設定する WiFi.mode( WIFI_OFF ); //これを使う場合、Core Debug Level "なし" に設定する Serial.printf( "Free heap after TLS %u\r\n", xPortGetFreeHeapSize() ); isWifi_connect_first = false; wifi_connect_last_time = millis(); isReset_jjy = true; isJJY_through = false; } } //******** 電波時計用JJY信号発信 ********************** void sendJJY() { if(isReset_jjy == true) { if(second() == 0){ delay(1500); //ゼロ秒の場合は次のゼロ秒までやり過ごすために、1.5秒待つ。 } while(second() != 0){ //ゼロ秒になるまで待つ delay(1); //ウォッチドッグタイマを動作させるために必要 } last_time_jjy = millis(); isReset_jjy = false; } getNowTime(); //現在時刻初期化 sendMarker(); //M マーカー送信 sendMinuteTimeCode(); //タイムコード(分)送信 sendMarker(); //P1 ポジションマーカー送信 sendHourTimeCode(); //タイムコード(時)送信 sendMarker(); //P2 ポジションマーカー送信 sendDayTotalParityTimeCode(); //タイムコード(通算日)送信 sendMarker(); //P4 ポジションマーカー送信 sendYearTimeCode(); //タイムコード(年)送信 sendMarker(); //P5 ポジションマーカー送信 sendWeekdayUruuTimeCode(); //タイムコード(曜日、うるう秒)送信 sendMarker(); //P0 ポジションマーカー送信 if(isJJY_through == true) return; Serial.printf("\r\n[Total = %ld ms]\r\n", millis() - last_time_jjy); while(second() != 0){ //もし、時間がズレた場合、ゼロ秒になるまで時間調整する Serial.print('.'); delay(1); //ウォッチドッグタイマを動作させるために必要 } } //******** マーカーおよびポジションマーカー送信 ************************ void sendMarker(){ if(isJJY_through == true) return; //ボタンスイッチで記事変更決定したらこの関数をスルーする Serial.print(".M."); oscillator(gpio_high, 200); oscillator(gpio_low, 800); // oscillator(gpio_low, 791); // delay(10); //ウォッチドッグタイマを動作させるために必要 } //******** タイムコード(分)送信 ************** void sendMinuteTimeCode(){ if(isJJY_through == true) return; //ボタンスイッチで記事変更決定したらこの関数をスルーする uint8_t min10 = (uint8_t)floor((double)min_now / 10.0); uint8_t min01 = min_now % 10; uint8_t m[7] = {0}; //分のパリティ parity2 計算用配列初期化 //---------10の位------------- m[6] = bitRead( min10, 2 ); Serial.print( m[6] ); oscillatorTimeCodeBit( m[6] ); m[5] = bitRead( min10, 1 ); Serial.print( m[5] ); oscillatorTimeCodeBit( m[5] ); m[4] = bitRead( min10, 0 ); Serial.print( m[4] ); oscillatorTimeCodeBit( m[4] ); Serial.print('0'); oscillatorTimeCodeBit(0); //---------1の位-------------- m[3] = bitRead( min01, 3 ); Serial.print( m[3] ); oscillatorTimeCodeBit( m[3] ); m[2] = bitRead( min01, 2 ); Serial.print( m[2] ); oscillatorTimeCodeBit( m[2] ); m[1] = bitRead( min01, 1 ); Serial.print( m[1] ); oscillatorTimeCodeBit( m[1] ); m[0] = bitRead( min01, 0 ); Serial.print( m[0] ); oscillatorTimeCodeBit( m[0] ); //-------分パリティ計算------- parity2 = (m[6] + m[5] + m[4] + m[3] + m[2] + m[1] + m[0]) % 2; } //******** タイムコード(時)送信 ************* void sendHourTimeCode(){ if(isJJY_through == true) return; //ボタンスイッチで記事変更決定したらこの関数をスルーする uint8_t hour10 = (uint8_t)floor((double)hour_now / 10.0); uint8_t hour01 = hour_now % 10; uint8_t h[6] = {0}; //時のパリティ parity1 計算用配列初期化 Serial.print('0'); oscillatorTimeCodeBit(0); Serial.print('0'); oscillatorTimeCodeBit(0); //---------10の位------------- h[5] = bitRead( hour10, 1 ); Serial.print( h[5] ); oscillatorTimeCodeBit( h[5] ); h[4] = bitRead( hour10, 0 ); Serial.print( h[4] ); oscillatorTimeCodeBit( h[4] ); Serial.print('0'); oscillatorTimeCodeBit(0); //---------1の位------------- h[3] = bitRead( hour01, 3 ); Serial.print( h[3] ); oscillatorTimeCodeBit( h[3] ); h[2] = bitRead( hour01, 2 ); Serial.print( h[2] ); oscillatorTimeCodeBit( h[2] ); h[1] = bitRead( hour01, 1 ); Serial.print( h[1] ); oscillatorTimeCodeBit( h[1] ); h[0] = bitRead( hour01, 0 ); Serial.print( h[0] ); oscillatorTimeCodeBit( h[0] ); //-------分パリティ計算------- parity1 = (h[5] + h[4] + h[3] + h[2] + h[1] + h[0]) % 2; } //******** タイムコード(その年の1月1日からの通算日)送信 *********** void sendDayTotalParityTimeCode(){ if(isJJY_through == true) return; //ボタンスイッチで記事変更決定したらこの関数をスルーする uint8_t t_day100 = (uint8_t)floor((double)day_total / 100.0); uint8_t t_day10 = ((uint8_t)(floor((double)day_total / 10.0))) % 10; uint8_t t_day01 = day_total % 10; uint8_t b; Serial.print('0'); oscillatorTimeCodeBit(0); Serial.print('0'); oscillatorTimeCodeBit(0); //---------100の位------------- b = bitRead( t_day100, 1 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( t_day100, 0 ); Serial.print( b ); oscillatorTimeCodeBit( b ); Serial.print('0'); oscillatorTimeCodeBit(0); //---------10の位------------- b = bitRead( t_day10, 3 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( t_day10, 2 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( t_day10, 1 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( t_day10, 0 ); Serial.print( b ); oscillatorTimeCodeBit( b ); sendMarker(); //P3 ポジションマーカー //---------1の位------------- b = bitRead( t_day01, 3 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( t_day01, 2 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( t_day01, 1 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( t_day01, 0 ); Serial.print( b ); oscillatorTimeCodeBit( b ); Serial.print('0'); oscillatorTimeCodeBit(0); Serial.print('0'); oscillatorTimeCodeBit(0); //---------パリティビット--------- Serial.print(parity1); oscillatorTimeCodeBit(parity1); Serial.print(parity2); oscillatorTimeCodeBit(parity2); //---------予備ビット------------- Serial.print('0'); oscillatorTimeCodeBit(0); //SU1 予備ビット } //**********タイムコード(年)下2桁送信********************** void sendYearTimeCode(){ if(isJJY_through == true) return; //ボタンスイッチで記事変更決定したらこの関数をスルーする uint8_t year10 = (uint8_t)(floor((double)year_now / 10.0)); uint8_t year01 = year_now % 10; uint8_t b; //---------予備ビット--------- Serial.print('0'); oscillatorTimeCodeBit(0); //SU2 予備ビット //---------10の位------------- b = bitRead( year10, 3 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( year10, 2 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( year10, 1 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( year10, 0 ); Serial.print( b ); oscillatorTimeCodeBit( b ); //---------1の位------------- b = bitRead( year01, 3 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( year01, 2 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( year01, 1 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( year01, 0 ); Serial.print( b ); oscillatorTimeCodeBit( b ); } //********** タイムコード(曜日、うるう秒)送信 ************ void sendWeekdayUruuTimeCode(){ uint8_t b; //---------曜日------------- b = bitRead( weekday_now, 2 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( weekday_now, 1 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( weekday_now, 0 ); Serial.print( b ); oscillatorTimeCodeBit( b ); //---------うるう秒--------- Serial.print('0'); oscillatorTimeCodeBit(0); //LS1:うるう秒 Serial.print('0'); oscillatorTimeCodeBit(0); //LS2:うるう秒 //-------------------------- Serial.print('0'); oscillatorTimeCodeBit(0); Serial.print('0'); oscillatorTimeCodeBit(0); Serial.print('0'); oscillatorTimeCodeBit(0); Serial.print('0'); oscillatorTimeCodeBit(0); } //********** タイムコードビット送信 ***************** void oscillatorTimeCodeBit(uint8_t b){ if(isJJY_through == true) return; //ボタンスイッチで記事変更決定したらこの関数をスルーする if( b ){ //2進数の1 oscillator(gpio_high, 500); oscillator(gpio_low, 500); // oscillator(gpio_low, 491); // delay(10); //ウォッチドッグタイマを動作させるために必要 }else{ //2進数の0 oscillator(gpio_high, 800); oscillator(gpio_low, 200); // oscillator(gpio_low, 191); // delay(10); //ウォッチドッグタイマを動作させるために必要 } } //*********** 40kHz High レベル発信****************** void oscillator(const uint8_t pin, uint16_t time_osc){ uint32_t last_time = millis(); for(;;){ long startCount; startCount = getCpuCcount(); digitalWrite(pin, HIGH); while(getCpuCcount() - startCount < 2960) NOP(); startCount = getCpuCcount(); digitalWrite(pin, LOW); while (getCpuCcount() - startCount < 2700) NOP(); if(millis() - last_time == time_osc) return; } } //*********CPUクロックでカウントする***************** static inline unsigned getCpuCcount(void){ unsigned r; asm volatile ("rsr %0, ccount" : "=r"(r)); return r; } //*********** その年の1月1日からの通算日計算 ****** uint16_t calcDayTotal(){ //Arduino Timeライブラリ関数を使う tmElements_t tm = {0, 0, 0, 0, 1, 1, (uint8_t)(year()-1970)}; time_t t = makeTime(tm); return (now() - t) / SECS_PER_DAY + 1; } //*********** 現在時刻代入 ************************ void getNowTime(){ min_now = minute(); hour_now = hour(); year_now = year() - 2000; if(year_now > 100) year_now = 0; //有り得ない数値は0にする weekday_now = weekday() - 1; day_total = calcDayTotal(); Serial.printf("\r\nYear=%d / TotalDay=%d / Weekday=%d / %02d:%02d\r\n", year_now, day_total, weekday_now, hour_now, min_now); } //******時計表示フォント設定**************************** void setupWatchFont(){ uint8_t i; for( i = 0; i < 4; i++ ){ //時、分のフォント色設定 mM5.watch.font[i].red = 31, mM5.watch.font[i].green = 0, mM5.watch.font[i].blue = 31; } for( i = 4; i < 6; i++ ){ //秒のフォント色設定 mM5.watch.font[i].red = 0, mM5.watch.font[i].green = 63, mM5.watch.font[i].blue = 31; } //コロンフォント色設定 red(0-31), green(0-63), blue(0-31) mM5.watch.colon1_font.red = 31, mM5.watch.colon1_font.green = 63, mM5.watch.colon1_font.blue = 0; mM5.watch.colon2_font.red = 0, mM5.watch.colon2_font.green = 0, mM5.watch.colon2_font.blue = 31; uint16_t x0 = 0, y0 = 29; //時計表示左端の座標 uint8_t x_size = 3, y_size = 3; mM5.watch.init( x0, y0, x_size, y_size ); } //******年月日表示フォント設定************************** void setupYmdFont(){ //red(0-31), green(0-63), blue(0-31) mM5.watch.ymd_font.y0 = 79; mM5.watch.ymd_font.Xsize = 2, mM5.watch.ymd_font.Ysize = 2; } //******Yahooニュース天気予報フォント設定**************** void setupWeatherFont(){ mM5.yahoo[0].weather_fnt.Xsize = 2, mM5.yahoo[0].weather_fnt.Ysize = 2; mM5.yahoo[0].weather_fnt.y0 = 111; } //******Yahooニュース記事フォント設定******************** void setupNewsFont(){ mM5.yahoo[0].news_font.y0 = 144; mM5.yahoo[0].news_font.Xsize = 3, mM5.yahoo[0].news_font.Ysize = 3; // red(0-31), green(0-63), blue(0-31) mM5.yahoo[0].news_font.red = 31, mM5.yahoo[0].news_font.green = 63, mM5.yahoo[0].news_font.blue = 0; mM5.yahoo[0].news_font.bg_red = 0, mM5.yahoo[0].news_font.bg_green = 0, mM5.yahoo[0].news_font.bg_blue = 25; mM5.yahoo[1].news_font.y0 = 192; mM5.yahoo[1].news_font.Xsize = 3, mM5.yahoo[1].news_font.Ysize = 3; // red(0-31), green(0-63), blue(0-31) mM5.yahoo[1].news_font.red = 31, mM5.yahoo[1].news_font.green = 63, mM5.yahoo[1].news_font.blue = 0; mM5.yahoo[1].news_font.bg_red = 0, mM5.yahoo[1].news_font.bg_green = 0, mM5.yahoo[1].news_font.bg_blue = 25; //ヒープ領域配列確保。※不要な時は必ずdeleteしておく。 for( int i = 0; i < 2; i++ ){ mM5.yahoo[i].news_scl_set.disp_txt_len = 13; //ディスプレイに表示する半角相当文字数 mM5.yahoo[i].initScrolle( mM5.yahoo[i].news_font, mM5.yahoo[i].news_scl_set ); } Serial.printf("Free Heap Size = %d\r\n", esp_get_free_heap_size()); } //*****メッセージ表示設定************************** void setupMessageWindow(){ mM5.wifi_msg.m_padding = 4; //pixel単位 mM5.wifi_msg.m_txt_length = 6; //文字表示数(半角相当) //背景色のみ設定 red(0-31), green(0-63), blue(0-31) mM5.watch.ntp_msg.m_x0 = 56; mM5.watch.ntp_msg.m_padding = 4; //pixel単位 mM5.watch.ntp_msg.m_txt_length = 5; //文字表示数(半角相当) //背景色のみ設定 red(0-31), green(0-63), blue(0-31) mM5.watch.ntp_msg.m_bg_red = 10; mM5.watch.ntp_msg.m_bg_green = 10; mM5.watch.ntp_msg.m_bg_blue = 15; mM5.yahoo[0].weather_msg.m_x0 = 104; mM5.yahoo[0].weather_msg.m_padding = 4; //pixel単位 mM5.yahoo[0].weather_msg.m_txt_length = 4; //文字表示数(半角相当) //背景色のみ設定 red(0-31), green(0-63), blue(0-31) mM5.yahoo[0].weather_msg.m_bg_red = 10; mM5.yahoo[0].weather_msg.m_bg_green = 10; mM5.yahoo[0].weather_msg.m_bg_blue = 15; mM5.yahoo[0].news_msg.m_x0 = 144; mM5.yahoo[0].news_msg.m_padding = 4; //pixel単位 mM5.yahoo[0].news_msg.m_txt_length = 10; //文字表示数(半角相当) //背景色のみ設定 red(0-31), green(0-63), blue(0-31) mM5.yahoo[0].news_msg.m_bg_red = 10; mM5.yahoo[0].news_msg.m_bg_green = 10; mM5.yahoo[0].news_msg.m_bg_blue = 15; mM5.yahoo[1].news_msg.m_x0 = 232; mM5.yahoo[1].news_msg.m_padding = 4; //pixel単位 mM5.yahoo[1].news_msg.m_txt_length = 10; //文字表示数(半角相当) //背景色のみ設定 red(0-31), green(0-63), blue(0-31) mM5.yahoo[1].news_msg.m_bg_red = 10; mM5.yahoo[1].news_msg.m_bg_green = 10; mM5.yahoo[1].news_msg.m_bg_blue = 15; } //*******WiFiアクセスポイント接続************* void WiFi_AP_Connect(){ mM5.wifi_msg.WifiStatus = mM5.wifi_msg.WifiConnecting; //WiFiメッセージウィンドウ設定 Serial.println(); Serial.println( F("Connecting Wifi...") ); Serial.println( ssid ); int16_t wifi_state = WiFi.status(); Serial.printf( "\r\nWiFi.status = %d\r\n", wifi_state ); if( isWifi_connect_first == true ){ //WiFiが急に接続できなくなった場合の応急処置 WiFi.disconnect( true, true ); //WiFi OFF, eraseAP=true delay(1000); WiFi.begin( ssid, password ); while ( WiFi.status() != WL_CONNECTED ) { delay(500); Serial.print("."); } isWifi_connect_first = false; }else{ uint32_t last_time = millis(); if( wifi_state != WL_CONNECTED ){ WiFi.begin( ssid, password ); //常時 WiFi ON の場合、ここをコメントアウト while ( wifi_state != WL_CONNECTED ) { delay(500); Serial.print("."); if( millis() - last_time > 20000 ) break; //Time OUT } } //マルチタスクでメッセージウィンドウを正しく表示させるための処置 if( millis() - last_time < 1000 ) delay(2000); } wifi_state = WiFi.status(); Serial.printf("\r\nWiFi.status = %d\r\n", wifi_state); if( wifi_state == WL_CONNECTED ){ Serial.println(""); Serial.println( "WiFi connected" ); Serial.println( "IP address: " ); Serial.println( WiFi.localIP() ); wifi_connect_last_time = millis(); } if( wifi_state == WL_CONNECTED ){ mM5.wifi_msg.WifiStatus = mM5.wifi_msg.WifiConnected; //WiFiメッセージウィンドウ設定 }else{ mM5.wifi_msg.WifiStatus = mM5.wifi_msg.WifiFailed; //WiFiメッセージウィンドウ設定 Serial.println( F("WiFi AP Not Found") ); } } //***** セレクトボックス表示設定 *************** void selectUrl(){ mgo_tec_esp32_bv1::BtnDispSelectBox Sel; const uint8_t max_select1 = 2; String sel_news_line[ max_select1 ] = { "News 0", "News 1" }; uint16_t x0 = 96, y0 = 50, x1 = 240; //セレクトボックス位置 uint8_t max_in_page_num1 = 3; //セレクトボックスの行数設定 uint8_t default_sel_num = 0; //デフォルトのセレクト uint8_t news_num = Sel.dispBtnSelStrDef( mM5.btnA, sel_news_line, x0, y0, x1, default_sel_num, max_in_page_num1, max_select1 ); uint8_t max_in_page_num2 = 5; //セレクトボックスの行数設定 if( news_num == 0 ){ url0_num = Sel.dispBtnSelUrlDef( mM5.btnA, sel_news, x0, y0, x1, url0_num, max_in_page_num2, max_select2 ); isChange_news = true; isJJY_through = true; }else if( news_num == 1 ){ url1_num = Sel.dispBtnSelUrlDef( mM5.btnA, sel_news, x0, y0, x1, url1_num, max_in_page_num2, max_select2 ); isChange_news = true; isJJY_through = true; } } //**************************************** void button_action(){ mM5.btnA.buttonAction(); switch( mM5.btnA.ButtonStatus ){ case mM5.btnA.MomentPress: Serial.println("\r\nButton A Moment Press"); selectUrl(); break; case mM5.btnA.ContPress: Serial.println("\r\n-------------Button A Cont Press"); break; default: break; } mM5.btnB.buttonAction(); switch( mM5.btnB.ButtonStatus ){ case mM5.btnB.MomentPress: Serial.println("\r\nButton B Moment Press"); mM5.yahoo[0].news_scl_set.interval++; if( mM5.yahoo[0].news_scl_set.interval> 200 ) mM5.yahoo[0].news_scl_set.interval = 200; break; case mM5.btnB.ContPress: Serial.println("\r\n-------------Button B Cont Press"); mM5.yahoo[0].news_scl_set.interval--; if( mM5.yahoo[0].news_scl_set.interval < 0 ) mM5.yahoo[0].news_scl_set.interval = 0; break; default: break; } mM5.btnC.buttonAction(); switch( mM5.btnC.ButtonStatus ){ case mM5.btnC.MomentPress: Serial.println("\r\nButton C Moment Press"); mM5.yahoo[1].news_scl_set.interval++; if( mM5.yahoo[1].news_scl_set.interval> 200 ) mM5.yahoo[1].news_scl_set.interval = 200; break; case mM5.btnC.ContPress: Serial.println("\r\n-------------Button C Cont Press"); mM5.yahoo[1].news_scl_set.interval--; if( mM5.yahoo[1].news_scl_set.interval < 0 ) mM5.yahoo[1].news_scl_set.interval = 0; break; default: break; } }
この記事は古い記事で、上記のソースコードも正常に動作しなくなっています。
Yahoo RSSサイトの変更および、Arduino core for the ESP32 stable ver 1.0.5以降は、ルートCA証明書公開鍵をセットしていないと受信できなくなりました。
よって、まず、以下の記事を参照していただき、Yahoo RSSサイトのルートCA証明書公開鍵をダウンロードしてください。
Arduino – ESP32 WiFiClientSecure ライブラリで、安定して https ( SSL )記事をGETする方法
その公開鍵を以下のようにダブルクォーテーション "
や\n" \
で括って編集して、const char* yahoo_root_ca
という文字列変数を定義しておきます。
const char* yahoo_root_ca= \ "-----BEGIN CERTIFICATE-----\n" \ "MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY\n" \ "MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t\n" \ "dW5pY2F0aW..................(中略)........................QyMDQ5\n" \ "WjBQMQswCQ..................(中略)........................cwJQYD\n" \ "VQQLEx5TZW..................(中略)........................qGSIb3\n" \ "DQEBAQUAA4..................(中略)........................rw8yl8\n" \ "9f/uKuDp6b..................(中略)........................AZJ6kJ\n" \ "DKaVv0uMDP..................(中略)........................7AwFb9\n" \ "Ms+k2Y7CI9..................(中略)........................hWZq/N\n" \ "QV3Is00qVU..................(中略)........................lLqCHJ\n" \ "xrHty8OVYN..................(中略)........................A9MB0G\n" \ "A1UdDgQWBB..................(中略)........................YDVR0T\n" \ "AQH/BAUwAw..................(中略)........................NgE+vG\n" \ "kl3g0dNq/v..................(中略)........................POCEfr\n" \ "Uj94nK9Nrv..................(中略)........................QVy+n5\n" \ "Bw+SUEmK3T..................(中略)........................NnPaJU\n" \ "JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot\n" \ "RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw==\n" \ "-----END CERTIFICATE-----\n";
これをArduino-ESP32スケッチソースコードの13行目あたりに貼り付けて、グローバル変数としておきます。
(スタック領域変数でも良いですし、SDカードやSPIFFSから読み込むようにしても良いと思います。)
その後、130-132行目を以下のように書き換えます。
※Arduino core for the ESP32 ver stable 1.0.5
※ESP32_mgo_tec beta ver 1.0.72
mM5.yahoo[0].getYahooJweatherRCA( yahoo_root_ca, 1, yahoo_weather_host, yahoo_weather_target_url ); mM5.yahoo[0].getYahooJnewsRCA( yahoo_root_ca, 1, yahoo_news_host, sel_news[url0_num].url ); mM5.yahoo[1].getYahooJnewsRCA( yahoo_root_ca, 1, yahoo_news_host, sel_news[url1_num].url );
これでコンパイルすれば、記事を取得できると思われます。
(2021/3/7時点)
【ちょっとだけ解説】
5-6行目で、ご自分のルーターの SSID とパスワードに書き換えてください。
30行目で、Yahoo! Japan RSS の天気予報ページで、ご自分の地域の URL に書き換えてください。
基本的に、以前のこちらの記事と前回の記事のプログラムを合わせただけですが、マルチタスクの動かし方が難しいです。
電波時計用 JJY 信号発信は、20分間は中断なく発信した方が良いです。
何故かと言うと、私の手持ちの SEIKO 電波時計は、完全に受信し切るまで15分かかる場合がありました。
ですから、それ以上の20分 ( 1200000 ミリセコンド )という設定にしました。
それは、38行で設定しています。
一方、Yahooニュースや天気予報はできるだけ頻繁に更新したいのですが、電波時計を優先すると20分間は記事を取得しないことにしました。
そして、今回のポイントは、113行目の
disableCore0WDT();
という関数です。
最初の方で述べたように、Twitter 上で Kenta IDA さんから教えて頂いた情報を元に使ってみました。
Arduino – ESP32 マルチタスクの CPU core 0 で、ウォッチドッグタイマ ( WDT )を停止させる関数です。
これは、Arduino – ESP32 の stable 1.0.1 から新しく出来た関数です。
これに対して、メインloop内の core 1 では、恐らく以下の関数
disableLoopWDT();
が使えると思います。
これらは、以下のソースファイル
esp32-hal-misc.c
に定義されています。
この関数を使わないと、114-118行の core 0 の無限ループで、20分間はdelayが一切無いので、プログラム動作開始してから7秒後くらいで WDT エラーを吐き出し、強制リセットされてしまいます。
ウォッチドッグタイマは、マイコン側がCPUの動作状況やメモリ動作状況、その他 WiFi や Bluetooth等の動作状況を監視するものなので、本来は停止させない方が良いものだと思います。
Arduino – ESP32 の場合は、所々適度にdelayを置くことによって、ウォッチドッグタイマが動作するようになっています。
ですが、今回のように、電波時計用 JJY 信号発信を中断なく発信させたい場合は、delayをできるだけ置きたくなかったのです。
前回の記事では、ところどころ delay(1) を置いておけば、WDT エラーでリセットすることは無かったのですが、今回のようにガッツリとマルチタスクをフル稼働させる場合は、delay(1) では数十分後くらいに WDT エラーでリセットしてしまいました。
GitHub の Issue でも上がっていましたが、数秒毎に最低 delay(10) くらいは置いた方が良いという報告もありました。
しかし、それはかなりの長時間なので、JJY信号に影響が出てしまいます。
そんなこんなで、今回、disableCore0WDT() という関数を初めて使ってみましたが、特に弊害が無いように見受けられます。
もし、何か影響が分かったら誰か教えてくださると助かります。
@tnkmasayuki さんから教えて頂き気付きました。
失礼いたしました。
m(_ _)m
loop()関数内ではdelay(1)を置かなくてもエラーが出ませんが、ウォッチドッグタイマを無効にすると、無線機能に悪影響を与えると思われます。
以下の記事でその理由を紹介しています。
M5StackとM5CameraでWiFi, UDPによる動画転送を長時間連続安定動作させる実験
(2019/12/18)
もし、影響が出た場合は、182行、398行、402行をコメントアウトして、183-184行、398-399行、403-404行のコメントを解除してください。
これならば、ウォッチドッグタイマも動作して、WDTエラーは出ません。
CPU core 0 は無線関連の監視に使われている可能性があるので、他のプログラムではもしかしたら影響があるかもしれませんね。
LEDC PWM で安定したクロックを使ったスケッチの入力 (2019/04/04追加)
実は、Arduino – ESP32 の LEDC PWM ライブラリ関数を使えば、バッチリ安定した 40kHz のクロックを出力することができ、全ての電波時計が最短時間でガッツリ合うようになりました。
これは、以下の記事で LEDC ライブラリを使って、自分の思い通りのクロックを出力できるようになったことによります。
Arduino – ESP32 の PWM ( LEDC )で 40MHzまでの安定した高周波パルスを思い通りに出せたぞ
これを使えば、いわゆるジッタが減り、安定します。
先のスケッチより格段に優れています。
※Yahoo RSS 天気 は2022/03/31 で配信終了してしまいました。残念、、、。
このコードでは天気予報は表示されません。
よって、気象庁ホームページから天気予報を取得する方法に更新しました。
詳しくは以下の記事を参照してください。
【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)
//use Arduino core for the ESP32 stable 1.0.1 #define MGO_TEC_BV1_M5STACK_SD_SKETCH #include <mgo_tec_bv1_m5stack_sd_yahoo.h> const char* ssid = "xxxxxxxx"; //ご自分のルーターのSSIDに書き換えてください const char* password = "xxxxxxxx"; //ご自分のルーターのパスワードに書き換えてください const char* utf8sjis_file = "/font/Utf8Sjis.tbl"; //UTF8 Shift_JIS 変換テーブルファイル名を記載しておく //const char* shino_full_font_file = "/font/shnmk16.bdf"; //オリジナル東雲全角フォントファイル const char* shino_half_font_file = "/font/shnm8x16.bdf"; //半角フォントファイル名を定義 const char* shino_full_font_file = "/font/MYshnmk16.bdf"; //自作改変全角東雲フォントファイル const char* my_font_file = "/font/MyFont.fnt"; //自作フォントファイル名を定義(天気予報用) //-----Yahooニュース切替select box関連初期化----- const uint8_t max_select2 = 9; mgo_tec_esp32_bv1::SelectUrl sel_news[ max_select2 ] = { { "主要", "/rss/topics/top-picks.xml" }, { "スポーツ", "/rss/topics/sports.xml" }, { "エンタメ", "/rss/topics/entertainment.xml" }, { "IT", "/rss/topics/it.xml" }, { "経済", "/rss/topics/business.xml" }, { "科学", "/rss/topics/science.xml" }, { "国際", "/rss/topics/world.xml" }, { "地域", "/rss/topics/local.xml" }, { "国内", "/rss/topics/domestic.xml" } }; //------Yahooニュース表示関連初期化---------------- const char* yahoo_news_host = "news.yahoo.co.jp"; const char* yahoo_weather_host = "rss-weather.yahoo.co.jp"; const char* yahoo_weather_target_url = "/rss/days/4410.xml"; //地域:東京 uint8_t url0_num = 0; uint8_t url1_num = 1; boolean isChange_news = false; //------ NTP時刻文字表示系 引数初期化----- int timezone = 9; //Tokyo const char *ntp_server_name = "time.windows.com"; //-----Webデータ取得変数------------- const uint32_t interval_web_get_time = 1200000; //20分間隔でNTP時刻およびYahoo記事取得 uint32_t wifi_connect_last_time = 0; boolean isWifi_connect_first = true; boolean isClear_disp = false; //-----電波時計合わせ用定義---------- const uint8_t gpio_high = 17; const uint8_t gpio_low = 16; uint8_t min_now, hour_now, year_now, weekday_now; uint16_t day_total; uint8_t parity1, parity2; //パリティ定義 uint32_t last_time_jjy = 0; boolean isReset_jjy = true; //NTP時刻取得したり、Web記事取得した場合 true; boolean isJJY_through = false; //-----LEDC PWM用定義---------- const uint8_t ledc_ch_low = 6; //channel max 7 const uint8_t ledc_ch_high = 7; //channel max 7 const uint8_t ledc_timer_bit = 1; //max 16bit const uint8_t ledc_duty = 1; double ledc_base_freq = 40000.0; //***********セットアップ**************************** void setup() { delay(1000); Serial.begin(115200); mM5.init( utf8sjis_file, shino_half_font_file, shino_full_font_file ); //天気予報用自作フォント初期化 mM5.yahoo[0].initWeather( my_font_file ); LCD.brightness( 50 ); //LCD LED Full brightness setupWatchFont(); //時計表示フォントセットアップ setupYmdFont(); //年月日表示フォントセットアップ setupWeatherFont(); //天気予報フォントセットアップ setupNewsFont(); //ニュース記事文字列のセットアップ setupMessageWindow(); //メッセージウィンドウ表示セットアップ //起動時のみのメッセージウィンドウ設定 mM5.msg.m_padding = 4; mM5.msg.dispMsgWindow( 0, "WiFi Connecting..." ); getWeb(); mM5.watch.watchDispReset(); //時計表示が崩れる為に一旦リセット //---------------------------------- setTime( now() + 1 ); //標準時とキッチリ合わせるため、1秒早める。 TaskHandle_t th; //マルチタスクハンドル定義 xTaskCreatePinnedToCore(Task1, "Task1", 8192, NULL, 5, &th, 0); //マルチタスク起動 } //***********メインループ**************************** void loop(){ //Statusメッセージ表示 mM5.wifi_msg.dispWifiStatusMsgShort(); //WiFiステータス表示 mM5.watch.ntp_msg.dispWebGetStatusMsgShort( mM5.watch.ntp_msg_status, "NTP" ); mM5.yahoo[0].weather_msg.dispWebGetStatusMsgShort( mM5.yahoo[0].weather_msg_status, "天" ); mM5.yahoo[0].news_msg.dispWebGetStatusMsgShort( mM5.yahoo[0].news_msg_status, sel_news[url0_num].name ); mM5.yahoo[1].news_msg.dispWebGetStatusMsgShort( mM5.yahoo[1].news_msg_status, sel_news[url1_num].name ); if( isChange_news == true ){ LCD.displayClear( 0, 24, 319, 239 ); mM5.watch.watchDispReset(); mM5.yahoo[0].m_isWeather_get = true; mM5.watch.m_changeYMD = true; isChange_news = false; } mM5.watch.scrolleWatch(); //時計スクロール mM5.watch.displayColon2(); //時計コロン表示 mM5.watch.displayYMDW(); //年月日、曜日表示 //Yahoo! Japan RSS 天気予報表示 mM5.yahoo[0].dispYahooJweatherMyFont( mM5.yahoo[0].weather_fnt ); //Yahoo! Japan RSS ニューススクロール mM5.yahoo[0].scrolleYahooJnews2( mM5.yahoo[0].news_font, mM5.yahoo[0].news_scl_set ); mM5.yahoo[1].scrolleYahooJnews2( mM5.yahoo[1].news_font, mM5.yahoo[1].news_scl_set ); button_action(); //ボタン操作 } //************ マルチタスクループ ****************** void Task1( void *pvParameters ) { ledcSetup(ledc_ch_high, ledc_base_freq, ledc_timer_bit); ledcSetup(ledc_ch_low, ledc_base_freq, ledc_timer_bit); ledcAttachPin(gpio_high, ledc_ch_high); ledcAttachPin(gpio_low, ledc_ch_low); disableCore0WDT(); //Arduino-ESP32 stable 1.0.1 WDT動作をストップ for(;;){ last_time_jjy = millis(); getWeb(); //Web記事およびNTP時刻取得 sendJJY(); //Web記事およびNTP時刻取得 } } //*******NTPサーバー、Yahoo! Japan RSS 記事取得******* void getWeb(){ if( isWifi_connect_first || isChange_news || isJJY_through || (millis() - wifi_connect_last_time > interval_web_get_time) ){ WiFi_AP_Connect(); //WiFi起動、アクセスポイント接続 if( isChange_news == false ){ mM5.watch.getNTPserverSel( timezone, ntp_server_name ); } mM5.yahoo[0].getYahooJweather( yahoo_weather_host, yahoo_weather_target_url ); mM5.yahoo[0].getYahooJnews( yahoo_news_host, sel_news[url0_num].url ); mM5.yahoo[1].getYahooJnews( yahoo_news_host, sel_news[url1_num].url ); delay(1000); WiFi.disconnect( true,true ); //WiFi OFF, eraseAP=true これを使う場合、Core Debug Level "なし" に設定する WiFi.mode( WIFI_OFF ); //これを使う場合、Core Debug Level "なし" に設定する Serial.printf( "Free heap after TLS %u\r\n", xPortGetFreeHeapSize() ); isWifi_connect_first = false; wifi_connect_last_time = millis(); isReset_jjy = true; isJJY_through = false; } } //******** 電波時計用JJY信号発信 ********************** void sendJJY() { if(isReset_jjy == true) { if(second() == 0){ delay(1500); //ゼロ秒の場合は次のゼロ秒までやり過ごすために、1.5秒待つ。 } while(second() != 0){ //ゼロ秒になるまで待つ delay(1); //ウォッチドッグタイマを動作させるために必要 } last_time_jjy = millis(); isReset_jjy = false; } getNowTime(); //現在時刻初期化 sendMarker(); //M マーカー送信 sendMinuteTimeCode(); //タイムコード(分)送信 sendMarker(); //P1 ポジションマーカー送信 sendHourTimeCode(); //タイムコード(時)送信 sendMarker(); //P2 ポジションマーカー送信 sendDayTotalParityTimeCode(); //タイムコード(通算日)送信 sendMarker(); //P4 ポジションマーカー送信 sendYearTimeCode(); //タイムコード(年)送信 sendMarker(); //P5 ポジションマーカー送信 sendWeekdayUruuTimeCode(); //タイムコード(曜日、うるう秒)送信 sendMarker(); //P0 ポジションマーカー送信 if(isJJY_through == true) return; Serial.printf("\r\n[Total = %ld ms]\r\n", millis() - last_time_jjy); while(second() != 0){ //もし、時間がズレた場合、ゼロ秒になるまで時間調整する Serial.print('.'); delay(1); //ウォッチドッグタイマを動作させるために必要 } } //******** マーカーおよびポジションマーカー送信 ************************ void sendMarker(){ if(isJJY_through == true) return; //ボタンスイッチで記事変更決定したらこの関数をスルーする Serial.print(".M."); oscillator(ledc_ch_high, 200); oscillator(ledc_ch_low, 800); } //******** タイムコード(分)送信 ************** void sendMinuteTimeCode(){ if(isJJY_through == true) return; //ボタンスイッチで記事変更決定したらこの関数をスルーする uint8_t min10 = (uint8_t)floor((double)min_now / 10.0); uint8_t min01 = min_now % 10; uint8_t m[7] = {0}; //分のパリティ parity2 計算用配列初期化 //---------10の位------------- m[6] = bitRead( min10, 2 ); Serial.print( m[6] ); oscillatorTimeCodeBit( m[6] ); m[5] = bitRead( min10, 1 ); Serial.print( m[5] ); oscillatorTimeCodeBit( m[5] ); m[4] = bitRead( min10, 0 ); Serial.print( m[4] ); oscillatorTimeCodeBit( m[4] ); Serial.print('0'); oscillatorTimeCodeBit(0); //---------1の位-------------- m[3] = bitRead( min01, 3 ); Serial.print( m[3] ); oscillatorTimeCodeBit( m[3] ); m[2] = bitRead( min01, 2 ); Serial.print( m[2] ); oscillatorTimeCodeBit( m[2] ); m[1] = bitRead( min01, 1 ); Serial.print( m[1] ); oscillatorTimeCodeBit( m[1] ); m[0] = bitRead( min01, 0 ); Serial.print( m[0] ); oscillatorTimeCodeBit( m[0] ); //-------分パリティ計算------- parity2 = (m[6] + m[5] + m[4] + m[3] + m[2] + m[1] + m[0]) % 2; } //******** タイムコード(時)送信 ************* void sendHourTimeCode(){ if(isJJY_through == true) return; //ボタンスイッチで記事変更決定したらこの関数をスルーする uint8_t hour10 = (uint8_t)floor((double)hour_now / 10.0); uint8_t hour01 = hour_now % 10; uint8_t h[6] = {0}; //時のパリティ parity1 計算用配列初期化 Serial.print('0'); oscillatorTimeCodeBit(0); Serial.print('0'); oscillatorTimeCodeBit(0); //---------10の位------------- h[5] = bitRead( hour10, 1 ); Serial.print( h[5] ); oscillatorTimeCodeBit( h[5] ); h[4] = bitRead( hour10, 0 ); Serial.print( h[4] ); oscillatorTimeCodeBit( h[4] ); Serial.print('0'); oscillatorTimeCodeBit(0); //---------1の位------------- h[3] = bitRead( hour01, 3 ); Serial.print( h[3] ); oscillatorTimeCodeBit( h[3] ); h[2] = bitRead( hour01, 2 ); Serial.print( h[2] ); oscillatorTimeCodeBit( h[2] ); h[1] = bitRead( hour01, 1 ); Serial.print( h[1] ); oscillatorTimeCodeBit( h[1] ); h[0] = bitRead( hour01, 0 ); Serial.print( h[0] ); oscillatorTimeCodeBit( h[0] ); //-------分パリティ計算------- parity1 = (h[5] + h[4] + h[3] + h[2] + h[1] + h[0]) % 2; } //******** タイムコード(その年の1月1日からの通算日)送信 *********** void sendDayTotalParityTimeCode(){ if(isJJY_through == true) return; //ボタンスイッチで記事変更決定したらこの関数をスルーする uint8_t t_day100 = (uint8_t)floor((double)day_total / 100.0); uint8_t t_day10 = ((uint8_t)(floor((double)day_total / 10.0))) % 10; uint8_t t_day01 = day_total % 10; uint8_t b; Serial.print('0'); oscillatorTimeCodeBit(0); Serial.print('0'); oscillatorTimeCodeBit(0); //---------100の位------------- b = bitRead( t_day100, 1 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( t_day100, 0 ); Serial.print( b ); oscillatorTimeCodeBit( b ); Serial.print('0'); oscillatorTimeCodeBit(0); //---------10の位------------- b = bitRead( t_day10, 3 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( t_day10, 2 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( t_day10, 1 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( t_day10, 0 ); Serial.print( b ); oscillatorTimeCodeBit( b ); sendMarker(); //P3 ポジションマーカー //---------1の位------------- b = bitRead( t_day01, 3 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( t_day01, 2 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( t_day01, 1 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( t_day01, 0 ); Serial.print( b ); oscillatorTimeCodeBit( b ); Serial.print('0'); oscillatorTimeCodeBit(0); Serial.print('0'); oscillatorTimeCodeBit(0); //---------パリティビット--------- Serial.print(parity1); oscillatorTimeCodeBit(parity1); Serial.print(parity2); oscillatorTimeCodeBit(parity2); //---------予備ビット------------- Serial.print('0'); oscillatorTimeCodeBit(0); //SU1 予備ビット } //**********タイムコード(年)下2桁送信********************** void sendYearTimeCode(){ if(isJJY_through == true) return; //ボタンスイッチで記事変更決定したらこの関数をスルーする uint8_t year10 = (uint8_t)(floor((double)year_now / 10.0)); uint8_t year01 = year_now % 10; uint8_t b; //---------予備ビット--------- Serial.print('0'); oscillatorTimeCodeBit(0); //SU2 予備ビット //---------10の位------------- b = bitRead( year10, 3 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( year10, 2 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( year10, 1 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( year10, 0 ); Serial.print( b ); oscillatorTimeCodeBit( b ); //---------1の位------------- b = bitRead( year01, 3 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( year01, 2 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( year01, 1 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( year01, 0 ); Serial.print( b ); oscillatorTimeCodeBit( b ); } //********** タイムコード(曜日、うるう秒)送信 ************ void sendWeekdayUruuTimeCode(){ uint8_t b; //---------曜日------------- b = bitRead( weekday_now, 2 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( weekday_now, 1 ); Serial.print( b ); oscillatorTimeCodeBit( b ); b = bitRead( weekday_now, 0 ); Serial.print( b ); oscillatorTimeCodeBit( b ); //---------うるう秒--------- Serial.print('0'); oscillatorTimeCodeBit(0); //LS1:うるう秒 Serial.print('0'); oscillatorTimeCodeBit(0); //LS2:うるう秒 //-------------------------- Serial.print('0'); oscillatorTimeCodeBit(0); Serial.print('0'); oscillatorTimeCodeBit(0); Serial.print('0'); oscillatorTimeCodeBit(0); Serial.print('0'); oscillatorTimeCodeBit(0); } //********** タイムコードビット送信 ***************** void oscillatorTimeCodeBit(uint8_t b){ if(isJJY_through == true) return; //ボタンスイッチで記事変更決定したらこの関数をスルーする if( b ){ //2進数の1 oscillator(ledc_ch_high, 500); oscillator(ledc_ch_low, 500); }else{ //2進数の0 oscillator(ledc_ch_high, 800); oscillator(ledc_ch_low, 200); } } //*********** 40kHz High レベル発信****************** void oscillator(uint8_t ledc_ch, uint16_t time_osc){ uint32_t last_time = millis(); ledcWrite(ledc_ch, ledc_duty); //しきい値 1 duty: 50% for(;;){ if(millis() - last_time >= time_osc) { ledcWrite(ledc_ch, 0); return; } } } //*********** その年の1月1日からの通算日計算 ****** uint16_t calcDayTotal(){ //Arduino Timeライブラリ関数を使う tmElements_t tm = {0, 0, 0, 0, 1, 1, (uint8_t)(year()-1970)}; time_t t = makeTime(tm); return (now() - t) / SECS_PER_DAY + 1; } //*********** 現在時刻代入 ************************ void getNowTime(){ min_now = minute(); hour_now = hour(); year_now = year() - 2000; if(year_now > 100) year_now = 0; //有り得ない数値は0にする weekday_now = weekday() - 1; day_total = calcDayTotal(); Serial.printf("\r\nYear=%d / TotalDay=%d / Weekday=%d / %02d:%02d\r\n", year_now, day_total, weekday_now, hour_now, min_now); } //******時計表示フォント設定**************************** void setupWatchFont(){ uint8_t i; for( i = 0; i < 4; i++ ){ //時、分のフォント色設定 mM5.watch.font[i].red = 31, mM5.watch.font[i].green = 0, mM5.watch.font[i].blue = 31; } for( i = 4; i < 6; i++ ){ //秒のフォント色設定 mM5.watch.font[i].red = 0, mM5.watch.font[i].green = 63, mM5.watch.font[i].blue = 31; } //コロンフォント色設定 red(0-31), green(0-63), blue(0-31) mM5.watch.colon1_font.red = 31, mM5.watch.colon1_font.green = 63, mM5.watch.colon1_font.blue = 0; mM5.watch.colon2_font.red = 0, mM5.watch.colon2_font.green = 0, mM5.watch.colon2_font.blue = 31; uint16_t x0 = 0, y0 = 29; //時計表示左端の座標 uint8_t x_size = 3, y_size = 3; mM5.watch.init( x0, y0, x_size, y_size ); } //******年月日表示フォント設定************************** void setupYmdFont(){ //red(0-31), green(0-63), blue(0-31) mM5.watch.ymd_font.y0 = 79; mM5.watch.ymd_font.Xsize = 2, mM5.watch.ymd_font.Ysize = 2; } //******Yahooニュース天気予報フォント設定**************** void setupWeatherFont(){ mM5.yahoo[0].weather_fnt.Xsize = 2, mM5.yahoo[0].weather_fnt.Ysize = 2; mM5.yahoo[0].weather_fnt.y0 = 111; } //******Yahooニュース記事フォント設定******************** void setupNewsFont(){ mM5.yahoo[0].news_font.y0 = 144; mM5.yahoo[0].news_font.Xsize = 3, mM5.yahoo[0].news_font.Ysize = 3; // red(0-31), green(0-63), blue(0-31) mM5.yahoo[0].news_font.red = 31, mM5.yahoo[0].news_font.green = 63, mM5.yahoo[0].news_font.blue = 0; mM5.yahoo[0].news_font.bg_red = 0, mM5.yahoo[0].news_font.bg_green = 0, mM5.yahoo[0].news_font.bg_blue = 25; mM5.yahoo[1].news_font.y0 = 192; mM5.yahoo[1].news_font.Xsize = 3, mM5.yahoo[1].news_font.Ysize = 3; // red(0-31), green(0-63), blue(0-31) mM5.yahoo[1].news_font.red = 31, mM5.yahoo[1].news_font.green = 63, mM5.yahoo[1].news_font.blue = 0; mM5.yahoo[1].news_font.bg_red = 0, mM5.yahoo[1].news_font.bg_green = 0, mM5.yahoo[1].news_font.bg_blue = 25; //ヒープ領域配列確保。※不要な時は必ずdeleteしておく。 for( int i = 0; i < 2; i++ ){ mM5.yahoo[i].news_scl_set.disp_txt_len = 13; //ディスプレイに表示する半角相当文字数 mM5.yahoo[i].initScrolle( mM5.yahoo[i].news_font, mM5.yahoo[i].news_scl_set ); } Serial.printf("Free Heap Size = %d\r\n", esp_get_free_heap_size()); } //*****メッセージ表示設定************************** void setupMessageWindow(){ mM5.wifi_msg.m_padding = 4; //pixel単位 mM5.wifi_msg.m_txt_length = 6; //文字表示数(半角相当) //背景色のみ設定 red(0-31), green(0-63), blue(0-31) mM5.watch.ntp_msg.m_x0 = 56; mM5.watch.ntp_msg.m_padding = 4; //pixel単位 mM5.watch.ntp_msg.m_txt_length = 5; //文字表示数(半角相当) //背景色のみ設定 red(0-31), green(0-63), blue(0-31) mM5.watch.ntp_msg.m_bg_red = 10; mM5.watch.ntp_msg.m_bg_green = 10; mM5.watch.ntp_msg.m_bg_blue = 15; mM5.yahoo[0].weather_msg.m_x0 = 104; mM5.yahoo[0].weather_msg.m_padding = 4; //pixel単位 mM5.yahoo[0].weather_msg.m_txt_length = 4; //文字表示数(半角相当) //背景色のみ設定 red(0-31), green(0-63), blue(0-31) mM5.yahoo[0].weather_msg.m_bg_red = 10; mM5.yahoo[0].weather_msg.m_bg_green = 10; mM5.yahoo[0].weather_msg.m_bg_blue = 15; mM5.yahoo[0].news_msg.m_x0 = 144; mM5.yahoo[0].news_msg.m_padding = 4; //pixel単位 mM5.yahoo[0].news_msg.m_txt_length = 10; //文字表示数(半角相当) //背景色のみ設定 red(0-31), green(0-63), blue(0-31) mM5.yahoo[0].news_msg.m_bg_red = 10; mM5.yahoo[0].news_msg.m_bg_green = 10; mM5.yahoo[0].news_msg.m_bg_blue = 15; mM5.yahoo[1].news_msg.m_x0 = 232; mM5.yahoo[1].news_msg.m_padding = 4; //pixel単位 mM5.yahoo[1].news_msg.m_txt_length = 10; //文字表示数(半角相当) //背景色のみ設定 red(0-31), green(0-63), blue(0-31) mM5.yahoo[1].news_msg.m_bg_red = 10; mM5.yahoo[1].news_msg.m_bg_green = 10; mM5.yahoo[1].news_msg.m_bg_blue = 15; } //*******WiFiアクセスポイント接続************* void WiFi_AP_Connect(){ mM5.wifi_msg.WifiStatus = mM5.wifi_msg.WifiConnecting; //WiFiメッセージウィンドウ設定 Serial.println(); Serial.println( F("Connecting Wifi...") ); Serial.println( ssid ); int16_t wifi_state = WiFi.status(); Serial.printf( "\r\nWiFi.status = %d\r\n", wifi_state ); if( isWifi_connect_first == true ){ //WiFiが急に接続できなくなった場合の応急処置 WiFi.disconnect( true, true ); //WiFi OFF, eraseAP=true delay(1000); WiFi.begin( ssid, password ); while ( WiFi.status() != WL_CONNECTED ) { delay(500); Serial.print("."); } isWifi_connect_first = false; }else{ uint32_t last_time = millis(); if( wifi_state != WL_CONNECTED ){ WiFi.begin( ssid, password ); //常時 WiFi ON の場合、ここをコメントアウト while ( wifi_state != WL_CONNECTED ) { delay(500); Serial.print("."); if( millis() - last_time > 20000 ) break; //Time OUT } } //マルチタスクでメッセージウィンドウを正しく表示させるための処置 if( millis() - last_time < 1000 ) delay(2000); } wifi_state = WiFi.status(); Serial.printf("\r\nWiFi.status = %d\r\n", wifi_state); if( wifi_state == WL_CONNECTED ){ Serial.println(""); Serial.println( "WiFi connected" ); Serial.println( "IP address: " ); Serial.println( WiFi.localIP() ); wifi_connect_last_time = millis(); } if( wifi_state == WL_CONNECTED ){ mM5.wifi_msg.WifiStatus = mM5.wifi_msg.WifiConnected; //WiFiメッセージウィンドウ設定 }else{ mM5.wifi_msg.WifiStatus = mM5.wifi_msg.WifiFailed; //WiFiメッセージウィンドウ設定 Serial.println( F("WiFi AP Not Found") ); } } //***** セレクトボックス表示設定 *************** void selectUrl(){ mgo_tec_esp32_bv1::BtnDispSelectBox Sel; const uint8_t max_select1 = 2; String sel_news_line[ max_select1 ] = { "News 0", "News 1" }; uint16_t x0 = 96, y0 = 50, x1 = 240; //セレクトボックス位置 uint8_t max_in_page_num1 = 3; //セレクトボックスの行数設定 uint8_t default_sel_num = 0; //デフォルトのセレクト uint8_t news_num = Sel.dispBtnSelStrDef( mM5.btnA, sel_news_line, x0, y0, x1, default_sel_num, max_in_page_num1, max_select1 ); uint8_t max_in_page_num2 = 5; //セレクトボックスの行数設定 if( news_num == 0 ){ url0_num = Sel.dispBtnSelUrlDef( mM5.btnA, sel_news, x0, y0, x1, url0_num, max_in_page_num2, max_select2 ); isChange_news = true; isJJY_through = true; }else if( news_num == 1 ){ url1_num = Sel.dispBtnSelUrlDef( mM5.btnA, sel_news, x0, y0, x1, url1_num, max_in_page_num2, max_select2 ); isChange_news = true; isJJY_through = true; } } //**************************************** void button_action(){ mM5.btnA.buttonAction(); switch( mM5.btnA.ButtonStatus ){ case mM5.btnA.MomentPress: Serial.println("\r\nButton A Moment Press"); selectUrl(); break; case mM5.btnA.ContPress: Serial.println("\r\n-------------Button A Cont Press"); break; default: break; } mM5.btnB.buttonAction(); switch( mM5.btnB.ButtonStatus ){ case mM5.btnB.MomentPress: Serial.println("\r\nButton B Moment Press"); mM5.yahoo[0].news_scl_set.interval++; if( mM5.yahoo[0].news_scl_set.interval> 200 ) mM5.yahoo[0].news_scl_set.interval = 200; break; case mM5.btnB.ContPress: Serial.println("\r\n-------------Button B Cont Press"); mM5.yahoo[0].news_scl_set.interval--; if( mM5.yahoo[0].news_scl_set.interval < 0 ) mM5.yahoo[0].news_scl_set.interval = 0; break; default: break; } mM5.btnC.buttonAction(); switch( mM5.btnC.ButtonStatus ){ case mM5.btnC.MomentPress: Serial.println("\r\nButton C Moment Press"); mM5.yahoo[1].news_scl_set.interval++; if( mM5.yahoo[1].news_scl_set.interval> 200 ) mM5.yahoo[1].news_scl_set.interval = 200; break; case mM5.btnC.ContPress: Serial.println("\r\n-------------Button C Cont Press"); mM5.yahoo[1].news_scl_set.interval--; if( mM5.yahoo[1].news_scl_set.interval < 0 ) mM5.yahoo[1].news_scl_set.interval = 0; break; default: break; } }
この記事は古い記事で、上記のソースコードも正常に動作しなくなっています。
Yahoo RSSサイトの変更および、Arduino core for the ESP32 stable ver 1.0.5以降は、ルートCA証明書公開鍵をセットしていないと受信できなくなりました。
よって、まず、以下の記事を参照していただき、Yahoo RSSサイトのルートCA証明書公開鍵をダウンロードしてください。
Arduino – ESP32 WiFiClientSecure ライブラリで、安定して https ( SSL )記事をGETする方法
その公開鍵を以下のようにダブルクォーテーション "
や\n" \
で括って編集して、const char* yahoo_root_ca
という文字列変数を定義しておきます。
const char* yahoo_root_ca= \ "-----BEGIN CERTIFICATE-----\n" \ "MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY\n" \ "MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t\n" \ "dW5pY2F0aW..................(中略)........................QyMDQ5\n" \ "WjBQMQswCQ..................(中略)........................cwJQYD\n" \ "VQQLEx5TZW..................(中略)........................qGSIb3\n" \ "DQEBAQUAA4..................(中略)........................rw8yl8\n" \ "9f/uKuDp6b..................(中略)........................AZJ6kJ\n" \ "DKaVv0uMDP..................(中略)........................7AwFb9\n" \ "Ms+k2Y7CI9..................(中略)........................hWZq/N\n" \ "QV3Is00qVU..................(中略)........................lLqCHJ\n" \ "xrHty8OVYN..................(中略)........................A9MB0G\n" \ "A1UdDgQWBB..................(中略)........................YDVR0T\n" \ "AQH/BAUwAw..................(中略)........................NgE+vG\n" \ "kl3g0dNq/v..................(中略)........................POCEfr\n" \ "Uj94nK9Nrv..................(中略)........................QVy+n5\n" \ "Bw+SUEmK3T..................(中略)........................NnPaJU\n" \ "JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot\n" \ "RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw==\n" \ "-----END CERTIFICATE-----\n";
これをArduino-ESP32スケッチソースコードの13行目あたりに貼り付けて、グローバル変数としておきます。
(スタック領域変数でも良いですし、SDカードやSPIFFSから読み込むようにしても良いと思います。)
その後、138-140行目を以下のように書き換えます。
※Arduino core for the ESP32 ver stable 1.0.5
※ESP32_mgo_tec beta ver 1.0.72
mM5.yahoo[0].getYahooJweatherRCA( yahoo_root_ca, 1, yahoo_weather_host, yahoo_weather_target_url ); mM5.yahoo[0].getYahooJnewsRCA( yahoo_root_ca, 1, yahoo_news_host, sel_news[url0_num].url ); mM5.yahoo[1].getYahooJnewsRCA( yahoo_root_ca, 1, yahoo_news_host, sel_news[url1_num].url );
これでコンパイルすれば、記事を取得できると思われます。
(2021/3/7時点)
52-56行で、LEDC PWM を出力する GPIO を定義しています。
LEDC のチャンネルは0~15まで設定できますが、このスケッチでは0~7までにしないとうまく動きませんでした。
そして、チャンネル0は M5Stack のバックライトに使っているので、それ以外にします。
そして、1bitの場合はデューティ比を1にすると50%にできます。
私個人としては、こちらのスケッチを強くおススメします。
コンパイル書き込み実行
では、M5Stack と JJY 発信モジュールとを接続してください。
接続方法は、先に紹介した接続図を参照してください。
そして、ご自分の WiFiルーターを起動して、インターネットに接続しておいてください。
その後、M5Stack をコンパイル書き込み実行させ、シリアルモニターを 115200 で起動しておいてください。
すると、下図のようにNTPサーバーから時刻を取得し、Yahoo! Japan RSS ページからニューストピックスの見出しと、天気予報を取得します。
その後、現在時刻がゼロ秒になった時に、電波時計用の JJY 信号発信がスタートします。
アンテナコードを大き目の輪の状態にして、以下のように電波受信し易い場所に電波時計を置きます。
アンテナコードを平行に置いてしまうと電波が相殺されてしまうので、電流の流れる方向が相殺されない位置を意識して置くとよいと思います。
そして、電波時計に内蔵されている受信アンテナは方向性があるので、電波時計の種類によって以下のようにアンテナコードの置き方を変えてください。
因みに、アンテナコードは時計から離してしまうと受信できなくなるのでご注意ください。
電波時計によってはまず西日本の 60kHz電波を検出開始して、検出できなければ、東日本の 40kHz を受信するというモードになっている場合があるので、完全に受信完了するまでに15分以上かかる場合があります。
ですから、スケッチの 38行の秒数を変えて、ご自分の電波時計に合った時間に合わせてください。
私は20分( 1200000 ミリセコンド)にしました。
あとは最初に紹介した動画のように動作すればOKです。
もし、20分が長すぎて、ニュース記事をもっと頻繁に取得したければ、M5Stack と JJY発信モジュールを分離させるしかありません。
むしろ、本当はそっちの方が確実ですが・・・。
電波時計が受信しない時の意外な原因
いろいろ位置を変えても、なぜか電波時計が受信しない時があります。
私も、10年前くらいに買った電波ソーラー腕時計が、ある時は受信したのに、翌日は全く受信しないことがありました。
実は、この原因は内蔵バッテリーが弱ってきていることが考えられます。
今回、私の場合、この実験をやりすぎて、一日に数十回電波受信を実行していたので、一気にバッテリーを消耗させてしまったものと考えられます。
腕時計のバッテリーインジケーターは正常位置なのですが、なぜか受信しないのです。
試しに、数時間、直射日光に当てて、その後受信できるか試してみたら、すんなり受信しました。
もしかしたら、古い電波ソーラー時計はバッテリーを疑ってみた方が良いかと思います。
5年を超えたら、バッテリーは寿命だと思った方が良いです。
編集後記
どうでしょうか?
Circuit Simulator Applet を使えば、自分の様なアマチュア素人でも、ディスクリートで思ったような回路が作ることが出来るようになりました。
AD9833のようなサイン波発生ドライバーチップなどを使わなくても、トランジスタを1個足しただけでパワーアップできることが簡単に分かりましたし、エミッタフォロア(コレクタ接地)も初めて効果的に使えた感じです。
参考書やネットを見ても、コレクタ接地とエミッタ接地の使用用途や利点がイマイチ分からなかったのですが、こうやって作ってみると、その利点や短所が良く分かりますね。
そして、アイロンビーズ(パーラービーズ)は、サクッと外箱ケースを作ることができるので、意外と使えることが分かりました。
私的には3Dプリンターより手軽で、割と気に入っています。
ということで、今回で電波時計 JJY 発振回路の私のリベンジ工作は完結したいと思います。
今度こそ、ディープラーニングの勉強を始めたいと思います。
ではまた・・・。
コメント