m5stack_tr_jjy00

M5Stack Yahooニュース・天気予報・時計に、電波時計 JJY 発信モジュールを追加して、マルチタスクで動かしてみた

記事公開日:2019年1月31日


スポンサーリンク

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 信号を同時に発信するので、いろいろ工夫するところがあります。

//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 ] = {
  { "主要", "/pickup/rss.xml" },
  { "スポーツ", "/pickup/sports/rss.xml" },
  { "エンタメ", "/pickup/entertainment/rss.xml" },
  { "IT", "/pickup/computer/rss.xml" },
  { "経済", "/pickup/economy/rss.xml" },
  { "科学", "/pickup/science/rss.xml" },
  { "国際", "/pickup/world/rss.xml" },
  { "地域", "/pickup/local/rss.xml" },
  { "国内", "/pickup/domestic/rss.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;
  }
}

【ちょっとだけ解説】

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() という関数を初めて使ってみましたが、特に弊害が無いように見受けられます。
もし、何か影響が分かったら誰か教えてくださると助かります。

もし、影響が出た場合は、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までの安定した高周波パルスを思い通りに出せたぞ

これを使えば、いわゆるジッタが減り、安定します。
先のスケッチより格段に優れています。

//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 ] = {
  { "主要", "/pickup/rss.xml" },
  { "スポーツ", "/pickup/sports/rss.xml" },
  { "エンタメ", "/pickup/entertainment/rss.xml" },
  { "IT", "/pickup/computer/rss.xml" },
  { "経済", "/pickup/economy/rss.xml" },
  { "科学", "/pickup/science/rss.xml" },
  { "国際", "/pickup/world/rss.xml" },
  { "地域", "/pickup/local/rss.xml" },
  { "国内", "/pickup/domestic/rss.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;
  }
}

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 信号発信がスタートします。

m5stack_tr_jjy30.jpg


アンテナコードを大き目の輪の状態にして、以下のように電波受信し易い場所に電波時計を置きます。
アンテナコードを平行に置いてしまうと電波が相殺されてしまうので、電流の流れる方向が相殺されない位置を意識して置くとよいと思います。

m5stack_tr_jjy31.jpg


そして、電波時計に内蔵されている受信アンテナは方向性があるので、電波時計の種類によって以下のようにアンテナコードの置き方を変えてください。
因みに、アンテナコードは時計から離してしまうと受信できなくなるのでご注意ください。

m5stack_tr_jjy32.jpg


電波時計によってはまず西日本の 60kHz電波を検出開始して、検出できなければ、東日本の 40kHz を受信するというモードになっている場合があるので、完全に受信完了するまでに15分以上かかる場合があります。

ですから、スケッチの 38行の秒数を変えて、ご自分の電波時計に合った時間に合わせてください。
私は20分( 1200000 ミリセコンド)にしました。

あとは最初に紹介した動画のように動作すればOKです。

もし、20分が長すぎて、ニュース記事をもっと頻繁に取得したければ、M5Stack と JJY発信モジュールを分離させるしかありません。
むしろ、本当はそっちの方が確実ですが・・・。

電波時計が受信しない時の意外な原因

いろいろ位置を変えても、なぜか電波時計が受信しない時があります。

私も、10年前くらいに買った電波ソーラー腕時計が、ある時は受信したのに、翌日は全く受信しないことがありました。

実は、この原因は内蔵バッテリーが弱ってきていることが考えられます。
今回、私の場合、この実験をやりすぎて、一日に数十回電波受信を実行していたので、一気にバッテリーを消耗させてしまったものと考えられます。
腕時計のバッテリーインジケーターは正常位置なのですが、なぜか受信しないのです。

試しに、数時間、直射日光に当てて、その後受信できるか試してみたら、すんなり受信しました。

もしかしたら、古い電波ソーラー時計はバッテリーを疑ってみた方が良いかと思います。
5年を超えたら、バッテリーは寿命だと思った方が良いです。

編集後記

どうでしょうか?

Circuit Simulator Applet を使えば、自分の様なアマチュア素人でも、ディスクリートで思ったような回路が作ることが出来るようになりました。

AD9833のようなサイン波発生ドライバーチップなどを使わなくても、トランジスタを1個足しただけでパワーアップできることが簡単に分かりましたし、エミッタフォロア(コレクタ接地)も初めて効果的に使えた感じです。

参考書やネットを見ても、コレクタ接地とエミッタ接地の使用用途や利点がイマイチ分からなかったのですが、こうやって作ってみると、その利点や短所が良く分かりますね。

そして、アイロンビーズ(パーラービーズ)は、サクッと外箱ケースを作ることができるので、意外と使えることが分かりました。
私的には3Dプリンターより手軽で、割と気に入っています。

ということで、今回で電波時計 JJY 発振回路の私のリベンジ工作は完結したいと思います。

今度こそ、ディープラーニングの勉強を始めたいと思います。

ではまた・・・。

このブログの維持運営にご支援いただけると助かります。
支援方法はこちらの記事をご覧ください。
(管理人:mgo-tec)

 


スポンサーリンク


 

mgo-tec電子工作ブログ管理人おすすめ
Amazon.co.jp
M5Stack Basic
スイッチサイエンス
Amazon.co.jp
ESPr Developer 32
スイッチサイエンス(Switch Science)
Amazon.co.jp
Amazon.co.jp

コメントを残す

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

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください