e-Paperを利用したカレンダーの製作(STM32 Bulepill)　実験編

# 概要

紙のカレンダーを毎年買い換えますよね、これを代替できるようなものを作れないかと思っていました。
処理は相当軽いものでしょうから、できれば、ボタン電池ぐらいで動作し続けるものにしたいと思います。
表示デバイスは電子ペーパであれば要件を満たせるでしょう、計時機能も必要なので、RTCを持っていて、低消費電力のマイコンを探していったところ、STM32に行きつきました。Arduinoによる開発もできるので、簡易に始められそうです。電子ペーパのサンプルプログラムもSTM32用もArduino用もあるので、この点もクリアしています。
今回は集めた部品と各動作のテストの方法・結果などをまとめました。

# 部品

以下の部品を揃えました

## Blue pill
STM32のマイコンでサイズが小さく、安価であることからこれを利用することに決めました。RTC用の32.768kHzのクリスタルも載っていますし、電池駆動用の端子もあります。同じBlue pillでも、販売元によってだいぶ品質にばらつきがありそうです。少し探した結果、こちらの[サイト](https://www.switch-science.com/products/6281?_pos=1&_sid=ee445eef0&_ss=r)から品質がよさそうな、Robot dynの物を購入しました。

## e-Paper
WaveShareさんのも以外はあまりないようです、サイズと価格で648x480、3色(白・黒・赤)のものがよさそうだったのですが国内の小売店には在庫がありません。WaveShareさんの[サイト](https://www.waveshare.com/product/displays/e-paper/epaper-1/5.83inch-e-paper-hat-b.htm)から直接購入しました。

## 電波時計モジュール
カレンダーではありますが、時計合わせが必要で、どういう方法がよいか検討しました。ボタンを準備して、操作して、時間合わせでもいいのですが、UIを作るのがちょっと面倒です。
NTPが便利ですが、さすがにこのためだけにWIFIやLANを準備するのはToo Muchだし、消費電力も上がってしまいます。そこで、省電力で時刻合わせをする方法として、電波時計のモジュールを利用することにしました。[Aitendoさん](https://www.aitendo.com/product/18956)から入手しています。



# 基本設計

集めた部品を以下のように接続します。
開発用には安価に販売されているSTLINK V2のUSB版を、また、デバッグ出力の確認用にUSBシリアル変換アダプタもかまします。

![キャプションを入力できます](https://camo.elchika.com/a905a6c081773623b517dedc9ca6332f360346ce/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f62376366383337612d373938622d343866612d393832652d6534383166313931653836312f33316233353265392d376634372d343039332d386130342d633165666139343063303934/)

接続の様子はこのようになります。

![キャプションを入力できます](https://camo.elchika.com/3b5c553b2d56039f2ba1017f9b67d1e02bc78c3e/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f62376366383337612d373938622d343866612d393832652d6534383166313931653836312f39376563643630392d323465372d346334662d623166322d313137326633383265353765/)

開発ですが、Arduinoが簡単なのでArduinoで開発します、IDEはVSCodeとPlatformIOがコンパイルも早く便利です。



# 各機能のテスト

まずは単体で各機能が動作することを確認します。

## e-Paper

サンプルを元に作る方法もあるのですが、ライブラリがないものか探してみました。
するとこちらの方がライブラリを作成されていました、多数の電子ペーパーモジュールに対応しているようです。
[GxEPD2](https://github.com/ZinggJM/GxEPD2)

早速ライブラリを導入して、サンプルを動かしてみます。
GxEPD2_Example.inoを利用しました。
変更しなければいけない箇所が結構あります。変更点を以下にまとめておきます。

今回は3色のパネルなので、以下のGxEPD2_3C.hのみコメントを外します。また、その下段はselection_new_style.hをコメント外します。その下段は利用するものだけコメントを外し、他はコメントアウトします。

```C:main.cpp
//#include <GxEPD2_BW.h>
#include <GxEPD2_3C.h>
//#include <GxEPD2_4C.h>
//#include <GxEPD2_7C.h>
#include <Fonts/FreeMonoBold9pt7b.h>

// select the display constructor line in one of the following files (old style):
//#include "GxEPD2_display_selection.h"
//#include "GxEPD2_display_selection_added.h"

// or select the display class and display driver class in the following file (new style):
#include "GxEPD2_display_selection_new_style.h"

#if !defined(__AVR) && !defined(STM32F1xx)

// note 16.11.2019: the compiler may exclude code based on constant if statements (display.epd2.panel == constant),
//                  therefore bitmaps may get optimized out by the linker
ここから先はすべてコメントアウトしたうえで、利用するパネルの分のみコメントをはずします。

// comment out unused bitmaps to reduce code space used
//#include "bitmaps/Bitmaps80x128.h"  // 1.02" b/w
//#include "bitmaps/Bitmaps152x152.h" // 1.54" b/w
//#include "bitmaps/Bitmaps200x200.h" // 1.54" b/w
//#include "bitmaps/Bitmaps104x212.h" // 2.13" b/w flexible GDEW0213I5F
...<省略>
↓これだけ利用する
#include "bitmaps/Bitmaps3c648x480.h" // 5.83" b/w/r
...<省略>

```
今回は3色のパネルなので、以下のdefine GxEPD2_DISPLAY_CLASS GxEPD2_3Cのみコメントを外します。その下段は利用するものだけコメントを外し、他はコメントアウトします。

```C:GxEPD2_display_selection_new_style.h

// select the display class (only one), matching the kind of display panel
//#define GxEPD2_DISPLAY_CLASS GxEPD2_BW
#define GxEPD2_DISPLAY_CLASS GxEPD2_3C
//#define GxEPD2_DISPLAY_CLASS GxEPD2_4C
//#define GxEPD2_DISPLAY_CLASS GxEPD2_7C

// select the display driver class (only one) for your  panel
//#define GxEPD2_DRIVER_CLASS GxEPD2_102     // GDEW0102T4   80x128, UC8175, (WFT0102CZA2)
//#define GxEPD2_DRIVER_CLASS GxEPD2_150_BN  // DEPG0150BN 200x200, SSD1681, (FPC8101), TTGO T5 V2.4.1
...<省略>
↓これだけ利用する
#define GxEPD2_DRIVER_CLASS GxEPD2_583c_Z83 // GDEW0583Z83 648x480, EK79655 (GD7965), (WFT0583CZ61)
...<省略>
```


実際に動作させるとだいぶ長いこと色々と表示してくれます。こんなイメージということで、画像を数枚載せてみます。

![キャプションを入力できます](https://camo.elchika.com/6d3eca44fe8b770e47f43875706ef7e63b463df0/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f62376366383337612d373938622d343866612d393832652d6534383166313931653836312f64666566363365392d373839302d343738302d393065372d366637616235303936656532/)

![キャプションを入力できます](https://camo.elchika.com/2ad4912b9fa03821e418bfaa49d2d11e21126029/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f62376366383337612d373938622d343866612d393832652d6534383166313931653836312f63356330353434642d376134652d343165632d626339342d323030343262363530626465/)

![キャプションを入力できます](https://camo.elchika.com/66663b0319113db8e409fa9bfd792609ba6f3a09/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f62376366383337612d373938622d343866612d393832652d6534383166313931653836312f34353535663933622d643435352d343239302d396462372d646666303834303165626263/)

![キャプションを入力できます](https://camo.elchika.com/03818d69b988237c64b9d6e33a179c73d663f3c5/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f62376366383337612d373938622d343866612d393832652d6534383166313931653836312f35366137653563322d373365372d343436392d616632332d616362633035323739346532/)


## RTC

こちらもライブラリがあります。
[STM32RTC](https://github.com/stm32duino/STM32RTC)

サンプルを動作させてみます。

以下変更点です。

1. 32.768kHzのクロックを使用するので、この部分のコメントを外しています、rtc.setClockSource(STM32RTC::LSE_CLOCK);

1.  // Set the timeと // Set the dateの箇所は初回実行時のみコメントをしないでおき実行します。これで日時が設定されます。 次回以降の為に上記箇所をコメントアウトしておきます。電源を切ってからでも、バッテリーで日時が保持されますので、電源を入れなおすと、続きの日時から表示がされました。

```C:main.cpp
#include <Arduino.h>
/*
  SimpleRTC

  This sketch shows how to configure the RTC and to display
  the date and time periodically

  Creation 12 Dec 2017
  by Wi6Labs
  Modified 03 Jul 2020
  by Frederic Pillon for STMicroelectronics

  This example code is in the public domain.

  https://github.com/stm32duino/STM32RTC
*/

#include <STM32RTC.h>

/* Get the rtc object */
STM32RTC& rtc = STM32RTC::getInstance();

/* Change these values to set the current initial time */
const byte seconds = 0;
const byte minutes = 0;
const byte hours = 16;

/* Change these values to set the current initial date */
/* Monday 15th June 2015 */
const byte weekDay = 0;
const byte day = 1;
const byte month = 9;
const byte year = 24;

void setup()
{
  Serial.begin(9600);

  // Select RTC clock source: LSI_CLOCK, LSE_CLOCK or HSE_CLOCK.
  // By default the LSI is selected as source.
  rtc.setClockSource(STM32RTC::LSE_CLOCK);

  rtc.begin(); // initialize RTC 24H format

  // Set the time
  //rtc.setHours(hours);
  //rtc.setMinutes(minutes);
  //rtc.setSeconds(seconds);

  // Set the date
  //rtc.setWeekDay(weekDay);
  //rtc.setDay(day);
  //rtc.setMonth(month);
  //rtc.setYear(year);

  // you can use also
  // 初回コンパイル時には以下の2つのコメントを外します。これで日時が設定されます。その後コメントアウトして、再度書き込みしておきます。
  //rtc.setTime(hours, minutes, seconds);
  //rtc.setDate(weekDay, day, month, year);
}

void loop()
{
  // Print date...
  Serial.printf("%02d/%02d/%02d ", rtc.getDay(), rtc.getMonth(), rtc.getYear());

  // ...and time
  Serial.printf("%02d:%02d:%02d.%03d\n", rtc.getHours(), rtc.getMinutes(), rtc.getSeconds(), rtc.getSubSeconds());

  delay(1000);
}
```

以下、16:00:57の箇所で電源をぬきました。その後再度接続すると、16:01:24からとなり、RTCの計時は継続していたことがわかります。

![キャプションを入力できます](https://camo.elchika.com/92a01e785112012c1ddf3d5bd55910a9a0ee2fbf/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f62376366383337612d373938622d343866612d393832652d6534383166313931653836312f33653735666564612d326233652d343039362d393661662d306562646134376233363666/)


## 電波時計モジュール

こちらもライブラリがあります。
[JJYReceiver](https://github.com/Blue-Crescent/JJYReceiver)

サンプルを動作させてみます。
STM32に対応しているかわからないもの、こちらのサンプルを動作させてました。
sample_lgt8f328
変更点はDATA/PON/SELの信号線の番号を実際に合わせて更新したのみだと思います。
マンション内なので、電波がつかめず、[M5StickC用JJYアンテナ基板](https://www.switch-science.com/products/6527?_pos=1&_sid=51810abb9&_ss=r)とM5StickCを購入して、シミュレーションしたJJY信号を利用しました。M5StickC用JJYアンテナ基板に関しては、作者の方が[こちら](https://qiita.com/BotanicFields/items/a78c80f947388caf0d36)で詳細を解説されています。大変便利なものでありがたかったです。M5Stickも初めて利用しましたが、これも便利なものですね。


```C:main.cpp
#include <JJYReceiver.h>
#include <SoftwareSerial.h>
#include <MsTimer2.h>

#define DATA 2
#define PON 8
#define SEL 12

#define MONITORPIN LED_BUILTIN

SoftwareSerial dSerial(7, 6); // RX, TX

JJYReceiver jjy(DATA,SEL,PON); // JJYReceiver lib set up.

void setup() {
  // For LGT8f328 board setting. This can be ignored for other board.
  pinMode(10, INPUT);  pinMode(11, INPUT); // DATA
  pinMode(9, INPUT); // PON

  // Debug print
  dSerial.begin(57600);

  // 10msec Timer for clock ticktock (Mandatory)
  MsTimer2::set(10, ticktock);
  MsTimer2::start();
  // DATA pin signal change edge detection. (Mandatory)
  attachInterrupt(digitalPinToInterrupt(DATA), isr_routine, CHANGE);
  
  // Optional for debug, light on for LED connection check.
  digitalWrite(MONITORPIN,HIGH);
  delay(1000);

  // JJY Library
  jjy.begin(); // Start JJY Receive
  jjy.monitor(MONITORPIN); // Optional. Debug LED inidicator.
  jjy.freq(40); // Carrier frequency setting. Default:40
  
  dSerial.println("JJY Initialized.");
}

void isr_routine() { // pin change interrupt service routine
  jjy.jjy_receive(); 
}
void ticktock() {  // 10 msec interrupt service routine
  jjy.delta_tick();
}

void loop() {
  time_t now = jjy.get_time();
  time_t lastreceived = jjy.getTime();
  tm tm_info;

  if(lastreceived != -1){
    localtime_r(&now, &tm_info);
    const char *days[] = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"};  
    String str0 = String(tm_info.tm_year + 1900);
    String str1 = String(tm_info.tm_mon + 1);
    String str2 = String(tm_info.tm_mday);
    String str3 = String(tm_info.tm_hour);
    char buf1[3];
    sprintf(buf1, "%02d", tm_info.tm_min);
    char buf2[3];
    sprintf(buf2, "%02d", tm_info.tm_sec); 
    String strm = String(days[tm_info.tm_wday]);
    dSerial.print(str0+"/"+str1+"/"+str2+" "+strm+" "+str3+":"+buf1+" "+buf2);  // Print current date time.

    dSerial.print(" Last received:");    
    String str = String(ctime(&lastreceived));
    dSerial.println(str);  // Print last received time
    
  }else{
    String str0 = "Receiving quality:";
    String str1 = String(jjy.quality);
    dSerial.print(str0 + str1);
    
    String str = String(ctime(&now));
    dSerial.println(" "+str);
  }
  if((now - lastreceived) > 3600 && lastreceived != -1){ // receive from last over an hour.
    jjy.begin();
  } 
  delay(1000);
}

```

シミュレータ基板のコイルと電波時計モジュールのコイルの方向を合わせて接近させます。

![キャプションを入力できます](https://camo.elchika.com/59ff72bb24481f2e1f44d9083a7d215d670211ff/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f62376366383337612d373938622d343866612d393832652d6534383166313931653836312f39373261346631342d353239662d346161662d613136312d613231633532346133313035/)

シリアルモニタの出力を待つこと数分で、時刻が同期されことが確認できました。


![キャプションを入力できます](https://camo.elchika.com/35ec27cb30b3114ce06f9cd7d5fe08677aa9f485/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f62376366383337612d373938622d343866612d393832652d6534383166313931653836312f37346630343832642d353530342d343730362d623932392d653163653733316435313566/)


今後はこれらのコンポーネントを組み合わせて、卓上表示できるような、カレンダーのソフトを作成していこうと思います。